Mint korábban ígértem, továbbfejlesztjük az előző kis jQuery widget-et, mégpedig úgy, hogy bármilyen elem bármilyen eseményeihez köthető legyen, sőt javascript kifejezéseket is kitudjunk értékelni. Tehát a cél a következő HTML markup életképessé tétele:
<ul id="menu"> <li> <!-- Egyszerű string command. --> <a role="button" href="#" data-command='selectItem'> <span>Select</span> </a> </li> <li> <!-- A "$value" automatikusan behelyettesítésre kerül a "select" aktuális értével --> <!-- ill. megadunk 2 eseményt "click" és "change", amikor eltüzelődjön a command. --> <select data-command='{"name":"selectItem","params":"$value","events":"click change"}'> <option>Item 1</option> <option>Item 2</option> </select> </li> <li> <!-- A JavaScript kifejezés kiértékelésre kerül a "select" kontextusába. --> <!-- (ha nem adunk meg eseményt, akkor "select"-hez auto a "change" lesz attacholva) --> <select data-command='{"name":"selectItem","params":"$(this).val()","eval":true}'> <option>Item 1</option> <option>Item 2</option> </select> </li> </ul> <script type="text/javascript"> $(function () { $('#menu').commands({ source: { // // A "selectItem" command, amit bekötöttünk. // selectItem: function (options) { alert(JSON.stringify(options)); } } }) // // Rögtön elsütjük az első "select" parancsát // (a jQuery selector ilyenkor a kontextusban marad). // .commands('trigger', 'select[data-command]:first'); }); </script>
Ahhoz, hogy mindezt életre hívjuk a következőképp kell módosítanunk a korábbi kódunkat:
(function ($, undefined) { $.widget('ui.commands', { options: { source: null }, // // A widget konstruktora, ameny egyszer hívodik meg / DOM element. // Van egy _init() metódus is, a különbség, hogy az mindig eltüzelődik // amikor az adott elemen megpróbáljuk inicializálni többször a widgetet. // Tulajdonképp az is használható lenne a lentebb majd említett parse() helyett. // _create: function () { this._attachEvents(); }, // // Hozzáadjuk az eseménykezelőket // a DOM elemekhez, amik "this.element" // kontextusába esnek. // _attachEvents: function () { // // A jQuery widgetek konvencionálisan // "self" elnevezést használnak a widget-re, // ha már szükség van rá egy loopban például. // A loop-on belül pedig $this -t az aktuális elemre. // var self = this; $('[data-command]', this.element).each(function () { // // Megrpóbáljuk kihalászni az eseményeket // a data-command-ból. Ha nincs megadva explicit, // akkor mi magunk választunk egy event handler-t // a DOM elem típusa alapján. // var events = self._parseEventsByElement(this) || self._createEventsByElement(this); if (events) { for (var i = 0; i < events.length; i++) { // // A widget minden eseményét a ".commands" névtérbe // kötjük (pl. "click.commands"). A jQuery "bind" // erre ad lehetőséget nekünk, így együtt tudjuk // kezelni az összes widget eseményünket amikor unbindoljuk. // A $.proxy még mindig kell a kontextus átadás miatt. // $(this).bind( events[i] + '.commands', $.proxy(self._commandEvent, self) ); } } }); }, // // Eltávolítunk minden ".commands" névteres eseménykezelőt // ami a "this.element" kontextusán belül taláható. // _detachEvents: function () { $('[data-command]', this.element).unbind('.commands'); }, // // Parsoljuk az explicit megadott eseményeket, amiket // egy szóköz választ el egymástól. // _parseEventsByElement: function (element) { var events = $(element).data('command').events; if (!events) { return null; } return events.split(' '); }, // // Magunk definiálunk eseményeket a megadott DOM elemhez. // _createEventsByElement: function (element) { var events; switch (element.nodeName) { case 'INPUT': { var type = element.getAttribute('type'); events = (type === 'submit' || type === 'button' ? 'click' : 'change'); break; } case 'SELECT': case 'TEXTAREA': { events = 'change'; break; } default: { events = 'click'; } } return events.split(' '); }, // // Az eseménykezelő, ami a kötött eseményekre eltüzelődik. // _commandEvent: function (event) { event.preventDefault(); this.trigger(event.currentTarget); }, // // Eltávolít és újra hozzáad minden eseménykezelőt. // A "live" megszűnt, de így orvosolható ha szükséges. // parse: function () { this._detachEvents(); this._attachEvents(); }, // // Eltüzeli a kötött parancsot a megadott elemen. // A "target" nemcsak DOM elem lehet, hanem // lehet akár egy jQuery selector is, a lényeg, hogy // az "this.element" kontextusába működjön. // trigger: function (target) { var element = $(target, this.element); if (!element.length) { return; } var command = element.data('command'); if (!command) { return; } // // Végrehajtjuk az elementen megadott parancsot. // Ha a params === '$value', akkor kinyerjük // az element értékét ( $(...).val() ) és azt passzoljuk // mint paramétert, ha szükség van kiértékelésre (JS expression), // akkor a "target" element kontextusába futtatjuk a kódot. // this.execute.call(this, { name: command.name || command, params: command.params ? command.params === '$value' ? element.val() : command.eval ? function () { return eval(command.params) } .call(target) : command.params : null }); }, // // Végrehajtunk egy parancsot, ahol az // options.name a parancs neve, az optons.params // a (kiértékelt) paramétereket tartalmazza. // execute: function (options) { var source, method; if ((source = this.options.source) && $.isFunction(method = source[options.name])) { options.params ? method.call(source, options.params) : method.call(source); } }, // // Felszabadítjuk az erőforrásokat. // Az összes ".commands" eseménykezelőt eltávolítjuk // a kontextuson belül. // destroy: function () { this._detachEvents(); $.Widget.prototype.destroy.call(this); } }); })(jQuery);
Ezt még kiegészíthetjük billentyűzet kezeléssel, gyakorlatilag bármivel amit a data-command attribútumban megtudunk adni. A lényegét tekintve a dolognak az, hogy így már egy sokkal rugalmasabb, bármire attacholható command managert kapunk. A command-ok nagy előnye a sima event handlerek írogatásával szemben az, hogy egyrészt nincs szükség gányolásra, szépen elválik a view a logikától, viszonylag könnyen karbantartható, könnyedén tudok egy meglévő parancsot definiálni egy másik elemhez is, mint ahogy azt a fenti példában is láthattuk.
Ez a módszer szépen keverhető a jQuery template és datalink megoldásokkal.