YUI 3.x Home -

YUI Library Examples: Overlay: IO Plugin

Overlay: IO Plugin

This example shows how you can use Widget's plugin infrastructure to add additional features to an existing widget.

We create an IO plugin class for Overlay called StdModIOPlugin. The plugin adds IO capabilities to the Overlay, bound to one of its standard module sections (header, body or footer).

Creating an IO Plugin For Overlay

Setting Up The YUI Instance

For this example, we'll pull in overlay; the io, json and substitute utility modules and the plugin module. The io module provides the XHR support we need for the IO plugin. The json and substitute modules provide the support we need to parse/transform JSON responses into HTML. The Plugin base class is the class we'll extend to create our io plugin class for Overlay. The code to set up our sandbox instance is shown below:

  1. YUI({...}).use("overlay", "substitute", "io", "json", "plugin", function(Y) {
  2. // We'll write our code here, after pulling in the default Overlay widget,
  3. // the IO utility, the Plugin base class along with the
  4. // Substitute and JSON utilities
  5. }
YUI({...}).use("overlay", "substitute", "io", "json", "plugin", function(Y) {
    // We'll write our code here, after pulling in the default Overlay widget, 
    // the IO utility, the Plugin base class along with the 
    // Substitute and JSON utilities
}

Using the overlay module will also pull down the default CSS required for overlay, on top of which we only need to add our required look/feel CSS for the example.

StdModIOPlugin Class Structure

The StdModIOPlugin class will extend the Plugin base class. Since Plugin derives from Base, we follow the same pattern we use for widgets and other utilities which extend Base to setup our new class.

Namely:

  • Setting up the constructor to invoke the superclass constructor
  • Setting up a NAME property, to identify the class
  • Setting up the default attributes, using the ATTRS property
  • Providing prototype implementations for anything we want executed during initialization and destruction using the initializer and destructor lifecycle methods

Additionally, since this is a plugin, we provide a NS property for the class, which defines the property which will refer to the StdModIOPlugin instance on the host class (e.g. overlay.io will be an instance of StdModIOPlugin)

.
  1. /* Standard Module IO Plugin Constructor */
  2. function StdModIOPlugin(config) {
  3. StdModIOPlugin.superclass.constructor.apply(this, arguments);
  4. }
  5.  
  6. /*
  7.  * The namespace for the plugin. This will be the property on the widget,
  8.  * which will reference the plugin instance, when it's plugged in
  9.  */
  10. StdModIOPlugin.NS = "io";
  11.  
  12. /*
  13.  * The NAME of the StdModIOPlugin class. Used to prefix events generated
  14.  * by the plugin class.
  15.  */
  16. StdModIOPlugin.NAME = "stdModIOPlugin";
  17.  
  18. /*
  19.  * The default set of attributes for the StdModIOPlugin class.
  20.  */
  21. StdModIOPlugin.ATTRS = {
  22. uri : {...},
  23. cfg : {...},
  24. formatter : {...},
  25. section: {...},
  26. loading: {...}
  27. };
  28.  
  29. /* Extend the base plugin class */
  30. Y.extend(StdModIOPlugin, Y.Plugin, {
  31.  
  32. // Lifecycle methods.
  33. initializer: function() {...},
  34.  
  35. // IO Plugin specific methods
  36. refresh : function() {...},
  37.  
  38. // Default IO transaction handlers
  39. _defSuccessHandler : function(id, o) {...},
  40. _defFailureHandler : function(id, o) {...},
  41. _defStartHandler : function(id, o) {...},
  42. _defCompleteHandler : function(id, o) {...},
  43. _defFormatter : function(val) {...}
  44. });
/* Standard Module IO Plugin Constructor */
function StdModIOPlugin(config) {
    StdModIOPlugin.superclass.constructor.apply(this, arguments);
}
 
/* 
 * The namespace for the plugin. This will be the property on the widget, 
 * which will reference the plugin instance, when it's plugged in
 */
StdModIOPlugin.NS = "io";
 
/*
 * The NAME of the StdModIOPlugin class. Used to prefix events generated
 * by the plugin class.
 */
StdModIOPlugin.NAME = "stdModIOPlugin";
 
/*
 * The default set of attributes for the StdModIOPlugin class.
 */
StdModIOPlugin.ATTRS = {
    uri : {...},
    cfg : {...},
    formatter : {...},
    section: {...},
    loading: {...}
};
 
/* Extend the base plugin class */
Y.extend(StdModIOPlugin, Y.Plugin, {
 
    // Lifecycle methods.
    initializer: function() {...},
 
    // IO Plugin specific methods
    refresh : function() {...},
 
    // Default IO transaction handlers
    _defSuccessHandler : function(id, o) {...},
    _defFailureHandler : function(id, o) {...},
    _defStartHandler : function(id, o) {...},
    _defCompleteHandler : function(id, o) {...},
    _defFormatter : function(val) {...}
});

Plugin Attributes

The StdModIOPlugin is a fairly simple plugin class. It provides incremental functionality. It does not need to modify the behavior of any methods on the host Overlay instance, or monitor any Overlay events (unlike the AnimPlugin example).

It sets up the following attributes, which are used to control how the IO plugin's refresh method behaves:

uri
The uri to use for the io request
cfg
The io configuration object, to pass to io when initiating a transaction
formatter
The formatter to use to formatting response data. The default implementation simply passes back the response data passed in, unchanged.
section
The Standard Module section to which the io plugin instance is bound. Response data will be used to populate this section, after passing through the configured formatter.
loading
The default content to display while an io transaction is in progress

In terms of code, the attributes for the plugin are set up using the standard ATTRS property:

  1. /* Setup local variable for Y.WidgetStdMod, since we use it
  2.   multiple times to reference static properties */
  3. var StdMod = Y.WidgetStdMod;
  4.  
  5. ...
  6.  
  7. /* Attribute definition */
  8. StdModIOPlugin.ATTRS = {
  9.  
  10. /*
  11.   * The uri to use for the io request
  12.   */
  13. uri : {
  14. value:null
  15. },
  16.  
  17. /*
  18.   * The io configuration object, to pass to io when initiating
  19.   * a transaction
  20.   */
  21. cfg : {
  22. value:null
  23. },
  24.  
  25. /*
  26.   * The default formatter to use when formatting response data. The default
  27.   * implementation simply passes back the response data passed in.
  28.   */
  29. formatter : {
  30. valueFn: function() {
  31. return this._defFormatter;
  32. }
  33. },
  34.  
  35. /*
  36.   * The Standard Module section to which the io plugin instance is bound.
  37.   * Response data will be used to populate this section, after passing through
  38.   * the configured formatter.
  39.   */
  40. section: {
  41. value:StdMod.BODY,
  42. validator: function(val) {
  43. return (!val || val == StdMod.BODY
  44. || val == StdMod.HEADER
  45. || val == StdMod.FOOTER);
  46. }
  47. },
  48.  
  49. /*
  50.   * The default loading indicator to use, when an io transaction is in progress.
  51.   */
  52. loading: {
  53. value: '<img class="yui-loading" width="32px" \
  54. height="32px" src="assets/img/ajax-loader.gif">'
  55. }
  56. };
/* Setup local variable for Y.WidgetStdMod, since we use it 
   multiple times to reference static properties */
var StdMod = Y.WidgetStdMod;
 
...
 
/* Attribute definition */
StdModIOPlugin.ATTRS = {
 
    /*
     * The uri to use for the io request
     */
    uri : {
        value:null
    },
 
    /*
     * The io configuration object, to pass to io when initiating 
     * a transaction
     */
    cfg : {
        value:null
    },
 
    /*
     * The default formatter to use when formatting response data. The default
     * implementation simply passes back the response data passed in. 
     */
    formatter : {
        valueFn: function() {
            return this._defFormatter;
        }
    },
 
    /*
     * The Standard Module section to which the io plugin instance is bound.
     * Response data will be used to populate this section, after passing through
     * the configured formatter.
     */
    section: {
        value:StdMod.BODY,
        validator: function(val) {
            return (!val || val == StdMod.BODY 
                         || val == StdMod.HEADER 
                         || val == StdMod.FOOTER);
        }
    },
 
    /*
     * The default loading indicator to use, when an io transaction is in progress.
     */
    loading: {
        value: '<img class="yui-loading" width="32px" \ 
                    height="32px" src="assets/img/ajax-loader.gif">'
    }
};

The formatter attribute uses valueFn to define an instance based default value; pointing to the _defFormatter method on the StdModIOPlugin instance.

Lifecycle Methods: initializer, destructor

For the purposes of this example, the initializer for the plugin activates the Flash based XDR transport so that the plugin is able to dispatch both in-domain and cross-domain requests (the transport used for any particular uri is controlled through the plugin's cfg attribute).

The destructor terminates any existing transaction, if active when the plugin is destroyed (unplugged).

  1. initializer: function() {
  2. Y.io.transport({
  3. id:'flash',
  4. yid: Y.id,
  5. src:'../../build/io/IO.swf?stamp=' + (new Date()).getTime()
  6. });
  7. }
  8.  
  9. /*
  10.  * Destruction code. Terminates the activeIO transaction if it exists
  11.  */
  12. destructor : function() {
  13. if (this._activeIO) {
  14. Y.io.abort(this._activeIO);
  15. this._activeIO = null;
  16. }
  17. },
initializer: function() {
    Y.io.transport({
        id:'flash',
        yid: Y.id,
        src:'../../build/io/IO.swf?stamp=' + (new Date()).getTime()
    });
}
 
/*
 * Destruction code. Terminates the activeIO transaction if it exists
 */
destructor : function() {
    if (this._activeIO) {
        Y.io.abort(this._activeIO);
        this._activeIO = null;
    }
},

The refresh Method

The refresh method is the main public method which the plugin provides. It's responsible for dispatching the IO request, using the current state of the attributes defined of the plugin. Users will end up invoking the method from the plugin instance attached to the Overlay (overlay.io.refresh()).

  1. refresh : function() {
  2. section = this.get("section");
  3.  
  4. if (section && !this._activeIO) {
  5. var uri = this.get("uri");
  6.  
  7. if (uri) {
  8.  
  9. cfg = this.get("cfg") || {};
  10. cfg.on = cfg.on || {};
  11.  
  12. cfg.on.start = cfg.on.start || Y.bind(this._defStartHandler, this);
  13. cfg.on.complete = cfg.on.complete || Y.bind(this._defCompleteHandler, this);
  14.  
  15. cfg.on.success = cfg.on.success || Y.bind(this._defSuccessHandler, this);
  16. cfg.on.failure = cfg.on.failure || Y.bind(this._defFailureHandler, this);
  17.  
  18. cfg.method = cfg.method; // io defaults to "GET" if not defined
  19.  
  20. Y.io(uri, cfg);
  21. }
  22. }
  23. }
refresh : function() {
    section = this.get("section");
 
    if (section && !this._activeIO) {
        var uri = this.get("uri");
 
        if (uri) {
 
            cfg = this.get("cfg") || {};
            cfg.on = cfg.on || {};
 
            cfg.on.start = cfg.on.start || Y.bind(this._defStartHandler, this);
            cfg.on.complete = cfg.on.complete || Y.bind(this._defCompleteHandler, this);
 
            cfg.on.success = cfg.on.success || Y.bind(this._defSuccessHandler, this);
            cfg.on.failure = cfg.on.failure || Y.bind(this._defFailureHandler, this);
 
            cfg.method = cfg.method; // io defaults to "GET" if not defined
 
            Y.io(uri, cfg);
        }
    }
}

The refresh method, as implemented for the scope of this example, sets up the io configuration object for the transaction it is about to dispatch, filling in the default handlers for io's start, complete, success and failure events, if the user does not provide custom implementations.

The Default IO Event Handlers

The default success listener, pulls the response data from the response object, and uses it to update the content in the Overlay section defined by the section attribute. The response data is passed through the formatter configured for the plugin, converting it to the desired output format:

  1. _defSuccessHandler : function(id, o) {
  2. var response = o.responseXML || o.responseText;
  3. var section = this.get("section");
  4. var formatter = this.get("formatter");
  5.  
  6. // Invoke Overlay method to set content in the currently configured section
  7. this.get("host").setStdModContent(section, formatter(response));
  8. }
_defSuccessHandler : function(id, o) {
    var response = o.responseXML || o.responseText;
    var section = this.get("section");
    var formatter = this.get("formatter");
 
    // Invoke Overlay method to set content in the currently configured section
    this.get("host").setStdModContent(section, formatter(response));
}

The default failure listener, displays an error message in the currently configured section, when io communication fails:

  1. _defFailureHandler : function(id, o) {
  2. this.get("host").setStdModContent(this.get("section"), "Failed to retrieve content");
  3. }
_defFailureHandler : function(id, o) {
    this.get("host").setStdModContent(this.get("section"), "Failed to retrieve content");
}

The default start event listener renders the loading content, which remains in place while the transaction is in process, and also stores a reference to the "inprogress" io transaction:

  1. _defStartHandler : function(id, o) {
  2. this._activeIO = o;
  3. this.get("host").setStdModContent(this.get("section"), this.get("loading"));
  4. },
_defStartHandler : function(id, o) {
    this._activeIO = o;
    this.get("host").setStdModContent(this.get("section"), this.get("loading"));
},

The default complete event listener clears out the "inprogress" io transaction object:

  1. _defCompleteHandler : function(id, o) {
  2. this._activeIO = null;
  3. }
_defCompleteHandler : function(id, o) {
    this._activeIO = null;
}

Using the Plugin

All objects derived from Base are Plugin Hosts. They provide plug and unplug methods to allow users to add/remove plugins to/from existing instances. They also allow the user to specify the set of plugins to be applied to a new instance, along with their configurations, as part of the constructor arguments.

In this example, we'll create a new instance of an Overlay:

  1. /* Create a new Overlay instance, with content generated from script */
  2. var overlay = new Y.Overlay({
  3. width:"40em",
  4. visible:false,
  5. align: {
  6. node:"#show",
  7. points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL]
  8. },
  9. zIndex:10,
  10. headerContent: generateHeaderMarkup(),
  11. bodyContent: "Feed data will be displayed here"
  12. });
/* Create a new Overlay instance, with content generated from script */
var overlay = new Y.Overlay({
    width:"40em",
    visible:false,
    align: {
        node:"#show",
        points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL]
    },
    zIndex:10,
    headerContent: generateHeaderMarkup(),
    bodyContent: "Feed data will be displayed here"
});

And then use the plug method to add the StdModIOPlugin, providing it with a configuration to use when sending out io transactions (The Animation Plugin example shows how you could do the same thing during construction):

  1. /*
  2.  * Add the Standard Module IO Plugin, and configure it to use flash,
  3.  * and a formatter specific to the pipes response we're expecting
  4.  * from the uri request we'll send out.
  5.  */
  6. overlay.plug(StdModIOPlugin, {
  7. uri : pipes.baseUri + pipes.feeds["ynews"].uri,
  8. cfg:{
  9. xdr: {
  10. use:'flash'
  11. }
  12. },
  13. formatter: pipes.formatter
  14. });
/*
 * Add the Standard Module IO Plugin, and configure it to use flash, 
 * and a formatter specific to the pipes response we're expecting 
 * from the uri request we'll send out.
 */
overlay.plug(StdModIOPlugin, {
    uri : pipes.baseUri + pipes.feeds["ynews"].uri,
    cfg:{
        xdr: {
            use:'flash'
        }
    },
    formatter: pipes.formatter
});

For this example, the io plugin is configured to use the flash cross-domain transport, to send out requests to the pipes API for the feed the user selects from the dropdown.

Getting Feed Data Through Pipes

We setup an object (pipes) to hold the feed URIs, which can be looked up and passed to the plugin to dispatch requests for new data:

  1. /* The Pipes feed URIs to be used to dispatch io transactions */
  2.  
  3. var pipes = {
  4. baseUri : 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run? \
  5. _id=6b7b2c6a32f5a12e7259c36967052387& \
  6. _render=json&url=http:/'+'/',
  7. feeds : {
  8. ynews : {
  9. title: 'Yahoo! US News',
  10. uri: 'rss.news.yahoo.com/rss/us'
  11. },
  12. yui : {
  13. title: 'YUI Blog',
  14. uri: 'feeds.yuiblog.com/YahooUserInterfaceBlog'
  15. },
  16. slashdot : {
  17. title: 'Slashdot',
  18. uri: 'rss.slashdot.org/Slashdot/slashdot'
  19. },
  20. ...
  21. },
  22.  
  23. ...
/* The Pipes feed URIs to be used to dispatch io transactions */
 
var pipes = {
    baseUri : 'http:/'+'/pipes.yahooapis.com/pipes/pipe.run? \
               _id=6b7b2c6a32f5a12e7259c36967052387& \
               _render=json&url=http:/'+'/',
    feeds : {
        ynews : {
            title: 'Yahoo! US News',
            uri: 'rss.news.yahoo.com/rss/us'
        },
        yui : {
            title: 'YUI Blog',
            uri: 'feeds.yuiblog.com/YahooUserInterfaceBlog'
        },
        slashdot : {
            title: 'Slashdot',
            uri: 'rss.slashdot.org/Slashdot/slashdot'
        },
        ...
    },
 
    ...

The data structure also holds the default formatter (pipes.formatter) required to convert the JSON responses from the above URIs to HTML. The JSON utility is first used to parse the json response string. The resulting object is iterated around, using Y.each, and substitute is used to generate the list markup:

  1. ...
  2.  
  3. // The default formatter, responsible for converting the JSON responses received,
  4. // into HTML. JSON is used for the parsing step, and substitute for some basic
  5. // templating functionality
  6.  
  7. formatter : function (val) {
  8. var formatted = "Error parsing feed data";
  9. try {
  10. var json = Y.JSON.parse(val);
  11. if (json && json.count) {
  12. var html = ['<ul class="yui-feed-data">'];
  13. var linkTemplate =
  14. '<li><a href="{link}" target="_blank">{title}</a></li>';
  15.  
  16. // Loop around all the items returned, and feed
  17. // them into the template above, using substitute.
  18. Y.each(json.value.items, function(v, i) {
  19. if (i < 10) {
  20. html.push(Y.substitute(linkTemplate, v));
  21. }
  22. });
  23. html.push("</ul>");
  24. formatted = html.join("");
  25. } else {
  26. formatted = "No Data Available";
  27. }
  28. } catch(e) {
  29. formatted = "Error parsing feed data";
  30. }
  31. return formatted;
  32. }
...
 
// The default formatter, responsible for converting the JSON responses received,
// into HTML. JSON is used for the parsing step, and substitute for some basic 
// templating functionality
 
formatter : function (val) {
    var formatted = "Error parsing feed data";
    try {
        var json = Y.JSON.parse(val);
        if (json && json.count) {
            var html = ['<ul class="yui-feed-data">'];
            var linkTemplate = 
                '<li><a href="{link}" target="_blank">{title}</a></li>';
 
            // Loop around all the items returned, and feed 
            // them into the template above, using substitute.
            Y.each(json.value.items, function(v, i) {
                if (i < 10) {
                    html.push(Y.substitute(linkTemplate, v));
                }
            });
            html.push("</ul>");
            formatted = html.join("");
        } else {
            formatted = "No Data Available";
        }
    } catch(e) {
        formatted = "Error parsing feed data";
    }
    return formatted;
}

The change handler for the select dropdown binds everything together, taking the currently selected feed, constructing the URI for the feed, setting it on the plugin, and sending out the request:

  1. /* Handle select change */
  2. Y.on("change", function(e) {
  3. var val = this.get("value");
  4. if (val != -1) {
  5. // Set the new URI value on the io plugin
  6. overlay.io.set("uri", pipes.baseUri + pipes.feeds[val].uri);
  7.  
  8. // Send out a request to refresh the current section's contents
  9. overlay.io.refresh();
  10. }
  11. }, "#feedSelector");
/* Handle select change */
Y.on("change", function(e) {
    var val = this.get("value");
    if (val != -1) {
        // Set the new URI value on the io plugin
        overlay.io.set("uri", pipes.baseUri + pipes.feeds[val].uri);
 
        // Send out a request to refresh the current section's contents
        overlay.io.refresh();
    }
}, "#feedSelector");

Copyright © 2009 Yahoo! Inc. All rights reserved.

Privacy Policy - Terms of Service - Copyright Policy - Job Openings