{"id":305,"date":"2009-01-28T19:47:16","date_gmt":"2009-01-29T01:47:16","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?page_id=305"},"modified":"2015-02-10T18:49:20","modified_gmt":"2015-02-11T00:49:20","slug":"extending-jquery-ui-widgets","status":"publish","type":"page","link":"https:\/\/bililite.com\/blog\/extending-jquery-ui-widgets\/","title":{"rendered":"Extending jQuery UI Widgets"},"content":{"rendered":"<h2>This page is obsolete; current versions are on my github pages at <a href=\"http:\/\/github.bililite.com\/extending-widgets.html\">github.bililite.com\/extending-widgets.html<\/a>. This page is being kept here for historical purposes.<\/h2>\r\n<h4>Avoiding Bloat in Widgets<\/h4>\r\n<p>A while back, Justin Palmer wrote an excellent article on\r\n\"<a href=\"http:\/\/alternateidea.com\/blog\/articles\/2006\/12\/4\/avoiding-bloat-in-widgets\">Avoiding Bloat in Widgets<\/a>.\"\r\nThe basic premise (no suprise to anyone whose ever dealt with object-oriented programming) is that your widgets should not\r\ndo everything possible; they should do one thing well but be flexible enough to allow others to\r\nmodify them.<\/p>\r\n<p>He describes two ways of extending objects: subclassing and aspect-oriented programming (AOP). Subclassing creates new\r\nclasses, while AOP modfies the methods of a single object. Both can be useful.<\/p>\r\n<p>So let's make Palmer's <code class=\"language-javascript\">Superbox<\/code> widget (it just moves randomly about the screen with mouse clicks):<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\nvar Superbox = {\r\n\t_init: function(){\r\n\t\tvar self = this;\r\n\t\tthis.element.click(function(){\r\n\t\t\tself.move();\r\n\t\t});\r\n\t},\r\n\tmove: function(){\r\n\t\tthis.element.css (this._newPoint());\r\n\t},\r\n\t_newPoint: function(){\r\n\t\treturn {top: this._distance(), left: this._distance()};\r\n\t},\t\r\n\t_distance: function(){\r\n\t\treturn Math.round (Math.random()*this.options.distance);\r\n\t},\r\n\toptions: {\r\n\t\tdistance: 200\r\n\t}\r\n};\r\n$.widget ('ui.superbox', Superbox);\r\n<\/code><\/pre>\r\n<p>I've factored apart a lot of the code, so we have plenty of \"hooks\" to use to extend the method without\r\ncopying code. Note that none of the code refers to \"superbox\" directly, so we can create subclasses that don't know the superclass's name.<\/p>\r\n<div class=\"target\" id=\"experiment1\">Experiment 1 (Click Me)<\/div>\r\n<div><input type=\"button\" id=\"home1\" value=\"Bring It Home\"\/><\/div>\r\n<script>\r\n$(function($){\r\n\t$('.target').css({\r\n\t\tposition: 'relative',\r\n\t\tbackground: 'green',\r\n\t\tpadding: '5px',\r\n\t\theight: '100px',\r\n\t\twidth: '100px',\r\n\t\tcolor: 'white'\r\n\t});\r\n\t$('#experiment1').superbox();\r\n\t$('#home1').click(function(){\r\n\t\t$('#experiment1').css({top: 0, left:0});\r\n\t});\r\n});\r\n<\/script>\r\n\r\n<h4>Subclassing Widgets<\/h4>\r\n<p><strong>Note that jQuery UI 1.9 incorporates most of these ideas, so you may not need to use\r\n<code class=\"language-javascript\" >$.ui.widget.subclass<\/code> at all; it is built in. Use <code class=\"language-javascript\" >$.widget('ui.subclass', $.ui.baseclass);<\/code>. See <a href=\"http:\/\/bililite.com\/blog\/2013\/01\/08\/extending-jquery-ui-widgets-with-jquery-ui-1-9\/\" title=\"Extending jQuery UI Widgets with jQuery UI 1.9\">my post<\/a> on this.<\/strong><\/p>\r\n<p><a href=\"\/inc\/jquery.ui.subclass.js\">Download the code<\/a>.<\/p>\r\n<p>The widget factory (<code>$.widget<\/code>) allows you to use one widget as the base for another,\r\nbut that's not the same as subclassing; it copies all the methods from one widget to the next.<\/p>\r\n<p>So let's use real inheritance to make a new class, <code class=\"language-javascript\">supererbox<\/code>, that moves rather than jumps to its new location.\r\nI'll use Richard Cornford's <a href=\"http:\/\/groups.google.com\/group\/comp.lang.javascript\/msg\/e04726a66face2a2\">variation<\/a> on Douglas Crockford's <a href=\"http:\/\/javascript.crockford.com\/prototypal.html\">prototypal inheritance pattern<\/a> to simplify\r\nsubclassing (you could use a fancier one like Dean Edward's <a href=\"http:\/\/dean.edwards.name\/weblog\/2006\/03\/base\/\">Base<\/a>,\r\nor manipulate <code class=\"language-javascript\">prototype<\/code>s yourself). I'll use Dean Edward's technique for \r\n<a href=\"http:\/\/ejohn.org\/blog\/simple-javascript-inheritance\/\">calling\r\noverridden superclass functions<\/a>.<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\nvar object = (function(){\r\n    function F(){}\r\n    return (function(o){\r\n        F.prototype = o;\r\n        return new F();\r\n    });\r\n})();\r\n<\/code><\/pre>\r\n<p>And create an empty \"base\" widget class.<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.widget('ui.widget',{});\r\n<\/code><\/pre>\r\n<p>And add a method to create subclasses that inherit from the base.<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\nvar OVERRIDE = \/xyz\/.test(function(){xyz;}) ? \/\\b_super\\b\/ : \/.*\/; \r\n$.ui.widget.subclass = function subclass (name){\r\n\t$.widget(name,{}); \/\/ Slightly inefficient to create a widget only to discard its prototype, but it's not too bad\r\n\tname = name.split('.');\r\n\tvar widget = $[name[0]][name[1]], superclass = this, superproto = superclass.prototype;\r\n\t\r\n\t\r\n\tvar args = $.makeArray(arguments); \/\/ get all the add-in methods\r\n\tvar proto = args[0] = widget.prototype = object(superproto); \/\/ and inherit from the superclass\r\n\t$.extend.apply(null, args); \/\/ and add them to the prototype\r\n\twidget.subclass = subclass;\r\n\t\r\n\t\/\/ Subtle point: we want to call superclass _create, _init and _destroy if they exist\r\n\t\/\/ (otherwise the user of this function would have to keep track of all that)\r\n\t\/\/ and we want to extend the options with the superclass's options. We copy rather than subclass\r\n\t\/\/ so changing a default in the subclass won't affect the superclass\r\n\tfor (key in proto) if (proto.hasOwnProperty(key)) switch (key){\r\n\t\tcase '_create':\r\n\t\t\tvar create = proto._create;\r\n\t\t\tproto._create = function(){\r\n\t\t\t\tsuperproto._create.apply(this);\r\n\t\t\t\tcreate.apply(this);\r\n\t\t\t};\r\n\t\tbreak;\r\n\t\tcase '_init':\r\n\t\t\tvar init = proto._init;\r\n\t\t\tproto._init = function(){\r\n\t\t\t\tsuperproto._init.apply(this);\r\n\t\t\t\tinit.apply(this);\r\n\t\t\t};\r\n\t\tbreak;\r\n\t\tcase 'destroy':\r\n\t\t\tvar destroy = proto.destroy;\r\n\t\t\tproto.destroy = function(){\r\n\t\t\t\tdestroy.apply(this);\r\n\t\t\t\tsuperproto.destroy.apply(this);\r\n\t\t\t};\r\n\t\tbreak;\r\n\t\tcase 'options':\r\n\t\t\tvar options = proto.options;\r\n\t\t\tproto.options = $.extend ({}, superproto.options, options);\r\n\t\tbreak;\r\n\t\tdefault:\r\n\t\t\tif ($.isFunction(proto[key]) && $.isFunction(superproto[key]) && OVERRIDE.test(proto[key])){\r\n\t\t\t\tproto[key] = (function(name, fn){\r\n\t\t\t\t\treturn function() {\r\n\t\t\t\t\t\tvar tmp = this._super;\r\n\t\t\t\t\t\tthis._super = superproto[name];\r\n\t\t\t\t\t\ttry { var ret = fn.apply(this, arguments); }   \r\n\t\t\t\t\t\tfinally { this._super = tmp; }\t\t\t\t\t\r\n\t\t\t\t\t\treturn ret;\r\n\t\t\t\t\t};\r\n\t\t\t\t})(key, proto[key]);\r\n\t\t\t}\r\n\t\tbreak;\r\n\t}\r\n};\r\n<\/code><\/pre>\r\n<p>And use it like this to create a new, subclassable superbox and a subclass of that:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.ui.widget.subclass('ui.superbox',{\r\n\t_init: function(){\r\n\t\tvar self = this;\r\n\t\tthis.element.click(function(){\r\n\t\t\tself.move();\r\n\t\t});\r\n\t},\r\n\tmove: function(){\r\n\t\tthis.element.css (this._newPoint());\r\n\t},\r\n\t_newPoint: function(){\r\n\t\treturn {top: this._distance(), left: this._distance()};\r\n\t},\t\r\n\t_distance: function(){\r\n\t\treturn Math.round (Math.random()*this.options.distance);\r\n\t},\r\n\toptions: {\r\n\t\tdistance: 200\r\n\t}\r\n});\r\n\r\n$.ui.superbox.subclass ('ui.supererbox', {\r\n\t\/\/ overriding and new methods\r\n\tmove: function(){\r\n\t\tthis.element.animate(this._newPoint(), this.options.speed);\r\n\t},\r\n\thome: function(){\r\n\t\tthis.element.animate({top:0, left:0}, this.options.speed);\r\n\t},\r\n\toptions: {\r\n\t\tspeed: 'normal'\r\n\t}\r\n});\r\n<\/code><\/pre>\r\n<p>The function signature is <code class=\"language-javascript\">$.namespace.widget.subclass(name &lt;String&gt;, [newMethods &lt;Object&gt;]*)<\/code>,\r\nwhere you can use as many newMethod objects as you want. This lets you use mixin objects, like <code class=\"language-javascript\">$.ui.mouse<\/code>,\r\nthat add a specific set of methods.<\/p>\r\n<p>We now have a new widget called supererbox that is just like superbox but moves smoothly.<\/p>\r\n<div class=\"target\" id=\"experiment2\">Experiment 2 (Click Me)<\/div>\r\n<div><input type=\"button\" id=\"home2\" value=\"Bring It Home\"\/><\/div>\r\n<script>\r\n$(function(){\r\n\t$('#experiment2').supererbox();\r\n\t$('#home2').click(function(){\r\n\t\t$('#experiment2').supererbox('home');\r\n\t});\r\n});\r\n<\/script>\r\n<h4>Calling Superclass Methods<\/h4>\r\n<p>If we want to use the superclass methods in our method, we use <code class=\"language-javascript\">this._super<\/code>:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.ui.supererbox.subclass('ui.superboxwithtext', {\r\n\tmove: function(){\r\n\t\tthis.options.count = this.options.count || 0;\r\n\t\t++this.options.count;\r\n\t\tthis.element.text('Move number '+this.options.count);\r\n\t\tthis._super(); \r\n\t}\r\n});\r\n<\/code><\/pre>\r\n<div class=\"target\" id=\"experiment3\">Experiment 3 (Click Me)<\/div>\r\n<div><input type=\"button\" id=\"home3\" value=\"Bring It Home\"\/><\/div>\r\n<script>\r\n$(function(){\r\n\t$('#experiment3').superboxwithtext();\r\n\t$('#home3').click(function(){\r\n\t\t$('#experiment3').superboxwithtext('home').text('Experiment 3 (Click Me)');\r\n\t});\r\n});\r\n<\/script>\r\n\r\n<h4>Aspect Oriented Programming<\/h4>\r\n<p>Aspect oriented programming allows the user of an object to modify its behavior after it has been\r\ninstantiated.\r\nNew methods don't so much override the old ones as supplement them, adding code before or after (or both) the\r\noriginal code, without hacking at the original class definition.<\/p>\r\n<p>We'll add methods for widgets that are stolen straight from Justin Palmer's article:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.extend($.ui.widget.prototype, { \/\/ note that we could extend $.Widget.prototype to add these to all widgets, rather than ones descended from $.ui.widget\r\n\tyield: null,\r\n\treturnValues: { },\r\n\tbefore: function(method, f) {\r\n\t\tvar original = this[method];\r\n\t\tthis[method] = function() {\r\n\t\t\tf.apply(this, arguments);\r\n\t\t\treturn original.apply(this, arguments);\r\n\t\t};\r\n\t},\r\n\tafter: function(method, f) {\r\n\t\tvar original = this[method];\r\n\t\tthis[method] = function() {\r\n\t\t\tthis.returnValues[method] = original.apply(this, arguments);\r\n\t\t\treturn f.apply(this, arguments);\r\n\t\t}\r\n\t},\r\n\taround: function(method, f) {\r\n\t\tvar original = this[method];\r\n\t\tthis[method] = function() {\r\n\t\t\tvar tmp = this.yield;\r\n\t\t\tthis.yield = original;\r\n\t\t\tvar ret = f.apply(this, arguments);\r\n\t\t\tthis.yield = tmp;\r\n\t\t\treturn ret;\r\n\t\t}\r\n\t}\r\n});\r\n<\/code><\/pre>\r\n<p>And now we can use these methods in our code.<\/p>\r\n<p>For example, let's say we have a cool plugin to make an element pulsate (I know, UI has a <code class=\"language-javascript\">pulsate<\/code> method that does this):<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.fn.pulse = function (opts){\r\n\topts = $.extend ({}, $.fn.pulse.defaults, opts);\r\n\tfor (i = 0; i &lt; opts.times; ++i){\r\n\t\tthis.animate({opacity: 0.1}, opts.speed).animate({opacity: 1}, opts.speed);\r\n\t}\r\n\treturn this;\r\n};\r\n$.fn.pulse.defaults = {\r\n\tspeed: 'fast',\r\n\ttimes: 2\r\n};\r\n<\/code><\/pre>\r\n\r\n<p>And we'll create a supererbox object, then make it pulse before moving:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$('#experiment4').supererbox().supererbox('before','move', function() {\r\n\tthis.element.pulse();\r\n});\r\n<\/code><\/pre>\r\n<div class=\"target\" id=\"experiment4\">Experiment 4 (Click Me)<\/div>\r\n<div><input type=\"button\" id=\"home4\" value=\"Bring It Home\"\/><\/div>\r\n<script>\r\n$(function(){\r\n\t$('#home4').click(function(){\r\n\t\t$('#experiment4').supererbox('home');\r\n\t});\r\n});\r\n<\/script>\r\n<p>Or even make it pulse before and after moving:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$('#experiment5').supererbox().supererbox('around','move', function() {\r\n\tthis.element.pulse();\r\n\tthis.yield();\r\n\tthis.element.pulse();\r\n});\r\n<\/code><\/pre>\r\n<div class=\"target\" id=\"experiment5\">Experiment 5 (Click Me)<\/div>\r\n<div><input type=\"button\" id=\"home5\" value=\"Bring It Home\"\/><\/div>\r\n<script>\r\n$(function(){\r\n\t$('#home5').click(function(){\r\n\t\t$('#experiment5').supererbox('home');\r\n\t});\t\r\n});\r\n<\/script>\r\n<p>Note that we didn't create any new classes to get this new behavior;\r\nwe added the behavior to each object after the object was created.<\/p>\r\n<p>Note that I did not use the widget factory directly. It may be possible to make this more efficient, but I haven't analyzed the code in jQuery UI 1.8 closely enough.<\/p>","protected":false},"excerpt":{"rendered":"This page is obsolete; current versions are on my github pages at github.bililite.com\/extending-widgets.html. This page is being kept here for historical purposes. Avoiding Bloat in Widgets A while back, Justin Palmer wrote an excellent article on \"Avoiding Bloat in Widgets.\" The basic premise (no suprise to anyone whose ever dealt with object-oriented programming) is that [&hellip;]","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":1,"comment_status":"open","ping_status":"closed","template":"","meta":{"footnotes":""},"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/305"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/comments?post=305"}],"version-history":[{"count":17,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/305\/revisions"}],"predecessor-version":[{"id":3486,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/305\/revisions\/3486"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=305"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}