PluginHost.js revision 64953b0596d07dbee9a9b521ed61c434ce4709b5
/**
* The base module provides the Base class, which objects requiring managed attributes
* and custom event support can extend. The base module also provides two ways to reuse
* code - An augmentable PluginHost interface which provides plugin support
* (which is augmented to the Base class) and Base.build which provides a way to
* build custom classes using extensions.
*
* @module base
*/
/**
* <p>
* An augmentable class, which when added to a "Base" based class, allows
* the class to support Plugins, providing plug and unplug methods and the ability
* to add Plugins through the configuration literal passed to the constructor.
* </p>
* <p>
* The PlugHost's _initPlugins and _destroyPlugins should be invoked by the
* host class at the appropriate point in the instances lifecyle. This is done
* by default for Base class, so developers extending base don't need to do
* anything to get plugin support.
* </p>
* @class PluginHost
*/
var L = Y.Lang;
function PluginHost(config) {
this._plugins = {};
}
PluginHost.prototype = {
/**
* Adds a plugin to the host object. This will instantiate the
* plugin and attach it to the configured namespace on the host object.
*
* @method plug
* @chainable
* @param p {Function | Object |Array} Accepts the plugin class, or an
* object literal with a "fn" property specifying the plugin class and
* a "cfg" property specifying the configuration for the Plugin.
* <p>
* Additionally an Array can also be passed in, with the above function or
* object literal values, allowing the user to add multiple plugins in a single call.
* </p>
* @param config (Optional) If the first argument is the plugin class, the second argument
* can be the configuration for the plugin.
* @return {Base} A reference to the host object
*/
plug: function(p, config) {
if (p) {
if (L.isFunction(p)) {
this._plug(p, config);
} else if (L.isArray(p)) {
for (var i = 0, ln = p.length; i < ln; i++) {
this.plug(p[i]);
}
} else {
this._plug(p.fn, p.cfg);
}
}
return this;
},
/**
* Removes a plugin from the host object. This will destroy the
* plugin instance and delete the namepsace from the host object.
*
* @method unplug
* @param {String | Function} plugin The namespace of the plugin, or the plugin class with the static NS namespace property defined. If not provided,
* all registered plugins are unplugged.
* @return {Base} A reference to the host object
* @chainable
*/
unplug: function(plugin) {
if (plugin) {
this._unplug(plugin);
} else {
var ns;
for (ns in this._plugins) {
if (this._plugins.hasOwnProperty(ns)) {
this._unplug(ns);
}
}
}
return this;
},
/**
* Determines if a plugin has plugged into this host.
*
* @method hasPlugin
* @param The plugin's namespace
* @return {boolean} returns true, if the plugin has been plugged into this host, false otherwise.
*/
hasPlugin : function(ns) {
return (this._plugins[ns] && this[ns]);
},
/**
* Initializes static plugins registered on the host (using the
* Base.plug static method) and any plugins passed to the
* instance through the "plugins" configuration property.
*
* @method _initPlugins
* @param {Config} config The configuration object literal for the host.
* @private
*/
_initPlugins: function(config) {
// Class Configuration
var classes = this._getClasses(),
plug = [],
unplug = {},
constructor, i, classPlug, classUnplug, pluginClassName;
//TODO: Room for optimization. Can we apply statically/unplug in same pass?
for (i = classes.length - 1; i >= 0; i--) {
constructor = classes[i];
classUnplug = constructor._UNPLUG;
if (classUnplug) {
// subclasses over-write
Y.mix(unplug, classUnplug, true);
}
classPlug = constructor._PLUG;
if (classPlug) {
// subclasses over-write
Y.mix(plug, classPlug, true);
}
}
for (pluginClassName in plug) {
if (plug.hasOwnProperty(pluginClassName)) {
if (!unplug[pluginClassName]) {
this.plug(plug[pluginClassName]);
}
}
}
// User Configuration
if (config && config.plugins) {
this.plug(config.plugins);
}
},
/**
* Unplugs and destroys all plugins on the host
* @method _destroyPlugins
* @private
*/
_destroyPlugins: function() {
this._unplug();
},
/**
* Private method used to instantiate and attach plugins to the host
*
* @method _plug
* @param {Function} PluginClass The plugin class to instantiate
* @param {Object} config The configuration object for the plugin
* @private
*/
_plug: function(PluginClass, config) {
if (PluginClass && PluginClass.NS) {
var ns = PluginClass.NS;
config = config || {};
config.host = this;
if (this.hasPlugin(ns)) {
// Update config
this[ns].setAttrs(config);
} else {
// Create new instance
this[ns] = new PluginClass(config);
this._plugins[ns] = PluginClass;
}
}
},
/**
* Unplugs and destroys a plugin already instantiated with the host.
*
* @method _unplug
* @private
* @param {String | Function} plugin The namespace for the plugin, or a plugin class with the static NS property defined.
*/
_unplug : function(plugin) {
var ns = plugin,
plugins = this._plugins;
if (L.isFunction(plugin)) {
ns = plugin.NS;
if (ns && (!plugins[ns] || plugins[ns] !== plugin)) {
ns = null;
}
}
if (ns) {
if (this[ns]) {
this[ns].destroy();
delete this[ns];
}
if (plugins[ns]) {
delete plugins[ns];
}
}
}
};
/**
* Registers plugins to be instantiated at the class level (plugins
* which should be plugged into every instance of the class by default).
*
* @method PluginHost.plug
* @static
*
* @param {Function} hostClass The host class on which to register the plugins
* @param {Function | Array} plugin Either the plugin class, an array of plugin classes or and array of object literals (with fn and cfg properties defined)
* @param {Object} config (Optional) If plugin is the plugin class, the configuration for the plugin
*/
PluginHost.plug = function(hostClass, plugin, config) {
// Cannot plug into Base, since Plugins derive from Base [ will cause infinite recurrsion ]
var p, i, l, name;
if (hostClass !== Y.Base) {
hostClass._PLUG = hostClass._PLUG || {};
if (!L.isArray(plugin)) {
if (config) {
plugin = {fn:plugin, cfg:config};
}
plugin = [plugin];
}
for (i = 0, l = plugin.length; i < l;i++) {
p = plugin[i];
name = p.NAME || p.fn.NAME;
hostClass._PLUG[name] = p;
}
}
};
/**
* Unregisters any class level plugins which have been registered by the host class, or any
* other class in the hierarchy.
*
* @method PluginHost.unplug
* @static
*
* @param {Function} hostClass The host class from which to unregister the plugins
* @param {Function | Array} plugin The plugin class, or an array of plugin classes
*/
PluginHost.unplug = function(hostClass, plugin) {
var p, i, l, name;
if (hostClass !== Y.Base) {
hostClass._UNPLUG = hostClass._UNPLUG || {};
if (!L.isArray(plugin)) {
plugin = [plugin];
}
for (i = 0, l = plugin.length; i < l; i++) {
p = plugin[i];
name = p.NAME;
if (!hostClass._PLUG[name]) {
hostClass._UNPLUG[name] = p;
} else {
delete hostClass._PLUG[name];
}
}
}
};
Y.namespace("Plugin").Host = PluginHost;