Skip to content

Instantly share code, notes, and snippets.

@maccman
Created June 2, 2011 18:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save maccman/1004987 to your computer and use it in GitHub Desktop.
Save maccman/1004987 to your computer and use it in GitHub Desktop.
unless typeof exports is "undefined"
Spine = exports
else
Spine = @Spine = {}
Spine.version = "0.0.4"
$ = Spine.$ = @jQuery || @Zepto || -> arguments[0]
makeArray = Spine.makeArray = (args) ->
Array.prototype.slice.call(args, 0)
isArray = Spine.isArray = (value) ->
Object.prototype.toString.call(value) == "[object Array]"
Events = Spine.Events =
bind: (ev, callback) ->
evs = ev.split(" ")
calls = @_callbacks || @_callbacks = {}
for name in evs
@_callbacks[name] ?= []
@_callbacks[name].push(callback)
@
trigger: (args...) ->
ev = args.shift()
list = @_callbacks?[ev]
return false unless list
for callback in list
if callback.apply(this, args) is false
break
true
unbind: (ev, callback) ->
unless ev
@_callbacks = {}
return @
list = @_callbacks?[ev]
return @ unless list
unless callback
delete @_callbacks[ev]
return @
for cb, i in list
if callback == cb
list.splice(i, 1)
break
@
Log = Spine.Log =
trace: true
logPrefix: "(App)"
log: (args...) ->
return unless @trace
return if typeof console == "undefined"
if @logPrefix then args.unshift(@logPrefix)
console.log.apply(console, args)
@
# Classes (or prototypial inheritors)
unless typeof Object.create is "function"
Object.create = (o) ->
F = ->
F.prototype = o
new F()
moduleKeywords = ["included", "extended"]
Class = Spine.Class =
inherited: ->
created: ->
prototype:
initialize: ->
init: ->
create: (include, extend) ->
object = Object.create(@)
object.parent = @
object:: = object.fn = Object.create(@::)
object.include(include) if include
object.extend(extend) if extend
object.created()
@inherited(object)
object
init: ->
instance = Object.create(@::)
instance.parent = @
instance.initialize.apply(instance, arguments)
instance.init.apply(instance, arguments)
instance
proxy: (func) ->
=> func.apply(this, arguments)
proxyAll: (names...) ->
@[name] = @proxy(@[name]) for name in names
include: (obj) ->
for key, value of obj
unless key in moduleKeywords
@::[key] = value
included = obj.included
included.apply(this) if included
@
extend: (obj) ->
for key, value of obj
unless key in moduleKeywords
@[key] = value
extended = obj.extended
extended.apply(this) if extended
@
Class::proxy = Class.proxy
Class::proxyAll = Class.proxyAll
Class.inst = Class.init
Class.sub = Class.create
Spine.guid = `function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}).toUpperCase();
}`
Model = Spine.Model = Class.create()
Model.extend(Events)
Model.extend
setup: (name, atts) ->
model = Model.sub()
model.name = name if name
model.attributes = atts if atts
model
created: (sub) ->
@records = {}
if @attributes then makeArray(@attributes)
else @attributes = []
find: (id) ->
record = @records[id]
throw("Unknown record") unless record
record.clone()
exists: (id) ->
try
return @find(id)
catch e
return false
refresh: (values) ->
values = @fromJSON(values)
@records = {}
for record in values
record.newRecord = false
@records[record.id] = record
@trigger("refresh")
@
select: (callback) ->
result = (record for id, record of @records when callback(record))
@cloneArray(result)
findByAttribute: (name, value) ->
for id, record of @records
if record[name] == value
return record.clone()
null
findAllByAttribute: (name, value) ->
@select (item) ->
item[name] == value
each: (callback) ->
for key, value of @records
callback(value)
all: ->
@cloneArray(@recordsValues())
first: ->
record = @recordsValues()[0]
record?.clone()
last: ->
values = @recordsValues()
record = values[values.length - 1]
record?.clone()
count: ->
@recordsValues().length
deleteAll: ->
for key, value of @records
delete @records[key]
destroyAll: () ->
for key, value of @records
@records[key].destroy()
update: (id, atts) ->
@find(id).updateAttributes(atts)
create: (atts) ->
record = @init(atts)
record.save()
destroy: (id) ->
@find(id).destroy()
sync: (callback) ->
@bind("change", callback)
fetch: (callbackOrParams) ->
if typeof(callbackOrParams) == "function"
@bind("fetch", callbackOrParams)
else
@trigger("fetch", callbackOrParams)
toJSON: ->
@recordsValues()
fromJSON: (objects) ->
return unless objects
if typeof objects == "string"
objects = JSON.parse(objects)
if isArray(objects)
return (@init(value) for value in objects)
else
@init(objects)
# Private
recordsValues: ->
result = []
for key, value of @records
result.push(value)
result
cloneArray: (array) ->
result = []
result.push value.clone() for value in array
result
Model.include
model: true
newRecord: true
init: (atts) ->
@load atts if atts
@trigger("init", this)
isNew: () ->
@newRecord
isValid: () ->
not @validate()
validate: ->
load: (atts) ->
for key, value of atts
@[key] = value
attributes: ->
result = {}
result[key] = @[key] for key in @parent.attributes
result.id = @id
result
eql: (rec) ->
rec && rec.id == @id && rec.parent == @parent
save: ->
error = @validate()
if error
@trigger("error", @, error)
return false
@trigger("beforeSave", @)
if @newRecord then @create() else @update()
@trigger("save", @)
return @
updateAttribute: (name, value) ->
@[name] = value
@save()
updateAttributes: (atts) ->
@load(atts)
@save()
destroy: ->
@trigger("beforeDestroy", @)
delete @parent.records[@id]
@destroyed = true
@trigger("destroy", @)
@trigger("change", @, "destroy")
dup: ->
result = @parent.init(@attributes())
result.newRecord = @newRecord
result
clone: ->
Object.create(@)
reload: ->
return @ if @newRecord
original = @parent.find(@id)
@load(original.attributes())
return original
toJSON: ->
@attributes()
exists: ->
@id && @id of @parent.records
# Private
update: ->
@trigger("beforeUpdate", @)
records = @parent.records
records[@id].load @attributes()
clone = records[@id].clone()
@trigger("update", clone)
@trigger("change", clone, "update")
create: ->
@trigger("beforeCreate", @)
@id = Spine.guid() unless @id
@newRecord = false
records = @parent.records
records[@id] = @dup()
clone = records[@id].clone()
@trigger("create", clone)
@trigger("change", clone, "create")
bind: (events, callback) ->
@parent.bind events, (record) =>
if record && @eql(record)
callback.apply(@, arguments)
trigger: ->
@parent.trigger.apply(@parent, arguments)
# Controllers
eventSplitter = /^(\w+)\s*(.*)$/
Controller = Spine.Controller = Class.create
tag: "div"
initialize: (options) ->
@options = options
for key, value of @options
@[key] = value
@el = document.createElement(@tag) unless @el
@el = $(@el)
@events = @parent.events unless @events
@elements = @parent.elements unless @elements
@delegateEvents() if @events
@refreshElements() if @elements
@proxyAll.apply(@, @proxied) if @proxied
$: (selector) ->
$(selector, @el)
delegateEvents: ->
for key of @events
methodName = @events[key]
method = @proxy(@[methodName])
match = key.match(eventSplitter)
eventName = match[1]
selector = match[2]
if selector == ''
@el.bind(eventName, method)
else
@el.delegate(selector, eventName, method)
refreshElements: ->
for key, value of @elements
@[value] = @$(key)
delay: (func, timeout) ->
setTimeout(@proxy(func), timeout || 0)
Controller.include(Events)
Controller.include(Log)
Spine.App = Class.create()
Spine.App.extend(Events)
Controller.fn.App = Spine.App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment