6

I've been following along with a Railscast tutorial of backbone.js and I wanted to extend the functionality to include keyboard control. I added the following to my show view:

class Raffler.Views.EntryShow extends Backbone.View
  template: JST['entries/show']

  events:
    'click .back': 'showListing'
    'keyup': 'goBack'

  showListing: ->
    Backbone.history.navigate("/", trigger: true)

  goBack: (e) ->
    console.log e.type, e.keyCode

  render: ->
    $(@el).html(@template(entry: @model))
    this

On my show template I have the following:

<a href="#" class="back">Back</a>
<%= @entry.get('name') %></td>

If I select the back link using the tab key, then start hitting random keys I get output in my javascript console. However if I load the page and do not select the link and just start hitting keys I get no output in my console.

How do I bind the event to the document so that it will listen to any keys pressed when loading the screen?

map7
  • 5,096
  • 6
  • 65
  • 128
  • 1
    possible duplicate of [backbone.js - filtering a collection with the value from a input](http://stackoverflow.com/questions/9244773/backbone-js-filtering-a-collection-with-the-value-from-a-input) – mu is too short Feb 16 '12 at 03:18
  • it is the same functionality but i doubt it can be seen as a duplicate, this person specifically asks how he can work around the scope of a view, bind a key up event to the document and not to a single input. the document most likely outside of the view's scope. in his own example he binds to just 'keyup' which means he is binding it to the container element of his view, which is most likely not the whole page. – Sander Feb 16 '12 at 09:39

2 Answers2

6

You will need to work around backbone's scope for views. when you are doing something like this:

  events:
    'click .back': 'showListing'
    'keyup': 'goBack'

you are binding your goBack function to the keyup event raised on your container element of your view. (by default the div in which the view is rendered)

instead of doing that, if you want to bind to something outside your view (which doesn't have it's own view!(*))

Raffler.Views.EntryShow = Backbone.View.extend({
  template: JST['entries/show'],

  events: {
    'click .back': 'showListing'
  },

  initialize: function () {
    $('body').keyup(this.goBack);
  },

  showListing: function () {
    Backbone.history.navigate("/", trigger: true);
  },

  goBack: function (e) {
    console.log e.type, e.keyCode;
  },

  render: function () {
    $(this.el).html(this.template(entry: @model));
    return this;
  }

});

(*)remark as marked above, you best do this only when the item you want to bind to does not have it's own view, if you have a view for your full page (an app view or something like that) you could bind the keyup in there, and just raise an event App.trigger('keypressed', e); for example.

you can then in your EntryShow view, bind to that App's keypressed event.

App.bind('keypressed', goBack);

keep in mind that you should do something as a delayed event or grouping keypresses together in some situations, as firing every keypress that happens in the body, might be a big performance hit. especially on older browsers.

Sander
  • 13,301
  • 15
  • 72
  • 97
  • This works for me on the initial load of a view. But when the user navigates to the view a second time, the binding is reapplied and the method is fired twice on each 'keyup' event. – wuliwong Feb 13 '13 at 20:34
  • you can solve that in 2 ways, you can use a view manager, like backbone marionette (or one you write yourself) and unbind the keypressed event when the view closes. Or you can leave it bound, but work with some processed state. when you bind to the keypressed event you only do it once, check if keypressedprocessed class is on the container. if not, bind to it, else just don't. (after binding you have to set the class so the second time you visit you don't bind again.) – Sander Feb 14 '13 at 10:19
4

Your events will be scoped to your view element @el. To capture events on the document, you have to roll that yourself:

initialize: ->
  $(document).on "keyup", @goBack

remove: ->
  $(document).off "keyup", @goBack

Should do the trick.

Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
  • 1
    This answer addresses the removal of the binding but `remove()` isn't automatically called, correct? You need something that is called every time a user navigates away from a view. Instead, I just make sure any previous bindings are removed before I apply them in my current view. It's kinda hacky, but it works. – wuliwong Feb 13 '13 at 21:45
  • @wuliwong yep I don't think `remove()` is automatically called. What is the opposite of `initialize` that gets automatically called when user navigates away from the view? Couldn't find one in the doc. Any solutions? – mc9 Dec 08 '15 at 22:53