DPH.AM

I like to draw, code, and build stuff.

Bind and Trigger

Bind and trigger in JavaScript is one of those design patterns that are widely used but not discussed anywhere outside of the scope of the framework that utilizes it. A google search for “bind and trigger” brings up mostly references to jQuery discussions, and their context seems to revolve around attaching event handlers to DOM nodes rather than implementing it in your own application.

Javascript is an event-driven language. As such, it’s common practice to pass in function callbacks to be triggered by an event. For instance, an AJAX request requires a function callback since the callstack will finish long before the request is completed. Passing in a function callback is the only way to guarantee that your function will get called after the request has completed and with the correct data from the request. But what if you want multiple callbacks to happen after the request is done? Or if you’re building an application which accepts plugins; how do you allow these plugins to hook into events triggered from the core application? This is where the bind and trigger paradigm really shines.

Sidenote. There are plenty of frameworks out there like jQuery and Spine which have a very complete implementation of the bind and trigger paradigm. Mine is very incomplete for the sake of explaining the model.

In this example, we will build a Button Class which allows you to create an anchor element.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Button = function(param) {
   var text, href, anchor;

   if(typeof param !== "object") {
      // ok, seriously?
      return false;
   }
   anchor = document.createElement('a');
   anchor.innerHTML = param.text;
   anchor.setAttribute('href', param.href);

   this.anchor = anchor;
   this.param = param;
};

var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});

Now lets assume that when a button is appended to the page, you want a callback to happen. This is pretty common in an element design pattern where anchors are the elements and your application wants to proxy the button’s append event. This is typically done two ways; through the method’s parameter callback and Class initialize parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Callback from initialized parameter
Button.prototype.appendTo = function(container) {
   container.appendChild(this.anchor);
   if(typeof this.param.onAppend === "function") {
      this.param.onAppend(container);
   }
};
var button = new Button({
   text: "Link Text",
   href: "http://dph.am",
   onAppend: function(container) {
      console.log("Anchor was appended to", container);
   }
});
button.appendTo(document.body);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Callback from method parameter
Button.prototype.appendTo = function(el, callback) {
   el.appendChild(this.anchor);
   if(typeof callback === "function") {
      callback();
   }
};
var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});
button.appendTo(document.body, function(container){
   console.log("Anchor was appended to", container);
}

These pattern brings up a few problems.

  1. For the first method, callback must be defined when initializing. You might not know what that callback is the when you initialize the Button class.
  2. For the second method, you must define the callback every time you call button.appendTo(). This is a bit tedious.
  3. Only one callback can happen!

The bind and trigger pattern essentially solves all these problems. You can attach the callback anytime you want, you only need to attach it one time, and best of all, you can “bind” a number of callbacks to be “triggered” later. To do that, we extend our prototype.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
var Button = function(param) {
   // place the following line in your class init block
   this.events = {};
};

// Bind method
Button.prototype.bind = function(event, callback) {
   if(typeof event === "string") {
      if(typeof this.events[event] !== "object") {
         // This event type doesn't exist, create a stack for it
         this.events[event] = [];
      }

      if(typeof callback === "function") {
         // Push this callback into the event's stack
         this.events[event].push(callback);
      }
   }
};

// Trigger method
Button.prototype.trigger = function(event, param) {
   // Making sure the event type stack exists
   if(typeof event === "string" &&
      typeof this.events[event] === "object" &&
         this.events[event] instanceof Array) {

      // Loop through event's stack
      for(var i=0, len=this.events[event].length; i<len; i++) {
         if(typeof this.events[event][i] === "function") {
            // Fires callback with given parameter
            this.events[event][i](param);
         }
      }
   }
};

// Update appendTo method to trigger an "onAppend" event
Button.prototype.appendTo = function(container) {
   container.appendChild(this.anchor);
   this.trigger("onAppend", container);
};

// Now create a button instance and bind to the "onAppend" event
var button = new Button({
   text: "Link Text",
   href: "http://dph.am"
});
button.bind("onAppend", function(container) {
   console.log("Callback 1 fired, button appended to", container);
});

// In fact, you can bind multiple callbacks to that event
button.bind("onAppend", function(container) {
   console.log("Callback 2 fired, button appended to", container);
});

// Now when you call the appendTo() method
// all the "onAppend" callbacks will get triggered
button.appendTo(document.body);

Again, this is a very bare implementation. In practice, one would define an unbind method so that certain bounded callbacks can be unbind and maybe performing a call or apply on each callbacks to give them the instance’s context.

Comments