Browse files

Initial release.

  • Loading branch information...
0 parents commit 0f1a1b131138466580f7f02f6720402df654cd37 Chakrit Wichian committed Jun 16, 2010
Showing with 362 additions and 0 deletions.
  1. +61 −0 README.markdown
  2. +52 −0 jquery-xpubsub.js
  3. +2 −0 jquery-xpubsub.min.js
  4. +65 −0 test-chat.html
  5. +82 −0 test-chat.js
  6. +22 −0 test-mvc.html
  7. +78 −0 test-mvc.js
61 README.markdown
@@ -0,0 +1,61 @@
+
+# jQuery-xPubSub
+
+A very minimal client-side publish/subscribe system on top of jQuery.
+
+Publish/subscribe allows greater degree of separation between components when
+designing large or complex javascript application. See the `test-mvc.html`
+file for an example.
+
+Under the hood, it is just a sugar coating on top of `$.bind` and `$.trigger`
+with an additional `$.connect` that helps reduce a lot of
+glue code for you. But in reality, publish/subscribe is an entirely different
+mode of thought.
+
+In large events-based system, large portion of your code will be glue code
+gathering data from one component and feeding it into another anyway, so why
+not make that "gathering data" and "feeding" a first-class thing and model
+your component accordingly?
+
+If you're drowing in javascript glue code, connecting one interface to another
+looking up multiple files at a time just to debug one small bug, then xPubSub
+might help you. Especially so if you prefer functional-style js. :)
+
+* `channel` - Can be any valid JS object that works with the `$(obj).bind`
+ function. This is best used (not required) in conjunction with the
+ `$.newChannel` method below.
+* `data` - Can be anything that isn't `null` or `undefined`.
+
+This plugin provides the following methods:
+
+* `$.publish(channel, data)`
+
+ Publish `data` to channel `channel` if `data` is not `null` or `undefined`.
+
+* `$.subscribe(channel, handler)`
+
+ Subscribe to channel `channel` using handler `handler`.
+
+ Handler should have signature: `function (data) { }` with `data` being
+ the data object passed exactly as published.
+
+* `$.connect(fromChannel, toChannel, translator)`
+
+ Connect two channels together, so when data arrives on `fromChannel`, it is
+ automatically published to `toChannel` as well.
+
+ Optionally, a `translator` function with signature:
+ `function(data) { return translatedData; }` maybe provided.
+ The `data` parameter being published to the `fromChannel`. It should returns
+ a translated version of `data` to be published to `toChannel`.
+
+ The `translator` function may also be used as a kind of filter, by selectively
+ returning `null`. Returning `null` from the `translator` function will
+ prevents the message from being published to `toChannel`
+
+* `$.newChannel(channelName)`
+
+ Convenience method for creating new channel endpoints. This is *not* required.
+ Channel endpoint can be any valid javascript object such as a simple `{}`
+ which the pub/sub system can bind (using `$.bind`) events to.
+
52 jquery-xpubsub.js
@@ -0,0 +1,52 @@
+
+// jquery-xpubsub.js - a very minimal pubsub framework on top of jQuery
+// contact: Chakrit Wichian (http://chakrit.net/);
+(function () {
+
+ var $ = jQuery,
+ pubsubEvent = 'pubsub.xpubsub';
+
+ function publish(target, data) {
+ $(target).trigger(pubsubEvent, data);
+ }
+
+ function subscribe(target, callback) {
+ $(target).bind(pubsubEvent, {}, function (e, msg) { callback(msg); });
+ }
+
+ function connect(fromChannel, toChannel, translator) {
+ var trans;
+
+ if (!translator) {
+ trans = function (data) { return data; };
+
+ } else if (typeof translator == 'function') {
+ trans = function (data) { return translator(data); };
+
+ } else if (typeof translator == 'object') {
+ trans = function (data) { return $.extend(data, translator); };
+
+ };
+
+ subscribe(fromChannel, function (data) {
+ var transData = trans(data);
+
+ if (transData !== null && typeof (transData) !== 'undefined')
+ publish(toChannel, transData);
+ });
+ }
+
+ // wire up to jQuery
+ $.publish = publish;
+ $.subscribe = subscribe;
+ $.connect = connect;
+
+ // channel is simply any distinct object
+ // used as a target for jQuery.bind and trigger
+ $.newChannel = function (name) {
+ return name ?
+ { toString: function () { return name; } } :
+ {};
+ };
+
+})();
2 jquery-xpubsub.min.js
@@ -0,0 +1,2 @@
+// jQuery-xPubSub - by Chakrit Wichian (http://chakrit.net)
+(function(){function g(b,f){c(b).trigger(h,f)}function i(b,f){c(b).bind(h,{},function(d,e){f(e)})}var c=jQuery,h="pubsub.xpubsub";c.publish=g;c.subscribe=i;c.connect=function(b,f,d){var e;if(d)if(typeof d=="function")e=function(a){return d(a)};else{if(typeof d=="object")e=function(a){return c.extend(a,d)}}else e=function(a){return a};i(b,function(a){a=e(a);a!==null&&typeof a!=="undefined"&&g(f,a)})};c.newChannel=function(b){return b?{toString:function(){return b}}:{}}})();
65 test-chat.html
@@ -0,0 +1,65 @@
+<html>
+<head>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+ <script src="jquery-xpubsub.min.js"></script>
+ <script src="test-chat.js"></script>
+ <style>
+ .chatroom
+ {
+ float: left;
+ width: 300px;
+ }
+ </style>
+</head>
+<body>
+ <div class="chatroom">
+ <h2>
+ Room #1</h2>
+ <div>
+ <strong>Jack:</strong>
+ <input type="text" />
+ <button>
+ Send</button>
+ </div>
+ <div>
+ <strong>John:</strong>
+ <input type="text" />
+ <button>
+ Send</button>
+ </div>
+ <ul>
+ </ul>
+ </div>
+ <div class="chatroom">
+ <h2>
+ Room #2</h2>
+ <div>
+ <strong>Jim:</strong>
+ <input type="text" />
+ <button>
+ Send</button>
+ </div>
+ <div>
+ <strong>Jane:</strong>
+ <input type="text" />
+ <button>
+ Send</button>
+ </div>
+ <ul>
+ </ul>
+ </div>
+ <div class="chatroom" id="opRoom">
+ <h2>
+ OP monitor channel
+ </h2>
+ <div>
+ <strong>OP:</strong>
+ <input type="text" />
+ <button>
+ Broadcast</button>
+ </div>
+ <ul>
+ </ul>
+ </div>
+</body>
+</html>
82 test-chat.js
@@ -0,0 +1,82 @@
+
+$(function () {
+
+ var monitor = $.newChannel("OP Monitoring Channel"),
+ broadcast = $.newChannel("OP Broadcasting Channel");
+
+
+ // utility functions
+ function findParts(target) {
+ target = $(target);
+
+ return {
+ isOpRoom: target.is('#opRoom'),
+ name: $('h2', target).text(),
+ list: $('ul', target),
+ members: $.map($('div', target), function (mem) {
+ return {
+ name: $('strong', mem).text(),
+ input: $('input', mem),
+ sendButton: $('button', mem)
+ };
+ })
+ };
+ }
+
+ function formatMsg(msg) {
+ return "[" + msg.from + "] " + msg.msg;
+ }
+
+
+ // setup chat pub/sub
+ function setupChat(room) {
+
+ // send message to the channel on click
+ $(room.members).each(function () {
+ var me = this;
+
+ me.sendButton.click(function () {
+ $.publish(room, {
+ isBroadcast: room.isOpRoom,
+ from: me.name,
+ msg: me.input.val()
+ });
+ });
+ });
+
+ // append an item to the list on incoming messages
+ $.subscribe(room, function (msg) {
+ $(document.createElement("li"))
+ .text(formatMsg(msg))
+ .appendTo(room.list);
+ });
+
+ // logs all non-op room to the monitoring channel
+ // and make them receive all broadcasts
+ if (!room.isOpRoom) {
+
+ $.connect(broadcast, room);
+ $.connect(room, monitor, function (msg) {
+ if (msg.isBroadcast) return null; // prevents infinite loop
+
+ msg.from = msg.from + "@" + room.name;
+ return msg;
+ });
+
+ } else {
+
+ // op can see the monitor channel and can broadcasts messages
+ $.connect(monitor, room);
+ $.connect(room, broadcast, function (msg) {
+ return msg.isBroadcast ? msg : null; // prevents infinite loop
+ });
+ }
+ }
+
+
+ $(".chatroom").each(function () {
+ setupChat(findParts(this));
+ });
+
+
+});
22 test-mvc.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+ <script src="stacktrace.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+ <script src="jquery-xpubsub.min.js"></script>
+ <script src="test-mvc.js"></script>
+</head>
+<body>
+ <h2>
+ Twitter Search: "jQuery"</h2>
+ <div>
+ <button id="prevButton">
+ Previous
+ </button>
+ <button id="nextButton">
+ Next
+ </button>
+ </div>
+ <ul id="tweetsList">
+ </ul>
+</body>
+</html>
78 test-mvc.js
@@ -0,0 +1,78 @@
+
+$(document).ready(function () {
+
+
+ // ___________________________________________________________________________
+ // MODEL
+ var Model = new function () {
+
+ var me = this,
+ _searchUrl = "http://search.twitter.com/search.json?callback=?",
+ _currentPage = 1;
+
+ me.pageDeltas = $.newChannel("Model.pageDeltas");
+ me.tweets = $.newChannel("Model.tweets");
+
+ // turn incoming pageDeltas into outgoing list of tweets
+ $.connect(me.pageDeltas, me.tweets, function (pageDelta) {
+ return function (cont) {
+
+ _currentPage += pageDelta;
+
+ var query = {
+ q: "jquery",
+ rpp: 10,
+ page: _currentPage
+ };
+
+ $.getJSON(_searchUrl, query, function (json) { cont(json.results); });
+ };
+ });
+
+ };
+
+
+ // ___________________________________________________________________________
+ // VIEW
+ var View = new function () {
+
+ var me = this,
+ _tweetsList = $("#tweetsList"),
+ _nextButton = $("#nextButton"),
+ _prevButton = $("#prevButton");
+
+ me.pageDeltas = $.newChannel("Views.pageDeltas");
+ me.tweets = $.newChannel("Views.tweets");
+
+
+ // publish deltas to pageDeltas channel on page changes
+ _nextButton.click(function () { $.publish(me.pageDeltas, 1); });
+ _prevButton.click(function () { $.publish(me.pageDeltas, -1); });
+
+ // on incoming tweets, display them in a list
+ $.subscribe(me.tweets, function (tweetsCont) {
+ tweetsCont(function (tweets) {
+
+ _tweetsList.html("");
+
+ $(tweets).each(function () {
+ $(document.createElement("li"))
+ .text("@" + this.from_user + ": " + this.text)
+ .appendTo(_tweetsList);
+ });
+
+ });
+ });
+
+ };
+
+
+ // ___________________________________________________________________________
+ // CONTROLLER(?)
+
+ $.connect(View.pageDeltas, Model.pageDeltas);
+ $.connect(Model.tweets, View.tweets);
+
+ $.publish(Model.pageDeltas, 0);
+
+});

0 comments on commit 0f1a1b1

Please sign in to comment.