Wednesday, November 2, 2011

Delegated Events in Backbone.js (I'm an Idiot)

‹prev | My Chain | next›

Thanks to astute readers from last night, I now know that I have been operating under a very mistaken assumption for quite some time now. Specifically, jQuery delegated events can prevent propagation.

Revisiting the no-action form from last night:

    var CalendarFilter = Backbone.View.extend({
      template: template(
        '<form>' +
        '<input type="text" name="filter">' +
        '<input type="button" class="filter" value="Filter">' +
        '</form>'
      ),
      render: function() {
        $(this.el).html(this.template());
        return this;
      },
      // ...
    });
It turns out that I can handle this form without explicitly checking for the Enter key:
    var CalendarFilter = Backbone.View.extend({
      // ....
      events: {
        'click .filter':  'filter',
        'keyup input[type=text]': 'filter',
        'submit form': 'filter'
      },
      filter: function(e) {
        var filter = $('input[type=text]', this.el).val();

        // Don't trigger events like this.  See Custom Events in 
        // Recipes with Backbone for more info.
        this.collection.each(function(model) {
          model.trigger('calendar:filter', filter);
        });
        return false;
      }
    });
As any experienced web developer knows, any form with a submit button is submitted when the user hits Enter in a text field. Checking for event.keyCode == 13 and then manually submitting the form is just silly. All browsers (even Internet Explorer) do it for you.

The reason that the above code works where it did not last night is because the filter handler ends with a return false. Returning false from an event handler is jQuery shorthand for
event.stopPropagation();
event.preventDefault();
It is, in fact, the event.preventDefault() that last night's code was missing. I had been relying on the event.stopPropagation() only. As more than one reader reminded my yesterday, form submission is a built-in, default behavior, not an "ordinary" event. Hence the need to prevent the default action.

Part of my confusion has also surrounded the difference between live() events and delegate() events in jQuery. Live events are bound on the document itself. As such, by the time events bubble all the way up there, it is far too late to call event.stopPropagation() or event.preventDefault(). Both would have already fired on any and all DOM elements under document.

When I first experimented with delegated events, I mistakenly interpreted my inability to prevent form submission as being caused by delegated events retaining live events' inability to stopPropagation / preventDefault. It turns out that it was much simpler than that: I was not invoking preventDefault(). In my defense, I still find the delegate() documentation a little hard to read on the subject. Thankfully, the replacement for delegate(), on(), has much better discussion of all of this.

I spend the rest of my time tonight working through my other Backbone.js forms, enabling them to properly handle Enter without resorting to keyCode hackery. And I feel much better for it. Thanks again, to my "Unknown" reader for pointing out my mistake.

I think I have a few old blog posts in need of an update...


Day #183

No comments:

Post a Comment