Website: http://web.jobbole.com/83591/
Background
I am a front-end creep, I do some simple business development in an internet company.
One day, I received a request to do a lottery function. The company's predecessors have completed the business logic, and have provided the interface of the business function, just need me to make the page and complete the event binding.
I've written the page with a button element with an ID of Lucky-draw. Next, I need to tie the point-click event to it. That's what I wrote:
var btn = document.getElementById (' Lucky-draw ') btn.onclick=function() {Bx.luckydraw ()}
This is where Bx.luckydraw () is the business interface provided by predecessors, and executes it to run the following lottery function.
I tested it and the code worked properly, so I was happy to be ready to go online.
First off
However, the predecessors told me that these important functions of the buttons need to add statistics. It's hard for me too, because I'm familiar with the API of the statistical system. So I changed the code for the event binding:
function () {Bx.luckydraw () bx.track (' Lucky-draw ')}
This is effective, but the seniors tell me that for some reason, the statistical code and the business code are distributed in different locations, and the above code needs to be disassembled. So I try to modify this:
function () {Bx.luckydraw ()} // Other codes ... function() {bx.track (' Lucky-draw ')}
The result found that the lottery function failed when the button was clicked. Originally, using an event property such as the. onclick to bind an event has a very large drawback, and the duplicate assignment overrides the old value. In other words, this method can only bind the last assignment of an event handler function.
I took the bullet to ask seniors, only to know that this way has long been deprecated, should use the DOM standard event binding API to deal with (in the older version of IE, there are some compatibility issues, do not expand here). So my code changed to this:
Btn.addeventlistener (' click ',function() {Bx.luckydraw ()},false)// Other codes ... Btn.addeventlistener (' click ',function() {bx.track (' Lucky-draw ')},false )
All functions are finally normal, and I am happy to be ready to go online.
Second Pass
It turns out that I'm still naïve and PM won't tell you all the needs at once. Originally, this lottery function also needs to do A/B testing, that is, only half of the users will see this lottery function.
This means that the user's page may not have btn this element at all, so the Btn.addeventlistener (...) sentence is simply wrong. Therefore, before I bind the event handler for the button, I have to judge it first:
if (BTN) {Btn.addeventlistener (' click ',function() {Bx.luckydraw ()},false)} // other codes ... if (BTN) {Btn.addeventlistener (' click ',function() {bx.track (' Lucky-draw ')}, false)}
While such code works on all users ' pages, these pre-judged looks like a sore egg. Again I asked my predecessors with doubts. The elder kindly looked at me and uttered a classic saying:
Fool, why not use the Magnum JQuery?
It turns out that the magic of jQuery allows us to overlook a lot of details, such as the absence of elements that will be silently digested. And there is no compatibility problem with JQuery's event binding approach, and the API looks good. Good good, no matter how the great gods on the internet spray jQuery, but it is simply my Savior Ah!
As a result, my code becomes the following:
var $btn = $ (' #lucky-draw ') $btn. On (' click ',function() { bx.luckydraw ()}) // Other codes ... $btn. On (' click ',function() { bx.track (' Lucky-draw ')})
My code looks like that, and I'm happy to be ready to go online.
Third off
Of course, my story won't end so soon. You know, for a front-end team to pursue, the continuous improvement of the user experience is the eternal goal. For example, our site uses a number of methods to improve page load performance, part of the page content is not originally in the page, but when the user needs, by JavaScript dynamically generated.
Take this lottery function, the lottery button exists in a "surprise" tab, and this tab in the initial state is not content, only when the user switches to this tab, the content will be filled by JS. The schematic code is this:
$ ('. Tabs >. Surprise '). On (' click ',function() {var htmlsurprisetab = [' <div > ',' <button id= ' lucky-draw ' >lucky draw</button> ',' </div> '].join ( ") $ ('. Tab-panels >. Surprise '). HTML (htmlsurprisetab)// BTNReady})
This means that I write the event binding code that needs to be written in//BTN ready. This deep coupling looks very unsatisfactory and I need to find a way to solve it.
I remember when I read the JQuery document I saw a method called event delegation that binds an element to an event before it is added to the page. So, I try to write this:
$ (document.body). On (' Click ', ' #lucky-draw ',function() {Bx.luckydraw ()})
Sure enough, I succeeded! Sometime, this demand is finally on the line.
After further research, I learned that the essence of "event delegation" is the use of event bubbling characteristics. Binds an event handler to a container element and bubbles to the container when the element in the container triggers the event . At this point you can determine who the source of the event is, and then execute the corresponding event handler function. Since the event handler is bound to the container element, it is not related even if the container is empty, as long as the contents of the container are added, the whole function is ready.
Although the principle of event delegation sounds a little more complicated, my code is not complicated because JQuery provides perfect support for event delegation.
Think of one more step
After this exercise, I gained a lot of experience, and I also learned to go further and find problems and think. For example, on our web page, there are usually multiple buttons, and the script code that binds the events might be like this:
$body = $ (document.body) $body. On (' click ', ' #lucky-draw ',function() {Bx.luckydraw ()}) $body. On (' click ', ' #some-btn ',function() {// do something ... }) $body. On (' click ', ' #another-btn ',function() {// do something else ... })
I vaguely feel that this is not right Ah! While these codes work, each of these buttons is bound to bind an event handler for the BODY element, and, according to intuition, it needs to be optimized for a piece of code that is similar in length. So, if I could integrate these similar codes, it would be beneficial both in terms of resource consumption and in code organization.
So, I tried to merge all of these event-delegate code into a single binding. First, in order to implement the merge, I need to find common ground for these buttons. Naturally, I let them have the same class:
<Buttonclass= "Action"ID= "Lucky-draw">Lucky Draw</Button><Buttonclass= "Action"ID= "Some-action">Button</Button><ahref="#"class= "Action"ID= "Another-action">Link</a><ahref="#"class= "Action"ID= "Another-action-2">Link</a>
Then I tried to handle all of these buttons with one event delegate:
$body. On (' click ', '. Action ',function() {// when click any '. Action ', WE COME here .})
It is clear that all elements with the action class name will trigger the above event handler when clicked. So, next, we'll distinguish the source of the event here and perform the corresponding task:
$body. On (' click ', '. Action ',function() {switch(the. Id) {case ' Lucky-draw ': Bx.luckydraw ()break case ' some-btn ':// do Something ... Break // ... }})
In this way, all the decentralized event delegate codes are merged into one place. In this unified event handler, we use the ID to distinguish the individual buttons.
But the ID has some problems, because the same page can not have the same name of the element, I believe the front-end engineers are sensitive to the ID, in the daily development to try to avoid abuse. In addition, if more than one button needs to perform the same task, but its ID must be different, the correspondence between these IDs and their corresponding tasks is not clear enough.
Instead, I use the custom properties of HTML5 to mark individual buttons:
<Buttonclass= "Action"data-action= "Lucky-draw">Lucky Draw</Button><Buttonclass= "Action"data-action= "Some-action">Button</Button><ahref="#"class= "Action"data-action= "Another-action">Link</a><ahref="#"class= "Action"data-action= "Another-action-2">Link</a>
Here I use the Data-action property to mark the actions to be performed when each button element is clicked. Looking back, because each button uses this property, they already have a new common denominator, and class is not necessary in common, so our HTML code can simplify some:
<Buttondata-action= "Lucky-draw">Lucky Draw</Button><Buttondata-action= "Some-action">Button</Button><ahref="#"data-action= "Another-action">Link</a><ahref="#"data-action= "Another-action-2">Link</a>
At the same time JS code also need to make corresponding adjustments:
$body. On (' click ', ' [data-action] ', function () { var actionname = $ (this ). Data (' Action ' switch Span style= "color: #000000;" > (ActionName) { case ' Lucky-draw ' : Bx.luckydraw () break case " Some-btn ' : // Do something ... break // ...
Our code looks pretty good, but I can't stop it and continue to improve. That long switch statement looks a little bloated. the usual way to optimize a switch is to use the object's key name and key values to organize the correspondence . So I continued to change:
varActionList = {' Lucky-draw ':function() {Bx.luckydraw ()},' Some-btn ':function(){//Do something ...}// ...} $body. On (' Click ', ' [data-action] ',function(){varActionName = $ ( This). Data (' action ')varAction =Actionlist[actionname]if($.isfunction (action)) action ()})
After this adjustment, I found that the nesting of the code was lighter, and the buttons ' tags and what they wanted to do were also organized into ActionList objects, which looked fresher.
In this organization, it's easy to expand if the page needs a new button:
// HTML$body. Append (' <a href= "#" data-action= "more-action" >Link</a> ')// JS$.extend (actionlist,{' more-action ':function() {// ... }})
Here, this whole set of practice is finally going to be like that!
Open source
I myself used this method to participate in the development of many projects, in dealing with event binding, it saves me a lot of energy. I suddenly realized that it might also be suitable for more people and more projects. You might as well open it up!
So I released the Action on this project. This compact class library helps developers easily and randomly bind a point-and-click event, using the concept of "action" to mark a button and what it will do after it is clicked; The API provides a handy way to define actions:
Action.add ({' my-action ':function() {// ... }})
You can also manually trigger an action that has already been defined:
Action.trigger (' my-action ')
Application
Action This class library has been adopted by the mobile Web UI framework Cmui as a global base service. The various UI components within the Cmui are implemented based on the event binding mechanism of the Action. Let's look at the application of Action in Cmui (schematic code) as an example of a dialog box component:
Cmui.dialog ={Template: [' <div class= ' dialog ' > ',' <a href= ' # "data-action=" Close-dialog ">x</a>",' ,' <div class= ' content ><%-data.html%></div> ',' </div> '].join (‘‘), Init:function() {Action.add ({' Close-dialog ':function() { $( This). Closest ('. Dialog '). Hide ()})},open:function(config) {varHTML = render ( This. Template,config) $ (HTML). AppendTo (' Body '). Show ()}}cmui.dialog.init ()
As soon as the CMUI.dialog.init () method executes, the dialog component is ready. We call the CMUI.dialog.open () method directly in the business and some of the configuration information needed to construct the dialog box, which can be created and opened.
You can find that in the process of constructing the dialog, we did not do any event binding work, the dialog box's Close button will naturally have the click-Close Function! The reason is that the Close button (<a href= "#" data-action= "Close-dialog" >x</a>) has itself declared by the Data-action attribute the action to be performed when it is clicked (' Close-dialog '), and this action is already defined at the time the component is initialized (CMUI.dialog.init ()).
More about Action: Https://github.com/cssmagic/action/wiki
Cmui Project: Https://github.com/CMUI/CMUI
JQuery's Event binding api:http://api.jquery.com/on/
Front-End Advanced Path: Click event Bindings