{"id":81,"date":"2008-08-13T21:10:06","date_gmt":"2008-08-14T03:10:06","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?p=81"},"modified":"2009-01-28T20:08:47","modified_gmt":"2009-01-29T02:08:47","slug":"extending-jquery-ui-widgets-revisited","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2008\/08\/13\/extending-jquery-ui-widgets-revisited\/","title":{"rendered":"Extending jQuery UI Widgets Revisited"},"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<p>This is an updated version of a <a href=\"\/blog\/2008\/08\/04\/extending-jquery-ui-widgets\/\">tutorial I\r\nwrote<\/a> a bit back, improved thanks to conversations with Scott Gonzalez\r\nof the jQuery UI team. Thanks!<\/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 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<!--more-->\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\n$.yi = {}; \/\/ create our namespace\r\nvar Superbox = {\r\n\tinit: 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\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<\/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>Now let's make a new class, <code class=\"language-javascript\">supererbox<\/code>, that moves rather than jumps to its new location.\r\nI'll use 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).<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\nfunction object(o){\r\n\tfunction F(){};\r\n\tF.prototype = o;\r\n\treturn new F;\r\n}\r\n\r\n$.widget.subclass = function (name, superclass){\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]]; \/\/ $.widget should return the object itself!\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\t\r\n\t\/\/ Subtle point: we want to call superclass 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\tif (widget.prototype.hasOwnProperty('init')){;\r\n\t  var init = widget.prototype.init;\r\n\t\twidget.prototype.init = function(){\r\n\t\t\tsuperclass.prototype.init.apply(this);\r\n\t\t\tinit.apply(this);\r\n\t\t}\r\n\t};\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\/\/ allow for subclasses to call superclass methods\r\n$.widget.prototype.callSuper = function(superclass, method){\r\n\tsuperclass = superclass.split('.'); \/\/ separate namespace and name\r\n\treturn $[superclass[0]][superclass[1]].prototype[method].apply(this, Array.prototype.slice.call(arguments, 2)); \/\/ corrected from (arguments, 3)\r\n};\r\n\/\/ For the purposes of this demo, the above function was defined after we created superbox,\r\n\/\/ so superbox does not include it. We'll include it manually\r\n$.yi.superbox.prototype.callSuper = $.widget.prototype.callSuper;\r\n<\/code><\/pre>\r\n<p>And use it like this:<\/p>\r\n<pre><code class=\"language-javascript demo\">\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\r\n<\/code><\/pre>\r\n<p>The function signature is <code class=\"language-javascript\">$.widget.subclass(name &lt;String&gt;, superclass &lt;Object&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\">callSuper<\/code>:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.widget.subclass('yi.superboxwithtext', $.yi.supererbox, {\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<\/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').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><code class=\"language-javascript demo\">\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<\/code><\/pre>\r\n<p>And we use it just like we might use <code class=\"language-javascript\">$.ui.mouse<\/code>: <code class=\"language-javascript\">$.widget('ns.foo', $.extend({}, {...methods for foo...}, $.widget.aspect)<\/code>\r\nor, with our subclassing, $.<code class=\"language-javascript\">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 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 version of supererbox that allows for AOP:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.widget.subclass ('yi.supererbox2', $.yi.supererbox, $.widget.aspect);\r\n<\/code><\/pre>\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').supererbox2().supererbox2('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').supererbox2('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').supererbox2().supererbox2('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').supererbox2('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>","protected":false},"excerpt":{"rendered":"This page is obsolete (it uses jQuery UI 1.5). Please see the updated page. This is an updated version of a tutorial I wrote a bit back, improved thanks to conversations with Scott Gonzalez of the jQuery UI team. Thanks! Avoiding Bloat in Widgets A while back, Justin Palmer wrote an excellent article on \"Avoiding [&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\/81"}],"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=81"}],"version-history":[{"count":20,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/81\/revisions"}],"predecessor-version":[{"id":312,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/81\/revisions\/312"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=81"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=81"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=81"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}