smackbone
Version:
an object oriented model framework
340 lines (276 loc) • 8.58 kB
text/coffeescript
if exports?
Smackbone = exports
_ = require 'underscore'
Smackbone.$ =
done: (func) ->
func {}
ajax: (options) ->
# console.log "method:#{options.type} url:#{options.url}"
@
else
root = this
_ = root._
Smackbone = root.Smackbone = {}
Smackbone.$ = root.$
class Smackbone.Event
trigger: (name, args...) ->
events = ?[name]
events, args... if events?
allEvents = ?.all
allEvents, name, args... if allEvents?
@
on: (names, callback) ->
?= {}
throw new Error 'Must have a valid function callback' if not _.isFunction callback
throw new Error 'Illegal event name' if /\s/g.test name
nameArray = names.split ' '
for name in nameArray
events = [name] or [name] = []
events.push
callback: callback
self: @
@
off: (name, callback) ->
?= {}
if not callback?
= {}
return @
for name in name.split(' ')
events = [name] ? []
names = if name then [name] else (key for key in )
for name in names
newEvents = []
[name] = newEvents
for event in events
if callback isnt event.callback
newEvents.push event
if newEvents.length is 0
delete [name]
@
_triggerEvents: (events, args...) ->
for event in events
event.callback args...
class Smackbone.Model extends Smackbone.Event
constructor: (attributes, options) ->
= {}
= _.uniqueId 'm'
= 0
= 'id'
= {}
= []
attributes if attributes?
if ?
for key, modelClass of
if not key
key, new modelClass {}
? attributes
toJSON: ->
properties = _.clone
for key of
delete properties[key]
properties
isNew: ->
not @[]?
clone: ->
new
_createModelFromName: (name, value, backupClass) ->
modelClass = ?[value[]] ? ?[name] ? ? backupClass
if modelClass? then new modelClass value else value
move: (currentId, nextId) ->
o = currentId
throw new Error "Id '#{currentId}' didn't exist." if not o?
currentId
nextId, o
set: (key, value, options) ->
throw new Error 'can not set with undefined' if not key?
if typeof key is 'object'
attributes = key
options = value
value = undefined
else
(attributes = {})[key] = value
if attributes[]?
oldId = @[] or
@[] = attributes[]
?.move oldId, @[]
= _.clone
current =
previous =
changedPropertyNames = []
addedAttributes = []
removedAttributes = []
= {}
for name, value of
if not attributes[name]?
removedAttributes.push name
for name, value of attributes
if !_.isEqual current[name], value
changedPropertyNames.push name
if !_.isEqual previous[name], value
[name] = value
if current[name]?.set? and not (value instanceof Smackbone.Model) and value?
existingObject = current[name]
existingObject.set value, options
else
if not (value instanceof Smackbone.Model)
value = name, value
current[name] = value
= _.keys(current).length
if value instanceof Smackbone.Model and not value._parent?
value._parent = @
if not value[]?
value[] = name
addedAttributes.push value
= (v for n, v of )
unless options?.silent
for value in addedAttributes
'add', value, @, options
for changeName in changedPropertyNames
"change:#{changeName}", current[changeName], @, options
'change', @, options if changedPropertyNames.length > 0
if options?.triggerRemove
for value in removedAttributes
value
contains: (key) ->
?
add: (object) ->
object
remove: (object) ->
object
each: (func) ->
func value, key for key, value of
get: (key) ->
throw new Error 'Must have a valid object for get()' if not key?
if typeof key is 'string'
key = key[0...-1] if key[key.length-1] is '/'
parts = key.split '/'
model = @
for id in parts
throw new Error "Couldn't lookup '#{id}' in '#{key}'" if not model?
if model instanceof Smackbone.Model
model = model._properties[id]
else
model = model[id]
model
else
[key[] ? key.cid ? key]
at: (index) ->
[index]
first: ->
0
last: ->
.length - 1
unset: (key, options) ->
key = key[] ? key.cid ? key
model = [key]
delete [key]
= (v for n, v of )
= _.keys().length
model?.trigger? 'unset', model, @, options
'remove', model, @, key, options
path: ->
if ? then "#{@_parent.path()}/#{@[@idAttribute] ? ''}" else ? ''
_root: ->
model = @
for i in [0..10]
if not model._parent?
break
model = model._parent
if not model._parent
model
else
console.warn "couldn't find root for:", @
undefined
fetch: (queryObject, options) ->
.trigger 'fetch_request', , @, queryObject, options
'fetch', @, queryObject, options
_triggerUp: (name, args...) ->
model = @
path = ''
for i in [0..20]
if not model?
break
model.trigger name, path, args...
path = "/#{model[@idAttribute] ? ''}#{path}"
model = model._parent
save: (options) ->
.trigger 'save_request', , @, options
'up_save_request', @, options
destroy: (options) ->
'destroy', @, options
if not
.trigger 'destroy_request', , @, options
?.remove @
reset: (a, b, options) ->
key for key, value of
a, b, options if a?
'reset', @, options
isEmpty: ->
is 0
class Smackbone.Collection extends Smackbone.Model
create: (object) ->
model = object.id, object
model
model.save()
model
If the receiver is a collection, then it uses the id of the objects to set the properties.
set: (key, value, options) ->
if typeof key is 'object'
array = if _.isArray key then key else [key]
attributes = {}
options = value
for o in array
id = o[] ? o.cid
if not id?
o = undefined, o, Smackbone.Model
id = o[] ? o.cid
if o instanceof Smackbone.Model
o._parent = @ if not o._parent?
attributes[id] = o
else
(attributes = {})[key] = value
super attributes, options
toJSON: ->
_.toArray super()
class Smackbone.Syncer extends Smackbone.Event
constructor: (options) ->
= options.model
.on 'fetch_request',
.on 'save_request',
.on 'destroy_request',
_onFetchRequest: (path, model, queryObject, options) =>
options = options ? {}
request =
type: 'GET'
done: (response) =>
method = if options.reset then 'reset' else 'set'
model[method] response
_.extend request, options
request, path, queryObject
_onSaveRequest: (path, model) =>
options =
type: if model.isNew() then 'POST' else 'PUT'
data: model
done: (response) =>
model.set response
options, path
_onDestroyRequest: (path, model) =>
options =
type: 'DELETE'
data: model
done: (response) =>
model.reset()
options, path
_encodeQueryObject: (queryObject) ->
array = ("#{key}=#{value}" for key, value of queryObject)
if array.length then encodeURI('?' + array.join('&')) else ''
_request: (options, path, queryObject) ->
queryString = queryObject
options.url = ( ? '') + path + queryString
if options.type is 'GET'
options.data = undefined
else
options.data = JSON.stringify options.data?.toJSON()
options.contentType = 'application/json'
'request', options
Smackbone.$.ajax(options).done options.done