⬆️ ⬇️

JQuery UI widget factory

All jQuery UI widgets are created on a simple basis - Factory Widgets. It provides a flexible framework for creating complex, structured plugins with a compatible API. With its help, you can create not only jQuery UI plugins, but also any object-oriented components without inventing bicycles. It does not depend on other components jQuery UI, on the contrary, most of the components of the UI depends on it.



What is it?



The widget factory is a global jQuery method - jQuery.widget - taking 2 or 3 arguments.



jQuery.widget( 'namespace.widgetname', namespace.superwidget, //   -    //       {...} //  , //      ); 


The first argument of a string type containing the namespace for your widget, and its name, are separated by a period. The namespace is required and refers to the place in the global jQuery object where the widget's prototype will be stored. If the namespace is not set, the factory will create it for you. (comment of the translator - But a plug-in without a namespace simply will not work, in any case I did not succeed. Anyway, namespaces are useful.) The name of the plug-in is stored as the name of the plug-in and the prototype. For example:

')

 jQuery.widget("demo.multi", {...}) 


will create jQuery.demo.multi and jQuery.demo.multi.prototype .



The second (optional) argument, the prototype for inheriting from it. For example, in jQuery UI there is a mouse plugin on which all plug-ins for interactions [with the mouse] are based. To achieve this, draggable , droppable , etc. are inherited from the mouse plugin:



 jQuery.widget( "ui.draggable", $.ui.mouse, {...} ); 


If you do not specify this argument, the widget is inherited directly from the jQuery.Widget “main widget” (note the difference between jQuery.widget with a small w and jQuery.Widget with a large W).



The final argument is the object literal to be used as a prototype for each widget instance. The factory creates a chain of prototypes, connecting the widget's prototype with all the widgets from which it is inherited, up to the basic jQuery.Widget .



When jQuery.widget is called in the jQuery prototype (jQuery.fn), a new method is created corresponding to the name of the widget, in our case it will be jQuery.fn.multi . The .fn method serves as an interface between the DOM elements obtained in the jQuery object and the widget instances. A widget instance is created for each element in the jQuery object.



Benefit



The simplified approach described in the Guidelines for plug-in development leaves many questions with a specific implementation when it comes to structured code and OOP-oriented plug-ins. In addition, they do not offer any solutions for common problems. The widget factory provides the jQuery UI API to communicate with the plugin instance and solve several repetitive tasks.







Creating your own prototype



Infrastructure


The object literal passed as a prototype can be arranged as you please, but it must contain options (note of the translator - the default options), _create _create , _setOption , and destroy .



Example
 (function( $ ) { $.widget( "demo.multi", { //          options: { clear: null }, //   _create: function() { }, //   _setOption       _setOption: function( key, value ) { switch( key ) { case "clear": //   break; } //  jQuery UI 1.8,     _setOption    $.Widget.prototype._setOption.apply( this, arguments ); //  jQuery UI 1.9  ,     _super this._super( "_setOption", key, value ); }, //   destroy      DOM,     destroy: function() { //  jQuery UI 1.8    destroy    $.Widget.prototype.destroy.call( this ); //  jQuery UI 1.9        _destroy        } }); }( jQuery ) ); 


Encapsulation in methods


Your object will most likely contain methods for handling various specific operations, for example, building and inserting new elements or handling events. It would be reasonable to use isolated methods for each operation, instead of handling the heap of everything in the _create method. This will not duplicate the code in case of changes.



For example, in a hypothetical widget that extends <select multiple> , someone will want to run over the child <option> to create the corresponding <li> and <ul> . This can be implemented through the _create method:



 _create: function() { var self = this; this.list = $( "<ul>" ).insertAfter( this.element ); this.element.hide().find( "option" ).each(function( i, el ) { var $el = $( el ), text = $( el ).text(), item = $( "<li class='multi-option-item'>" + text + "</li>" ); item.appendTo( self.list ).click(function(){ console.log( $el.val() ); }); }); } 




Unfortunately, if you leave the code in _create this, it will create difficulties in defining the links between the original <option> element and the list items we create, or the problem when changing the state of <option> elements that were added or removed from the original <select> already after initializing the widget. Instead, we will make the refresh method, responsible for working with the element, and _create from the _create method. We will also move the processing of clicks on the elements of the list separately, and we will use event delegation so as not to hang new handlers after creating a new item.



Example
 _create: function() { this.list = $( "<ul>" ) .insertAfter( this.element ) .delegate( "li.multi-option-item", "click", $.proxy( this._itemClick, this ) ); this.element.hide(); this.refresh(); }, refresh: function() { //     this.items = this.items || $(); //  ,        this.element.find( "option:not(.demo-multi-option)" ).each( $.proxy(function( i, el ) { //  ,         var $el = $( el ).addClass( "demo-multi-option" ), text = $el.text(), //    item = $( "<li class='multi-option-item'>" + text + "</li>" ) .data( "option.multi", el ) .appendTo( this.list ); //    this.items = this.items.add( item ); },this)); //      ,          this.items = this.items.filter( $.proxy(function( i, item ) { var isInOriginal = $.contains( this.element[0], $.data( item, "option.multi" ) ); if ( !isInOriginal ) { $( item ).remove(); } return isInOriginal; }, this )); }, _itemClick: function( event ) { console.log( $( event.target ).val() ); } 


Private v. Public methods


As you probably noticed, we wrote some methods with underscores at the beginning, while some without. Prefix methods are treated as private. The factory blocks all attempts to call them through $.fn



 $( "#something" ).multi( "_create" ) 


The code above will raise an exception. But, since they are present in the widget prototype, they are considered private only by agreement. When calling via .data() you can call any of these methods directly:



 $( "#something" ).data( "multi" )._create() 


How to make the right decision? If users of your widgets need certain methods, then make them public. The example with refresh indicative: since the user can control the elements of the select, we must ensure that he can update it. On the other hand, a service function for event handling, such as _itemClick , is required only for internal use and is not needed at all in the public interface of the plugin.



Properties


this.element



The element used by the plugin instance. For example:



 $( "#foo" ).myWidget() 


In this case, this.element will be a jQuery object containing the element with id foo . For multiple items for which .myWidget() is called, a separate instance of the plugin will be called for each item. In other words, this.element will always contain only one element.



this.options



Options used to customize the plugin. When creating an instance, any options passed by the user are automatically merged with those specified in $.demo.multi.prototype.options . Custom options overwrite default options.



this.namespace



The namespace of the plugin, in our case “demo”. Usually not used in plugins.



this.name



The name of the plugin, in our case "multi". A bit more useful than this.namespace , but in most cases also not used.



this.widgetEventPrefix



The property used to name the plugin events. For example, dialog has a close callback; when it is executed, the dialogclose event dialogclose . The name of the event consists of the event prefix and the name of the callback. By default, the value of widgetEventPrefix is the same as the name of the widget, but can be overwritten. For example, if a user started dragging an item, we do not want the draggablestart event to pop up, we want it to be called dragstart , for this we make the event prefix equal to "drag". If the name of the callback matches the event prefix, the event will be without a prefix. This will avoid events like dragdrag .



this.widgetBaseClass



Useful for creating class names for widget elements. For example, if you want to mark an item as active



 element.addClass( this.widgetBaseClass + "-active" ) 


For most plugins, this is not necessary, because it's easier to write something like .addClass( "demo-multi-active" ) . The given example is more relevant for the factory itself and abstract plug-ins, such as $.ui.mouse .



Methods


(approx. translator - best of all, of course, to read directly the documentation )



_create



The method in which everything is configured that relates to your widget — creating elements, hanging events, etc. The method is called once, immediately after creating an instance.



_init



The method that is called each time the widget is called, regardless of the number of arguments passed. During the first call, _init is called after _create . It can also be called at any time after the widget is created, in this case, _init can be used for re-initialization, without executing a wipe and re-creating.



destroy



A method that destroys a plugin instance and performs other actions you need. All modifications that your plugin makes should be destroyed by the destroy method. Including the removal of classes, events, the destruction of created elements, etc. This is the starting point for the destruction of your plugin, but for each plugin it is written individually, based on your needs.



option



Used to set options after creating an instance. The method signature is similar to the .css() and .attr() methods. You can specify only a name to get a value, or a name together with a value, in order to set it, or to transfer an object, to set several values. The method calls _setOptions , so it should not be changed by third-party plugins.



_setOptions



Private method, used to set preferences after creating an instance. The method calls _setOption, so it should not be changed by third-party plugins.



_setOption



Called when the user changes the value via the option method. This method can be changed in your plugin, if you need special behavior when changing certain options. For example, if the value of the window title changes in the dialog box, you need to run the title update.



 _setOption: function(key, value) { if (key === 'title') { this.titleElement.text(value); } $.Widget.prototype._setOption.apply(this, arguments); } 


By calling the parent _setOption method, we set the new value of the option. This should not be performed _setOption . Sometimes it is necessary to compare the old and the new values ​​in order to determine the correct behavior. You can compare this.options[key] with the value, since the call to the _setOption parent method is already at the very end. If you do not need to compare anything, you can make a call to the parent _setOption at the very beginning of the method.



enable



The helper invoking option('disabled', false) . You can also catch the call of this helper by checking:



 if (key === "disabled") 


in your _setOption .



disable



The helper invoking option('disabled', true) . You can also catch the call of this helper by checking:



 if (key === "disabled") 


in your _setOption.



_trigger



This method must be used for all callbacks. The name of the executed callback is the only required parameter. All callbacks also trigger an event (see description of this.widgetEventPrefix above). You can also pass the event object that started the callback. For example, the drag event is triggered by a mousemove event, and it must be passed to _trigger . The third parameter is an object with data that is passed to the callback and event handlers. The data transmitted in this object should relate only to the current event, and should not be given to other methods of the plugin.

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



All Articles