Jeff Finley
Jeff Finley

Reputation: 13

jquery custom event not working in backbone

Question is why isn't the Backbone view executing the jQuery custom event handler?

I'm trying to listen for the enter key on an text box and when the enter key is pressed, and only the enter key, I want to execute a function. I'm using require.js 2.1.15, jquery 1.11.1, backbone 1.1.2 and underscore 1.7.

The idea is to register the "enter.js" plugin with jquery.

(function (factory) {
        if (typeof define === 'function' && define.amd) {
            // AMD. Register as an anonymous module depending on jQuery.
            console.log("require.js registered");
            define(['jquery'], factory);
        } else {
            // No AMD. Register plugin with global jQuery object.
            console.log("no amd registered");
            factory(jQuery);
        }
    }(function ($) {
        console.log('registered event listener for input on the enter key');
        $('input').keyup(function(e) {
            console.log("key input");
            if(e.keyCode == 13){
                console.log("enter key assessed");
                $(this).trigger('enter');
            }
        })
    }));

This executes and registers with no errors in the console.
require.js registered enter.js:12 registered event listener for input on the enter key Problem.js:4

Now the backbone code:

define(function (require) {
  "use strict";

  console.log("start of ArithmeticView.js");
  var
  $                 = require('jquery'),
  enter     = require('../../$plugins/enter'),
  _                 = require('underscore'),
  Backbone  = require('backbone'),
  tpl               = require('text!tpls/ArithmeticView.html'),
  ProblemModels         = require('../models/Problem'),

  template  = _.template(tpl);


  return Backbone.View.extend( {

    el: 'body',


    events: {
      "enter .answer": "solve",
    },
  initialize: function(){
      this.render();
      this.displayProblem();
    },
    render: function() {
      console.log("render ArithmeticView");
      return this.$el.html(template());
    },
    solve: function() {
            console.log("solving ProblemModels.Problem");
        var givenAnswer = $(".answer").val();
            if(givenAnswer == this.problem.get("answer")) {
                return this.displayProblem();
             }
            return this;   
    },
    displayProblem: function() {
        this.problem = new ProblemModels.Problem;
            $('.problemArea').text(this.problem.get("question"));
            return this;
    }    
  });
});

When I debug in Chrome console I can see the enter listener attached to the textarea with the class answer attached to it. I don't see anywhere in the console the actual function code as coded in the jQuery plugin enter.

Upvotes: 1

Views: 527

Answers (1)

seebiscuit
seebiscuit

Reputation: 5053

Updated:

I totally understand your specifications, in fact I personally like the way you're handling the 'enter' event. Except that the way you're implementing it, it will never work with dynamically loading HTML (think templates).

The reason your view isn't listening to the 'enter' event is because it's not there

Yea, I know, I know. The enter.js plugin is binding to all <input> elements. All of them that are there at the time it's doing the binding. From the jQuery docs (emphasis theirs):

Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on()

When you populate and attach template code, _.template returns all new DOM elements. Even bindings to all elements of a type like $('input').on will not protectively bind to new <input> elements.

Workaround

In my opinion, your best bet is to rebind your plugin after you render the view.

(Normally you'd want to use delegation to get around this problem, and read the jQuery.event.target property in your plugin to see if the keypress came from an <input> element, but you can't use that in your view events hash because Backbone can't detect delegation.)


Alternative workaround

Handle the enter keypress in each method that you need to listen for.

Just replace

"enter .answer": "solve"

with

"keypress .answer": "solve"

and add the following in your solve method

solve: function(event) {
  event.stopPropagation();
  if (event.keyCode == 13) {
    console.log("solving ProblemModels.Problem");
    var givenAnswer = $(".answer").val();
    if(givenAnswer == this.problem.get("answer")) {
      return this.displayProblem();
    }
    return this;  
  } 
}

Backbone will always send a jQuery event object to the callback of a view event. jQuery packages the keyCode property in the event object which saves the key detected on the keypress event. In your case (and if you look at your plugin) all we do is test whether the key pressed was the enter key, with a key code of 13.

The above reproduces your desired effect without the complexity of registering and then loading the plugin into scope with Require. I know it's not as neatly encapsulated as using the enter.js plugin but it makes the code more straightforward and it may possibly run leaner without the plugin overhead.

Upvotes: 1

Related Questions