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.

Basic YUI3 Plugin Example

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).

<script src="http://yui.yahooapis.com/combo?3.1.1/build/yui/yui.js&amp;3.1.1/build/loader/loader.js"><!--mce:0--></script>

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.

Basic YUI3 Plugin Example

YUI 3 Loader & Aggregated Files

With the launch of the ArenaNet Blog I’ve been working a lot with YUI3 since it is the JS framework that powers all the interactivity on the site. The other great thing it does is provide a Loader that can pull in JS files on-demand as defined by a configuration object you pass to it. Loader has the capability to roll up multiple requests into a single file using a CDN but the functionality to do the same without using a CDN appears to be broken. Adam Moore from the YUI team & I have been discussing the issue on YUI’s bug tracker without a resolution yet.

Since I need to keep moving on this stuff I’ve had to devise my own solution to the issue I’m experiencing. For background, we use Apache Ant to create a “Deploy” version of the WordPress theme on the blog. This process runs through CSS/JS & renames files, compresses them, updates references, does DataURI stuff, etc. It’s pretty nice and allows for easily deploying a very optimized site without making development a nightmare. As part of that I’ve had it roll up two JS files that are always requested together into one file to avoid making an extra HTTP request. Unfortunately when I tried a simple replace of the previous filenames with the new aggregate file’s name YUI 3′s Loader was loading the file twice.

So until I can get a working version of what Adam has suggested here’s how I’ve set up the build system to allow for replacing individual modules in development mode with a single aggregate file for deploying.

//DEV CONFIG
YUI_config = {
    //yes this is a little gross but sadly it's necessary to make loader work how we want w/ ant
    /* dev */ ignore : [ "sidebar-rollup" ], //dev */
    /* live ignore : [ "chiclet", "slider-plugin" ], //live */
    groups : {
        anet : {
            combine : false,
            base : "/blog/wp-content/themes/arenanet/js/lib/",
            modules : {
                "chiclet" : {
                    path : "chiclet.js",
                    skinnable: false,
                    requires : [ "base", "widget", "anim-base", "anim-easing", "sidebar-rollup" ]
                },
 
                "slider-plugin" : {
                    path : "slider.js",
                    requires : ["plugin", "node", "anim-base", "anim-easing", "sidebar-rollup" ]
                },
 
                "gallery-lightbox" : {
                    path : "lightbox.js",
                    requires : ["base", "node", "anim-base", "selector-css3"]
                },
 
                "scroller-plugin" : {
                    path : "scroll.js",
                    requires : ["plugin", "node", "selector-css3", "event-delegate", "anim-base", "anim-scroll", "anim-easing"]
                },
 
                "social-counts" : {
                    path : "social.js",
                    requires : [ "gallery-yql", "node" ]
                },
 
                "sidebar-rollup" : {
                    path : "sidebar.js"
                }
            }
        }
    }
};

which is modified by the Ant build script to

//DEPLOY MODE
YUI_config = {
    //yes this is a little gross but sadly it's necessary to make loader work how we want w/ ant
    /* dev  ignore : [ "sidebar-rollup" ], //dev */
    /* live */ ignore : [ "chiclet", "slider-plugin" ], //live */
    groups : {
        anet : {
            //omitted for brevity, see above code block because this doesn't change between them
            }
        }
    }
};

The comments get stripped out during compression so it doesn’t look quite so gnarly when sent to end-users. It’s also pretty easy to do as an Ant task, all it takes is this

<!-- update references to old js files to new concatted file -->
<replace file="${js.dir}/loader.js" token="/* dev */" value="/* dev " summary="yes" />
<replace file="${js.dir}/loader.js" token="/* live " value="/* live */" summary="yes" />

to do the switch. Later in my custom loader script those two modules are used with

YUI().use("node", "event-base", "imageloader", "selector-css3", "chiclet", "slider-plugin", function(Y) {
    // Code goes here...
});

which will always make sure that both the “Chiclet” and “Slider-Plugin” modules are available. In dev mode it will request chiclet.js & slider.js versus just asking for sidebar.js when deployed.

Does anybody have a better way of doing this? I think Adam’s version is nicer looking in the config but I’m not sure how it will work if a) the rollup file doesn’t exist or b) you don’t want to use the rollup except in production. I guess you could always just do something similar to what I’m doing now to configure it. That all assumes that the issues with it not actually loading the module get figured out, because if the modules aren’t added to the Y instance what’s the point?

JSLint in Programmer’s Notepad, Revisited

This is an update to my previous post, Programmer’s Notepad and JSLint. After a few dev versions where the tools didn’t work they’re back in fighting shape. There were a few annoyances with my previous version around the actual running of JSLint. The WSH build of JSLint on www.jslint.com is set to return after only a single error & reads from Standard Input.

I finally got fed up enough with these limitations to figure out how to fix them.

  1. Grab the fulljslint.js from the very bottom of http://www.jslint.com/lint.html
  2. Grab wsh.js
  3. Concatenate the files together, I saved mine as C:\Users\Pcavit\tools\jslint.js for easy referencing via the %USERPROFILE% environment variable
  4. Then I had to modify the wsh.js portion to read in the file from disk instead of Standard Input & output multiple errors. Here’s my version:
(function () {
	var fso = new ActiveXObject("Scripting.FileSystemObject"),
            f = fso.OpenTextFile(WScript.Arguments(0), 1),
            contents = "";
 
	while(!f.AtEndOfStream) {
		contents += f.ReadAll();
	}
 
	if (!JSLINT(contents, { browser: true, css: true, onevar: true, bitwise: true, regexp: true, newcap: true, immed: true })) {
		var e, i, l;
 
		for(i = 0, l = JSLINT.errors.length; i &lt; l; i++) {
			e = JSLINT.errors[i];
 
			WScript.StdErr.WriteLine('Lint at line ' + e.line + ' character ' + e.character + ': ' + e.reason);
			WScript.StdErr.WriteLine((e.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
			WScript.StdErr.WriteLine("");
		}
 
		WScript.Quit(1);
       }
}());

Once you’ve done that you’ll want to change a few tools settings.

And now you can get multiple error output, with clickable errors. It’s totally awesome.

Awesome PHP debugging function

I still haven’t found a good free setup for debugging PHP but this is the next best thing. Jacob Rosenberg posted a copy of his yo() debugging function. We used it extensively when building http://video.yahoo.com and I can vouch for its awesomeness.

The Greatest PHP Debugging Function of All Time.

I did have one issue with it though, for whatever reason the install of PHP on my DreamHost account wasn’t ever able to pick up any vars if I used the yo() shortcut. I had to modify the shortcut so that it passed func_get_args() to the static function. Weird, and annoying that everything was now wrapped in a superfluous Array() but not the end of the world.

I’m happy to have yo() available to me again for what little PHP work I do these days. It makes me feel warm and safe.

YUI 2.8 Uploader

I know this is old but I finally tried upgrading my uploader implementation at http://tivac.com/upload/ to YUI 2.8. Ran into some nasty problems where the SWF would never fire its contentReady event.

Turns out, it’s a known issue with the Uploader component in YUI 2.8 and it’ll be fixed in the next patch release.

Important Issue: Due to a current bug, the current version of uploader.swf hosted on yui.yahooapis.com in the YUI 2.8 branch is NOT compatible with the uploader.js hosted on yui.yahooapis.com. Until the next bugfix release, you can work around this issue by either locally hosting the older version of uploader.swf (available here), or locally hosting the uploader.js and making the following changes to it:

  • On line 509, in swfObj.addVariable(“elementID”, swfID);, replace “elementID” with “YUISwfId”.
  • On line 512, in swfObj.addVariable(“eventHandler”, “YAHOO.widget.FlashAdapter.eventHandler”);, replace “eventHandler” with “YUIBridgeCallback”.

That only had me frustrated for 5 minutes, thank god I read the documentation.

Arena.net

While I don’t work on the team responsible for the Arena.net site I help out all over the company wherever I can. When it was decided that the old site needed to be updated I got the call. After some discussions around what exactly the site should encompass it came down to wanting to have the site be an aggregator of updates by arena.net on various different services.

Here’s the rundown:

I certainly wasn’t looking forward to building parsers for the multiple different feed types that entails, but YQL came along and saved the day again. I only had to do simple things manually like make sure that Twitter links & @username replies were properly wrappped in <a> tags. To keep from bashing on YQL every time the page is loaded it stores the transformed results of the updates for an hour in a local APC cache. This provided the best of both worlds and made caching super-easy.

The site itself was built using YUI grids, YUI 3b1, and DD_belatedPNG to solve transparent PNG issues in IE6. It’s also using my favorite new PHP framework, Nice Dog. It’s a nano framework (about 100 lines) that is more of a VC than MVC. For a site like Arena.net it’s perfect as there’s no need for models. I could’ve plugged in an ORM to make it the full stack but when getting all your data is as simple as

apc_fetch($key);

the need to build a “proper” MVC site kinda isn’t there any longer.

PatCavit.com

Sunday morning I had a few too many skittles after waking up and decided I wanted to build an actual landing page on patcavit.com

So then I started on it, I had grand plans. There were even icons that I was going to grab from… somewhere. That part of it was rather poorly planned. After doing the basic layout I spent about an hour trying to find the right icons. Eventually I threw up my hands and just dropped the whole idea.

Here’s what I’d planned for it to look like:
052520091392.jpg

Notice my awesome hand-drawn icons… :(

Today I was feeling more up for a challenge though. I did some reading and decided to use YQL thanks to Chris Heilmann’s screencast illustrating how he used it to pull multiple sources of data together. After deciding to just ditch the icons I picked a few colors for the top-level sections instead, and here’s what I came up with.

patcavit.com.png

I like it well enough. Still wish I could’ve found icons I was happy with.

YUI Uploader Updated

The uploader I wrote and talked about in YUI Upload Implementation has been updated to work with YUI 2.6.0 and also with the new Flash security rules for browsing files. The changes were relatively minor, but here’s a quick explanation.

A new element had to be added that could overlay the button that lets you select files. Once the JS fires it makes that element the exact same size/position of the button you can see and then the YUI Upload fills that overlay with a flash object. While the button still exists when you click the event is actually captured by the flash object. This gets around the tighter flash security rules while not changing the upload experience at all. It’s not a perfect solution but at least the uploader works again.

Changed code looks like this in upload.js

var button = YUD.getRegion('browse');
var overlay = YUD.get('btn_overlay');
YUD.setStyle(overlay, 'width', button.right - button.left + "px");
YUD.setStyle(overlay, 'height', button.bottom - button.top + "px");
YUD.setStyle(overlay, 'top', button.top + "px");
YUD.setStyle(overlay, 'left', button.left + "px");
this.uploader = new YAHOO.widget.Uploader('btn_overlay');

Updated uploader still lives at http://tivac.com/upload/

Xbox Live Friends 1.3

In a fit of inspiration I’ve updated the xbox live friends gadget with offline friends and hopefully taken care of the phantom friend problem in a more thorough way. Also went ahead and added a refresh button because two minutes is apparently too much time for some people to wait.

Pictures!

You know what time it is!

Download the gadget

Quick Vista Gadget Development Note

This is more of a reminder for me than anything else, but since I kept searching for this tonight with little luck maybe somebody else will get some use out of it.

When developing a Vista gadget it can be realy useful to set up a gadget folder somewhere other than “%userprofile%\appdata\local\microsoft\windows sidebar\gadgets”. Unfortunately to have the sidebar notice it you’ll need to symlink it. That’s a pretty easy process, and note that just making a shortcut to the folder WON’T WORK.

  1. Open an explorer window to %userprofile%\appdata\local\microsoft\windows sidebar\gadgets
  2. Open a cmd prompt with elevated privileges (start, “cmd”, shift right-click, run as admin)
  3. cd in cmd to %userprofile%\appdata\local\microsoft\windows sidebar\gadgets
  4. mklink /D "Gadget Name.gadget" "c:\path\to\development gadget"

That’ll set up a symlink so you can edit your gadget wherever you want and still have Vista add it to the gadget browser. Handy for those of us that like having all our programming projects in one spot.