UNPKG

3vot-model

Version:

3VOT Model based on SpineJS

393 lines (306 loc) 8.96 kB
Events = require("./events") Module = require("./module") class Model extends Module @extend Events @records : [] @irecords : {} @attributes : [] @configure: (name, attributes...) -> @className = name @deleteAll() @attributes = attributes if attributes.length @attributes and= makeArray(@attributes) @attributes or= [] @unbind() this @toString: -> "#{@className}(#{@attributes.join(", ")})" @find: (id) -> record = @exists(id) throw new Error("\"#{@className}\" model could not find a record for the ID \"#{id}\"") unless record return record @exists: (id) -> @irecords[id]?.clone() @addRecord: (record) -> if record.id and @irecords[record.id] @irecords[record.id].remove() record.id or= record.cid @records.push(record) @irecords[record.id] = record @irecords[record.cid] = record @refresh: (values, options = {}) -> @deleteAll() if options.clear records = @fromJSON(values) records = [records] unless isArray(records) @addRecord(record) for record in records @sort() result = @cloneArray(records) @trigger('refresh', result, options) result @select: (callback) -> (record.clone() for record in @records when callback(record)) @findByAttribute: (name, value) -> for record in @records if record[name] is value return record.clone() null @findAllByAttribute: (name, value) -> @select (item) -> item[name] is value @each: (callback) -> callback(record.clone()) for record in @records @all: -> @cloneArray(@records) @first: -> @records[0]?.clone() @last: -> @records[@records.length - 1]?.clone() @count: -> @records.length @deleteAll: -> @records = [] @irecords = {} @destroyAll: (options) -> record.destroy(options) for record in @records @update: (id, atts, options) -> @find(id).updateAttributes(atts, options) @create: (atts, options) -> record = new @(atts) record.save(options) @destroy: (id, options) -> @find(id).destroy(options) @change: (callbackOrParams) -> if typeof callbackOrParams is 'function' @bind('change', callbackOrParams) else @trigger('change', arguments...) @fetch: (callbackOrParams) -> if typeof callbackOrParams is 'function' @bind('fetch', callbackOrParams) else @trigger('fetch', arguments...) @toJSON: -> @records @fromJSON: (objects) -> return unless objects if typeof objects is 'string' objects = JSON.parse(objects) if isArray(objects) (new @(value) for value in objects) else new @(objects) @fromForm: -> (new this).fromForm(arguments...) @sort: -> if @comparator @records.sort @comparator this # Private @cloneArray: (array) -> (value.clone() for value in array) @idCounter: 0 @uid: (prefix = '') -> uid = prefix + @idCounter++ uid = @uid(prefix) if @exists(uid) uid # Instance constructor: (atts) -> super @load atts if atts @cid = atts?.cid or @constructor.uid('c-') isNew: -> not @exists() isValid: -> not @validate() validate: -> load: (atts) -> if atts.id then @id = atts.id for key, value of atts if atts.hasOwnProperty(key) and typeof @[key] is 'function' @[key](value) else @[key] = value this get: (attr) -> return @[attr]; set: (attr, value) -> @[attr] = value; attributes: -> result = {} for key in @constructor.attributes when key of this if typeof @[key] is 'function' result[key] = @[key]() else result[key] = @[key] result.id = @id if @id result eql: (rec) -> !!(rec and rec.constructor is @constructor and (rec.cid is @cid) or (rec.id and rec.id is @id)) save: (options = {}) -> unless options.validate is false error = @validate() if error @trigger('error', error) return false @trigger('beforeSave', options) record = if @isNew() then @create(options) else @update(options) @stripCloneAttrs() @trigger('save', options) record stripCloneAttrs: -> return if @hasOwnProperty 'cid' # Make sure it's not the raw object for own key, value of @ delete @[key] if @constructor.attributes.indexOf(key) > -1 this updateAttribute: (name, value, options) -> atts = {} atts[name] = value @updateAttributes(atts, options) updateAttributes: (atts, options) -> @load(atts) @save(options) changeID: (id) -> return if id is @id records = @constructor.irecords records[id] = records[@id] delete records[@id] @id = id @save() remove: -> # Remove record from model records = @constructor.records.slice(0) for record, i in records when @eql(record) records.splice(i, 1) break @constructor.records = records # Remove the ID and CID delete @constructor.irecords[@id] delete @constructor.irecords[@cid] destroy: (options = {}) -> @trigger('beforeDestroy', options) @remove() @destroyed = true # handle events @trigger('destroy', options) @trigger('change', 'destroy', options) if @listeningTo @stopListening() @unbind() this dup: (newRecord = true) -> atts = @attributes() if newRecord delete atts.id else atts.cid = @cid new @constructor(atts) clone: -> createObject(this) reload: -> return this if @isNew() original = @constructor.find(@id) @load(original.attributes()) original refresh: (data) -> # go to the source and load attributes root = @constructor.irecords[@id] root.load(data) @trigger('refresh') @ toJSON: -> @attributes() toString: -> "<#{@constructor.className} (#{JSON.stringify(this)})>" fromForm: (form) -> result = {} ### for checkbox in $(form).find('[type=checkbox]:not([value])') result[checkbox.name] = $(checkbox).prop('checked') for checkbox in $(form).find('[type=checkbox][name$="[]"]') name = checkbox.name.replace(/\[\]$/, '') result[name] or= [] result[name].push checkbox.value if $(checkbox).prop('checked') for key in $(form).serializeArray() result[key.name] or= key.value ### @load(result) exists: -> @constructor.exists(@id) # Private update: (options) -> @trigger('beforeUpdate', options) records = @constructor.irecords records[@id].load @attributes() @constructor.sort() clone = records[@id].clone() clone.trigger('update', options) clone.trigger('change', 'update', options) clone create: (options) -> @trigger('beforeCreate', options) @id or= @cid record = @dup(false) @constructor.addRecord(record) @constructor.sort() clone = record.clone() clone.trigger('create', options) clone.trigger('change', 'create', options) clone bind: (events, callback) -> @constructor.bind events, binder = (record) => if record && @eql(record) callback.apply(this, arguments) # create a wrapper function to be called with 'unbind' for each event for singleEvent in events.split(' ') do (singleEvent) => @constructor.bind "unbind", unbinder = (record, event, cb) => if record && @eql(record) return if event and event isnt singleEvent return if cb and cb isnt callback @constructor.unbind(singleEvent, binder) @constructor.unbind("unbind", unbinder) this one: (events, callback) -> @bind events, handler = => @unbind(events, handler) callback.apply(this, arguments) trigger: (args...) -> args.splice(1, 0, this) @constructor.trigger(args...) listenTo: -> Events.listenTo.apply @, arguments listenToOnce: -> Events.listenToOnce.apply @, arguments stopListening: -> Events.stopListening.apply @, arguments unbind: (events, callback) -> if arguments.length is 0 @trigger('unbind') else if events for event in events.split(' ') @trigger('unbind', event, callback) Model::on = Model::bind Model::off = Model::unbind # Utilities & Shims createObject = Object.create or (o) -> Func = -> Func.prototype = o new Func() isArray = (value) -> Object::toString.call(value) is '[object Array]' isBlank = (value) -> return true unless value return false for key of value true makeArray = (args) -> Array::slice.call(args, 0) Model.isBlank = isBlank; Model.sub = (instances, statics) -> class Result extends this Result.include(instances) if instances Result.extend(statics) if statics Result.unbind?() Result Model.setup = (name, attributes = []) -> class Instance extends this Instance.configure(name, attributes...) Instance Model.host = "" module?.exports = Model