if module? and not window?.module?
_ = require('underscore')
Backbone = require('backbone')
CRUD = ['create', 'read', 'update', 'delete']
HANDLER_DEFAULTS =
addOptions: trueThis 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: trueReturns a copy of the class with sync extended by the handlers.
wrap = (cls, handlers) ->
nCls = cls
nCls:: = _.clone cls::
bind nCls, handlers
nClsMutates 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::syncmake 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
clsBuild 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 handlersNormalize the handler to always be an object with a make method.
handlersToObjects handlershandlers.defaults can contain default options for
all of the handlers
applyDefaults handlersThis 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
elseThis 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, optionsIn 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
reqDo 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? dataHandler 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: handlerUse 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, handlerHandlers 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