#329 ✓not_for_core
Lea Hayes

Possible Addition? EventBinder class encapsulates an entire event

Reported by Lea Hayes | September 7th, 2008 @ 07:52 PM

I have created a JavaScript class which I feel facilitates the usage of a class.

This is something which I have used quite a few times now, and I thought that it was worth suggesting as an addition for your prototype library.

I have attached a working version of this class. I have provided a with and without version for comparison.

Instead of the following:



var MyObject = Class.create({
	initialize: function(parentHtml) {
		// Html element.
		this.htmlElement = new Element('a');
		this.htmlElement.innerHTML = 'Test Element - Click Me!';
		this.htmlElement.href = 'javascript:';
		parentHtml.appendChild(this.htmlElement);
	}
});

...

	// This is the part of interest.
	var test = new MyObject($('placeHolder'));
        test.boundHandler1 = myHandler1.bindAsEventListener(test, 'attr1');
	test.htmlElement.observe('click', test.boundHandler1);

	test.boundHandler2 = myHandler2.bindAsEventListener(test, 'attr1', 'attr2');
	test.htmlElement.observe('customEvent:special', test.boundHandler2);

	test.htmlElement.fire('customEvent:special', 'memo');

It becomes possible to use the following:


var MyObject = Class.create({
	initialize: function(parentHtml) {
		// Html element.
		this.htmlElement = new Element('a');
		this.htmlElement.innerHTML = 'Test Element - Click Me!';
		this.htmlElement.href = 'javascript:';
		parentHtml.appendChild(this.htmlElement);
		// Events.
		this.eventClick = new EventBinder(this, this.htmlElement, 'click');
		this.eventSpecial = new EventBinder(this, this.htmlElement, 'custom:special');
	}
});

...

	// This is the part of interest.
	var test = new MyObject($('placeHolder'));
	test.eventClick.add(myHandler1, 'attr1');
	test.eventSpecial.add(myHandler2, 'attr1', 'attr2');

	test.eventSpecial.fire('memo');

The EventBinder class removes the need to remember and maintain event names and encapsulated HTML nodes. I have found this particularly useful for server generated code. If any changes to the events are required, only the construction of the EventBinder need updating.

The EventBinder also maintains a cache of the bound event handlers. This means that there is no need to manually preserve local copies of them for when they need to be unbound.

Comments and changes to this ticket

  • Lea Hayes

    Lea Hayes September 8th, 2008 @ 03:32 AM

    I have just finished testing two additional methods which I added to the class. I wanted to verify that these two methods were working correctly before posting them.

    update: function(owner, element, eventName)

    Allows the owner object, HTML element, or eventName to be changed. This method transforms any previously added handlers. This method is useful when the HTML content of an object is dynamically altered, yet the same handlers are to be maintained.

    call: function(event, memo)

    Can be used to provide access to event object for custom events.

  • Juriy Zaytsev

    Juriy Zaytsev September 8th, 2008 @ 06:00 AM

    Why not just give class a "public" observe method?

    
    var MyClass = Class.create({
      initialize: function(parentHhtml) {
        this.htmlElement = new Element('a', { href: 'javascript:' });
        this.htmlElement.update('Test Element - Click Me!')
        $(parentHtml).appendChild(this.htmlElement);
      },
      observe: function(eventName, handler) {
        this.htmlElement.observe(eventName, handler);
      }
    });
    
    // ...
    
    var myClass = new MyClass('placeholder');
    myClass.observe('click', function(){ ... })
    
    
  • Lea Hayes

    Lea Hayes September 8th, 2008 @ 02:06 PM

    Hi Juriy Zaytsev!

    It is true that you can create a selection of local methods to achieve this.

    However with the localized version it is often necessary to manually store and maintain handlers which have been bound so that at a later stage they can be unbound correctly. The EventBinder class maintains its own cache of bound event handlers, along with the original non-bound handlers.

    This means that the original non-bound handlers can be used as keys to add and remove handlers without having to keep and maintain a manual cache of these.

    
    
    myClass.eventClick.add(myHandler1);
    myClass.eventClick.add(myHandler2);
    
    myClass.eventClick.remove(myHandler1);
    
    

    As opposed to the alternative which requires storage of a manually maintained cache of the bound handlers. In a more complex scenario the following could lead to a more error prone implementation. Or if placed into various localized methods for each class, there would be a lot of duplicated code, and thus possibly more difficult to maintain.

    
    
    myClass.cachedHandler1 = myHandler1.bindAsEventListener(myClass);
    myClass.observe('click', myClass.cachedHandler1);
    myClass.cachedHandler2 = myHandler2.bindAsEventListener(myClass);
    myClass.observe('click', myClass.cachedHandler2);
    
    myClass.stopObserving('click', myClass.cachedHandler1);
    myClass.cachedHandler1 = null;
    
    

    > In addition the second version of the EventBinder provides the additional method 'update' which rebinds all previously added event handlers to a changed context. When a HTML element or associated object is to be replaced (or even event name for whatever reason), all previously bound handlers are stopped, and bound and observed against the new context. I have found this feature particuarly useful for scenarios where HTML content is frequently updated.

    I find that the EventBinder reduces the amount of code repetition because all of this functionality is in a handy reusable class. And because the actual event binding process is controlled via the constructor and update methods, it is also very easy to maintain, without having to manually update an entire script.

    I noticed that the second file I sent you was before I corrected the call function, it was not parsing along the memo data...I have attached the right version to this post should you be interested. I appologize for that misstake, I must have selected the wrong directory when I uploaded it.

  • Juriy Zaytsev

    Juriy Zaytsev September 8th, 2008 @ 02:46 PM

    Interesting, although I would still do something like this:

    
    var MyClass = Class.create({
      initialize: function(element) {
        this.element = $(element);
      },
      observe: function(eventName, handler) {
        handler.__bound = handler.bind(this);
        this.element.observe(eventName, handler.__bound);
      },
      stopObserving: function(eventName, handler) {
        this.element.stopObserving(eventName, handler.__bound || handler);
      }
    });
    
    var my = new MyClass(document.body);
    function handler(e){ console.log(this, e) };
    
    my.observe('click', handler); // attaches observer
    my.stopObserving('click', handler); // strips observer
    

    As far as reuse, observe/stopObserving could always go into mixin that can be added to any class which needs such behavior:

    
    var Observable = {
      observe: function(eventName, handler) {
        handler.__bound = handler.bind(this);
        this.element.observe(eventName, handler.__bound);
      },
      stopObserving: function(eventName, handler) {
        this.element.stopObserving(eventName, handler.__bound || handler);
      }
    };
    
    var MyClass = Class.create(Observable, {
      initialize: function(element){
        this.element = $(element);
      }
    });
    
  • Tobie Langel

    Tobie Langel September 18th, 2008 @ 12:55 PM

    • State changed from “new” to “not_for_core”
    • Tag changed from enhancement, events to enhancement, events

    This belongs in an add-on imho.

  • Joe Gornick

    Joe Gornick September 18th, 2008 @ 06:44 PM

    To extend on Juriy's code, I've included a mixin for observing custom events for a class.

    
    // Credit - Juriy Zaytsev
    (function() {
      function _getElement(object) {
        return (object._eventElement = object._eventElement || new Element('code'));
      }
    
      Class.Observable = {
        observe: function(eventName, handler) {
          _getElement(this).observe(eventName, handler);
          return this;
        },
    
        stopObserving: function(eventName, handler) {
          _getElement(this).stopObserving(eventName, handler);
          return this;
        },
    
        fire: function(eventName, memo) {
          _getElement(this).fire(eventName, memo);
          return this;
        }
      }
    })();
    
    var MyClass = Class.create(Class.Observable, {
      initialize: function(element){
        this.element = $(element);
      },
    
      test: function(message) {
        this.fire('myclass:tested', { message: message });
      }
    });
    
    document.observe('dom:loaded', function() {
      // Initialize our class
      var myClass = new MyClass();
    
      // Observe the custom event
      myClass.observe('myclass:tested', function(e) {
        console.log(e.memo.message);
      });
    
      // Call a method which fires the custom event
      myClass.test('this is a testing message');
    });
    
  • Tobie Langel

    Tobie Langel July 24th, 2009 @ 03:55 AM

    • Tag changed from enhancement, events to section:dom

    [not-tagged:"events" tagged:"section:dom" bulk edit command]

  • Tobie Langel

    Tobie Langel July 24th, 2009 @ 03:56 AM

    [not-tagged:"events" tagged:"section:dom" bulk edit command]

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

The Prototype JavaScript library.

Shared Ticket Bins

Attachments

Pages