Custom Events and Pub/Sub

Walter Rumsby

DOM Events

<a href="#" id="foo">Click Me</a>

$('#foo').on('click', function (e) {
  // do something
});

Ext.get('foo').on('click', function (e) {
  // do something
});

Custom Events

<a href="#" id="foo">Show Window</a>

var popup = new XERO.widget.Window({
  ...
});

popup.on('show', function () {
  // do something 
};

Ext.get('foo').on('click', function (e) {
  popup.show();
  e.preventDefault();
});

Custom Events





Yup :)

Framework Support

All frameworks provide custom-event support.

Because jQuery is very DOM-centric its support is a little weird, but there are lots of custom event libraries you can use with jQuery, e.g. eve, evento, etc.

Ext's support is actually not too bad. Every Ext component extends Ext.util.Observable.

Pub-Sub

XERO.bus.on('notification:received', function (e) {
  // do something interesting
});

Ext.Ajax.request({
  ...
  success: function (response) {
    var payload;
    ...
    XERO.bus.fireEvent('notification:received', payload);
  },
  ...
});

Pub-Sub





Yup :)



(aka Event Bus, etc)

Loose coupling

Instead of:

add: function (item) {
    this.items.push(item);
    a.doSomething();
    b.doSomethingElse();
    c.youGetTheIdea();
}

Do this:

add: function (item) {
  this.items.push(item);

  XERO.bus.fireEvent('items:add', {
    target: this,
    item: item
  });
}

XERO.bus.on('items:add', function (e) {
  a.doSomething();
});

XERO.bus.on('items:add', function (e) {
  b.doSomethingElse();
});

Publisher and subscribers are independent, don't need to know about each other and can be set up in any order.

This let's us do interesting things, like add or remove publishers or subscribers after the page is initially rendered.

An event bus is really just a singleton instance of the custom event provider, e.g.:

XERO.bus = new Ext.util.Observable();

Backbone

Backbone & Events

Models fire change events.

Collections fire add & remove events.

Views listen for DOM events. They also listen for custom events fired by models and collections.

Views can fire custom events too.

0...n views per model.

Use custom events for model-to-view communication & pub/sub for view-to-view communication.


Backbone and other JavaScript MV* frameworks promote breaking down your code into modules dedicated to particular tasks.

Overview

Identify Modules

What are the models and views here?

Model

(function () {
  'use strict';

  // Global singleton, hmm, but better than before
  XERO.model.BankAccount = new XERO.model.Base({
    ns: 'account',
    attributes: {
      'shortCode': '<%= Model.ShortCode %>',
      'isAutoProvisioned': <%= Model.AutoProvisioned.ToString().ToLower() %>,
      'statusCode': '<%= Model.StatusCode %>',
      'transactions': <%=Model.HasAddedTransactions.ToString().ToLower() %>,
      'feedStatus': '<%= Model.BankFeedStatus %>',
      'canRefreshFeed': false
    }
  });  
} ());

View Template

<script id="account-options-template" type="text/x-template">
    <ul>
        <tpl if="!(isAutoProvisioned || statusCode == 'ACCOUNTSTATUS/ARCHIVED')">
            <li><a href="../ImportBankTransactions/<%=HttpUtility.UrlEncode(Model.ShortCode) %>">Import transactions</a></li>
        </tpl>
        <tpl if="statusCode != 'ACCOUNTSTATUS/ARCHIVED'">
            <li><a href="#" data-action="add-manual-transaction">Add manual transactions</a></li>
        </tpl>
        <tpl if="transactions">
            <li><a href="../../Search#{'searchLogic':'All','searchConditions':[{'type':'Account','mode':'Is','value':'<%=Model.ShortCode %>'}],'includeCategorised':true,'includeUncategorised':true,'includeToReview':true,'includeArchived':true,'tab':2}">View all transactions</a></li>
        </tpl>
        <li><a href="#" data-action="edit-account">Edit account</a></li>
        ...
    </ul>
</script>

View

Ext.onReady(function () {
    'use strict';

    var view = new XERO.view.Base({
        model: XERO.model.BankAccount,
        templateId: 'account-options-template',
        el: Ext.get('account-options-container'),
        events: {
            ...
        }
    });

    view.model.on('change', function (e) {
        // render just plugs in the model data into the view template
        // template decides what to display - code is simpler!
        view.render();
    });
});

And here?

Interesting Stuff

Combining Custom Events & Pub-Sub

YUI 3's EventTarget both fires events locally and broadcasts those events as messages.

XERO.util.Observable does the same thing, e.g.:

var object = Ext.extend(XERO.util.Observable, {
  ns: 'my',
  ...
});

object.on('something', function (e) { ... });

XERO.bus.on('my:something', function (e) { ... });

object.fireEvent('something', ...);
// fires 'something'
// broadcasts 'my:something'

More Info