Preventing Interface-Induced Backbone.sync Firestorms

Nate Hunzaker, Former Development Director

Article Category: #Code

Posted on

Draggable controls raise an interesting challenge when  Backbone.Views share a common model.

Such controls include colorpickers, slides, and positionable elements. When users move around a color wheel or acutely adjust the position of an element, change events should trigger to let other components know their portion of the interface needs updating.

For this to happen, the browser's data layer must update immediately. But how does the application know when to pass the torch to the database?

Requesting to persist data on every pixel deviation can be disastrous. When a user wants the perfect shade of yellow, there's nothing like a torrent of PUT requests to dampen their mood.

To illustrate:

Not exactly performant. Fortunately UnderscoreJS provides a great toolset for tackling exactly this problem.

Only you can prevent forest-fires, by debouncing

UnderscoreJS's debounce method is particularly well suited for our use case: it postpones invocation of a function until a specified expiration time that resets on every function call. This can be used to bundle together Backbone.sync executions from the same source, significantly lowering requests without nasty side effects.

Update: Originally I handled debouncing directly within the sync method of the Backbone.Model, however a sharp commenter has suggested a more optimal solution by moving this functionality into the Backbone.View that oversees the input.

To avoid directly manipulating the Backbone.sync method, an event must be bound in the view that controls such inputs. On change, the view should immediately provide actions required to update the user interface, requesting to sync in the background.

var View = Backbone.View.extend({
  events: {
    'change input.slider': 'onSliderChange'
  },

  saveInBackground: _.debounce(function(params) {
    return this.model.save(params, { silent: true })
  }, 800),

  onSliderChange: function(e) {
    // Take immediate action, such as setting the values locally:
    this.model.set({ value: e.target.value })

    // Then call a debounced save method, which will eventually
    // trigger Backbone.sync
    this.saveInBackground()
  }
})

Beautiful and Simple

This minor addition achieves exactly what is needed! No matter how furiously the user updates information, the view is now smart enough to send an AJAX request once, a short period after the drag motion has completed. Minor tweaks may be required depending on the purposes of the application, but the resulting code lets the user interface immediately respond to the change, while not causing servers to catch on fire.

Related Articles