Jump to content

Recommended Posts

This question is related to Javascript but I felt it was more of a design question so I'm placing it here; feel free to move it if necessary.

 

I've been working on a Javascript event wrapper to unify the interface between those browsers that support the DOM Level 2 specification and the world's favorite browser.

 

Here is a brief example of some Javascript using my wrapper:

if( Events.supported() ){ // Event wrapper is supported
  // We'll add two handlers to an element with id="testID"
  Events.addHandler("testID", "mousedown", myMousedown);
  Events.addHandler(document.getElementById("testID"), "mouseup", myMouseup);
}

function myMouseup(oEvent, oNode){
  oEvent = new Events.Event(oEvent, oNode);
  alert(oNode.id + " mouseup!");
}

function myMousedown(oEvent, oNode){
  oEvent = new Events.Event(oEvent, oNode);
  alert(oNode.id + " mousedown!");
}

 

Now you will notice a couple things about this wrapper:

  • 1) The first parameter to Events.addHandler can be either a string or a DOM object (that supports event handling)
  • 2) My event handlers take two parameters: oEvent and oNode.  oEvent is what you would expect, the event object in DOM L2 browsers and null in IE.  oNode is the node the event was originally attached to; this is primarily to help resolve the difference between the following possible properties of the event object: srcElement, target, and currentTarget.  It's also handy because 99% of the time the event handler needs to work with the node associated with the event anyways.

 

I also need to mention that my Events interface internally assigns all events to an anonymous function:

function Events.addHandler(n, e, h){
  /**
   * n  DOM Node
   * e  Event name (string)
   * h  Event handler (function)
   */
  
  // Do some stuff

  // Create the event handler
  var handler = function(oEvent){
    var oNode = n;
    h(oEvent, oNode);
  }

  // Attach event
  if( /* IE Event Model */ ){
    n.attachEvent( "on" + e, handler );
  }else if( /* DOM L2 Event Model */ ){
    n.addEventListener( e, handler, false );
  }else{ // Unsupported
    return false;
  }
  return true;
}

 

At this point you're probably slapping your forehead, "Roopurt, you idiot!  You can't detach events assigned as anonymous functions!"

 

Well, actually I can.  The last step in attaching an event handler is to create an object that records the event that was added:

  var e = new Object();
  e.node = n;           // Record the DOM Node
  e.handler = h;        // Record the function the client passed in
  e.actual = handler; // Record the anonymous function we assigned
  e.event = e;          // Record the event name

This object is then pushed onto the back of an Event Stack array, which is a private variable of my Events interface.  Later, when the Events client wishes to remove the handler, it can call Events.removeHandler with the same arguments passed to Events.addHandler.  In the removeHandler function, the interface will search for a matching Event Stack object and detach / removeEventListener to remove the event.  Long story short, a client to Events can do:

Events.addHandler("testID", "mousedown", myMousedown);
Events.removeHandler("testID", "mousedown", myMouseDown);

 

I'll flatter myself and say the whole method is rather genius except for one thing:  I'm stuck over the best way to handle the case where a client to Events adds a handler to a node and, before the handler is removed, the node is removed from the document via removeChild or replaceChild.  When this occurs, my Events interface will be stuck with an Event Stack object in its array that will never be removed and no longer has any business being there.

 

My initial way to overcome this obstacle is to set up a function on a timer that will search this array for objects who contain a node whose parentNode / parentElement is null, signifying the node has been removed from the document.  There are two problems with this approach:

  • 1)  I don't like timers.
  • 2)  More serious, just because a node is removed from the document doesn't mean it won't be re-inserted.  If the node is removed from the document and then re-inserted after the Event Stack object is removed, the client will have no way of removing the event!

 

My other idea, which I've not yet tested and don't know if it's possible, would be to override the default removeChild and replaceChild methods so that when they're called my Events interface will automatically remove Event Stack objects for that node.  However, the one potential problem I can see with this method would be if the client set the innerHTML property of a node such that an internal node with an assigned event was erased I don't know that either of these methods would trigger.  This problem is less worrisome though because I don't use the innerHTML property for more than testing.

 

Can anyone else offer up any other alternatives to overcome this problem?

 

P.S.  While writing this I had a light-bulb moment.  I'm going to change my anonymous event handler to:

var handler = function(oEvent){
  var oNode = n;
  oEvent = new Events.Event(oEvent, oNode);
  h(oEvent);
}

I'll make oNode become a property of my Events.Event object.  This will allow all of my event handler functions to become:

  function some_event_handler(oEvent){
    // oEvent is now an instance of my event object from the Events interface.
    // I no longer have to worry about if oEvent is set or if i should use the window.event object
    // YAY!
  }

Link to comment
https://forums.phpfreaks.com/topic/47509-javascript-timer-or-hook/
Share on other sites

Thinking about this more, I can get around the potential timer problem by enabling the client to disable / enable the timer.

 

// This code is going to detach and re-attach a node, so we must disable Events
// from cleaning it's event stack during this time.
var node = document.getElementById("someNode");
Events.stopCleaning();
node = node.parentNode.removeChild(node);
document.getElementById("someOtherNode").appendChild(node);
Events.startCleaning();

 

But if I'm going to place this extra level of burden (2 function calls) on the client, I might as well just not have the self-cleaning feature with the timer.  Instead, I could just add a single function: Events.removeNodeEvents(node); that removes all events registered for this node through my interface.

 

The client would then need to be responsible enough to remember to call this function before permanently removing a node from the document.  Basically the burden on the client is lessened from two function calls to one.  The interface also eliminates the timer using this method.

 

I'd still prefer a method that doesn't require any intervention by the client though, so I'll keep thinking about it.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.