{"id":26,"date":"2008-08-03T21:44:15","date_gmt":"2008-08-04T03:44:15","guid":{"rendered":"http:\/\/bililite.nfshost.com\/blog\/index.php\/2008\/08\/03\/jquery-ui-widgets\/"},"modified":"2009-03-05T13:38:06","modified_gmt":"2009-03-05T19:38:06","slug":"jquery-ui-widgets","status":"publish","type":"post","link":"https:\/\/bililite.com\/blog\/2008\/08\/03\/jquery-ui-widgets\/","title":{"rendered":"jQuery UI Widgets"},"content":{"rendered":"<p><strong>This page is obsolete (it uses jQuery UI 1.5). Please see <a href=\"\/blog\/understanding-jquery-ui-widgets-a-tutorial\/\">the updated page<\/a>.<\/strong><\/p>\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<!--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<p>Let's take a paragraph of class target:<\/p>\r\n<pre>\r\n&lt;p class=&quot;target&quot;&gt;This is a paragraph&lt;\/p&gt;\r\n<\/pre>\r\n<p class=\"target\">This is a test paragraph<\/p>\r\n<p id=\"test1\">And lets make it green. We know how; <code>$('.target).css({background: 'green')<\/code>.\r\n<input type=\"button\" value=\"test\"\/><\/p>\r\n<script>\r\n\t$('#test1 input[value=test]').toggle(function() {$('.target').css({background: 'green'})}, function(){$('.target').css({background: 'none'})});\r\n<\/script>\r\n<p id=\"test2\">Now, make it more general-purpose: a <a href=\"http:\/\/docs.jquery.com\/Plugins\/Authoring\">plugin<\/a>:\r\n<code>$.fn.green = function() {return this.css({background: 'green'})}<\/code>.\r\n<input type=\"button\" value=\"test\" id=\"button2\"\/><\/p>\r\n<script>\r\n\t$.fn.green = function() {return this.css({background: 'green'})};\r\n\t$('#test2 input[value=test]').toggle(function() {$('.target').green()}, function(){$('.target').css({background: 'none'})});\r\n<\/script>\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>element.myobject = new Myobject({'target': element})<\/code>. Sample code would be:<\/p>\r\n<pre>\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'];\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(15, Math.max(0,x)));\r\n\t\tthis.target.css({background: greenlevels[this.level]});\r\n\t}\r\n};\r\n<\/pre>\r\n<p>But this pollutes the <code>$.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>$.fn<\/code>, but the usual design pattern is to use a string to\r\nspecify which function to call. Thus, <code>element.green2()<\/code> to instantiate the plugin, \r\n<code>element.green2('darker')<\/code> or <code>element.green2('lighter')<\/code>\r\nto manipulate it:<\/p>\r\n<pre>\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<\/pre>\r\n<p class=\"target\">This is a test paragraph.<\/p>\r\n<p id=\"test3\"><input type=\"button\" value=\"on\"\/>\r\n<input type=\"button\" value=\"darker\"\/>\r\n<input type=\"button\" value=\"lighter\"\/>\r\n<input type=\"button\" value=\"off\"\/>\r\n<\/p>\r\n<script>\r\n\t$.fn.green2 = function(opt){\r\n\t\treturn this.each(function(){\r\n\t\t\tif (opt == undefined){ \/\/ initial call\r\n\t\t\t\tif (!this.green) this.green = new Green($(this)); \/\/ associate our state-keeping object with the element\r\n\t\t\t\tthis.green.setLevel(15);\r\n\t\t\t}else if (opt == 'off'){\r\n\t\t\t\tif (this.green) this.green.setLevel(16);\r\n\t\t\t\tdelete this.green;\r\n\t\t\t}else if (opt == 'darker'){\r\n\t\t\t\tif (this.green) this.green.setLevel(this.green.getLevel()-1);\r\n\t\t\t}else if (opt == 'lighter'){\r\n\t\t\t\tif (this.green) this.green.setLevel(this.green.getLevel()+1);\r\n\t\t\t}\r\n\t\t});\r\n\t};\r\n\r\n\tfunction Green(target){\r\n\t\tgreenlevels = ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'];\r\n\t\tthis.target = target; \/\/ associate the element with the object\r\n\t\tthis.level = 0;\r\n\t\tthis.getLevel = function() { return this.level; }\r\n\t\tthis.setLevel = function(x) {\r\n\t\t\tthis.level = Math.floor(Math.min(16, Math.max(0,x)));\r\n\t\t\tthis.target.css({background: greenlevels[this.level]});\r\n\t\t}\r\n\t};\r\n\t$('#test3 input[value=on]').click(function() {$('.target').green2()});\r\n\t$('#test3 input[value=darker]').click(function() {$('.target').green2('darker')});\r\n\t$('#test3 input[value=lighter]').click(function() {$('.target').green2('lighter')});\r\n\t$('#test3 input[value=off]').click(function() {$('.target').green2('off')});\r\n<\/script>\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 <code>this.green = new Green(this)<\/code> gives a DOM element a reference to a javascript object\r\nand\r\n<code>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.\r\n<p>jQuery solves the circular reference problem with the <code>$.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>init<\/code> is called on construction,\r\nthe function <code>destroy<\/code> is called on removal. Both of these are predefined but you can override them (and most likely\r\nwill need to override init). <code>element<\/code> is the associated jQuery object (what we called <code>target<\/code> above).<\/p>\r\n<pre>\r\n\r\nvar Green3  = {\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\tinit: function() { this.setLevel(15); },\r\n\tgetLevel: function() { return this.level; },\r\n\tsetLevel: function(x) {\r\n\t\tthis.level = Math.floor(Math.min(16, 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<\/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 \"yi.green\" . Unfortunately the namespacing is fake; the plugin is just called $().green(). The constructor function\r\nis $.yi.green, but you never use that.  But defining the widget couldn't be easier:<\/p>\r\n<pre>\r\n$.yi = $.yi || {}; \/\/ create the namespace\r\n$.widget(\"yi.green3\", Green3); \/\/ create the widget\r\n<\/pre>\r\n<p>(Yes, needing to create the namespace is a mistake that the authors of $.widget forgot.)<\/p>\r\n<h4>Manipulating Widgets<\/h4>\r\n<p>What about our manipulating functions? All the functions defined in the prototype are exposed automatically:\r\n<code>$('.target').green3()<\/code> creates the widgets; <code>$('.target').green3('darker')<\/code> manipulates them.<\/p>\r\n<p>If your function is intended to be a \"getter\"; something that returns a value rather than manipulates the objects\r\n(like <code>$('.target').html()<\/code> returns the innerHTML) then you need to tell the widget that by assigning a list of names\r\n(space or comma-delimited) or array of names. Note that only the value for the first element in the jQuery object will\r\nbe returned; exactly like <code>.html()<\/code> or <code>.val()<\/code>.<\/p>\r\n<pre>\r\n$.yi.green3.getter = \"getLevel otherGetter andAnother\";\r\n\/\/ or\r\n$.yi.green3.getter = \"getLevel, otherGetter, andAnother\";\r\n\/\/ or\r\n$.yi.green3.getter = [\"getLevel\",\"otherGetter\",\"andAnother\"];\r\n\r\n<\/pre>\r\n<p class=\"target\">This is a test paragraph.<\/p>\r\n<p id=\"test4\"><input type=\"button\" value=\"on\"\/>\r\n<input type=\"button\" value=\"darker\"\/>\r\n<input type=\"button\" value=\"lighter\"\/>\r\n<input type=\"button\" value=\"off\"\/>\r\n<\/p>\r\n<script>\r\n\tvar Green3  = {\r\n\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff'],\r\n\t\tlevel: 0,\r\n\t\tinit: function() { this.setLevel(15); },\r\n\t\tgetLevel: function() { return this.level; },\r\n\t\tsetLevel: function(x) {\r\n\t\t\tthis.level = Math.floor(Math.min(16, Math.max(0,x)));\r\n\t\t\tthis.element.css({background: this.greenlevels[this.level]});\r\n\t\t},\r\n\t\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\t\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\t\toff: function() {\r\n\t\t\tthis.element.css({background: 'none'});\r\n\t\t\tthis.destroy(); \/\/ use the predefined function\r\n\t\t}\r\n\t};\r\n\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t$.widget(\"yi.green3\", Green3);\r\n\t$.yi.green3.getter = \"getLevel\";\r\n\t\r\n\t$('#test4 input[value=on]').click(function() {$('.target').green3()});\r\n\t$('#test4 input[value=darker]').click(function() {$('.target').green3('darker')});\r\n\t$('#test4 input[value=lighter]').click(function() {$('.target').green3('lighter')});\r\n\t$('#test4 input[value=off]').click(function() {$('.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 two more functions that let us store and retrieve data for each instance individually:\r\n<code>setData<\/code> and <code>getData<\/code>. Note that these are functions of the widget object, not the jQuery one.\r\nThus:<\/p>\r\n<pre>\r\nfunction getLevel() { return this.getData('level'); }\r\nfunction setLevel(x) {\r\n\tvar level = Math.floor(Math.min(16, Math.max(0,x)));\r\n\tthis.setData('level', level);\r\n\tthis.element.css({background: greenlevels[level]});\r\n}\r\n<\/pre>\r\n<p>And you can set initial values for the data with the <code>defaults<\/code>\r\nobject: <code>$.yi.green4.defaults = {level: 15}<\/code> and override the defaults for any given object by passing\r\nan options object to the widget constructor: <code>$('.target').green4({level: 8})<\/code>.<\/p>\r\n<pre>\r\n(function($){\r\n\t\/\/ widget prototype. Everything here is public\r\n\tvar Green5  = {\r\n\t\tgetLevel: function () { return this.getData('level'); },\r\n\t\tsetLevel: function (x) {\r\n\t\t\tvar greenlevels = this.getData('greenlevels');\r\n\t\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\t\tthis.setData('level', level);\r\n\t\t\tthis.element.css({background: greenlevels[level]});\r\n\t\t},\r\n\t\tinit: function() { this.setLevel(this.getLevel()); }, \/\/ grab the default value and use it\r\n\t\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\t\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\t\toff: function() {\r\n\t\t\tthis.element.css({background: 'none'});\r\n\t\t\tthis.destroy(); \/\/ use the predefined function\r\n\t\t}\r\n\t};\r\n\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t$.widget(\"yi.green5\", Green5);\r\n\t$.yi.green5.defaults = {\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})(jQuery);\r\n<\/pre>\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<p class=\"target\">This is a test paragraph.<\/p>\r\n<p id=\"test5\"><input type=\"button\" value=\"on\"\/>\r\n<input type=\"button\" value=\"darker\"\/>\r\n<input type=\"button\" value=\"lighter\"\/>\r\n<input type=\"button\" value=\"off\"\/>\r\n<\/p>\r\n<script>\r\n\t(function($){\r\n\t\t\/\/ widget prototype. Everything here is public\r\n\t\tvar Green5  = {\r\n\t\t\tgetLevel: function () { return this.getData('level'); },\r\n\t\t\tsetLevel: function (x) {\r\n\t\t\t\tvar greenlevels = this.getData('greenlevels');\r\n\t\t\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\t\t\tthis.setData('level', level);\r\n\t\t\t\tthis.element.css({background: greenlevels[level]});\r\n\t\t\t},\r\n\t\t\tinit: function() { this.setLevel(this.getLevel()); }, \/\/ grab the default value and use it\r\n\t\t\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\t\t\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\t\t\toff: function() {\r\n\t\t\t\tthis.element.css({background: 'none'});\r\n\t\t\t\tthis.destroy(); \/\/ use the predefined function\r\n\t\t\t}\r\n\t\t};\r\n\t\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t\t$.widget(\"yi.green5\", Green5);\r\n\t\t$.yi.green5.defaults = {\r\n\t\t\tlevel: 15,\r\n\t\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff']\r\n\t\t};\r\n\t})(jQuery);\r\n\r\n\t$('#test5 input[value=on]').click(function() {$('.target').green5()});\r\n\t$('#test5 input[value=darker]').click(function() {$('.target').green5('darker')});\r\n\t$('#test5 input[value=lighter]').click(function() {$('.target').green5('lighter')});\r\n\t$('#test5 input[value=off]').click(function() {$('.target').green5('off')});\r\n<\/script>\r\n<h4>Callbacks<\/h4>\r\n<p>Now, the user of our widget may want to do other things when our widget changes. We can create callback functions\r\nthat the widget calls at critical points:<\/p>\r\n<pre>\r\nvar Green6 = {\r\n\tsetLevel = function(x){\r\n\t\tvar greenlevels = this.getData('greenlevels');\r\n\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.setData('level', level);\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t\tvar callback = this.getData('change');\r\n\t\tif (isFunction(callback)) callback(x);\r\n\t},\r\n\t\/\/ ... rest of widget definition\r\n};\r\n$.widget(\"yi.green6\", Green6);\r\n\r\n$('.target').green6({change: function(x) { alert (\"The color changed!\"); } });\r\n<\/pre>\r\n<p>Or we can use a real observer pattern with custom events:<\/p>\r\n<pre>\r\nvar Green6 = {\r\n\tsetLevel = function(x){\r\n\t\tvar greenlevels = this.getData('greenlevels');\r\n\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\tthis.setData('level', level);\r\n\t\tthis.element.css({background: greenlevels[level]});\r\n\t\tthis.element.triggerHandler(\"green6changed\", [x]);\r\n\t\t\/\/ this.element.trigger(\"green6changed\", [x]) would work as well, but be a bit slower; it tries and fails to\r\n\t\t\/\/ trigger the native \"green6changed\" event, which doesn't exist, and does a element.each(...) rather than\r\n\t\t\/\/ element[0] (both are equivalent, since element only has one item, but the each wastes time figuring that out).\r\n\t},\r\n\t\/\/ ... rest of widget definition\r\n};\r\n$.widget(\"yi.green6\", Green6);\r\n\r\n$('.target').green6().bind(\"green6changed\", function(e,level) { alert (\"The color changed!\"); });\r\n\/\/ note the function convention for an event handler: first parameter is the event, the rest are passed from the trigger.\r\n\/\/ for a custom event, when no real event is passed in, jQuery synthesizes a fake one.\r\n<\/pre>\r\n<p><code>$.widget<\/code> encourages both forms. <code>trigger<\/code> and <code>triggerHandler<\/code>\r\naccept a third parameter, which should be the callback supplied by the user. jQuery will take care of\r\nthe <code>isFunction()<\/code> test.\r\n<pre>\r\n\tthis.element.triggerHandler(\"green6changed\", [level], this.getData(\"change\"));\r\n<\/pre>\r\n<p>And the user can use either the callback or the custom event method. However, there is a difference:\r\ncustom event handlers get a synthetic event as the first parameter; callbacks only get the array explicitly given\r\nin the <code>trigger<\/code> call. If your callback needs event info (like mouse position), pass it directly:\r\n<code>this.element.triggerHandler(\"green6changed\", [eventObject, level], this.getData(\"change\"));<\/code>.<\/p>\r\n<p>The convention that UI uses is to have the callback name be a verb and the event name be a short prefix followed by the verb.<\/p>\r\n<p class=\"target\">This is a test paragraph with green level <span class=\"level\">undefined<\/span>.<\/p>\r\n<p id=\"test6\"><input type=\"button\" value=\"on\"\/>\r\n<input type=\"button\" value=\"darker\"\/>\r\n<input type=\"button\" value=\"lighter\"\/>\r\n<input type=\"button\" value=\"off\"\/>\r\n<\/p>\r\n<script>\r\n\t(function($){\r\n\t\t\/\/ widget prototype. Everything here is public\r\n\t\tvar Green6  = {\r\n\t\t\tgetLevel: function () { return this.getData('level'); },\r\n\t\t\tsetLevel: function (x) {\r\n\t\t\t\tvar greenlevels = this.getData('greenlevels');\r\n\t\t\t\tvar level = Math.floor(Math.min(greenlevels.length-1, Math.max(0,x)));\r\n\t\t\t\tthis.setData('level', level);\r\n\t\t\t\tthis.element.css({background: greenlevels[level]});\r\n\t\t\t\tthis.element.triggerHandler(\"green6changed\", [level], this.getData(\"change\"));\r\n\t\t\t},\r\n\t\t\tinit: function() { this.setLevel(this.getLevel()); }, \/\/ grab the default value and use it\r\n\t\t\tdarker: function() { this.setLevel(this.getLevel()-1); },\r\n\t\t\tlighter: function() { this.setLevel(this.getLevel()+1); },\r\n\t\t\toff: function() {\r\n\t\t\t\tthis.element.css({background: 'none'});\r\n\t\t\t\tthis.destroy(); \/\/ use the predefined function\r\n\t\t\t}\r\n\t\t};\r\n\t\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t\t$.widget(\"yi.green6\", Green6);\r\n\t\t$.yi.green6.defaults = {\r\n\t\t\tlevel: 15,\r\n\t\t\tgreenlevels: ['#000','#010','#020','#030','#040','#050','#060','#070','#080','#090','#0a0','#0b0','#0c0','#0d0','#0e0','#0f0', '#fff']\r\n\t\t};\r\n\t})(jQuery);\r\n\r\n\t$('#test6 input[value=on]').click(function() {$('.target').green6({\r\n\t\tchange: function(x){\r\n\t\t\t$('.target .level').text(x); \/\/ callback function\r\n\t\t}\r\n\t})}); \/\/ starting to look a lot like LISP :)\r\n\t$('#test6 input[value=darker]').click(function() {$('.target').green6('darker')});\r\n\t$('#test6 input[value=lighter]').click(function() {$('.target').green6('lighter')});\r\n\t$('#test6 input[value=off]').click(function() {$('.target').green6('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 ui.core.js provides a mixin object that\r\nincludes lots of useful methods for the mouse. All we need to do is add the $.ui.mouse object to our\r\nwidget prototype:<\/p>\r\n<pre>\r\nGreen7 = $.extend({}, Green6, $.ui.mouse);\r\n<\/pre>\r\n<p>And override <code>$.ui.mouse<\/code>'s functions (mouseStart, mouseDrag, mouseStop) to do something useful,\r\nand call <code>this.mouseInit<\/code>\r\nin your <code>this.init<\/code>.<\/p>\r\n<p>Let's add some mouse control to our greenerizer:\r\n<pre>\r\n\tGreen7 = $.extend({}, $.yi.green6.prototype, $.ui.mouse); \/\/ leave the old Green6 alone; create a new object\r\n\t\/\/ need to override the mouse functions after they are added to the object\r\n\tGreen7.mouseStart = function(e){\r\n\t\tthis.setData('xStart', e.pageX);\r\n\t\tthis.setData('levelStart', this.getData('level'));\r\n\t};\r\n\tGreen7.mouseDrag = function(e){\r\n\t\tthis.setLevel(this.getData('levelStart') +(e.pageX-this.getData('xStart'))\/this.getData('distance'));\r\n\t}\r\n\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t$.widget(\"yi.green7\", Green7);\r\n\t$.yi.green7.defaults = {\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<\/pre>\r\n<p class=\"target\">This is a test paragraph with green level <span class=\"level\">undefined<\/span>.<\/p>\r\n<p id=\"test7\"><input type=\"button\" value=\"on\"\/>\r\n<input type=\"button\" value=\"darker\"\/>\r\n<input type=\"button\" value=\"lighter\"\/>\r\n<input type=\"button\" value=\"off\"\/>\r\n<\/p>\r\n<script>\r\n(function($){\r\n\tGreen7 = $.extend({}, $.yi.green6.prototype, $.ui.mouse); \/\/ leave the old Green6 alone; create a new object\r\n\tGreen7.init = function(){\r\n\t\tthis.setLevel(this.getLevel());\r\n\t\tthis.mouseInit(); \/\/ don't forget to init the mouse \r\n\t};\r\n\t\/\/ need to override the mouse functions after they are added to the object\r\n\tGreen7.mouseStart = function(e){\r\n\t\tthis.setData('xStart', e.pageX);\r\n\t\tthis.setData('levelStart', this.getLevel());\r\n\t};\r\n\tGreen7.mouseDrag = function(e){\r\n\t\tthis.setLevel(this.getData('levelStart')+(e.pageX-this.getData('xStart'))\/this.getData('distance'));\r\n\t}\r\n\t$.yi = $.yi || {}; \/\/ create the namespace\r\n\t$.widget(\"yi.green7\", Green7);\r\n\t$.yi.green7.defaults = {\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})(jQuery);\r\n\r\n$('#test7 input[value=on]').click(function() {$('.target').green7({\r\n\tchange: function(x){\r\n\t\t$('.target .level').text(x); \/\/ callback function\r\n\t}\r\n})}); \/\/ starting to look a lot like LISP :)\r\n$('#test7 input[value=darker]').click(function() {$('.target').green7('darker')});\r\n$('#test7 input[value=lighter]').click(function() {$('.target').green7('lighter')});\r\n$('#test7 input[value=off]').click(function() {$('.target').green7('off')});\r\n<\/script>\r\n<p>The ever-alert reader will note what we've just done: subclassed green6 to make green7. This ought to be abstracted out\r\ninto its own method, something like \r\n<code>$.widget.subclass(\"yi.green7\", \"yi.green6\", $.ui.mouse, {mouseStart: function(){}, mouseDrag: function(){}})<\/code>\r\nbut that's a <a href=\"\/blog\/2008\/08\/13\/extending-jquery-ui-widgets-revisited\/\">topic for another day<\/a>.<\/p>\r\n","protected":false},"excerpt":{"rendered":"This page is obsolete (it uses jQuery UI 1.5). Please see the updated page. 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 button or something more complicated like a popup [&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\/26"}],"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=26"}],"version-history":[{"count":13,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/26\/revisions"}],"predecessor-version":[{"id":2991,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/posts\/26\/revisions\/2991"}],"wp:attachment":[{"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/media?parent=26"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/categories?post=26"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bililite.com\/blog\/wp-json\/wp\/v2\/tags?post=26"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}