I mentioned last time that I would talk about the IO Plugin that we have that makes one of the most common tasks of updating content.
So first, I'll show you how to use it, then we'll talk more about how to make your own plugin.
So let's set up our sandbox:
AUI().use('io-plugin', function(A){
// Our code will go here
});
So the IO plugin is essentially everything that A.io.request is. All the same arguments, same behavior, but what it does for you is kinda cool.
There is a common pattern we kept seeing in our ajax calls which was:
- Insert loading icon into region
- Do ajax call
- On success, update region with new content
Here is what the code would look might like before:
var contentNode = A.one('#contentNode');
if(contentNode) {
contentNode.html('<div class="loading-animation"></div>');
var myRequest = A.io.request('test.html', {
on: {
success: function(event, id, xhr) {
contentNode.html(this.get('responseData'));
}
}
});
}
It's a trivial process, but when you see it so often, you start to think that it's one of those patterns that could imagine yourself sitting in a padded room chanting over and over.
So we created a handy little plugin that handles this for us, and in a much nicer way. What this plugin does is does an ajax request the same as A.io.request, but also adds a loading mask to the area that we want to load content into. As soon as the content is ready, it parses through it and looks for any javascript that may be inside of it and executes it, and sets the content into our area.
var contentNode = A.one('#myNode');
if(contentNode) {
contentNode.plug(A.Plugin.IO, { uri: 'test.html' });
}
And that's it.
Here's what it looks like:
It basically will mask any existing content, and add a loading indicator that is centered above it.
Here is some of the cool stuff about the plugin (and all plugins in Alloy):
1. It has it's own namespace
This means that this plugin lives inside of it's own area on the node so that it won't clobber with other plugins. Here's a good reason. A.Plugin.IO inherits all of the methods and properties that are on A.io.request, so you can call things like .start(), .stop() (to start and stop the request of course), or .set('uri', 'new_test.html'), or .set('dataType', 'json') and everything else we covered in the <a href="http://www.liferay.com/web/nathan.cavanaugh/blog/-/blogs/alloyui-working-with-ajax?_33_redirect=%2Fweb%2Fnathan.cavanaugh%2Fblog">last post</a>.
If all of that was placed on the main object, then it would conflict with any methods that might exist already on that node, or maybe another plugin.
So instead, it's placed in a namespace, and you can access that like so:
contentNode.io
So if you want to set the dataType on the plugin to json, you can do:
contentNode.io.set('dataType', 'json');
or if you want to stop the connection:
contentNode.io.stop();
2. Plugins can be "unplugged"
This is incredibly useful if you're writing a plugin that should do some clean up work when a user is finished with it (for instance, if you have a plugin that adds in some children elements or adds on some class names to a container).
You would just call:
contentNode.unplug(A.Plugin.IO);
3. Plugins can be plugged to NodeLists as well as Nodes
So this would work as well:
var contentNodes = A.all('.content-nodes');
contentNodes.plug(A.Plugin.IO, { uri: 'test.html' });
Then we could grab the first item in the NodeList and access the plugin namespace
contentNodes.item(0).io.set('cache', false);
4. Plugins can also be on Widgets
I'll cover widgets more next time, but the same exact process applies, and in fact, the IO plugin is written in such a way that it knows whether it's in a Node or a Widget and will behave accordingly.
5. Plugging doesn't have to be a per instance affair.
You can do this:
A.Node.plug(A.Plugin.IO, {autoLoad: false, uri: 'test.html'});
Now you could do:
var contentNode = A.one('#contentNode');
if(contentNode) {
contentNode.io.start();
}
The difference is that since we called A.Node.plug() (which is a static method on the Node class), it plugs all newly created instances with that plugin.
I recommend doing it on a per instance basis, however, simply because 1, you'll consume less resources, and two, you don't have to worry about if your existing objects have been plugged.
6. You can plug with multiple plugins at once.
So for instance, you can do this:
contentNode.plug([
{ fn: A.Plugin.IO, cfg: {uri: 'test.html'} },
{ fn: A.Plugin.MyOtherPlugin }
]);
If that looks confusing, feel free to ignore it, but it simply is a way to pass in mutliple plugins and their configurations (if they need one) all at once.
Creating a plugin
What's the simplest way to get started creating a plugin? Well here's what's to remember: A plugin, at the very least, is a function, with a property on it called NS which will be it's namespace.
So for this example, I'm going to create a plugin that takes an input field, and inserts a " defaultValue". When you focus the field, if the value matches the " defaultValue", it will empty the field, and allow the user to enter their value. When the user moves away from the field, if they haven't entered anything new, it will add in the default text.
If you wish to jump to the demo, go ahead and take a look here:
Plugin Demo.
I'm going to start with this markup:
<input data-defaultValue="Enter Text" id="myInput" type="text" value="" />
HTML5 allows for custom attributes if you prefix the attribute with "data-", so you'll notice I added a new attribute called " data-defaultValue", which our plugin will read.
So I'll create the javascript:
var defaultValuePlugin = function(config) {
var node = config.host;
var defaultValue = node.getAttribute('data-defaultValue');
var startingValue = node.val();
if (!startingValue) {
node.val(defaultValue);
}
node.on('focus', function(event) {
var value = node.val();
if (value == defaultValue) {
node.val('');
}
});
node.on('blur', function(event) {
var value = node.val();
if (value == '') {
node.val(defaultValue);
}
});
};
defaultValuePlugin.NS = 'defaultValue';
Now all we have to do to get it working is simply plug it onto a node:
A.one('#myInput').plug(defaultValuePlugin);
I'll go over some points of the code above.
One is that, notice that the first line points to config.host. The argument config is the configuration object that is passed into the plugin, but by default the host is always passed into the plugin, so you always have access from the plugin to whatever is being plugged.
It's like a magic link to whatever item you're plugging.
The next lines I'm doing the basic work getting an attribute, setting a value if one hasn't been set, and in the bulk of it, attaching focus and blur listeners to do the checking for the value.
On the last line, I'm attaching a property called NS to the function that we created. This is the namespace that this plugin will live under, and even if we don't need to access anything specifically, it's there so we can plug something without worrying about it colliding with any other plugins.
This is really just scratching the surface of the power that the plugin system offers, but I wanted to show a simple case, rather than bog down in the mire of complexity. If there is any interest in seeing advanced plugins, I can always write an 11th blog post, but the
YUI3 page also offers a lot more info if you would like to investigate further as well.
Until next time, see you guys later!