Keyboard Shortcuts in Spine

Gmail has them. Github has them. Now your Spine app can have them (if it doesn’t already).

For repetitive tasks, keyboard shortcuts are a perfect medium for introducing efficiencies into an application. It’s a small feature with a lot of impact.

Keymaster

For this exercise, we’ll be using Thomas Fuchs’ Keymaster library. You’ll want to download it and drop it into vendor/assets/javascripts/ and include it in your manifest.

# application.js
//= require jquery
//= require jquery-ui
//= require keymaster
//= require app
//= require_tree .

Defining shortcuts is effortless and as simple as passing a shortcut key and function to the key method exposed by keymaster. From the README:

// define short of 'a'
key('a', function(){ alert('you pressed a!') });

Keymaster + Spine

Here’s where the magic happens. You’ll be adding some code to your controllers and adding a new class to extend from those controllers.

Spine controllers have a shortcut for adding event listeners to DOM elements using the events property.

events: 
    "click .close": "closeModal"
    "click .item":  "expandItem"

Keyboard shortcuts are nothing more than keyup or keydown events. Mimic this pattern and add a shortcuts property using the syntax {"shortcutKey": "functionName"} to your controller.

shortcuts: 
    "esc":    "closeModal"
    "enter":  "expandItem"
    "⌘+s":   "save"

Great! You’ve added a property to declare which function should get called when a certain key or key combination is pressed. The next step is to pass our shortcuts to keymaster to set up the key bindings.

To do that, create a file in app/assets/javascripts/app/lib/shortcuts.js.coffee that looks like the following:

class Spine.Shortcuts
  @extended: (klass) ->
    instanceMethods =
      delegateShortcuts: ->
        for shortcut, callback of @shortcuts
          match = shortcut.match(@eventSplitter)
          shortcutKey = match[1]
          scope = if match[2] == "" then "all" else match[2]
          key(shortcutKey, scope, @proxy(@[callback]))

      setScope: (scope) ->
        key.setScope(scope)

    @include instanceMethods

    old_init = @::init
    @::init = ->
      old_init?()
      @delegateShortcuts()

This class iterates through the shortcuts object and passes the keyboard key and the function to keymaster, albeit in a roundabout way by hooking into the initialization process and calling the delegateShortcuts() function to ensure the correct context.

From here, extend Spine.Shortcuts in the controllers where you’ve added shortcuts. It should look something like this:

class App.Items.ItemController extends Spine.Controller
  @extend Spine.Events
  @extend Spine.Shortcuts

Fire up your app and behold the power of keyboard shortcuts. Though, the hard part might be presenting these shortcuts to your users.

bryckbost@gmail.com

Comments

  1. February 25, 2012 at 23:13 PM

    Simliar project i just created for Backbone.js: https://github.com/bry4n/backbone-shortcuts

  2. techpeace@gmail.com
    Matt Buck
    March 12, 2012 at 2:23 AM

    Thanks so much! Exactly what I was looking for.