Basic YUI 3 Plugin tutorial
Ever since the release of the PR2 version of YUI3 the library has support for writing plugins. Unfortunately, this doesn’t seem to be taken advantage of all that often & comes up a lot in the IRC channel as something people don’t understand. So let’s see if we can’t clarify the matter a little.
At their core YUI3 Plugins are a way to add new behavior to JS objects. It’s really just that simple. Going into it a little deeper you can explain the idea behind them as providing a framework-backed way to add new functionality & behaviors to host objects without the host needing to know anything about the plugins. Taking advantage of this means that you can add lots of functionality to your objects without requiring a lot of code. It’s another example of YUI3′s great support for modularity of code. Want your widget to accept flaboozulms? Write a plugin! Want your widget to support flaboozulms & flibberdybops? Write a plugin for flibberdybops & then use both the flaboozulms & flibberdybops plugins together. It’s a really powerful idea.
With the pontificating out of the way, let’s touch on some code. I’ll be taking apart a VERY basic plugin I’ve created that listens for clicks on the host & changes the background color. This plugin specifically is targeted towards Node objects since the behavior involves listening for a click event, but the basic concepts could work for any host object. It’s not a very in-depth example of the power here but it is simple enough to show the basics.
Quick breakdown of what this page contains. It’s got your standard HTML skeleton to allow us to easily run JS in the browser, a single <div> we’ll be using to show the interactions, some CSS to style that <div>, and some chunks of JS.
The first bit of JS is including the YUI3 seed file, as well as the loader (because we know for certain we’ll be using it we can save a HTTP request by getting these together).
The next piece is declaring the plugin itself. This occurs within a YUI.add() block that allows us to later specify that we want to use the plugin. You could of course declare the function within the same YUI().use() section where you use it but that wouldn’t be very reusable.
YUI.add('basic-plugin', function(Y) {
/* Plugin code here */
}, "0.1", { requires : [ "base", "plugin", "node" ] });
Discussing the syntax for YUI.add() is a little outside the scope of this but you should note where we declare an array of modules that are required for the plugin to function.
Once inside of the YUI.add() it’s time to actually create the plugin object. It’ll be stuck onto the Y variable so that we’ve got a place to reference it in other code blocks that request our “basic-plugin”. The plugin object’s creation is handled using Y.Base.create(), a very useful framework function that takes care of inheritance & a few other pieces for you. It’s a little hand wavey but if you want the nitty-gritty details feel free to ask & I can provide them.
Y.namespace('Examples').BasicPlugin = Y.Base.create("BasicPlugin", Y.Plugin.Base, [], { ... }, { ... });
A quick breakdown of what we’re doing here, Y.namespace('Examples').BasicPlugin creates nested empty objects for us so everything stays nice and organized without any danger of collisions. Keeping things nested within namespaces like this helps to make sure we can use awesome names like “BasicPlugin” instead of stupid names like “CavitExampleBasicPlugin”. Nobody likes stupid names.
Next up is the assignment of the object returned by Y.Base.create() to our brand-new namespaced object. Y.Base.create() takes a name for the plugin, an class to inherit from, an array of classes to also optionally inherit from (unnecessary for our example), the prototype properties (more later), and static properties (again, more later). The function takes all of these objects & combines them to allow for easy instantiating of objects later.
Let’s start with the prototype properties object that we’re passing to Y.Base.create(). This object is used as the .prototype property of the class being created which means that it’ll be shared by every instance of this class. This is where any functions you want to live as part of the class go, along with any properties for that class instance.
Y.Base.create(..., ..., [], {
_handle : null,
initializer : function() { ... },
destructor : function() { ... }
}, { ... });
Since this is a “basic” plugin we’re only providing one property & two functions. The two functions (initializer() & destructor()) are special functions that YUI3 will call every time a new instance of this plugin is plugged or unplugged into a host. Starting with initializer() we can see that for this basic plugin all it’s doing is attaching a click event handler to the host (in this case a DOM node) that the plugin was plugged into.
this._handle = this.get("host").on("click", function(e) {
var tgt = e.currentTarget,
color = this.get("color");
this.set("color", tgt.getStyle("backgroundColor"));
tgt.setStyle("background", color);
}, this);
It’s pretty straightforward YUI3 code but let’s run through it quickly. We attach an event handler to click events on the host object (this.get("host") will get the special host attribute all plugins have) and provide a callback function to that handler. Note that we store a reference to the object returned by the event attachment, this’ll be important later. The callback saves the target of the click & the current color attribute’s value, then updates the color attribute with the target’s backgroundColor & finally updates the backgroundColor of the target. The only tricky part in here is the use of this.get() & this.set() which are used to access/update this plugins attributes data. We’ll talk about attributes in just a bit.
Next up is destructor() which is invoked whenever the plugin is destroyed (you’ll see how a little further down).
destructor : function() {
this._handle.detach();
this._handle = null;
}
This clean up function ensures we don’t leave little bits of code poop all over the place. The event handler is detached & the reference is nulled out to make sure that the garbage collector has no issue getting that little bit of memory back.
So at this point all we’ve really done is add an object to the Y variable that is accessible at Y.Example.BasicPlugin that when used in conjunction with .plug() later on will listen for clicks & change some background colors. I said we were making a basic plugin, excitement was never promised.
So if we step back up a bit to look at the next argument to Y.Base.create() we’ll notice that it is time to talk about the static properties. Here’s the code.
Y.Base.create(..., ..., {}, {...}, {
NS : "sp",
ATTRS : {
color : { value : "blue" }
}
});
Judging by the amount of code here it’s tempting to think that not a whole lot is happening. You’re right. More could be happening here but in the interest of time it’s been purposely kept very short. All this is doing is declaring two properties. NS is used for referencing the plugin object once it has been plugged into a host. The ATTRS property defines all the attributes that this plugin will support. The totally great thing about this is that it uses the YUI3 Attributes objects as its backend so you get a lot of power & custom events to allow you to validate/get updates/whatever you want when these things are changed. That’s outside the scope of this article but I could go on about that for a while. This is where the ability to use this.get()/this.set() comes from as those are brought in automatically to get/set attributes.
So at this point we’ve got a complete plugin that’s been registered using YUI.add(), what do we do with it? Well the whole point of using YUI.add() was so that anywhere we wanted to on the page we could make the plugin available simply by doing something like the following.
YUI().use("node", "basic-plugin", function(Y) { ... });
And since we made sure to add the plugin to a namespace hung off of the main YUI instance variable (by convention it’s simply “Y”) within the YUI().use() callback we can now access our plugin at Y.Examples.BasicPlugin. So what to do with it now that it’s hanging out waiting to be used? I dunno, how about we plug something?
Y.one("#wooga").plug(Y.Examples.BasicPlugin);
So we’ve just taken our plugin and applied it to a host object. In this case the host object is a YUI3 Node object. That initializer() function we declared as a prototype property inside of Y.Base.create() will now be called & the Node will now respond to clicks by changing its background color.
When you call .plug() you can also pass in a configuration object that’ll define defaults for the plugin, so for the second example we also define the color attribute.
Y.one("#booga").plug(Y.Examples.BasicPlugin, { color : "#666" });
This isn’t part of the live example but it’s an important concept, with YUI 3 plugins it’s just as easy to remove functionality as it is to add it. All you need to do is unplug() your plugin from the host. The plugin’s destructor() method is called and the host object loses whatever functionality was provided.
Y.one("#wooga").unplug(Y.Examples.BasicPlugin);
One more thing to note, aside from the YUI3 seed file & loader we never had to include any other <script> tags. This is because YUI3 is rad as hell and the Loader is able to determine based on the modules that YUI().use() asked for & the requires array for the YUI.add() which files it should automatically load from the Yahoo! CDN. It’s super-powerful and one of my favorite features in YUI3.
For a better overview, here’s the complete JS code w/ minimal comments
//create the module
YUI.add('basic-plugin', function(Y) {
//create namespaced plugin class
Y.namespace('Examples').BasicPlugin = Y.Base.create("BasicPlugin", Y.Plugin.Base, [], {
_handle : null,
//constructor
initializer : function() {
this._handle = this.get("host").on("click", function(e) {
var tgt = e.currentTarget,
color = this.get("color");
this.set("color", tgt.getStyle("backgroundColor"));
tgt.setStyle("background", color);
}, this);
},
//clean up on destruction
destructor : function() {
this._handle.detach();
this._handle = null;
}
},
{
NS : "bp",
ATTRS : {
color : { value : "#00F" }
}
});
}, "0.1", { requires : [ "base", "plugin", "node" ] });
YUI().use("node", "basic-plugin", function(Y) {
Y.one("#wooga").plug(Y.Examples.BasicPlugin);
Y.one("#booga").plug(Y.Examples.BasicPlugin, { color : "#666" });
});
And the entire shebang including all the HTML & CSS is available at the sample link.
July 1st, 2010 Comments Off







