{"id":27,"date":"2008-08-04T20:21:06","date_gmt":"2008-08-05T02:21:06","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/index.php\/2008\/08\/04\/extending-jquery-ui-widgets\/"},"modified":"2009-01-28T20:09:35","modified_gmt":"2009-01-29T02:09:35","slug":"extending-jquery-ui-widgets","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2008\/08\/04\/extending-jquery-ui-widgets\/","title":{"rendered":"Extending jQuery UI Widgets"},"content":{"rendered":"<p><strong>This page is obsolete (it uses jQuery UI 1.5). Please see <a href=\"\/blog\/extending-jquery-ui-widgets\/\">the updated page<\/a>.<\/strong><\/p>\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 who's 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<!--more-->\r\n<p>So let's make Palmer's <code>Superbox<\/code> widget (it just moves randomly about the screen with mouse clicks):<\/p>\r\n<pre>\r\nvar Superbox = {\r\n\tinit: function(){\r\n\t\tthis.element.click(function(){\r\n\t\t\t$.data(this,'superbox').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\tnewPoint: function(){\r\n\t\treturn {top: this.distance(), left: this.distance()};\r\n\t},\t\r\n\tdistance: function(){\r\n\t\treturn Math.round (Math.random()*this.getData('distance'));\r\n\t}\r\n};\r\n$.widget ('yi.superbox', Superbox);\r\n$.yi.superbox.defaults = { distance: 200 };\r\n<\/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. The <code>$.data(this,'superbox')<\/code> is the idiom to get the widget object from a DOM element.<\/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$.yi = {}; \/\/ $.widget should do this for us!\r\nvar Superbox = {\r\n\tinit: function(){\r\n\t\tthis.element.click(function(){\r\n\t\t\t$.data(this,'superbox').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\tnewPoint: function(){\r\n\t\treturn {top: this.distance(), left: this.distance()};\r\n\t},\t\r\n\tdistance: function(){\r\n\t\treturn Math.round (Math.random()*this.getData('distance'));\r\n\t}\r\n};\r\n$.widget ('yi.superbox', Superbox);\r\n$.yi.superbox.defaults = { distance: 200 };\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 (The First Try)<\/h4>\r\n<p>Now let's make a new class, <code>supererbox<\/code>, that moves rather than jumps to its new location.\r\nI'll use Douglas Crockford's <a href=\"ref?\">prototypal inheritance pattern<\/a> to simplify\r\nsubclassing (you could use a fancier one like Dean Edward's <a href=\"ref?\">base2<\/a>,\r\nor manipulate <code>prototype<\/code>s yourself).<\/p>\r\n<pre>\r\nfunction object(o){\r\n\tfunction F(){};\r\n\tF.prototype = o;\r\n\treturn new F;\r\n}\r\n\r\n\/\/ utility function\r\n\/\/ if eval weren't evil, it would just be eval('$.'+name);\r\nfunction widgetNamed(name){\r\n\tvar name = name.split('.');\r\n\tvar namespace = name[0];\r\n\tname = name[1];\r\n\treturn $[namespace][name];\r\n}\r\n\r\n$.widget.subclass = function (name, superName){\r\n\t$.widget(name); \/\/ Slightly inefficient to create a widget only to discard its prototype, but it's not too bad\r\n\t\r\n\t\/\/ $.widget ought to return the created object, obviating this call\r\n\tvar widget = widgetNamed(name);\r\n\tvar superclass = widgetNamed(superName);\r\n\t\r\n\twidget.prototype = object(superclass.prototype);\r\n\tvar args = Array.prototype.slice.call(arguments,1); \/\/ get all the add-in methods\r\n\targs[0] = widget.prototype;\r\n\t$.extend.apply(null, args); \/\/ and add them to the prototype\r\n\twidget.defaults = object(superclass.defaults);\r\n\treturn widget; \/\/ address my complaint above\r\n};\r\n<\/pre>\r\n<p>And use it like this:<\/p>\r\n<pre>\r\n$.widget.subclass ('yi.supererbox', 'yi.superbox', {\r\n\t\/\/ overriding and new methods\r\n\tmove: function(){\r\n\t\tthis.element.animate(this.newPoint(), this.getData('speed'));\r\n\t},\r\n\thome: function(){\r\n\t\tthis.element.animate({top:0, left:0}, this.getData('speed'));\r\n\t}\r\n});\r\n$.yi.supererbox.defaults.speed = 'normal';\r\n<\/pre>\r\n<p>The function signature is <code>$.widget.subclass(name &lt;String&gt;, superName &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>$.ui.mouse<\/code>,\r\nthat add a specific set of methods.<\/p>\r\n<h4>Getting Subclassing Right<\/h4>\r\n<p>This is pretty good as it stands, but the ever-astute reader will note that there are a number of problems with this method:<\/p>\r\n<ol>\r\n\t<li>Overriding methods replace the superclass methods entirely. There's no\r\n\tway to get at the original move if, for some reason, we wanted to call it.<\/li>\r\n\t<li>Methods in the superclass that refer to <code>$.data(this,'superbox')<\/code> will fail in subclassed\r\n\tcode. The subclassed objects are attached to <code>$.data(this,'supererbox')<\/code>. This didn't matter\r\n\tin our supererbox example, since we overrode the only method that mentioned superbox itself. But it would be\r\n\ta fatal problem it we, e.g., overrode <code>newPoint<\/code>.<\/li>\r\n\t<li>Subclasses shouldn't have to keep track of constructing and deconstructing their superclasses. As is it,\r\n\ta subclass needs to somehow call or copy the superclass <code>init<\/code> in its own <code>init<\/code>,\r\n\tand the superclass <code>destroy<\/code> in its own\r\n\t<code>destroy<\/code>. This didn't matter in our example, since we didn't use <code>init<\/code> or <code>destroy<\/code>,\r\n\tbut it could be a big problem.<\/li>\r\n<\/ol>\r\n<p>So we add some slightly kludgy code to keep track of all the superclass methods:<\/p>\r\n<pre>\r\n$.widget.subclass = function (name, superName){\r\n\t$.widget(name); \/\/ Slightly inefficient to create a widget only to discard its prototype, but it's not too bad\r\n\t\r\n\t\/\/ $.widget ought to return the created object, obviating this call\r\n\tvar widget = widgetNamed(name);\r\n\tvar superclass = widgetNamed(superName);\r\n\r\n\twidget.prototype = object(superclass.prototype);\r\n\tvar args = Array.prototype.slice.call(arguments,1); \/\/ get all the add-in methods\r\n\targs[0] = widget.prototype;\r\n\t$.extend.apply(null, args); \/\/ and add them to the prototype\r\n\twidget.defaults = object(superclass.defaults);\r\n\t\/\/ create the superclass chain, a hash of widget objects indexed by their names\r\n\tsuperName = superName.split('.')[1]; \/\/ remove the namespace\r\n\twidget.supers =  $.extend({}, superclass.supers); \/\/ get the old superclasses\r\n\twidget.supers[superName] = superclass; \/\/ and add the new\r\n\t\r\n\t\/\/ address the third problem above by making the init method of the subclass call the init method of the superclass\r\n\tvar init = widget.prototype.hasOwnProperty('init') ? widget.prototype.init : function(){};\r\n\twidget.prototype.init = function(){\r\n\t\tthis.element.data(superName, this); \/\/ address the second problem by adding this under the superclass's name as well\r\n\t\tsuperclass.prototype.init.apply(this); \/\/ address the third problem by calling the superclass's init\r\n\t\tinit.apply(this); \/\/ do the real init\r\n\t};\r\n\t\/\/ now do the same for destroy, if needed\r\n\tif (widget.prototype.hasOwnProperty('destroy')){\r\n\t\tvar destroy = widget.prototype.destroy;\r\n\t\twidget.prototype.destroy = function(){\r\n\t\t\tdestroy.apply(this);\r\n\t\t\tsuperclass.prototype.destroy.apply(this);\r\n\t\t}\r\n\t}\r\n\treturn widget; \/\/ address my complaint above\r\n};\r\n\r\n\/\/ address the first problem by creating method to call the superclass method\r\n\/\/ use as this.callSuper($.yi.superbox, 'foo', arg1, arg2); after mixing in the CallSuper class\r\nvar CallSuper = {\r\n\tcallSuper: function(superclass, method){\r\n\t\treturn superclass.prototype[method].apply(this, Array.prototype.slice.call(arguments, 3));\r\n\t}\r\n};\r\n<\/pre>\r\n<script>\r\nfunction object(o){\r\n\tfunction F(){};\r\n\tF.prototype = o;\r\n\treturn new F;\r\n}\r\n\r\n\/\/ utility function\r\n\/\/ if eval weren't evil, it would just be eval('$.'+name);\r\nfunction widgetNamed(name){\r\n\tvar name = name.split('.');\r\n\tvar namespace = name[0];\r\n\tname = name[1];\r\n\treturn $[namespace][name];\r\n}\r\n\r\n$.widget.subclass = function (name, superName){\r\n\t$.widget(name); \/\/ Slightly inefficient to create a widget only to discard its prototype, but it's not too bad\r\n\t\r\n\t\/\/ $.widget ought to return the created object, obviating this call\r\n\tvar widget = widgetNamed(name);\r\n\tvar superclass = widgetNamed(superName);\r\n\r\n\twidget.prototype = object(superclass.prototype);\r\n\tvar args = Array.prototype.slice.call(arguments,1); \/\/ get all the add-in methods\r\n\targs[0] = widget.prototype;\r\n\t$.extend.apply(null, args); \/\/ and add them to the prototype\r\n\twidget.defaults = object(superclass.defaults);\r\n\t\/\/ create the superclass chain, a hash of widget objects indexed by their names\r\n\tsuperName = superName.split('.')[1]; \/\/ remove the namespace\r\n\twidget.supers =  $.extend({}, superclass.supers); \/\/ get the old superclasses\r\n\twidget.supers[superName] = superclass; \/\/ and add the new\r\n\t\r\n\t\/\/ address the third problem above by making the init method of the subclass call the init method of the superclass\r\n\tvar init = widget.prototype.hasOwnProperty('init') ? widget.prototype.init : function(){};\r\n\twidget.prototype.init = function(){\r\n\t\tthis.element.data(superName, this); \/\/ address the second problem by adding this under the superclass's name as well\r\n\t\tsuperclass.prototype.init.apply(this); \/\/ address the third problem by calling the superclass's init\r\n\t\tinit.apply(this); \/\/ do the real init\r\n\t};\r\n\t\/\/ now do the same for destroy, if needed\r\n\tif (widget.prototype.hasOwnProperty('destroy')){\r\n\t\tvar destroy = widget.prototype.destroy;\r\n\t\twidget.prototype.destroy = function(){\r\n\t\t\tdestroy.apply(this);\r\n\t\t\tsuperclass.prototype.destroy.apply(this);\r\n\t\t}\r\n\t}\r\n\treturn widget; \/\/ address my complaint above\r\n};\r\n\r\n\/\/ address the first problem by creating method to call the superclass method\r\n\/\/ use as this.callSuper($.yi.superbox, 'foo', arg1, arg2);\r\nvar CallSuper = {\r\n\tcallSuper: function(superclass, method){\r\n\t\treturn superclass.prototype[method].apply(this, Array.prototype.slice.call(arguments, 3));\r\n\t}\r\n};\r\n<\/script>\r\n<script>\r\n$.widget.subclass (\"yi.supererbox\", \"yi.superbox\", {\r\n\t\/\/ overriding and new methods\r\n\tmove: function(){\r\n\t\tthis.element.animate(this.newPoint(), this.getData('speed'));\r\n\t},\r\n\thome: function(){\r\n\t\tthis.element.animate({top:0, left:0}, this.getData('speed'));\r\n\t}\r\n});\r\n$.yi.supererbox.defaults.speed = 'normal';\r\n<\/script>\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 mixin the <code>CallSuper<\/code> object and use <code>callSuper<\/code>:<\/p>\r\n<pre>\r\n$.widget.subclass('yi.superboxwithtext', 'yi.supererbox', CallSuper, {\r\n\tmove: function(){\r\n\t\tvar count = this.getData('count') || 0;\r\n\t\t++count;\r\n\t\tthis.setData('count', count);\r\n\t\tthis.element.text('Move number '+count);\r\n\t\tthis.callSuper($.yi.supererbox, 'move'); \/\/ note that we could just as well use $.yi.superbox for the original, \"jumpy\" move\r\n\t}\r\n});\r\n<\/pre>\r\n<script>\r\n$.widget.subclass('yi.superboxwithtext', 'yi.supererbox', CallSuper, {\r\n\tmove: function(){\r\n\t\tvar count = this.getData('count') || 0;\r\n\t\t++count;\r\n\t\tthis.setData('count', count);\r\n\t\tthis.element.text('Move number '+count);\r\n\t\tthis.callSuper($.yi.supererbox, 'move'); \/\/ note that we could just as well use $.yi.superbox for the original, \"jumpy\" move\r\n\t}\r\n});\r\n<\/script>\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').css({top: 0, left: 0}).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 addresses some of these problems, by keeping a reference to the superclass method.\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 create a mixin object for widgets that's stolen straight from Justin Palmer's article:<\/p>\r\n<pre>\r\n$.widget.aspect =  {\r\n  yield: null,\r\n  returnValues: { },\r\n  before: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      f.apply(this, arguments);\r\n      return original.apply(this, arguments);\r\n    };\r\n  },\r\n  after: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      this.returnValues[method] = original.apply(this, arguments);\r\n      return f.apply(this, arguments);\r\n    }\r\n  },\r\n  around: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      this.yield = original;\r\n      return f.apply(this, arguments);\r\n    }\r\n  }\r\n};\r\n<\/pre>\r\n<script>\r\n\/\/ stolen straight from Justin Palmer\r\n$.widget.aspect =  {\r\n  yield: null,\r\n  returnValues: { },\r\n  before: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      f.apply(this, arguments);\r\n      return original.apply(this, arguments);\r\n    };\r\n  },\r\n  after: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      this.returnValues[method] = original.apply(this, arguments);\r\n      return f.apply(this, arguments);\r\n    }\r\n  },\r\n  around: function(method, f) {\r\n    var original = this[method];\r\n    this[method] = function() {\r\n      this.yield = original;\r\n      return f.apply(this, arguments);\r\n    }\r\n  }\r\n};\r\n<\/script>\r\n<p>And we use it just like we might use <code>$.ui.mouse<\/code>: <code>$.widget('ns.foo', $.extend({}, {...methods for foo...}, $.widget.aspect)<\/code>\r\nor, with our subclassing, $.<code>widget.subclass('ns.foo-with-aspects', 'ns.foo', $.widget.aspect)<\/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>pulsate<\/code> method that does this):<\/p>\r\n<pre>\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<\/pre>\r\n<script>\r\n$.fn.pulse = function (opts){\r\n\topts = $.extend ({}, $.fn.pulse.defaults, opts);\r\n\tfor (i = 0; i < 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<\/script>\r\n<p>And we'll create a version of supererbox that allows for AOP:<\/p>\r\n<pre>\r\n$.widget.subclass ('yi.supererbox2', 'yi.supererbox', $.widget.aspect);\r\n<\/pre>\r\n<script>\r\n$.widget.subclass ('yi.supererbox2', 'yi.supererbox', $.widget.aspect);\r\n<\/script>\r\n<p>And we'll create a supererbox object, then make it pulse before moving:<\/p>\r\n<pre>\r\n$('#experiment4').supererbox2().supererbox2('before','move', function() {\r\n\tthis.element.pulse();\r\n});\r\n<\/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$('#experiment4').supererbox2().supererbox2('before','move', function() {\r\n\t\tthis.element.pulse();\r\n\t});\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>\r\n$('#experiment5').supererbox2().supererbox2('around','move', function() {\r\n\tthis.element.pulse();\r\n\tthis.yield();\r\n\tthis.element.pulse();\r\n});\r\n<\/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$('#experiment5').supererbox2().supererbox2('around','move', function() {\r\n\t\tthis.element.pulse();\r\n\t\tthis.yield();\r\n\t\tthis.element.pulse();\r\n\t});\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 once we added the aspect methods to create supererbox2, 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","protected":false},"excerpt":{"rendered":"This page is obsolete (it uses jQuery UI 1.5). Please see the updated page. 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 who's ever dealt with object-oriented programming) is that your widgets should not do everything possible; they [&hellip;]","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/27"}],"collection":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"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=27"}],"version-history":[{"count":8,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/27\/revisions"}],"predecessor-version":[{"id":313,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/27\/revisions\/313"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=27"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=27"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=27"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}