UNPKG

backbone.listenablemodel

Version:

Composite model for Backbone.js

199 lines (153 loc) 5.44 kB
# Backbone.ListenableModel.js 0.0.4 # (c) 2013 Di Wu MIT License # https://github.com/diwu1989/Backbone.ListenableModel # if executing in node if module?.exports? _ = require 'underscore' Backbone = require 'backbone' else _ = window._ Backbone = window.Backbone # save off the previous version of ListenableModel previousListenableModel = Backbone.ListenableModel # helper functions class Helper @getListenableCallback: (attributeName) -> callback = (eventName) -> args = [].slice.call arguments # rename the change event to include the attribute name args[0] = attributeName + '.' + args[0] @trigger.apply @, args if 'change' is eventName args[0] = 'change' @trigger.apply @, args return callback @isListenable: (value) -> # not an object unless _.isObject(value) return false # models are automatically allowed if value instanceof Backbone.Model return true # duck typing isListenable = value.on and value.off and value.trigger and value.listenTo and value.stopListening return isListenable @listenableDifference: (source, target) -> difference = {} # iterate over the key and value from source for key, value of source if target[key] isnt value and Helper.isListenable value # value is listenable and is different in the target difference[key] = value return difference @getSubmodel: (model, key) -> if undefined is key or null is key return [false] key = String(key) index = key.indexOf '.' if -1 is index # no submodel specified return [false] # get the attribute name and retrieve the submodel attributeName = key.substring 0, index submodel = model.get attributeName unless submodel # submodel doesn't exist, do not forward return [false] unless 'function' is typeof submodel.set and 'function' is typeof submodel.get # submodel doesn't duck type to a model, do not forward return [false] subkey = key.substring index + 1 return [submodel, subkey] @forwardSetToSubmodel: (model, key, value, options) -> [submodel, subkey] = Helper.getSubmodel model, key unless submodel return false submodel.set.apply submodel, [subkey, value, options] # set forwarded return true @forwardGetToSubmodel: (model, key) -> [submodel, subkey] = Helper.getSubmodel model, key unless submodel return [false] result = submodel.get.apply submodel, [subkey] return [true, result] class ListenableModel extends Backbone.Model @VERSION: '0.0.4' @noConflict: -> Backbone.ListenableModel = previousListenableModel return previousListenableModel constructor: -> # must set up the listenable callbacks hash before calling super constructor @_listenableCallbacks = {} # super constructor should eventually call set super get: (key) -> [useSubmodel, result] = Helper.forwardGetToSubmodel @, key if useSubmodel return result # did not forward to submodel super set: (key, val, options) -> if 'object' is typeof key for name, value of key if Helper.forwardSetToSubmodel @, name, value, options # already forwarded this attribute to the submodel delete key[name] else if Helper.forwardSetToSubmodel @, key, val, options # event forwarded, return this return @ previousAttributes = _.clone @attributes # call Backbone.Model.set result = super unless result # change failed validation return result # compute the listenable attributes and values that were dropped droppedListenables = Helper.listenableDifference previousAttributes, @attributes for attr, value of droppedListenables # find the previously attached callback callback = @_listenableCallbacks[attr] # stop listening @stopListening value, 'all', callback # remove record delete @_listenableCallbacks[attr] # compute the listenable attributes and values that were added addedListenables = Helper.listenableDifference @attributes, previousAttributes for attr, value of addedListenables # value is listenable, create a listener callbackListener = Helper.getListenableCallback attr # attach to value @listenTo value, 'all', callbackListener # keep track of the callback listener @_listenableCallbacks[attr] = callbackListener # return the result from super return result _validate: (attrs, options) -> # call Backbone.Model._validate result = super # failed vanilla validation unless result return result # ensure that attribute keys do not have spaces for key in _.keys attrs key = String(key) if -1 isnt key.indexOf ' ' error = "attribute name '#{key}' should not have space" @trigger 'invalid', @, error, _.extend(options || {}, {validationError: error}) return false if -1 isnt key.indexOf '.' error = "attribute name '#{key}' should not have period" @trigger 'invalid', @, error, _.extend(options || {}, {validationError: error}) return false return result # export out ListenableModel if module?.exports? module.exports = ListenableModel else Backbone.ListenableModel = ListenableModel