JQuery tips to make any component support DOM-like event management _ jquery

Source: Internet
Author: User
Tags eventbase
This article mainly introduces the jQuery technique to enable any component to support DOM-like event management. If you need it, refer to this article to introduce a jquery tips, allows any component object to support event management similar to DOM. That is to say, in addition to distributing events, adding or deleting event listeners, event bubbling and blocking default event behaviors are also supported. With the help of jquery, using this method to manage common object events is exactly the same as managing DOM object events, although when you finally see the specific content of this tip, you may think this is the case or not, but I think if you can change the implementation of the common publishing-subscription mode to an event mechanism similar to the DOM, the developed components will certainly have more flexibility and scalability, and I am also using this method for the first time (the reason why I am too knowledgeable). I think its use value is quite great, so I shared it.

Before officially introducing this technique, let's talk about one of the methods I have considered before, namely the publishing-subscription mode, to see what problems it can solve and what problems it has.

1. Publishing-subscription Mode

Many blogs, including books, use the publish-subscribe mode if javascript is to implement custom events of components. At first, I firmly believed this, so I used jquery's $. callbacks writes one:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('. /class'); function isFunc (f) {return Object. prototype. toString. apply (f) = '[object Function]';} /*** this basic class can enable the common class to be event-driven. * The on off trigger method similar to jq is provided, regardless of the one method or the namespace. * example: * var e = new EventBase (); * e. on ('load', function () {* console. log ('loaded'); *}); * e. trigger ('load'); // loaded * e. off ('load'); */var EventBase = Class ({instanceMembers: {init: function () {this. events = {}; // set $. the flag of Callbacks is set to an instance attribute so that the subclass can overwrite this. CALLBACKS_FLAG = 'unique';}, on: function (type, callback) {type = $. trim (type); // if the type or callback parameter is invalid, if (! (Type & isFunc (callback) return; var event = this. events [type]; if (! Event) {// define a new jq queue, and the queue cannot add duplicate callback events = this. events [type] = $. callbacks (this. CALLBACKS_FLAG);} // Add callback to this queue. This queue can access event through type. add (callback) ;}, off: function (type, callback) {type = $. trim (type); if (! Type) return; var event = this. events [type]; if (! Event) return; if (isFunc (callback) {// if both the type and callback are passed, the callback is removed from the queue corresponding to the type. remove (callback);} else {// otherwise, delete this. events [type] ;}}, trigger: function () {var args = []. slice. apply (arguments), type = args [0]; // convert the first parameter to typetype =$. trim (type); if (! Type) return; var event = this. events [type]; if (! Event) return; // use the remaining parameters to trigger the callback corresponding to the type. // At the same time, set the callback context to the event of the current instance. fireWith (this, args. slice (1) ;}}); return EventBase ;});

(Based on seajs and the inherited Library class. js introduced in "detailed introduction to the inheritance Implementation of Javascript)

As long as any component inherits this EventBase, it can inherit the on off trigger method provided by it to subscribe to, publish, and cancel a message. For example, the FileUploadBaseView that I want to implement below:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('. /class '); var EventBase = require ('. /eventbase'); var DEFAULTS = {data: [], // list of data to be displayed. The list element must be of the object type, for example, [{url: 'xxx.png '}, {url: 'yyyy.png '}] sizeLimit: 0, // used to limit the number of elements displayed in BaseView. If it is 0, readonly: false is not limited, // controls whether the elements in BaseView can be added or deleted. onBeforeRender: $. noop, // corresponds to the beforeRender event, which triggers onRender: $. noop, // corresponding to the render event. onBeforeAppend is triggered after the render method is called: $. noop, // corresponds to the beforeAppend event. onAppend: $ is triggered before the append method is called. noop, // corresponds to the append event. After the append method is called, onBeforeRemove is triggered: $. noop, // corresponds to the beforeRemove event. onRemove: $ is triggered before the remove method is called. noop // corresponding to the remove event, triggered after the remove method is called};/*** data parsing, add a unique identifier for each element _ uuid, easy to find */function resolveData (ctx, data) {var time = new Date (). getTime (); return $. map (data, function (d) {d. _ uuid = '_ uuid' + time + Math. floor (Math. random () * 100000);} var FileUploadBaseView = Class ({instanceMembers: {init: function (options) {this. base (); this. options = this. getOptions (options) ;}, getOptions: function (options) {return $. extend ({}, DEFAULTS, options) ;}, render: function () {}, append: function (data) {}, remove: function (prop ){}}, extend: EventBase}); return FileUploadBaseView ;});

The actual call test is as follows:



In the test, a FileUploadBaseView object f is instantiated, its name attribute is set, a listener related to hello is added through the on method, and the hello listener is triggered through the trigger method, two additional parameters are passed. In addition to accessing the data passed by the trigger through the function parameters of the listener, the listener can also access the f object through this.

From the current results, this method looks good, but I encountered a problem when I want to continue to implement FileUploadBaseView. You can see the subscription-related options when I design this component:

My original design is: These subscriptions are defined in pairs. A subscription corresponds to an instance method. For example, the subscription with before will be triggered before the corresponding instance method (render) is called, the subscription without before is triggered after the corresponding instance method (render) is called, and the subscription with before is required if false is returned, the corresponding instance method and Subsequent Subscription will not be executed. The final design requirement is that, before calling the component's instance method, it may be necessary to cancel the call of the current instance method for some special reasons. For example, some data cannot be removed when the remove method is called, then, you can perform some validation in the before subscription. If yes, true is returned. If no deletion is allowed, false is returned. Then, after triggering the before subscription in the instance method, add a judgment, this method is similar to the following:

However, this method can only be implemented in the callback function mode. In the publish-subscribe mode, the callback function will only be associated with a function reference, but in the publish-subscribe mode, the same message may have multiple subscriptions. If you apply this method to publish-subscribe, when you call this. when trigger ('beforerender') is used, all subscriptions associated with beforeRender are called once. Which subscription return value prevails? You may say that you can use the return value of the last subscription in the queue as the standard. In most cases, it may be okay to do this, however, when we add the logic of "taking the last subscription return value of the queue as the judgment standard" to EventBase, there is a big risk that when the outside is used, you must manage the subscription order clearly. You must put the subscription related to some special logic such as validation at the end, which has nothing to do with syntax and compilation, the development method that requires coding sequence will bring great security risks to the software. Who can ensure that the order of subscription can be controlled at any time in any scenario, what's more, there may be some new people in the company who don't even know that there are such restrictions on what you write.

The perfect way to solve this problem is like the DOM object event. When a message is published, instead of simply publishing a message string, the message is encapsulated into an object, this object will be passed to all its subscriptions. In which subscription, the logic after the message is published should be blocked, as long as the preventDefault () method of the message is called, after the message is published externally, call the isDefaultPrevented () method of the message to determine the message:

This approach is the same as using jquery to manage DOM object events, for example, most of the bootstrap components and the components I wrote in the previous blogs use this method to add additional Judgment logic, for example, the alert component of bootstrap has the following judgment when executing the close method:

Modifying EventBase based on this idea is a solution to the problem, but jquery's tips make it easier to manage the events of the entire common object, let's take a look at its true nature.

2. jquery tips

1) tip 1

When defining a component, this component is associated with a DOM object, for example, in the following form:

Then, we can add the on off trigger one common event management methods to this component, and then delegate these methods to the corresponding methods of $ element:

Through proxy, when calling the on method of the component, the on method of $ element is actually called, so that this type of component can support perfect event management.

2) Tip 2

The first technique can only be applied to components associated with DOM. How can we add a perfect event management mechanism like above for components that are completely unrelated to DOM? In fact, the method is also very simple, but I have never used it before, so this time I will feel particularly fresh:

If you pass an empty object to the jquery constructor, it returns a jquery object that perfectly supports event management. Besides the event management function, it is a jquery object. Therefore, all methods on the jquery prototype can be called. In the future, if you need to use other DOM-independent methods of jquery, you may also need to refer to this tips for implementation.

3. Perfect event management implementation

Considering that the two methods described in Part 1 contain repeated logic code, if they are combined, they can be applied to all development component scenarios, in this way, we can achieve the goal of enabling any object to support the event management function mentioned in the title and the beginning of this article. Therefore, we can combine the first two skills to transform EventBase as follows (is it simple enough ):

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('. /class');/*** this basic class enables common classes to manage events of jquery objects */var EventBase = Class ({instanceMembers: {init: function (_ jqObject) {this. _ jqObject = _ jqObject & _ jqObject instanceof $ & _ jqObject ||$ ({}) ;}, on: function () {return $. fn. on. apply (this. _ jqObject, arguments) ;}, one: function () {return $. fn. one. apply (this. _ jqObject, arguments) ;}, off: function () {return $. fn. off. apply (this. _ jqObject, arguments) ;}, trigger: function () {return $. fn. trigger. apply (this. _ jqObject, arguments) ;}}); return EventBase ;});

The actual call test is as follows:

1) simulate Components associated with DOM

Test code 1:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('mod/class '); var EventBase = require ('mod/eventbase'); var Demo = window. demo = Class ({instanceMembers: {init: function (element, options) {this. $ element = $ (element); this. base (this. $ element); // Add a listener this. on ('beforerender', $. proxy (options. onBeforeRender, this); this. on ('render', $. proxy (options. onRender, this ));}, Render: function () {// triggers the beforeRender event var e =$. event ('beforerender'); this. trigger (e); if (e. isDefaultPrevented () return; // main logic code console. log ('Render complete! '); // Trigger the render event this. trigger ('render') ;}}, extend: EventBase}); var demo = new Demo ('# demo', {onBeforeRender: function (e) {console. log ('beforerender event triggered! ');}, OnRender: function (e) {console. log ('Render event triggered! ') ;}}); Demo. render ();});

In this test, I defined a Demo component associated with DOM and inherited the EventBase event management class. I added a listener to both the beforeRender event and the render event, the render method also prints information to simulate the real logic. when instantiating the Demo, the # demo DOM element is used. The final test result is:

Exactly as expected.

Test code 2:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('mod/class '); var EventBase = require ('mod/eventbase'); var Demo = window. demo = Class ({instanceMembers: {init: function (element, options) {this. $ element = $ (element); this. base (this. $ element); // Add a listener this. on ('beforerender', $. proxy (options. onBeforeRender, this); this. on ('render', $. proxy (options. onRender, this ));}, Render: function () {// triggers the beforeRender event var e =$. event ('beforerender'); this. trigger (e); if (e. isDefaultPrevented () return; // main logic code console. log ('Render complete! '); // Trigger the render event this. trigger ('render') ;}}, extend: EventBase}); var demo = new Demo ('# demo', {onBeforeRender: function (e) {console. log ('beforerender event triggered! ');}, OnRender: function (e) {console. log ('Render event triggered! ') ;}}); Demo. on ('beforerender', function (e) {e. preventDefault (); console. log ('beforerender event triggered 2! ') ;}); Demo. on ('beforerender', function (e) {console. log ('beforerender event triggered 3! ') ;}); Demo. render ();});

In this test, I defined a DOM-related Demo component and inherited the EventBase event management class. I added three listeners to the beforeRender event, one of which added prevetDefault () and the callback is not the last one. The final test result is:

From the result, we can see that the main logic code of the render method and the subsequent render events are not executed, and all beforeRender listeners are executed. e. preventDefault () takes effect, and it does not affect the event queue of beforeRender.

2) simulate common objects not associated with DOM

Test code 1:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('mod/class '); var EventBase = require ('mod/eventbase'); var Demo = window. demo = Class ({instanceMembers: {init: function (options) {this. base (); // Add the listener this. on ('beforerender', $. proxy (options. onBeforeRender, this); this. on ('render', $. proxy (options. onRender, this);}, render: function () {// trigger the beforeRender event var e = $ . Event ('beforerender'); this. trigger (e); if (e. isDefaultPrevented () return; // main logic code console. log ('Render complete! '); // Trigger the render event this. trigger ('render') ;}}, extend: EventBase}); var demo = new Demo ({onBeforeRender: function (e) {console. log ('beforerender event triggered! ');}, OnRender: function (e) {console. log ('Render event triggered! ') ;}}); Demo. render ();});

In this test, I defined a DOM-independent Demo component and inherited the EventBase event management class. I added a listener to both the beforeRender event and the render event, the render method also prints information to simulate the real logic. The final test result is:

Exactly as expected.

Test code 2:

Define (function (require, exports, module) {var $ = require ('jquery '); var Class = require ('mod/class '); var EventBase = require ('mod/eventbase'); var Demo = window. demo = Class ({instanceMembers: {init: function (options) {this. base (); // Add the listener this. on ('beforerender', $. proxy (options. onBeforeRender, this); this. on ('render', $. proxy (options. onRender, this);}, render: function () {// trigger the beforeRender event var e = $ . Event ('beforerender'); this. trigger (e); if (e. isDefaultPrevented () return; // main logic code console. log ('Render complete! '); // Trigger the render event this. trigger ('render') ;}}, extend: EventBase}); var demo = new Demo ({onBeforeRender: function (e) {console. log ('beforerender event triggered! ');}, OnRender: function (e) {console. log ('Render event triggered! ') ;}}); Demo. on ('beforerender', function (e) {e. preventDefault (); console. log ('beforerender event triggered 2! ') ;}); Demo. on ('beforerender', function (e) {console. log ('beforerender event triggered 3! ') ;}); Demo. render ();});

In this test, I defined a DOM-independent Demo component and inherited the EventBase event management class. I added three listeners to the beforeRender event, one of which added prevetDefault () and the callback is not the last one. The final test result is:

From the result, we can see that the main logic code of the render method and the subsequent render events are not executed, and all beforeRender listeners are executed. e. preventDefault () takes effect, and it does not affect the event queue of beforeRender.

From the two tests, we have obtained a method that allows any object to support the jquery event management mechanism through the transformed EventBase. In the future, when we consider using the event mechanism for decoupling, you don't have to worry about the publishing-subscription mode described earlier. In addition, this method is more stable and more suitable for you to operate the DOM using jquery.

4. Summary

There are two points that need to be explained:

1) You can modify the regular publishing-subscription mode of the first part even if jquery does not follow the last line of thinking proposed in part 1, but it is more concise to use jquery;

2) jquery's event mechanism is used to manage events of any objects. On the one hand, the agent mode is used, and more importantly, the publish-subscribe mode is used, however, jquery helped us to transform the publishing-subscription implementation in the first part.

The above content is related to jQuery techniques to allow any component to support DOM-like event management. I hope it will help you!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.