{"id":286,"date":"2009-01-27T21:08:32","date_gmt":"2009-01-28T03:08:32","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/?page_id=286"},"modified":"2015-02-10T18:48:10","modified_gmt":"2015-02-11T00:48:10","slug":"understanding-jquery-ui-widgets-a-tutorial","status":"publish","type":"page","link":"https:\/\/bililite.com\/blog\/understanding-jquery-ui-widgets-a-tutorial\/","title":{"rendered":"Understanding jQuery UI widgets: A tutorial"},"content":{"rendered":"<h2>This page is obsolete; current versions are on my github pages at <a href=\"http:\/\/github.bililite.com\/understanding-widgets.html\">github.bililite.com\/understanding-widgets.html<\/a>. This page is being kept here for historical purposes.<\/h2>\r\n<script>$(function(){$('.target').css('border', '1px solid red');})<\/script>\r\n<p>This was written largely to help me make sense of using UI to create my own widgets,\r\nbut I hope it may help others. \"Widget\" to me means a user-interface element, like\r\na button or something more complicated like a popup date picker, but in jQuery UI terms\r\nit means a class, members of which are associated with HTML elements; things like\r\n<a href=\"http:\/\/docs.jquery.com\/UI\/Draggables\">Draggable<\/a> and \r\n<a href=\"http:\/\/docs.jquery.com\/UI\/Sortables\">Sortable<\/a>.\r\nIn fact, not everything that I would have called a widget uses <code>$.widget<\/code>; the UI datepicker does not.<\/p>\r\n<p>Dan Wellman has <a href=\"http:\/\/net.tutsplus.com\/tutorials\/javascript-ajax\/coding-your-first-jquery-ui-plugin\/\">another tutorial<\/a> that you may find helpful.<\/p> \r\n<!--more-->\r\n<h4>Modifying Elements: Plugins<\/h4>\r\n<p>That being as it may, let's use <code>$.widget<\/code>.<\/p>\r\n<div>\r\n\t<p>Let's take a paragraph of class target:<\/p>\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a paragraph&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<p>And lets make it green. We know how; <code class=\"language-javascript\">$('.target').css({background: 'green'})<\/code>.<\/p>\r\n\t<input type=\"button\" value=\"test\" id=\"button1\" \/>\r\n\t<script>\r\n\t\tvar targetstate = 'none';\r\n\t\t$('#button1').click(function() {\r\n\t\t\tif (targetstate == 'none'){\r\n\t\t\t\ttargetstate = 'green';\r\n\t\t\t\t$(this).prevAll('.target').css({background: 'green'});\r\n\t\t\t}else{\r\n\t\t\t\ttargetstate = 'none';\r\n\t\t\t\t$(this).prevAll('.target').css({background: 'none'});\r\n\t\t\t}\r\n\t\t});\r\n\t<\/script>\r\n\t<p>Now, make it more general-purpose: a <a href=\"http:\/\/docs.jquery.com\/Plugins\/Authoring\">plugin<\/a>:<\/p>\r\n\t<pre><code class=\"language-javascript demo\">$.fn.green = function() {return this.css({background: 'green'})};<\/code><\/pre>\r\n\t<input type=\"button\" value=\"test\" id=\"button2\"\/>\r\n\t<script>\r\n\t\t$('#button2').click(function() {\r\n\t\t\tif (targetstate == 'none'){\r\n\t\t\t\ttargetstate = 'green';\r\n\t\t\t\t$(this).prevAll('.target').green();\r\n\t\t\t}else{\r\n\t\t\t\ttargetstate = 'none';\r\n\t\t\t\t$(this).prevAll('.target').css({background: 'none'});\r\n\t\t\t}\r\n\t\t});\r\n\t<\/script>\r\n<\/div>\r\n<p>But this allows us to perform some behavior on the selected elements; it does not leave us with any way to\r\nkeep our plugin associated with that element, so we can do something with it later, like\r\n<code>$('.target').off()<\/code> to remove the green background, but only if we used green to\r\nput it there in the beginning. We also have no way of associating\r\nstate with the element, to do $('.target').darker(), which would require knowing how green the element is now.\r\n<h4>Keeping State in Plugins<\/h4>\r\nWe could create an object and associate it with an element using javascript \r\n<a href=\"http:\/\/develocity.blogspot.com\/2007\/04\/its-hard-to-know-where-to-start-but.html\">expandos<\/a>: \r\n<code class=\"language-javascript\">element.myobject = new Myobject({'target': element})<\/code>. Sample code would be:<\/p>\r\n<pre><code class=\"language-javascript\">\r\n$.fn.green2 = function() {\r\n\treturn this.each(function(){\r\n\t\t\tif (!this.green) this.green = new Green($(this)); \/\/ associate our state-keeping object with the element\r\n\t\t\tthis.green.setLevel(15);\r\n\t});\r\n};\r\n$.fn.off = function() {\r\n\treturn this.each(function(){\r\n\t\tif (this.green) this.green.setLevel(16);\r\n\t\tdelete this.green; \/\/ recover the memory\r\n\t});\r\n};\r\n$.fn.darker = function() {\r\n\treturn this.each(function(){\r\n\t\tif (this.green) this.green.setLevel(this.green.getLevel()-1);\r\n\t});\r\n};\r\n$.fn.lighter = function() {\r\n\treturn this.each(function(){\r\n\t\tif (this.green) this.green.setLevel(this.green.getLevel()+1);\r\n\t});\r\n};\r\n\r\nfunction Green(target){\r\n\tgreenlevels = ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0','#fff'];\r\n\tthis.target = target; \/\/ associate the element with the object\r\n\tthis.level = 0;\r\n\tthis.getLevel = function() { return this.level; }\r\n\tthis.setLevel = function(x) {\r\n\t\tthis.level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.target.css({background: greenlevels[this.level]});\r\n\t}\r\n};\r\n<\/code><\/pre>\r\n\r\n<p>But this pollutes the <code class=\"language-javascript\">$.fn<\/code> namespace terribly, with <code>off<\/code>, <code>darker<\/code> and <code>lighter<\/code>.\r\nThere are ways to create real namespaces within <code class=\"language-javascript\">$.fn<\/code>, but the usual design pattern is to use a string to\r\nspecify which function to call. Thus, <code class=\"language-javascript\">element.green2()<\/code> to instantiate the plugin, \r\n<code class=\"language-javascript\">element.green2('darker')<\/code> or <code class=\"language-javascript\">element.green2('lighter')<\/code>\r\nto manipulate it:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.fn.green2 = function(which){\r\n\treturn this.each(function(){\r\n\t\tif (which === undefined){ \/\/ initial call\r\n\t\t\tif (!this.green) this.green = new Green($(this)); \/\/ associate our state-keeping object with the element\r\n\t\t\tthis.green.setLevel(15);\r\n\t\t}else if (which == 'off'){\r\n\t\t\tif (this.green) this.green.setLevel(16);\r\n\t\t\tdelete this.green\r\n\t\t}else if (which == 'darker'){\r\n\t\t\tif (this.green) this.green.setLevel(this.green.getLevel()-1);\r\n\t\t}else if (which == 'lighter'){\r\n\t\t\tif (this.green) this.green.setLevel(this.green.getLevel()+1);\r\n\t\t}\r\n\t});\r\n};\r\n\r\nfunction Green(target){\r\n\tgreenlevels = ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'];\r\n\tthis.target = target; \/\/ associate the element with the object\r\n\tthis.level = 0;\r\n\tthis.getLevel = function() { return this.level; }\r\n\tthis.setLevel = function(x) {\r\n\t\tthis.level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.target.css({background: greenlevels[this.level]});\r\n\t}\r\n};\r\n<\/code><\/pre>\r\n<div id=\"test2\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a test paragraph&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n\t<script>\t\r\n\t\t$('#test2 input[value=on]').click(function() {$(this).prevAll('.target').green2()});\r\n\t\t$('#test2 input[value=darker]').click(function() {$(this).prevAll('.target').green2('darker')});\r\n\t\t$('#test2 input[value=lighter]').click(function() {$(this).prevAll('.target').green2('lighter')});\r\n\t\t$('#test2 input[value=off]').click(function() {$(this).prevAll('.target').green2('off')});\r\n\t<\/script>\r\n<\/div>\r\n<h4>The Problems with Associating an Object with a Plugin<\/h4>\r\n<p>But you get into trouble with circular\r\nreferences (note that \"<code class=\"language-javascript\">this.green = new Green($(this))<\/code>\" gives a DOM element a reference to a javascript object\r\nand\r\n\"<code class=\"language-javascript\">this.target = target<\/code>\" gives a javascript object a reference to a DOM element) and \r\n<a href=\"http:\/\/www.codeproject.com\/KB\/scripting\/leakpatterns.aspx\">memory leaks<\/a>:\r\nbrowsers (notably Internet Explorer) uses different garbage collectors for DOM elements and javascript objects.\r\nCircular references mean that each garbage collector thinks the other object is in use and won't delete them.<\/p>\r\n<p>We also need to remember to reclaim the memory (with <code>delete<\/code>) if we no longer need the plugin.<\/p>\r\n\r\n<p>jQuery solves the circular reference problem with the <code class=\"language-javascript\">$.fn.data<\/code> plugin:\r\n<code>$(element).data('myobject', new Myobject({'target': element}))<\/code>. But now we've got a lot of \"paperwork\" to\r\nkeep track of, and it hides the underlying program logic. As we know, \r\n<a href=\"http:\/\/blog.plover.com\/2006\/09\/11\/\">design patterns reflect language weakness<\/a>.\r\nIf we are\r\nconstantly re-implementing a pattern, we need to abstract it and make it automatic.<\/p>\r\n<h4>Solving the Problem: $.widget<\/h4>\r\nThat's where <code>$.widget<\/code> comes\r\nin. It creates a plugin and an associated javascript class and ties an instance of that class with each\r\nelement so we can interact with the object and act on the element, without getting into trouble with\r\nmemory leaks.<\/p>\r\n<p>You still need to create the constructor of your class, but instead of a real constructor function, you need\r\na prototype object with all the relevant methods. There are a few conventions: the function <code>_create<\/code> is called on construction, <code>_init<\/code> is called both on construction and for re-initializing\r\nthe function and <code>destroy<\/code> is called on removal. All of these are predefined as empty functions but you can override them (and most likely\r\nwill need to override <code>_init<\/code>). <code>element<\/code> is the associated jQuery object (what we called <code>target<\/code> above).<\/p>\r\n<p>Widget methods that start with \"<code>_<\/code>\" are pseudo-private; they cannot be called with the $(element).plugin('string') notation\r\n<pre><code class=\"language-javascript demo\">\r\nvar Green3  = {\r\n\t_init: function() { this.setLevel(15); },\r\n\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'],\r\n\tlevel: 0,\r\n\tgetLevel: function() { return this.level; },\r\n\tsetLevel: function(x) {\r\n\t\tthis.level = Math.floor(Math.min(this.greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.element.css({background: this.greenlevels[this.level]});\r\n\t},\r\n\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\toff: function() {\r\n\t\tthis.element.css({background: 'none'});\r\n\t\tthis.destroy(); \/\/ use the predefined function\r\n\t}\r\n};\r\n<\/code><\/pre>\r\n<p>Notice it's all program logic, no DOM or memory-related bookkeeping. Now we need to create a name, which must be preceded by a\r\nnamespace, like \"<code>ns.green<\/code>\" . Unfortunately the namespacing is fake; the plugin is just called <code>$().green()<\/code>. The constructor function\r\nis <code>$.ns.green<\/code>, but you never use that, so you might as well use the \"official\" namespace of \"<code>ui<\/code>\".  But defining the widget couldn't be easier:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\n$.widget(\"ui.green3\", Green3); \/\/ create the widget\r\n<\/code><\/pre>\r\n<h4>Manipulating Widgets<\/h4>\r\n<p>What about our manipulating functions? All the functions defined in the prototype that don't start with an underscore are exposed automatically:\r\n<code class=\"language-javascript\">$('.target').green3()<\/code> creates the widgets; <code class=\"language-javascript\">$('.target').green3('darker')<\/code> manipulates them.<\/p>\r\n\r\n<p>There are two kinds of plugin functions: getters and setters. <em>Setters<\/em> manipulate elements and can be chained in jQuery code; they just return the jQuery object they started with. For example, <code class=\"language-javascript\">$('.demo').css({color: 'red'}).text('stuff')<\/code>. <em>Getters<\/em> return information and break the chain, like <code class=\"language-javascript\">text = $('.demo').text()<\/code>. <code>widget<\/code> distinguishes between the two by the return value: if your function returns any value other than <code>undefined<\/code>, it assumes that it is a getter and returns that value (for the first element in the jQuery object, just like all jQuery getter functions). If it does not return a value, then it is assumed to be a setter and is called on each element in the jQuery object, and the jQuery object itself is returned for chaining.<\/p>\r\n<div id=\"test3\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a test paragraph.&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n<\/div>\r\n<script>\r\n\t$('#test3 input[value=on]').click(function() {$(this).prevAll('.target').green3()});\r\n\t$('#test3 input[value=darker]').click(function() {$(this).prevAll('.target').green3('darker')});\r\n\t$('#test3 input[value=lighter]').click(function() {$(this).prevAll('.target').green3('lighter')});\r\n\t$('#test3 input[value=off]').click(function() {$(this).prevAll('.target').green3('off')});\r\n<\/script>\r\n<p>Pass arguments to the manipulating functions after the name: <code>$('.target').green3('setLevel', 5)<\/code>.<\/p>\r\n<h4>Data for Each Widget<\/h4>\r\n<p>The astute reader will have noticed that <code>level<\/code> is a class variable; the same variable is used\r\nfor every green3 object. This is clearly not what we want; each instance should have its own copy.\r\n$.widget defines an object\r\n<code class=\"language-javascript\">this.options<\/code> for per-widget data.\r\nThus:<\/p>\r\n<pre><code class=\"language-javascript demo\">\r\nvar Green4  = {\r\n\tgetLevel: function () { return this.options.level; },\r\n\tsetLevel: function (x) {\r\n\t\tvar greenlevels = this.options.greenlevels;\r\n\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.options.level = level;\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t},\r\n\t_init: function() { this.setLevel(this.getLevel()); }, \/\/ grab the default value and use it\r\n\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\toff: function() {\r\n\t\tthis.element.css({background: 'none'});\r\n\t\tthis.destroy(); \/\/ use the predefined function\r\n\t},\r\n\toptions: { \/\/ initial values are stored in the widget's prototype\r\n\t\tlevel: 15,\r\n\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff']\r\n\t}\r\n};\r\n$.widget(\"ui.green4\", Green4);\r\n<\/code><\/pre>\r\n\r\n<p>And on creating an instance of a widget, pass an options object (the way most plugins do)\r\nand override the defaults:\r\n<code class=\"language-javascript\">$('.target').green4({level: 8})<\/code>.<\/p>\r\n<p>Note that I also put the list of colors into the defaults object, so it too can be overridden.\r\nThis widget probably shouldn't be called\r\n\"green\" anymore!<\/p>\r\n\r\n<div id=\"test4\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a test paragraph.&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n<\/div>\r\n<script>\r\n\t$('#test4 input[value=on]').click(function() {$(this).prevAll('.target').green4()});\r\n\t$('#test4 input[value=darker]').click(function() {$(this).prevAll('.target').green4('darker')});\r\n\t$('#test4 input[value=lighter]').click(function() {$(this).prevAll('.target').green4('lighter')});\r\n\t$('#test4 input[value=off]').click(function() {$(this).prevAll('.target').green4('off')});\r\n<\/script>\r\n<div id=\"test4-1\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;\r\n\tThis is a test paragraph called with .green4({\r\n\t\tlevel:3,\r\n\t\tgreenlevels: ['#000','#00f','#088', '#0f0', '#880', '#f00', '#808', '#fff']\r\n\t}).\r\n\t&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n<\/div>\r\n<script>\r\n\t$('#test4-1 input[value=on]').click(function() {$(this).prevAll('.target').green4({\r\n\t\tlevel:3,\r\n\t\tgreenlevels: ['#000','#00f','#088', '#0f0', '#880', '#f00', '#808', '#fff']\r\n\t})});\r\n\t$('#test4-1 input[value=darker]').click(function() {$(this).prevAll('.target').green4('darker')});\r\n\t$('#test4-1 input[value=lighter]').click(function() {$(this).prevAll('.target').green4('lighter')});\r\n\t$('#test4-1 input[value=off]').click(function() {$(this).prevAll('.target').green4('off')});\r\n<\/script>\r\n<h4>Callbacks, or, Keeping the Lines of Communication Open<\/h4>\r\n<p>The programmer who is embedding our widget in his page may want to do other things when the widget changes state. \r\nThere are two ways to alert the calling program that something has happened:<\/p>\r\n<dl>\r\n<dt>Tightly Coupled<\/dt>\r\n<dd>The caller can provide a function to call at the critical point. jQuery jargon calls this a \"callback;\" it's used in\r\n<a href=\"http:\/\/docs.jquery.com\/Effects\/show#speedcallback\">animations<\/a> and \r\n<a href=\"http:\/\/docs.jquery.com\/Ajax\/load#urldatacallback\">Ajax<\/a>. We can create callback functions\r\nthat the widget calls at critical points, and pass them to the widget-constructing plugin like any other option:\r\n<pre><code class=\"language-javascript\">\r\nvar Green5 = {\r\n\tsetLevel: function(x){\r\n\t\t\/\/...\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t\tvar callback = this.options.change;\r\n\t\tif ($.isFunction(callback)) callback(level);\r\n\t},\r\n\t\/\/ ... rest of widget definition\r\n};\r\n$.widget(\"ui.green5\", Green5);\r\n\r\n$('.target').green5({change: function(x) { alert (\"The color changed to \"+x); } });\r\n<\/code><\/pre>\r\n<\/dd>\r\n<dt>Loosely Coupled<\/dt>\r\n<dd>Also called the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Observer_pattern\">Observer Design Pattern<\/a>, the widget sends a signal to\r\nthe programming framework and the calling program informs the framework that it wants to know about the signal. Events like\r\nclicks and keystrokes work like this, and jQuery allows the widget to create custom events and for the calling program to\r\nbind an event handler to that custom event:\r\n<pre><code class=\"language-javascript\">\r\nvar Green5 = {\r\n\tsetLevel: function(x){\r\n\t\t\/\/...\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t\tthis.element.trigger ('green5change', level);\r\n\t},\r\n\t\/\/ ... rest of widget definition\r\n};\r\n$.widget(\"ui.green5\", Green5);\r\n\r\n$('.target').green5();\r\n$('.target').bind(\"green5change\", function(evt,x) { alert (\"The color changed to \"+x); });\r\n<\/code><\/pre>\r\n<\/dd>\r\n<\/dl>\r\n\r\n<p><code>$.widget<\/code> allows both forms with the <code>_trigger<\/code> method. In a widget object,\r\n<code class=\"language-javascript\">this._trigger(type, event, data)<\/code> takes a <code>type {String}<\/code> with the\r\nname of the event you want (use some short verb, like <code>'change'<\/code>) and optionally a \r\n<a href=\"http:\/\/docs.jquery.com\/Events\/jQuery.Event\">$.Event object<\/a> (if you want to pass things like timestamps and mouse locations.\r\nDon't worry about <code>event.type<\/code>; <code>_trigger<\/code> changes it to the constructed event name), and any data to be passed\r\nto the handler. <code>_trigger<\/code> creates a custom event name of <code>widgetName+type<\/code>, like <code>green6change<\/code>\r\n(<strike>why it doesn't do <code>type+'.'+widgetName<\/code> <a href=\"http:\/\/docs.jquery.com\/Namespaced_Events\">the way jQuery expects<\/a>\r\nis beyond me<\/strike> naming events this way has been the subject of <a href=\"http:\/\/groups.google.com\/group\/jquery-ui-dev\/browse_thread\/thread\/7c6973343a233e2a\/284544f3abbfafe4\">some<\/a> <a href=\"http:\/\/dev.jqueryui.com\/ticket\/2340\">discussion<\/a>), sets <code>event.type = custom event name<\/code> (creating a new <code>$.Event<\/code> if it was not provided)\r\nand calls <code class=\"language-javascript\">this.element.trigger(event, data)<\/code> and then looks for a callback with\r\n<code class=\"language-javascript\">callback = this._getData(type)<\/code> and calls it with \r\n<code class=\"language-javascript\">callback.call(this.element[0], event, data)<\/code>.<\/p>\r\n<p>Notice that this means the function signature is slightly different for the event handler and the callback if <code>data<\/code>\r\nis an array. <code>element.trigger()<\/code> uses <code>apply<\/code> to turn each item in the array into a separate argument.\r\nSo <code class=\"language-javascript\">this._trigger('change', 0, ['one', 'two'])<\/code> \r\nrequires an event handler of the form <code class=\"language-javascript\">function(event, a, b)<\/code> and a callback of the form\r\n<code class=\"language-javascript\">function(event, data)<\/code>.<\/p>\r\n<p>In practice, it's not as complicated as it sounds. For example, using both methods:<\/p>\r\n\r\n<pre><code class=\"language-javascript demo\">\r\nvar Green5  = {\r\n\tgetLevel: function () { return this.options.level; },\r\n\tsetLevel: function (x) {\r\n\t\tvar greenlevels = this.options.greenlevels;\r\n\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.options.level = level;\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t\tthis._trigger('change', 0, level);\r\n\t},\r\n\t_init: function() { this.setLevel(this.getLevel()); }, \/\/ grab the default value and use it\r\n\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\toff: function() {\r\n\t\tthis.element.css({background: 'none'});\r\n\t\tthis._trigger('done');\r\n\t\tthis.destroy(); \/\/ use the predefined function\r\n\t},\r\n\toptions: {\r\n\t\tlevel: 15,\r\n\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff']\r\n\t}\r\n};\r\n$.widget(\"ui.green5\", Green5);\r\n<\/code><\/pre>\r\n<div id=\"test5\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a test paragraph with green level &lt;span class=&quot;level&quot;&gt;undefined&lt;\/span&gt;.&lt;\/p&gt; \r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n<\/div>\r\n<pre><code class=\"language-javascript\">\r\n\/\/  The on button above does the following:\r\n$('.target').green5({\r\n\tchange: function(event, level) { $('.level', this).text(level); } \/\/ callback to handle change event\r\n});\r\n$('.target').bind('green5done', function() { $('.level', this).text('undefined');alert('bye!') }); \/\/ event handler for done event\r\n<\/code><\/pre>\r\n<script>\r\n$('#test5 input[value=on]').click(function() {\r\n\t$(this).prevAll('.target').green5({\r\n\t\tchange: function(evt, x){\r\n\t\t\t$('.level', this).text(x); \/\/ callback function\r\n\t\t}\r\n\t}).unbind('green5done') \/\/ in case 'on' is clicked more than once\r\n\t.bind('green5done', function() {\r\n\t\t$('.level', this).text('undefined');\r\n\t\talert('bye!');\r\n\t});\r\n});\r\n$('#test5 input[value=darker]').click(function() {$(this).prevAll('.target').green5('darker')});\r\n$('#test5 input[value=lighter]').click(function() {$(this).prevAll('.target').green5('lighter')});\r\n$('#test5 input[value=off]').click(function() {$(this).prevAll('.target').green5('off')});\r\n<\/script>\r\n<h4>Involving the Mouse<\/h4>\r\n<p>Now, a lot of what we want to do with widgets involves mouse tracking, so <code>ui.core.js<\/code> provides a mixin object that\r\nincludes lots of useful methods for the mouse. All we need to do is add the <code>$.ui.mouse<\/code> widget to our\r\nwidget prototype:<\/p>\r\n<pre><code class =\"language-javascript\">\r\nvar Green6 = {mouse-overriding function and widget-specific functions};\r\n$.widget ('ui.green6', $.ui.mouse, Green6);\r\n<\/code><\/pre>\r\n<p>And override <code>$.ui.mouse<\/code>'s functions (<code>_mouseStart<\/code>,\r\n<code>_mouseDrag<\/code>,\r\n<code>_mouseStop<\/code>) to do something useful,\r\nand call <code class=\"language-javascript\">this._mouseInit<\/code>\r\nin your <code class=\"language-javascript\">this._init<\/code> and <code class=\"language-javascript\">this._mouseDestroy<\/code>\r\nin your <code class=\"language-javascript\">this.destroy<\/code>. The mouse defaults are automagically including in your options object; see the <a href=\"http:\/\/jquery-ui.googlecode.com\/svn\/trunk\/ui\/jquery.ui.mouse.js\">mouse code<\/a> for details.<\/p>\r\n\r\n<p>Let's add some mouse control to our greenerizer:\r\n<pre><code class=\"language-javascript demo\">\r\nGreen6 = $.extend({}, $.ui.green5.prototype, { \/\/ leave the old Green5 alone; create a new object\r\n\t_init: function(){\r\n\t\t$.ui.green5.prototype._init.call(this); \/\/ call the original function\r\n\t\tthis._mouseInit(); \/\/ start up the mouse handling\r\n\t},\r\n\tdestroy: function(){\r\n\t\tthis._mouseDestroy();\r\n\t\t$.ui.green5.prototype.destroy.call(this); \/\/ call the original function\r\n\t},\r\n\t\/\/ need to override the mouse functions\r\n\t_mouseStart: function(e){\r\n\t\t\/\/ keep track of where the mouse started\r\n\t\tthis.xStart = e.pageX; \/\/ not in the options object; this is not something that can be initialized by the user\r\n\t\tthis.levelStart = this.options.level;\r\n\t},\r\n\t_mouseDrag: function(e){\r\n\t\tthis.setLevel (this.levelStart +(e.pageX-this.xStart)\/this.options.distance);\r\n\t},\r\n\toptions: {\r\n\t\tlevel: 15,\r\n\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'],\r\n\t\tdistance: 10\r\n\t}\r\n});\r\n$.widget(\"ui.green6\", $.ui.mouse, Green6);\r\n<\/code><\/pre>\r\n<div id=\"test6\">\r\n\t<pre><code class=\"language-html demo\">\r\n\t&lt;p class=&quot;target&quot;&gt;This is a test paragraph with green level &lt;span class=&quot;level&quot;&gt;undefined&lt;\/span&gt;.&lt;\/p&gt;\r\n\t<\/code><\/pre>\r\n\t<input type=\"button\" value=\"on\"\/>\r\n\t<input type=\"button\" value=\"darker\"\/>\r\n\t<input type=\"button\" value=\"lighter\"\/>\r\n\t<input type=\"button\" value=\"off\"\/>\r\n<\/div>\r\n<script>\r\n$('#test6 input[value=on]').click(function() {\r\n\t$(this).prevAll('.target').green6({\r\n\t\tchange: function(evt, x){\r\n\t\t\t$('.level', this).text(x);\r\n\t\t},\r\n\t\tdone: function(){\r\n\t\t\t$('.level', this).text('undefined');\r\n\t\t}\r\n\t});\r\n});\r\n$('#test6 input[value=darker]').click(function() {$(this).prevAll('.target').green6('darker')});\r\n$('#test6 input[value=lighter]').click(function() {$(this).prevAll('.target').green6('lighter')});\r\n$('#test6 input[value=off]').click(function() {$(this).prevAll('.target').green6('off')});\r\n<\/script>\r\n<p>The ever-alert reader will note what we've just done: subclassed green5 to make green6, including calls\r\nto \"super\" methods. This ought to be abstracted out\r\ninto its own method, something like \r\n<code class=\"language-javascript\">$.ui.green5.subclass(\"green6\", $.ui.mouse, {mouseStart: function(){}, mouseDrag: function(){}})<\/code>\r\nbut that's a <a href=\"\/blog\/extending-jquery-ui-widgets\/\">topic for another day<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"This page is obsolete; current versions are on my github pages at github.bililite.com\/understanding-widgets.html. This page is being kept here for historical purposes. This was written largely to help me make sense of using UI to create my own widgets, but I hope it may help others. \"Widget\" to me means a user-interface element, like a [&hellip;]","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"open","ping_status":"open","template":"","meta":{"footnotes":""},"_links":{"self":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/286"}],"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=286"}],"version-history":[{"count":48,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/286\/revisions"}],"predecessor-version":[{"id":3484,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/pages\/286\/revisions\/3484"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=286"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}