вторник, 22 марта 2011 г.

Набор утилит-классов для виджета(2. Base & Plugin)

yui3 предлагает автоматизировать аугментацию утилиты Attribute в свой кастомный класс, для этого существует класс Base, который и выполняет в себе аугментацию Атрибута. Если нужно реализовать в своем классе специализацию атрибута нужно просто наследовать класс Base.



Кроме аугментации Атрибута Бейс также аугментит класс Plugin.Host. Который позволяет в инстанция бейса подключать внешние обьекты-плагины. Внутренняя реализация плагина не важна, есть общепринятый интерфейс, который приносит дополнительный функционал в конкретную инстанцию. Для этого нужно сделать
ins.plug(pObj, configs)
То есть этот плагин может подключаться к разным инстанциям наследников класса бейс и привносить по желанию опциональный функционал.

В бейсе также реализовано статические методы плагин.хоста, что позволяет во все инстанции выбранного класса подключать выбранный плагин.
Base.plug(baseInheritorClass, pObj, config)

Но все же если в наш класс нужно привнести дополнительный функционал не только в констексте данной стрницы, а вообще расширить класс. Нам нужно воспользовать расширением.
1. То есть, если функционал может как быть в инстанции класса из нашей библиотеки, так и не быть, -- мы должны использовать прототипные методы плагин.хоста из наших инстанций.
2. Если в данный конкретный момент использования нашей библиотеки, всем нашим инстанциям нужен функционал плагина -- пользуемся статическим методом Base.plug.
3. Если наша библиотека всегда будет предлагать класс с расширенным функционалом, то нужно класс расширять.

Расширения делаются так:
/* Main Class */
function Panel(cfg) {
    Panel.superclass.constructor.apply(this, arguments);
}
 
Panel.ATTRS = {
    // Panel attributes
    close : { ... },
    minimize : { ... },
    shadow : { ... },
    ...
};
 
Y.extend(Panel, Y.Base, {
    // Panel methods
    show : function() { ... },
    hide : function() { ... },
    minimize : function() { ... }
};
 
/* Additional Resizable Feature */
function Resizable() {
    this._initResizable();
}
 
Resizable.ATTRS = {
    handles : { ... },
    constrain : { ... }
};
 
Resizable.prototype = {
    _initResizable : function() { ... } 
    lock : function() { ... }
};
 
/* Additional Modality Feature */
function Modal() {
    this._initModality();
}
 
Modal.ATTRS = {
    modal : { ... },
    region : { ... }
};
 
Modal.prototype = {
    _initModality : function() { ... },
    showMask() : function() { ... },
    hideMask() : function() { ... }
};
 
// Create a new class WindowPanel, which extends Panel, and 
// combines methods/attributes from Resizable and Modal
 
var WindowPanel = Y.Base.build("windowPanel", Panel, [Resizable, Modal]);
 
var wp = new WindowPanel({
    shadow: true, 
    modal: true, 
    handles:["e", "s", "se"]
});
 
wp.show();
wp.lock();

Это первый способ с помощью которого можно создать расширение(by Y.Base.build) - он создается новый класс (в конструкторе нового класса вызываются контрукторы раширений). Кстати все классы и расширяемый и расширяющие должны быть наследнками Y.Base.

Base.mix(class, extensions) -- расширяет существующий класс (а не делает его копию и изменяет еe, как build). Тоесть в случае билда, мы можем пользоваться тут же и расширенным классом и его оригинальным коллегой.

Base.create ( name , main , extensions , px , sx ) -- это третий способ. Он дает новому классу имя нейм, копирует класс мейн, расширяет его массивом классов из екстеншин, если нужно то добавляет в прототип класса методы из пеикс, и если нужно добавляет статические атрибуты/методы из есикс. Тот же билд только еще с двумя наворотами)

Хух! Со сложным и запутанным закончили, теперь переходим к простому и красивому...)
Класс бейс и его наследники дожны иметь обязательно два статических атрибута: NAME и ATTRS.
Первый это имя класса, которое например вставлется префиксом во все события вызванные нашим классом.
// NAME is used to prefix the provided event type, if not already prefixed,
// when publishing, firing and subscribing to events.
 
MyClass.prototype.doSomething = function() {
    // Actually fires the event type "myclass:enabled"
    this.fire("enabled");
}
 
...
 
var o = new MyClass(cfg);
 
o.on("enabled", function() {
    // Actually listening for "myclass:enabled".
});
 
o.on("myclass:enabled", function() {
    // Also listening for "myclass:enabled"
});

Второй это хранилище атрибутов с его настройками. В момент когда вызовется конструктор нашего класса, в него можно будет передать инициализации наших атрибутов, который пойдут вторым аргументом в метода атрибута this.addAttrs(ClassName.ATTRS, userValues).

Base содержит в себе финальные методы init и destroy. Возникает вопрос, как же мы можем поучавствовать в процессах инициализации и разрушения в нашем классе, наслденике от бейс? Дело в том, что эти два метода внутри себя вызывают методы this.initializer() и this.destructor(). Поэтому мы можем эти методы переоперделить в нашем прототипе:
Y.extend(MyClass, Y.Base, {
 
    // Prototype methods for your new class
 
    // Tasks MyClass needs to perform during
    // the init() lifecycle phase
    initializer : function(cfg) {
        this._wrapper = Y.Node.create('
'); }, // Tasks MyClass needs to perform during // the destroy() lifecycle phase destructor : function() { Y.Event.purgeElement(this._wrapper); this._wrapper.get("parentNode").removeChild(this._wrapper); this._wrapper = null; } });

Собственно плагины)
Простой плагин должен соответвовать простому правилу - иметь статическое свойство неймспейса, через которое к нему потом можно обратиться из хоста(обьекта к торому он был подключен)

И так плагин - обычный класс. Метод Plagin.Host.plug, который проаугменчен в Base, получает конструктор плагина, внутри всебе создает инстанцию, передав в контруктор ссылку на его хост:

// This AnchorPlugin is designed to be added to Node instances (the host will be a Node instance)
 
function AnchorPlugin(config) {
 
    // Hold onto the host instance (a Node in this case), 
    // for other plugin methods to use.
 
    this._node = config.host;
}
 
// When plugged into a node instance, the plugin will be 
// available on the "anchors" property.
AnchorPlugin.NS = "anchors"
 
AnchorPlugin.prototype = {
    disable: function() {
        var node = this._node;
        var anchors = node.queryAll("a");
        anchors.addClass("disabled");
        anchors.setAttribute("disabled", true);
    }
};

var container = Y.one("div.actions");
container.plug(AnchorPlugin);


container.anchors.disable();


Но у нас тажке плагин может не просто раширять функциоанльность обьекта, а и изменнять его функциональность. Например выполнять дополнительные действия, реагируя на события, которые генерит его хост; изменять методы хоста. В таком случае нам нужно наследовать класс Plugin.Base, который позволит все это сделать.
Как обычно этот класс наследник бейса. Ему как и простому плагину нужно обязательно установить статическое поле NS, и того у нас получается 3 статических поля:
  • NAME;
  • ATTRS;
  • NS.

Plugin.Base также приносит с собой следующие методы:
onHostEvent(type, fn, context)
Можно приктутить обработчик события на том этапе когда можно отменить дефолтовый обработчик
afterHostEvent(type, fn, context)
На этом этапе дефолтовый обоработчик отменять поздно.
beforeHostMethod(strMethod, fn, context)
Интересная штука, внутри этого метода бейс плагин пользуется улугами Y.Do, который реализует кастомный движок событий. Он предоставляет синтетические события DOM. С помощью этого метода мы можем выполниться некую логику перед каждым вызовом strMethod. И если нужно, то можно даже отмерить родной вызов метода хоста, вернув из fn return new Y.Do.Prevent();
afterHostMethod(strMethod, fn, context)
Таже малина только уже после вызова родного метода strMethod, тут его отменять поздно.
doBefore(strMethod, fn, context)
Этот интересный метод проверяет оператором IN, если есть метод strMethod в хосте, то догда выполняет beforeHostMethod с пришедшими аргументами, если нет - onHostEvent
doAfter(strMethod, fn, context)
Это брат предыдущего метода только на уже после события/вызова метода


Пример для события:
// A plugin which introduces rounded corners to a widget.
function RoundedCornersPlugin(config) {
    //...
}
 
RoundedCornersPlugin.NAME = 'roundedCornersPlugin';
RoundedCornersPlugin.NS = 'corners';
 
Y.extend(RoundedCornersPlugin, Y.Plugin.Base, {
 
    // Automatically called by Base, during construction
    initializer: function(config) { 
         // "render" is a widget event 
        this.afterHostEvent('render', this.insertCornerElements);
    },
 
    insertCornerElements: function() {
        var widget = this.get("host");
        var boundingBox = widget.get("boundingBox");
 
        var tl = Y.Node.create(TL_TEMPLATE);
        //...
 
        boundingBox.appendChild(tlNode);
        boundingBox.appendChild(trNode);
        boundingBox.appendChild(blNode);
        boundingBox.appendChild(brNode);
    }
});


Пример для метода:
// A plugin class designed to animate Widget's show and hide methods.
function WidgetAnimPlugin(config) {
    //...
}
 
WidgetAnimPlugin.NAME = 'widgetAnimPlugin';
WidgetAnimPlugin.NS = 'fx';
 
WidgetAnimPlugin.ATTRS = {
 
    animHidden : {
        //...
    },
 
    animVisible: {
        //...
    }
};
 
// Extend Plugin.Base, and override the default
// method _uiSetVisible, used by Widget to flip the visibility
Y.extend(WidgetAnimPlugin, Y.Plugin.Base, {
 
    initializer : function(config) {
 
        // Override Widget's _uiSetVisible method, with the custom animated method
        this.beforeHostMethod("_uiSetVisible", this._uiAnimSetVisible);
    },
 
    _uiAnimSetVisible : function(show) {
        // Instead of flipping visibility, use the animation
        // instances configured for the plugin to animate
        // hide/show.
        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();
            }
 
            // Prevent the default method from being invoked.
            return new Y.Do.Prevent();
        }
    }
});
 

Комментариев нет:

Отправить комментарий