tiger
Version:
A full port of Spine.js MVC framework to Titanium Mobile, with enhancements
534 lines (414 loc) • 11.6 kB
text/coffeescript
Events =
bind: (ev, callback) ->
evs = ev.split(' ')
calls = and or= {}
for name in evs
calls[name] or= []
calls[name].push(callback)
this
one: (ev, callback) ->
ev, ->
callback.apply(this, arguments)
trigger: (args...) ->
ev = args.shift()
list = and ?[ev]
return unless list
for callback in list
if callback.apply(this, args) is false
break
true
unbind: (ev, callback) ->
unless ev
= {}
return this
list = ?[ev]
return this unless list
unless callback
delete [ev]
return this
for cb, i in list when cb is callback
list = list.slice()
list.splice(i, 1)
[ev] = list
break
this
Log =
trace: true
logPrefix: '(App)'
log: (args...) ->
return unless
if then args.unshift()
console?.log?(args...)
this
moduleKeywords = ['included', 'extended']
class Module
: (obj) ->
throw new Error('include(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@::[key] = value
obj.included?.apply(this)
this
: (obj) ->
throw new Error('extend(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@[key] = value
obj.extended?.apply(this)
this
: (func) ->
=> func.apply(this, arguments)
proxy: (func) ->
=> func.apply(this, arguments)
constructor: ->
?(arguments...)
class Model extends Module
Events
: {}
: {}
: []
: (name, attributes...) ->
= name
= {}
= {}
= attributes if attributes.length
and= makeArray()
or= []
this
: -> "#{@className}(#{@attributes.join(", ")})"
: (id) ->
record = [id]
if !record and ("#{id}").match(/c-\d+/)
return
throw new Error('Unknown record') unless record
record.clone()
: (cid) ->
record = [cid]
throw new Error('Unknown record') unless record
record.clone()
: (id) ->
try
return
catch e
return false
: (values, options = {}) ->
if options.clear
= {}
= {}
records =
records = [records] unless isArray(records)
for record in records
record.id or= record.cid
[record.id] = record
[record.cid] = record
this
: (callback) ->
result = (record for id, record of when callback(record))
: (name, value) ->
for id, record of
if record[name] is value
return record.clone()
null
: (name, value) ->
(item) ->
item[name] is value
: (callback) ->
for key, value of
callback(value.clone())
: ->
: ->
record = [0]
record?.clone()
: ->
values =
record = values[values.length - 1]
record?.clone()
: ->
.length
: ->
for key, value of
delete [key]
: ->
for key, value of
[key].destroy()
: (id, atts, options) ->
.updateAttributes(atts, options)
: (atts, options) ->
record = new @(atts)
record.save(options)
: (id, options) ->
.destroy(options)
: (callbackOrParams) ->
if typeof callbackOrParams is 'function'
else
: (callbackOrParams) ->
if typeof callbackOrParams is 'function'
else
: ->
: (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)
: ->
(new this).fromForm(arguments...)
# Private
: ->
result = []
for key, value of
result.push(value)
result
: (array) ->
(value.clone() for value in array)
: 0
: (prefix = 'c-') ->
uid = prefix + ++
if uid then prefix else uid
# Instance
constructor: (atts) ->
super
atts if atts
or= .uid()
isNew: ->
not
isValid: ->
not
validate: ->
load: (atts) ->
for key, value of atts
if typeof @[key] is 'function'
@[key](value)
else
@[key] = value
this
attributes: ->
result = {}
for key in .attributes when key of this
if typeof @[key] is 'function'
result[key] = @[key]()
else
result[key] = @[key]
result.id = if
result
eql: (rec) ->
!!(rec and rec.constructor is and
(rec.cid is ) or (rec.id and rec.id is ))
save: (options = {}) ->
unless options.validate is false
error =
if error
return false
record = if then else
record
updateAttribute: (name, value, options) ->
@[name] = value
updateAttributes: (atts, options) ->
changeID: (id) ->
records = .records
records[id] = records[]
delete records[]
= id
destroy: (options = {}) ->
delete .records[]
delete .crecords[]
= true
this
dup: (newRecord) ->
newAtts =
if newRecord is false
newAtts.cid =
else
delete newAtts.id
new
clone: ->
createObject(this)
reload: ->
return this if
original = .find()
original
toJSON: ->
toString: ->
"<#{@constructor.className} (#{JSON.stringify(this)})>"
fromForm: (form) ->
result = {}
for key in $(form).serializeArray()
result[key.name] = key.value
exists: ->
&& of .records
# Private
update: (options) ->
records = .records
records[].load
clone = records[].clone()
clone.trigger('update', options)
clone.trigger('change', 'update', options)
clone
create: (options) ->
= unless
record =
.records[] = record
.crecords[] = record
clone = record.clone()
clone.trigger('create', options)
clone.trigger('change', 'create', options)
clone
bind: (events, callback) ->
.bind events, binder = (record) =>
if record &&
callback.apply(this, arguments)
.bind 'unbind', unbinder = (record) =>
if record &&
.unbind(events, binder)
.unbind('unbind', unbinder)
binder
one: (events, callback) ->
binder = events, =>
.unbind(events, binder)
callback.apply(this, arguments)
trigger: (args...) ->
args.splice(1, 0, this)
.trigger(args...)
unbind: ->
class Controller extends Module
Events
Log
eventSplitter: /^(\S+)\s*(.*)$/
tag: 'div'
constructor: (options) ->
= options
for key, value of
@[key] = value
= document.createElement() unless
= $()
@$el =
.addClass() if
.attr() if
= .events unless
= .elements unless
if
if
super
release: =>
'release'
.remove()
$: (selector) -> $(selector, )
delegateEvents: (events) ->
for key, method of events
if typeof(method) is 'function'
# Always return true from event handlers
method = do (method) => =>
method.apply(this, arguments)
true
else
unless @[method]
throw new Error("#{method} doesn't exist")
method = do (method) => =>
@[method].apply(this, arguments)
true
match = key.match()
eventName = match[1]
selector = match[2]
if selector is ''
.bind(eventName, method)
else
.delegate(selector, eventName, method)
refreshElements: ->
for key, value of
@[value] = @$(key)
delay: (func, timeout) ->
setTimeout(, timeout || 0)
html: (element) ->
.html(element.el or element)
append: (elements...) ->
elements = (e.el or e for e in elements)
.append(elements...)
appendTo: (element) ->
.appendTo(element.el or element)
prepend: (elements...) ->
elements = (e.el or e for e in elements)
.prepend(elements...)
replace: (element) ->
[previous, ] = [, $(element.el or element)]
previous.replaceWith()
# Utilities & Shims
$ = window?.jQuery or window?.Zepto or (element) -> element
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)
# Globals
Spine = = {}
module?.exports = Spine
Spine.version = '1.0.8'
Spine.isArray = isArray
Spine.isBlank = isBlank
Spine.$ = $
Spine.Events = Events
Spine.Log = Log
Spine.Module = Module
Spine.Controller = Controller
Spine.Model = Model
# Global events
Module.extend.call(Spine, Events)
# JavaScript compatability
Module.create = Module.sub =
Controller.create = Controller.sub =
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
Spine.Class = Module