if module? and not window?.module?
_ = require('underscore')
Backbone = require('backbone')
CRUD = ['create', 'read', 'update', 'delete']
HANDLER_DEFAULTS =
addOptions: true
This file provides a function, make
for building a new
sync function for your Backbone.Model which will call the methods
you define. It is particularily useful for integrating Backbone
with the JavaScript API interface of your choice.
The basic idea:
class MyAwesomeModel extends Backbone.Model
sync: Backbone.HookSync.make
create: myAwesomeCreator
update: 'create'
delete: 'default'
read:
do: myAwesomeReader
build: (method, model, options) ->
model.attributes
The file also provides two functions to add a new sync method to your existing classes.
bind
adds the method to an existing class:
class MyAwesomeModel extends Backbone.Model
# Model Dets Here
Backbone.HookSync.bind MyAwesomeModel,
create: newCreator
# Reads, Updates and Deletes will continue to use the
# models sync, or if one is not defined, Backbone.Sync
wrap
returns a new copy of your class with the sync method replaced:
class MyAwesomeModel extends Backbone.Model
# Some Modeling..
MoreAwesomeModel = Backbone.HookSync.wrap MyAwesomeModel,
update: _.noop
All three methods expect an object containing 1-4 of the CRUD methods:
Optionally, a sync
method which will be used with requests which
do not match one of the provided methods (or for methods defined as
'default').
Optionally, a defaults
object which provides defaults to the four
CRUD methods.
Each of the CRUD method keys can have any of the following values:
null
, or undefined
representing the default sync behaviorIf an object is used it can have the following attributes:
- function do
- The function to be called (make sure you use string notation ["do"] if
you're not writing CoffeeScript)
- function build(method, model, options)
- A function used to build the request passed
into do. Defaults to model.toJSON()
.
- boolean expandArguments[false]
- Should the array returned by build
be
expanded and passed into do as seperate arguments?
- boolean returnsPromise[false]
- Does do return a Deferred object? If so
it's done and fail methods will trigger the success and error
callbacks (and the default callbacks will be disabled).
- boolean addOptions[true]
- Should the options hash be merged in with
the return value of build?
If you're using expandArguments, addOptions: false is implied.
if module? and not window?.module?
_ = require('underscore')
Backbone = require('backbone')
CRUD = ['create', 'read', 'update', 'delete']
HANDLER_DEFAULTS =
addOptions: true
Returns a copy of the class with sync extended by the handlers.
wrap = (cls, handlers) ->
nCls = cls
nCls:: = _.clone cls::
bind nCls, handlers
nCls
Mutates an existing class to replace sync with a new sync method powered by the handlers.
Preserves a reference to the existing sync, so any method not handled will fall through to the original.
bind = (cls, handlers) ->
if cls::sync
make knows to look at handlers.sync if the sync method is not bound in the handlers (or is bound as 'default').
If there is no cls::sync (the default case for Backbone), make will use Backbone.sync.
handlers.sync ?= cls::sync
cls::sync = make handlers
cls
Build a sync function compatable with Backbone out of one or more CRUD functions. Anything you don't override will get passed through to the default sync function.
make = (handlers) ->
Handlers is a map of CRUD methods to the functions which should handle them + some other options.
Replace all of the string handler pointers with the actual handlers they point to. Replace 'default' with null
resolveHandlers handlers
Normalize the handler to always be an object with a make method.
handlersToObjects handlers
handlers.defaults
can contain default options for
all of the handlers
applyDefaults handlers
This is the sync-replacement we'll be returning
(method, model, options) ->
handler = handlers[method]
if handler
request = buildRequest handler, method, model, options
makeRequest handler, request, model, options
else
This method is most likely gonna be overriding the model's sync method, so calling @sync would recurse, so we let the fall-through sync be passed in as an option.
wrap
and bind
do this for you, automatically preserving the
previous sync in handlers.
(handlers.sync or Backbone.sync) method, model, options
In this context, a request is the object which will be passed
into the do
function passed in as a handler. Based on the
expandArguments
property, it can either be a single object,
or an array of arguments.
Unless you set handler.addOptions to false, the options object will be automatically merged with the attributes your return.
The build function defaults to model.toJSON
.
buildRequest = (handler, method, model, options) ->
builder = handler.build ? model.toJSON
req = builder.call model, method, model, options
if handler.addOptions and not handler.expandArguments
req = _.extend {}, options, req
req
Do it!
handler.returnsPromise gives you a convenient way to convert a method which normally returns a promise to work with Backbone's success and error handlers.
It will replace the passed-in success and error handlers with noops so they are not called twice.
makeRequest = (handler, request, model, options) ->
if handler.returnsPromise
oldOptions = _.pick request, 'success', 'error'
request.success = request.error = ->
if handler.expandArguments
resp = handler.do request...
else
resp = handler.do request, model, options
if handler.returnsPromise
resp.done (data) ->
callSuccessCallback(model, data, options, oldOptions)
model.trigger('sync', model, resp, options)
resp.fail (err) ->
oldOptions.error? err
model.trigger('error', err, model, options)
After Backbone 0.9.2, the fetch success wrapper Backbone creates changed the arguments supplied by the callback.
While this isn't ideal, it's nice to be backwards compatible to 0.9.2.
callSuccessCallback = (model, data, options, oldOptions) ->
if isBackboneVersionGreaterThan('0.9.2') and not isBackboneVersionGreaterThan('0.9.10')
oldOptions.success? model, data, options
else
oldOptions.success? data
Handler can be passed in as either functions, or objects which have some more options and functions. To make it easier, lets make them objects all of the time.
It modifies the passed in object in-place.
handlersToObjects = (handlers) ->
for type, handler of handlers when type in CRUD
if _.isFunction handler
handlers[type] =
do: handler
Use handlers.defaults as the defaults for all of the other handlers.
Modifies the passed-in object in-place.
applyDefaults = (handlers) ->
for type, handler of handlers when type in CRUD
handlers[type] = _.extend {}, HANDLER_DEFAULTS, handlers.defaults, handler
Handlers can be string references to the keys of other handlers, or 'default'.
Modifies the passed in object in-place.
resolveHandlers = (handlers) ->
for type, handler of handlers when type in CRUD
switch handler
when 'default'
In this context, default
means pass request through to
handlers.sync
or Backbone.sync
.
For convenience, normalize default to be falsy as we also support simply skipping the handler type to signify 'default'
handlers[type] = null
when 'create', 'update', 'read', 'delete'
You can alias the handler to another handler.
The most common usage of this is to map create to the same thing as update, e.g.:
make
create: myHandler
update: 'create'
This is only going to reliably work to one level of depth, you can't reference references.
handlers[type] = handlers[handler]
isBackboneVersionGreaterThan = (compare) ->
comparison = compare.split('.')
current = Backbone.VERSION.split('.')
parseInt(comparison[0]) < parseInt(current[0]) or
parseInt(comparison[1]) < parseInt(current[1]) or
parseInt(comparison[2]) < parseInt(current[2])
exports = {make, wrap, bind}
Backbone?.HookSync = exports
module?.exports = exports