📜 ⬆️ ⬇️

jQuery UI as plugin framework

Introduction


jQuery UI is best known as a set of ready-made widgets. Their main advantage, in my opinion, is a consistent API: each widget is managed equally. Their second advantage is that they keep their state: if you re-hang the widget on an element, the result will be the already existing widget instance.
But jQuery UI is not only a set of windows and tabs (not everyone's favorite). It is also a whole infrastructure for creating your own widgets: with a convenient consistent API, with state storage and with the possibility of inheritance. Oddly enough, this is news for many people, as a result of which this article appeared - just as it was news to me just a few months ago.


Lyrical digression


In order not to waste time and space, everywhere below the code means that window. $ == window.jQuery, undefined nobody messed up, and that we connect jQuery, only jQuery and nothing but jQuery, and that all ads are wrapped in something like of this:
( function ($) {
//
})(jQuery)

* This source code was highlighted with Source Code Highlighter .

It also implies that the reader is quite familiar with jQuery and at least read the documentation from the jQuery UI.

$ .Widget magic


All magic is in the $.widget method. It takes 2 (or 3 in the case of inheritance) of the parameter. Officially, this method is called "widget factory".
The first parameter is a string, it contains the namespace and the actual name of the widget, separated by a dot. For example, "my.myWidget" . Namespace is required; nesting is not supported. The second parameter is an object literal, which, in fact, describes our widget:
$.widget( "my.myWidget" , {
options: {
greetings: "Hello"
},
_create: function () {
this .element.html( this .options.greetings);
}
})

* This source code was highlighted with Source Code Highlighter .

The function in the field named _create serves as a constructor, and will be called when the widget instance is created; This instance is indicated by this .
this.element is the element on which the widget was hung. It is always a single item, not a collection (as is the case with regular plugins); if you hang a widget on a jQuery object that contains more than one element, then as many instances as there are elements will be created.
The options field stores the default settings for the widget. This field is inherited, so it will always be in the widget, even if it is not explicitly declared.
If you pass an object when you call a widget, then the passed object will be sludge (using the $.merge method) with default settings before the _create call.
The setOption method is responsible for working with settings:
$.widget( "my.myWidget" , {
options: {
greetings: "Hello"
},
_create: function () {
this ._render();
},
_render: function () {
this .element.html( this .options.greetings);
},
setOption: function (key, value) {
if (value != undefined) {
this .options[key] = value;
this ._render();
return this ;
}
else {
return this .options[key];
}
}
})

* This source code was highlighted with Source Code Highlighter .

It is used in the same way as in any standard widget:
var mw = $( '.mywidget' ).myWidget({greeting: 'Hi there!' })
console.log(mw.myWidget( 'option' , 'greeting' )); // 'Hi there!'
mw.myWidget( 'option' , 'greeting' , 'O HAI CAN I HAZ CHEEZBURGER?' );

* This source code was highlighted with Source Code Highlighter .

')

Private and public methods


The widget's method can be accessed in much the same way as we access the settings:
$.widget( "my.myWidget" , {
options: {
greetings: "Hello"
},
_create: function() {
this ._render();
},
_render: function() {
this .element.html( this .options.greetings);
},
sayHello: function(saying) {
alert(saying);
},
_setOption: function(key, value ) {
if (arguments.length == 1) {
this .options[key] = value ;
this ._render();
return this ;
}
else {
return this .options[key];
}
}
})
// …
mw.myWidget( "sayHello" , 42);

* This source code was highlighted with Source Code Highlighter .

But for this, this method must be public. How to make a method public in the paradigm of UI th plugins? It's simple: using public methods, the widget engine considers those whose names do not begin with an underscore. All other methods are private. Fields of widgets that are not functions are always private.
This, of course, is not in the full sense of public and private methods, but their emulation, however, is sufficient to delineate access.

Callbacks


In fact, these are just shortcuts for binding to user events inside the widget. They are passed to the widget in the same way as the settings.
$.widget( "my.myWidget" , {
options: {
greetings: "Hello"
},
_create: function () {
this ._render();
},
_render: function () {
this .element.html( this .options.greetings);
this ._trigger( "onAfterRender" , null , {theAnswer: 42})
}
})
// …
var mw = $( ".mywidget" ).myWidget(
{
greeting: "Hi there!" ,
onAfterRender: function (evt, data) {
console.log(data.theAnswer)
}
})


* This source code was highlighted with Source Code Highlighter .

This is equivalent to the good old .bind in this form:
mw.bind( 'onAfterRender.myWidget' , function (evt, data) {console.log(data.theAnswer)})
* This source code was highlighted with Source Code Highlighter .


Destructors


Out-of-the-box widgets tend to generate a bunch of markup. Good or bad - a discussion question. But partly because partly because the link to the widget instance is written to the expand attribute of the DOM element, the destructor must be called when you destroy the widget.
A method called destroy is called as the destructor. Unfortunately, it should always be called explicitly. In order to have complete happiness, the following call must be inside the destructor:
$.Widget.prototype.destroy.call( this );
* This source code was highlighted with Source Code Highlighter .


Inheritance


One of the most delicious things, although there is almost no information on it.
If the second argument is to pass some other widget A (our widget in this case is the third argument), the new widget B will be its descendant.
Suppose in our application - a bunch of dialog boxes, and all - modal. However, they should not be closed by Esc. Every time I do not want to write this:
$( '.dialog' ).dialog({
modal: true ,
closeOnEscape: false ,
// … ,
// …
})

* This source code was highlighted with Source Code Highlighter .


We can inherit from the standard dialog and override the default settings:
$.widget( "my.mydlg" , $.ui.dialog, {
options: {
modal: true ,
closeOnEscape: false ,
},
_create: function() {
$.ui.dialog.prototype._create.call( this );
}
})

* This source code was highlighted with Source Code Highlighter .

Now we replace the .dialog calls with the .mydlg in the whole code and enjoy the reduction of duplication. Unfortunately, you have to explicitly specify the ancestor and manually call its constructor.

Conclusion


It seems to me that UI widgets are a good means of modularizing code. In small and medium-sized projects, they themselves can provide an adequate application infrastructure.
You do not need to drag the entire, rather weighty, jQueryUI - just the core component.
The pattern that underlies this widget engine is called the bridge by the creators (although, of course, the $.widget method is a factory). Finishing the $ .widget method with a file, you can get widgets that themselves read their settings from the markup, themselves find vital elements for themselves, and are automatically organized into a hierarchical structure. But this is clearly a topic for a separate article.

Source: https://habr.com/ru/post/120074/


All Articles