插件让你可以无侵入地为宿主对象(host object)添加功能。宿主衍生自Base类。node、widget等对象都是宿主对象。我们可以继承Plugin.Base类创建插件类。但是这也不是必须的,可以通过其他方法创建插件类。
插件类用来向组件实例中添加小块功/特性,不需要将这些功能/特性整合进组件类中,组件类甚至可以完全对这些功能特性一无所知。这样,我们就可以在组件实例层级使用这些功能/特性,避免为实现某些功能特性而把而组件类构建得很大,或者为了实现不同的功能组合构建多个不同的组件类。
引入YUI种子文件和配置YUI实例就不累述了,详见 YUI 3: YUI 全局对象。
创建插件
以下内容是关于如何创建和使用插件类的,包含如下部分:
- 简单的插件
- 复杂的插件
- 扩展Plugin.Base类
- 插件监听器
简单的插件
对于简单的插件类,如果它们没有自己的事件和属性,不通过监听宿主对象的事件来修改宿主对象的默认行为,不重写宿主对象的方法的话,插件类可以只是简单的原生javascript类。
插件类唯一必须的是一个用作名字空间的静态属性“NS”。该静态属性的值用作从宿主实例上访问插件实例。也就是说,一旦插件被 “插到”宿主实例上后,可以通过hostObj.namespace得到plugin实例的引用。
当插件被“插到”一个宿主实例上后,插件的实例被创建,一个对宿主实例的引用被添加到传入插件构造器的配置对象上。这样,插件实例可以引用宿主实例。(当一个插件被从宿主实例上“拔出”后,该插件实例被销毁。)
以下是一个简单的插件类:
//这个AnchorPlugin插件被设计成Node实例的插件 (宿主是Node实例) function AnchorPlugin(config) { // 存放宿主实例(Node实例)的引用,以便插件的方法使用。 this._node = config.host; } // 被-插入Node实例后,在Node实例的"anchors"属性上能访问到插件 AnchorPlugin.NS = "anchors" AnchorPlugin.prototype = { disable: function() { var node = this._node; var anchors = node.queryAll("a"); anchors.addClass("disabled"); anchors.setAttribute("disabled", true); } };
下面代码展示在Node对象上插入“AnchorPlugin”插件:
var container = Y.one("div.actions"); container.plug(AnchorPlugin);
通过NS属性的值,可以在Node实例上访问到插件实例:
container.anchor.disable();
高级插件类
上述的简单插件类能满足简单功能/特性。但是,当你想在插件类中封装更复杂的功能/特性时,对attributes和events的支持就派上用场了。对于许多插件而言,你将需要改变宿主实例的默认行为(比如:一个Animation 插件可能需要改变widget类的默认show/hide行为)。
对于这样功能复杂的插件,你应该通过扩展Plugin.Base类来构建它。
插件类是Base的子类,因此,它也支持attribute、生命周期方法、自定义事件。另外,我们还可以在插件类中监听响应宿主实例触发的事件,或者在宿主实例某方法执行之前,注入插件自定义的逻辑代码(基于YUI3的AOP基础结构)。Plugin.Base类还在它的“host”属性中存放对宿主实例的引用,可以在插件实现中通过this.get(“host”)访问到宿主实例。
扩展Plugin.Base
你可以像扩展Base类一样扩展Plugin.Base类。需要注意的是扩展Plugin.Base类时,宿主实例被自动设置为插件类的“host”属性的值。而在简单的插件类中需要手动地通过构造器的配置对象把宿主实例设置成插件类的属性值,这样才能在插件类中访问到宿主实例。
高级插件类的结构和其他扩展自Base类的类是一样的。只是多了一个NS静态属性。 (see the Basedocumentation for details about NAME and ATTRS).
// A plugin class designed to animate Widget's show and hide methods. function WidgetAnimPlugin(config) { WidgetAnimPlugin.superclass.constructor.apply(this, arguments); } // Define Static properties NAME (to identify the class) and NS (to identify the namespace) WidgetAnimPlugin.NAME = 'widgetAnimPlugin'; WidgetAnimPlugin.NS = 'fx'; // Attribute definitions for the plugin WidgetAnimPlugin.ATTRS = { animHidden : { ... }, animVisible: { ... } }; // Extend Plugin.Base Y.extend(WidgetAnimPlugin, Y.Plugin.Base, { // Add any required prototype methods });
Plugin的监听器
扩展Plugin.Base类最大的好处就是可以通过Plugin.Base类提供的onHostEvent和afterHostEvent方法来监听宿主实例触发的事件,还可以通过beforeHostMethod和afterHostMethod方法来改变宿主实例的方法。
通过以上由Plugin.Base类提供的方法来改变宿主实例的默认方法,而不是通过修改宿主类来改变宿主实例的默认方法的好处是:通过“插入”方式做了修改的方法在“拔出”以后,会被还原。这很重要,插件被从宿主实例身上“拔出”后,应该被完全地销毁。
事件
正如上述所说,衍生自Plugin.Base类的插件类,可以监听并响应宿主实例触发的事件。
比如,当wideget被渲染的时候,他们都会触发“render”事件。你的插件类可能需要知道这个“render”事件是什么时候发生的,这样它才可以在宿主实例渲染的HTML代码中插入一些自定义的HTML代码。可以使用afterHostEvent方法实现:
// 一个插件类,设计成将widget的show/hide方法改成动画。 function WidgetAnimPlugin(config) { //... } WidgetAnimPlugin.NAME = 'widgetAnimPlugin'; WidgetAnimPlugin.NS = 'fx'; WidgetAnimPlugin.ATTRS = { animHidden : { //... }, animVisible: { //... } }; // 扩展Plugin.Base,重载默认方法_uiSetVisible。该方法原被用作改变显示状态。 Y.extend(WidgetAnimPlugin, Y.Plugin.Base, { initializer : function(config) { // 用自定义动画方法重载Widget的_uiSetVisible方法。 this.beforeHostMethod("_uiSetVisible", this._uiAnimSetVisible); }, _uiAnimSetVisible : function(show) { // hide/show.用为插件配置好的动画实例,把show/hide方法改成动画,替换原来改变显示状态的方式。 if (this.get("host").get("rendered")) { if (show) { this.get("animHidden").stop(); this.get("animVisible").run(); } else { this.get("animVisible").stop(); this.get("animHidden").run(); } // 阻止默认方法执行。 return new Y.Do.Prevent(); } } });