Demo: PubSub in jQuery
Recently I did a post on Data Binding in JavaScript using the YUI3 Library as my base. h3 and MikeInVB wanted to see it in jQuery, so I rewrote it and it turns out is a great example of PubSub (publish / subscribe) for jQuery.
For this example, we're using an object named DB, which is short for DataBind. Basically it's an object with getters and setters.
var DB = function (data) { this._d = null; this.set = function (data) { this._d = data; }; this.get = function () { return this._d; }; this.set(data); };
Really, this is just an abstracted way to store data. To create an instance of it, set it and get the value back, we would do something like:
var myData = new DB(); myData.set("We can't fight in here, this is a war room!"); console.log(myData.get();) // "We can't fight in here, this is a war room!"
But lets say, we want to subscribe to this data object, and be notified whenever it changes. Instead of polling it and checking to see if the value is the same as last time, we can use jQuery's custom events to trigger a custom event.
var DB = function (data) { this._d = null; this.set = function (data) { this._d = data; // notify all subscribers when this function is used $(this).trigger("change"); }; this.get = function () { return this._d; }; this.set(data); };
Now, we can subscribe to it like this:
$(myData).bind("change", function() { alert("The data was changed!"); });
Additionally, we can access that data in our handler:
$(myData).bind("change", function() { alert("My data was set to " + myData.get()); });
You can set up any number of subscribers to myData this way now, and they'll be triggered when that instance is set().
If you want to further improve DB, you can add a function to make it easier to attach change events, jQuery style:
var DB = function (data) { this._d = null; this.set = function (data) { this._d = data; // notify all subscribers when this function is used $(this).trigger("change"); }; this.get = function () { return this._d; }; // shortcut for functions trying to bind to the change custom event this.change = function (fn) { $(this).bind("change", fn); }; this.set(data); };
With our new shortcut, we can subscribe to it like this:
var alexSez = new DB(); alexSez.set("Initiative comes to thems that wait."); alexSez.change(function() { alert("Alex says: '" + myData.get() + "'"); }); alexSex.set("Welly, welly, welly, welly, welly, welly, well. To what do I owe the extreme pleasure of this surprising visit?") // an alert will come up that says "Alex says: 'Welly, welly, welly, welly, welly, welly, well. To what do I owe the extreme pleasure of this surprising visit?'".
In the same way, you can set up two input boxes to "share" the same value by subscribing to a shared instance of DB like this:
<input type="text" id="in1"/> <br/> <input type="text" id="in2"/>
var myData = new DB(); var inputs = $("#in1, #in2"); myData.change(function() { inputs.val(myData.get()); }); inputs.bind("keyup", function(ev) { myData.set($(ev.target).val()); });
Ta da!
If you're still interested in PubSub for jQuery, I recommend checking out Jamie Thompson's example using the $(document) namespace instead of specific object instances, or Peter Higgin's jQuery.pubsub, which reportedly is significantly faster, although doesn't allow object specific subscribing.
Demo: PubSub in jQuery
What do you think? Would you do this differently? Leave a comment, I read them all!
See the demo here: Data Binding in JavaScript
Back when I was in Flex land one of the things that I found to be really neat was data binding... you could essentially have widgets that would update if the data updated automagically. I also wanted to take a stab at doing publishing and subscribing in YUI3, so built this an object called DB, which stores data. It can do three things, it can set() data, it can get() data, and it's an EventTarget, so you can subscribe to it's change event:
var DB = function (data) { this._d = null; this.publish("change",{emitFacade: true}); this.set = function (data) { this._d = data; this.fire("change"); }; this.get = function () { return this._d; }; this.set(data); }; Y.augment(DB, Y.EventTarget);
And to instantiate an instance of DB you just do var myData = new DB();, and then you can subscribe to it any number of times to be notified when the data has been updated. For example:
<input type="text" id="in1"/> <br/> <input type="text" id="in2"/>
var myData = new DB(); var inputs = Y.all("#in1, #in2"); myData.on("change", function() { inputs.each(function(node) { node.set('value', myData.get()); }); }); inputs.on("keyup", function(ev) { myData.set(ev.currentTarget.get('value')); });
This code is pretty simple. In YUI3 it creates an instance of DB, which has it's own private data (unset initially), subscribes to the change event with a function that updates both text inputs on the page if myData gets set(), and then binds to the keyup event on the text inputs to call myData.set() whenever the contents of either text input has something typed into it. This results in a page where both inputs update whenever you type into either one.
See the demo here: Data Binding in JavaScript
Is this useful? Practical? UnJavaScripty? I don't know, but I'd love to hear what you think
I recently had to do some really heavy lifting with templates in JavaScript, and opted to use John Resig's "Micro-templating" within the Underscore.js Library. It's nice, you can write JavaScript within your templates to control your flow, like:
It's been <%= (new Date()).getTime() %> milliseconds since the epoch.
Which, if you're like me is great. You could use Google Closure Templates, which probably is faster, but then you would have to precompile your templates, which is a pain.
The only problem I personally had with the Resig solution was that if you tried to call a variable that didn't exist within your data object, it would fail horrifically, so I modified it slightly to be slightly more forgiving:
// JavaScript templating a-la ERB, pilfered from John Resig's // "Secrets of the JavaScript Ninja", page 83. // Single-quote fix from Rick Strahl's version. _.template = function(str, data) { var fn = new Function('obj', 'var p=[],print=function(){p.push.apply(p,arguments);};' + 'with(obj){p.push(\'' + str.replace(/[\r\t\n]/g, " ") .replace(/'(?=[^%]*%>)/g,"\t") .split("'").join("\\'") .split("\t").join("'") .replace(//g, "',(typeof($1)!='undefined') ? $1:'','") // modified to allow undefined variables pass through .split("").join("p.push('") + "');}return p.join('');"); return data ? fn(data) : fn; };
The 12th line where it says .replace(//g...) includes a test for the data that is being passed into the function that's being created to output just an empty string if the variable is undefined.