jquery tricks enable any component to support event management like Dom _jquery

Source: Internet
Author: User
Tags eventbase inheritance instance method

This article describes a little jquery trick that allows any component object to support event management like DOM, that is, in addition to distributing events, adding or removing event listeners, supporting event bubbling, blocking event default behavior, and so on. With jquery's help, using this method to manage common object events is exactly the same as managing DOM objects, although in the end when you look at the specifics of this little trick, you might find it so or so. But I think that if you can change the implementation of the common publish-subscribe model to a DOM-like event mechanism, the developed components will have greater flexibility and scalability, and I am also using this method for the first time (too shallow to see the reason), I think it's very useful value, so I share it out.

Before I introduce this technique formally, I'll start by saying one of the methods I've considered before, namely, the publish-subscribe model, to see what it solves and what it does.

1. Publish-Subscribe Mode

Many blogs, including books, say that JavaScript can implement custom events for components by using the Publish-subscribe model, which I firmly believed at first, and then using jquery $. Callbacks wrote a:

Define (function (Require, exports, module) {var $ = require (' jquery '); var Class = require ('./class '); function Isfunc (f) {return Object.prototype.toString.apply (f) = = ' [Object Function] ';}/** * This base class allows ordinary classes to have event-driven capabilities * providing similar JQ on-off trigger
method, regardless of one method or 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 = {};//$. The callbacks flag is set to an instance property so that subclasses can overwrite this.
Callbacks_flag = ' unique '; }, On:function (type, callback) {type = $.trim (type);//If invalid type or callback parameter does not process if (!) (
Type && Isfunc (callback)) return;
var event = This.events[type]; if (!event) {//defines a new JQ queue, and the queue cannot add a duplicate callback event = This.events[type] = $. Callbacks (this.
Callbacks_flag);
//Add callback to this queue, which can access Event.add (callback) via type; }, 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 Event.remove (callback);} else {//
Otherwise, the entire type corresponding to the queue delete This.events[type] is removed; }, Trigger:function () {var args = [].slice.apply (arguments), type = args[0];//The first argument to type type = $.trim (type); if (!t
YPE) return;
var event = This.events[type];
if (!event) return;
Use the remaining parameters to trigger the type-corresponding callback//while setting the context of the callback to the current instance Event.firewith (this, Args.slice (1));
}
}
});
return eventbase; });

(Inheritance library class.js based on SEAJS and "detailed JavaScript inheritance implementation")

As long as any component inherits this eventbase, it can inherit the on-off trigger method provided by it to complete the subscription of the message, publish and unsubscribe, such as the Fileuploadbaseview I want to implement below:

Define (function (Require, exports, module) {var $ = require (' jquery '); var Class = require ('./class '); var eventbase = re
Quire ('./eventbase '); var DEFAULTS = {data: []////////////////////////////////////////////////////////// The number of elements shown in, 0 for unrestricted readonly:false,//to control whether elements in Baseview allow additions and deletions of Onbeforerender: $.noop,//corresponding to BeforeRender events, Triggers the OnRender: $.noop before the Render method call,//corresponds to the render event, triggering the render after the Onbeforeappend method call: $.noop,//corresponding to the Beforeappend event, Triggers the onappend: $.noop before the Append method call,//corresponds to the Append event, triggering the append after the Onbeforeremove method call: $.noop,//corresponding to the Beforeremove event,
Triggers the onremove before the Remove method call: $.noop//corresponds to the Remove event, which is triggered after the Remove method call; /** * Data parsing, to 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.getoptio
NS (options); }, Getoptions:function (options) {return $.extend ({}, DEFAULTS, options);}, Render:function () {}, Append:function (data) {}, remove:funct
Ion (prop) {}}, extend:eventbase});
return fileuploadbaseview; });

The actual call test is as follows:



In the test, instantiate a Fileuploadbaseview object F, set its Name property, add a listener associated with hello via the on method, and finally trigger the Hello listener through the trigger method and pass an additional two arguments. In the listener, the F object can also be accessed through this, in addition to the data passed by the trigger through the function parameters of the listener.

From the current results, this approach looks good, but I encountered a problem when I wanted to continue to implement Fileuploadbaseview. You see, when I was designing this component, there were several subscription-related option:

My original design was: These subscriptions are paired definitions, a pair of subscriptions correspond to an instance method, such as a subscription with before, which is triggered before the corresponding instance method (render) call, and the subscription without before will be triggered after the corresponding instance method (render) call. It also requires that the subscription with before returns false and does not execute the corresponding instance method and subsequent subscriptions. The final design requirement is to consider that before invoking an instance method of a component, it is possible for some special reason to cancel the invocation of the current instance method, such as when the Remove method is invoked and the data cannot be removed, so you can do some validation in the before subscription. The ability to delete returns True, cannot delete returns false, and then add a judgment after triggering the before subscription in the instance method, similar to the following:

But this approach can only be implemented in a simple callback function pattern, in the publish-subscribe model is not feasible, because the callback function is only related to a function reference, and in the publish-subscribe mode, the same message may have multiple subscriptions, if you apply this practice to the publish-subscribe, When This.trigger (' BeforeRender ') is invoked, all subscriptions associated with the BeforeRender are called once, so which subscription's return value will prevail? Maybe you would say you can use the return value of the last subscription in the queue, in most cases, this may be fine, but when we add the logic of "the last subscription to the queue as the criterion of judgment" to Eventbase, there is a big risk that external use , be sure to manage the order of the subscription clearly, and make sure that the subscription that is related to some special logic of verification is put on the last side. And this is not related to grammar, compilation, the coding sequence has the requirements of the development of the software will bring a greater security risks, who can ensure that any time any scene can control the order of the subscription? , not to mention that there may be some new people in the company who don't even know what you're writing about.

The perfect way to solve the problem as with events in DOM objects, when a message is released, it is not simply a message string, but rather a message encapsulated as an object that is passed to all of its subscriptions, which subscribe to the logic that should prevent the message from being released. Just call the Preventdefault () method of the message, and then after the message is posted externally, the isdefaultprevented () method of the invocation message is judged:

This approach is the same thing as using jquery to manage DOM objects, like most of the components of bootstrap and the component I wrote in the previous blog to add additional logic to the judgment, For example, the alert component of Bootstrap has a certain kind of judgment when the Close method executes:

It's a solution to this idea, but one of the tricks of jquery is to make it easier for us to manage event management for the entire ordinary object, so let's take a look at its eventbase.

2. jquery Tips Mode

1) Skill One

If you are defining a component, this component is associated with a DOM object, such as the following form:

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

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

2 Skill Two

The first technique can only be applied to components that are associated with the DOM, how do you add a perfect event management mechanism like the previous one for those components that have absolutely no association with the DOM? In fact, the method is also very simple, but I have not really used it before, so this time to use it will feel particularly fresh:

Looking at the box in the screenshot, as long as you pass a null object to the jquery constructor, it returns a perfect jquery object that supports event management. And besides the function of event management, because it is a jquery object. So all of the methods on the jquery prototype can be invoked, and in the future it might be possible to refer to this little trick if you need to borrow other jquery-agnostic methods.

3. Perfect Event Management implementation

Given the 2 ways in which the 2nd part is presented, there are duplicate logical codes, if you combine them, you can apply all the development components of the scenario, and you can achieve the title of this article and the goal of allowing any object to support the event management function, so finally combining the previous two techniques, Transform the Eventbase as follows (is it simple enough):

Define (function (Require, exports, module) {
var $ = require (' jquery ');
var Class = require ('./class ');
/**
* This base class allows common classes to have the ability to manage the event management 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 the components associated with the DOM

Test code One:

 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.ba
Se (this. $element);
Add listening this.on (' BeforeRender ', $.proxy (Options.onbeforerender, this));
This.on (' Render ', $.proxy (Options.onrender, this)); }, Render:function () {//Trigger BeforeRender event var e = $.
Event (' BeforeRender ');
This.trigger (e);
if (e.isdefaultprevented ()) return;
Main logical code Console.log (' Render complete! ');
Triggering render event This.trigger (' render ');
}}, extend:eventbase}); var demo = new Demo (' #demo ', {onbeforerender:function (e) {console.log (' BeforeRender event triggered! ');}, Onrender:fu
Nction (e) {console.log (' render event triggered! ');});
Demo.render (); 
});

In this test, I defined a demo component associated with the DOM and inherited the class that eventbase this event management, adding a listener to both BeforeRender events and render events, and the Render method also has print information to simulate real logic. The #demo DOM element was used when the demo was instantiated, and the final test results were:

Fully consistent with expectations.

Test code two:

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.ba
Se (this. $element);
Add listening this.on (' BeforeRender ', $.proxy (Options.onbeforerender, this));
This.on (' Render ', $.proxy (Options.onrender, this)); }, Render:function () {//Trigger BeforeRender event var e = $.
Event (' BeforeRender ');
This.trigger (e);
if (e.isdefaultprevented ()) return;
Main logical code Console.log (' Render complete! ');
Triggering render event This.trigger (' render ');
}}, extend:eventbase}); var demo = new Demo (' #demo ', {onbeforerender:function (e) {console.log (' BeforeRender event triggered! ');}, Onrender:fu
Nction (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 (' BeforeRenderEvent triggered 3! ');
});
Demo.render (); });

In this test, I defined a DOM-related demo component and inherited a class that eventbase this event management, adding 3 listeners to the BeforeRender event, one with Prevetdefault (), and the callback is not the last one. The final test results are:

As you can see from the results, the main logical code for the render method is not executed with the subsequent render event, and all BeforeRender listeners are executed, indicating that E.preventdefault () is in effect. And it does not have an impact on BeforeRender event queues.

2 simulate a normal object that is not associated with the DOM

Test code One:

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 listening
this.on (' BeforeRender ', $.proxy (Options.onbeforerender, this));
This.on (' Render ', $.proxy (Options.onrender, this));
},
render:function () {
//triggering BeforeRender Event
var e = $. Event (' BeforeRender ');
This.trigger (e);
if (e.isdefaultprevented ()) return;
Main logical Code
console.log (' render complete! ');
Trigger 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 a class that eventbase this event management, adding a listener to both the BeforeRender event and the render event, and the Render method also has print information to simulate the real logic, The final test results are:

Completely in line with expectations.

test Code two:

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 Listener this.on (' BeforeRender '),
$.proxy (Options.onbeforerender, this));
This.on (' Render ', $.proxy (Options.onrender, this)); }, Render:function () {//Trigger BeforeRender event var e = $.
Event (' BeforeRender ');
This.trigger (e);
if (e.isdefaultprevented ()) return;
Main logical code Console.log (' Render complete! ');
Triggering 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 class Eventbase this event management, adding 3 listeners to the BeforeRender event, one with a prevetdefault (), and the callback is not the last one. The final test results are:

As you can see from the results, the main logical code for the render method is not executed with the subsequent render event, and all BeforeRender listeners are executed, indicating that E.preventdefault () is in effect. And it does not have an impact on BeforeRender event queues.

So from 2 tests, we've got a way to allow any object to support the jquery event management mechanism by Eventbase, and in the future when considering the event mechanism to decouple, you don't have to consider the release-subscription model that was first introduced. In contrast, this method is more powerful and stable and more consistent with your usual use of jquery to manipulate DOM.

4. This paper summarizes

There are 2 points that need to be explained:

1 even without jquery in accordance with the 1th part of the final ideas proposed, the first part of the regular release-subscription model can be modified, but with jquery more concise;

2 finally use the event mechanism of jquery to achieve the event management of any object, on the one hand, the agent mode is used, more importantly, it is still to use the publish-subscribe mode, but the final implementation is by jquery to help us to the first part of the release-subscribe to realize the transformation of the good.

The above is a knowledge of the jquery technique to support event management like DOM for any component, and I hope it helps!

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.