javascript - trick to handlers management

We have discussed "javascript - trick to fix event object" and "javascript - trick to centralized store".  with this two, we can build from and create a new set of addEvent and removeEvent methods (also some other methods inlcude triggerEvent and etc..) which are essential set of methods that we can have for handlers management. 

 

Since we cannot include one js file from another js file, (there are methods, however, it does not worth that complication in order to achieve that ).

 

 

first, let see the code that levergage event fixing and centralized store and then provided the method of addEvent and removeEvent . 

 

/**************************************
*@Name: addremoveevents.js
*  the code of the centralobjectsstore.js, which has the central data store, together with the fixevents.js ,which contains the method that simulat the DOM events. 
*  with the data store we can create new set of addEvent and removeEvent methods that work closer to our desired outcome in all browsers, as seen in List 13 - 4
*@Summary
* 
* @todo:
*   Test
***************************************/

/** 
* below is the code of centralizedatastore.js
*/
(function () {
  var cache = {}, guid = 1, expando = "data" + (new Date).getTime();
  this.getData = function (elem) {
    var id = elem[expando];
    if (!id) {
      id = elem[expando] = guid++;
      cache[id] = {};
    }
    return cache[id];
  };
  this.removeData = function (elem) {
    var id = elem[expando];
    if (!id) {
      return;
    }
    // Remove all stored data
    delete cache[id];
    // Remove the expando property from the DOM node
    try {
      delete elem[expando];
    } catch (e) {
      if (elem.removeAttribute) {
        elem.removeAttribute(expando);
      }
    }
  };
})();



/**
* below is the code of fixEvent.js
*/
function fixEvent(event) {

  if (!event || !event.stopPropagation) {
    alert("inside fixing event");
    var old = event || window.event;
    // Clone the old object so that we can modify the values
    event = {};
    for (var prop in old) {
      event[prop] = old[prop];
    }
    // The event occurred on this element
    if (!event.target) {
      event.target = event.srcElement || document;
    }
    // Handle which other element the event is related to
    event.relatedTarget = event.fromElement === event.target ?
      event.toElement :
      event.fromElement;
    // Stop the default browser action
    event.preventDefault = function () {
      event.returnValue = false;
      event.isDefaultPrevented = returnTrue;
    };
    event.isDefaultPrevented = returnFalse;
    // Stop the event from bubbling
    event.stopPropagation = function () {
      event.cancelBubble = true;
      event.isPropagationStopped = returnTrue;
    };
    event.isPropagationStopped = returnFalse;
    // Stop the event from bubbling and executing other handlers
    event.stopImmediatePropagation = function () {
      this.isImmediatePropagationStopped = returnTrue;
      this.stopPropagation();
    };
    event.isImmediatePropagationStopped = returnFalse;

    // Handle mouse position
    if (event.clientX != null) {
      var doc = document.documentElement, body = document.body;
      event.pageX = event.clientX +
        (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
        (doc && doc.clientLeft || body && body.clientLeft || 0);
      event.pageY = event.clientY +
        (doc && doc.scrollTop || body && body.scrollTop || 0) -
        (doc && doc.clientTop || body && body.clientTop || 0);
    }
    // Handle key presses
    event.which = event.charCode || event.keyCode;
    // Fix button for mouse clicks:
    // 0 == left; 1 == middle; 2 == right
    if (event.button != null) {
      event.button = (event.button & 1 ? 0 :
        (event.button & 4 ? 1 :
        (event.button & 2 ? 2 : 0)));
    }
  }
  return event;

  // move the following code from the end of function
  //  fixEvent(event) 
  function returnTrue() {
    return true;
  }
  function returnFalse() {
    return false;
  }
}

(function () {
  var guid = 1;
  this.addEvent = function (elem, type, fn) {
    var data = getData(elem), handlers;

    // We only need to generate one handler per element
    if (!data.handler) {
      // Our new meta-handler that fixes
      // the event object and the context
      data.handler = function (event) { // - the event is given the value when the actual event is fired. 
        event = fixEvent(event);
        
        var handlers = getData(elem).events[event.type];
        // Go through and call all the real bound handlers
        for (var i = 0, l = handlers.length; i < l; i++) {
          handlers[i].call(elem, event);
          // Stop executing handlers since the user requested it
          if (event.isImmediatePropagationStopped()) {
            break;
          }
        }
      };
    }
    // We need a place to store all our event data
    if (!data.events) {
      data.events = {};
    }
    // And a place to store the handlers for this event type
    handlers = data.events[type];
    if (!handlers) {
      handlers = data.events[type] = [];
      // Attach our meta-handler to the element,
      // since one doesn't exist
      if (document.addEventListener) {
        elem.addEventListener(type, data.handler, false);
      } else if (document.attachEvent) {
        elem.attachEvent("on" + type, data.handler);
      }
    }
    if (!fn.guid) {
      fn.guid = guid++;
    }
    handlers.push(fn);


  };

  this.removeEvent = function (elem, type, fn) {
    var data = getData(elem), handlers;
    // If no events exist, nothing to unbind
    if (!data.events) {
      return
    }
    // Are we removing all bound events?
    if (!type) {
      for (type in data.events) {
        cleanUpEvents(elem, type);
      }
      return;
    }
    // And a place to store the handlers for this event type
    handlers = data.events[type];
    // If nohandlers exist, nothing to unbind
    if (!handlers) {
      return;
    }
    // See if we're only removing a single handler
    if (fn && fn.guid) {
      for (var i = 0; i < handlers.length; i++) {
        // We found a match
        // (don't stop here, there could be a couple bound)
        if (handlers[i].guid === fn.guid) {
          // Remove the handler from the array of handlers
          handlers.splice(i--, 1);
        }
      }
    }
    cleanUpEvents(elem, type);
  };
  // A simple method for changing the context of a function
  this.proxy = function (context, fn) {
    // Make sure the function has a unique ID
    if (!fn.guid) {
      fn.guid = guid++;
    }
    // Create the new function that changes the context
    var ret = function () {
      return fn.apply(context, arguments);
    };
    // Give the new function the same ID
    // (so that they are equivalent and can be easily removed)
    ret.guid = fn.guid;
    return ret;
  };
  function cleanUpEvents(elem, type) {
    var data = getData(elem);
    // Remove the events of a particular type if there are none left
    if (data.events[type].length === 0) {
      delete data.events[type];
      // Remove the meta-handler from the element
      if (document.removeEventListener) {
        elem.removeEventListener(type, data.handler, false);
      } else if (document.detachEvent) {
        elem.detachEvent("on" + type, data.handler);
      }
    }
    // Remove the events object if there are no types left
    if (isEmpty(data.events)) {
      delete data.events;
      delete data.handler;
    }
    // Finally remove the expando if there is no data left
    if (isEmpty(data)) {
      removeData(elem);
    }
  }
  function isEmpty(object) {
    for (var prop in object) {
      return false;
    }
    return true;
  }
})();


function triggerEvent(elem, event) {
  var handler = getData(elem).handler, parent = elem.parentNode || elem.ownerDocument;
  if (typeof event === 'string') {
    event = { type: event, target: elem };
  }

  if (handler) {
    handler.call(elem, event);
  }


  // Bubble the event up the tree to the document,
  // Unless it's been explicitly stopped
  if (parent && !event.isPropagationStopped()) {
    triggerEvent(parent, event);
  // We're at the top document so trigger the default action
  } else if (!parent && !event.isDefaultPrevented()) {
    var targetData = getData(event.target), targetHandler = targetData.handler;
    // so if there is handler to the defalt handler , we execute it 
    if (event.target[event.type]) {  // I Suppose that it is the event.type rather than just the type
      // Temporarily disable the bound handler, 
      // don't want to execute it twice
      if (targetHandler) {
        targetData.handler = function () { };
      }
      // Trigger the native event (click, focus, blur)
      event.target[event.type](); // I suppose that it is the event.type rather than the type 

      // restore the handler
      if (targetHandler) {
        targetData.handler = targetHandler;
      }
    }
  }
}
 

 

the code above also has a triggerEvent method, which we might come back later. 

 

so some of the comment.

 

we are storing all incoming handlers in a central data store (the event types are within the 'event' property and each event type stores an array of all the handlers that are bound), instead of binding directly the handler to the element.  we use array because it allows to bind more than one handler and also it it will allow execution in the order.  

 

 

the method stopImmediatePropagation provide a means for one handler to stop the handlers that follows it. 

 

we then bind a generic handler to the element, the handler will then execute the bound handlers in the array in order . We only need to generate one of these handlers per element (since we capture the element via a closure which then use to look up the event data and correct the handler contexts -- this is about the fixEvent call).

 

While the handler comes in we also give it a unique ID. This isn't explicitly required in order to be able to remove the handler later. though we can compare the function, the id way has one advantage in that you can create a nice proxy function for chaning the context of the handler.

 

Below show you how to use the proxy method.  (need further verify) 

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="addremoveevents.js"></script>
    <script type="text/javascript">
      var obj = {
        count: 0,
        method: function () {
          this.count++;
        }
      };
      addEvent(window, "load", function () {
        // Attach the handler while enforcing the context to 'obj'
        addEvent(document.body, "click", proxy(obj, obj.method));
        // Remove on right click
        addEvent(document.body, "click", function (e) {
          if (e.button === 3) {
            // Note that we can remove the obj.method handler no problem
            removeEvent(document.body, "click", obj.method);
          }
        });
      });
</script>

</head>
<body>
Hello world
</body>
</html>
 

 

about the remove method.  It is capable of removing a single event handler, but it is also possible to remove all handlers of a particular type or even all events bound to the element. 

 

 

// Remove all bound events from the body
removeEvent( document.body );
// Remove all click events from the body
removeEvent( document.body, "click" );
// Remove a single function from the body
removeEvent( document.body, "click", someFn );
 

 

The important aspect behind removeEvent is in making sure that we clean up all our event handlers,

data store, and meta-handler properly.

 

Most of the logic for handling that is contained within the cleanUpEvents function. This function makes sure to systematically go through the element's data store and clean up any loose ends - eventually removing the meta-handler from the element itself if all handlers of a particular type have been removed from the element.

 

Below is the code that I used to test the handlers management. 

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="addremoveevents.js"></script>
    <script type="text/javascript">
      addEvent(window, "load", function () {
        var li = document.getElementsByTagName("li");
        for (var i = 0; i < li.length; i++) {
          (function (elem) {
            // joe's note, it is the same if you have the 'function handlers'
            //  or you simply have the 'function'
            addEvent(elem, "click", function (e) {
              // only do this on left click
              if (e.button === 0) { // the book has the value of e.button === 1
                this.style.backgroundColor = "green";
                removeEvent(elem, "click", handler);
              }
            });
          })(li[i]);
        }
      }
      );
    </script>
</head>
<body>
  <ul>
    <li>Click</li>
    <li>me</li>
    <li>once.</li>
  </ul>
</body>
</html>
 

 

 

There are three elements, if you click on one of them, the background of the element will turns green.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值