Preventing Interface-Induced Backbone.sync Firestorms
Nate Hunzaker, Former Development Director
Article Category:
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.