Updated to spine 1.3.0.
This commit is contained in:
parent
200952f691
commit
0f9aeb62df
37 changed files with 1509 additions and 2515 deletions
|
@ -40,13 +40,13 @@ class App.ControllerGenericNew extends App.ControllerModal
|
||||||
# save object
|
# save object
|
||||||
ui = @
|
ui = @
|
||||||
object.save(
|
object.save(
|
||||||
success: ->
|
done: ->
|
||||||
if ui.callback
|
if ui.callback
|
||||||
item = App[ ui.genericObject ].retrieve(@id)
|
item = App[ ui.genericObject ].retrieve(@id)
|
||||||
ui.callback( item )
|
ui.callback( item )
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
|
|
||||||
error: ->
|
fail: ->
|
||||||
ui.log 'errors'
|
ui.log 'errors'
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
)
|
)
|
||||||
|
@ -87,13 +87,13 @@ class App.ControllerGenericEdit extends App.ControllerModal
|
||||||
# save object
|
# save object
|
||||||
ui = @
|
ui = @
|
||||||
@item.save(
|
@item.save(
|
||||||
success: ->
|
done: ->
|
||||||
if ui.callback
|
if ui.callback
|
||||||
item = App[ ui.genericObject ].retrieve(@id)
|
item = App[ ui.genericObject ].retrieve(@id)
|
||||||
ui.callback( item )
|
ui.callback( item )
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
|
|
||||||
error: =>
|
fail: =>
|
||||||
ui.log 'errors'
|
ui.log 'errors'
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
)
|
)
|
||||||
|
@ -170,6 +170,7 @@ class App.ControllerGenericIndex extends App.Controller
|
||||||
objects: objects
|
objects: objects
|
||||||
overview: overview
|
overview: overview
|
||||||
attributes: attributes
|
attributes: attributes
|
||||||
|
groupBy: 'state'
|
||||||
)
|
)
|
||||||
|
|
||||||
binds = {}
|
binds = {}
|
||||||
|
|
|
@ -125,9 +125,9 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
||||||
|
|
||||||
# save object
|
# save object
|
||||||
object.save(
|
object.save(
|
||||||
success: =>
|
done: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
error: =>
|
fail: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -214,9 +214,9 @@ class App.ChannelEmailAddressEdit extends App.ControllerModal
|
||||||
|
|
||||||
# save object
|
# save object
|
||||||
object.save(
|
object.save(
|
||||||
success: =>
|
done: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
error: =>
|
fail: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -301,9 +301,9 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
||||||
|
|
||||||
# save object
|
# save object
|
||||||
object.save(
|
object.save(
|
||||||
success: =>
|
done: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
error: =>
|
fail: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -425,9 +425,9 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
|
||||||
|
|
||||||
# save object
|
# save object
|
||||||
object.save(
|
object.save(
|
||||||
success: =>
|
done: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
error: =>
|
fail: =>
|
||||||
@modalHide()
|
@modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -260,7 +260,7 @@ class Settings extends App.ControllerModal
|
||||||
@overview.view['d'] = params['attributes']
|
@overview.view['d'] = params['attributes']
|
||||||
|
|
||||||
@overview.save(
|
@overview.save(
|
||||||
success: =>
|
done: =>
|
||||||
if @reload_needed
|
if @reload_needed
|
||||||
@overview.trigger('local:refetch')
|
@overview.trigger('local:refetch')
|
||||||
else
|
else
|
||||||
|
|
|
@ -75,7 +75,7 @@ class App.SettingsAreaItem extends App.Controller
|
||||||
@setting['state'] = state
|
@setting['state'] = state
|
||||||
ui = @
|
ui = @
|
||||||
@setting.save(
|
@setting.save(
|
||||||
success: =>
|
done: =>
|
||||||
|
|
||||||
App.Event.trigger 'notify', {
|
App.Event.trigger 'notify', {
|
||||||
type: 'success'
|
type: 'success'
|
||||||
|
|
|
@ -282,7 +282,7 @@ class App.TicketCreate extends App.Controller
|
||||||
@formDisable(e)
|
@formDisable(e)
|
||||||
ui = @
|
ui = @
|
||||||
object.save(
|
object.save(
|
||||||
success: ->
|
done: ->
|
||||||
|
|
||||||
# notify UI
|
# notify UI
|
||||||
ui.notify
|
ui.notify
|
||||||
|
@ -306,8 +306,7 @@ class App.TicketCreate extends App.Controller
|
||||||
# if not, show start screen
|
# if not, show start screen
|
||||||
ui.navigate "#"
|
ui.navigate "#"
|
||||||
|
|
||||||
|
fail: ->
|
||||||
error: ->
|
|
||||||
ui.log 'save failed!'
|
ui.log 'save failed!'
|
||||||
ui.formEnable(e)
|
ui.formEnable(e)
|
||||||
)
|
)
|
||||||
|
@ -357,7 +356,7 @@ class UserNew extends App.ControllerModal
|
||||||
# save user
|
# save user
|
||||||
ui = @
|
ui = @
|
||||||
user.save(
|
user.save(
|
||||||
success: ->
|
done: ->
|
||||||
|
|
||||||
# force to reload object
|
# force to reload object
|
||||||
callbackReload = (user) ->
|
callbackReload = (user) ->
|
||||||
|
@ -370,7 +369,7 @@ class UserNew extends App.ControllerModal
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
App.User.retrieve( @id, callbackReload , true )
|
App.User.retrieve( @id, callbackReload , true )
|
||||||
|
|
||||||
error: ->
|
fail: ->
|
||||||
ui.modalHide()
|
ui.modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -178,12 +178,12 @@ class Index extends App.ControllerContent
|
||||||
@formDisable(e)
|
@formDisable(e)
|
||||||
ui = @
|
ui = @
|
||||||
object.save(
|
object.save(
|
||||||
success: ->
|
done: ->
|
||||||
|
|
||||||
# redirect to zoom
|
# redirect to zoom
|
||||||
ui.navigate '#ticket/zoom/' + this.id
|
ui.navigate '#ticket/zoom/' + this.id
|
||||||
|
|
||||||
error: ->
|
fail: ->
|
||||||
ui.log 'CustomerTicketCreate', 'error', 'can not create'
|
ui.log 'CustomerTicketCreate', 'error', 'can not create'
|
||||||
ui.formEnable(e)
|
ui.formEnable(e)
|
||||||
)
|
)
|
||||||
|
|
|
@ -94,7 +94,7 @@ class Index extends App.ControllerContent
|
||||||
|
|
||||||
# save user
|
# save user
|
||||||
user.save(
|
user.save(
|
||||||
success: (r) =>
|
done: (r) =>
|
||||||
|
|
||||||
if @master_user
|
if @master_user
|
||||||
@master_user = false
|
@master_user = false
|
||||||
|
@ -123,7 +123,7 @@ class Index extends App.ControllerContent
|
||||||
|
|
||||||
# rerender page
|
# rerender page
|
||||||
@render()
|
@render()
|
||||||
error: (data) ->
|
fail: (data) ->
|
||||||
|
|
||||||
App.Event.trigger 'notify', {
|
App.Event.trigger 'notify', {
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
|
|
@ -62,7 +62,7 @@ class Index extends App.ControllerContent
|
||||||
|
|
||||||
# save user
|
# save user
|
||||||
user.save(
|
user.save(
|
||||||
success: (r) =>
|
done: (r) =>
|
||||||
App.Auth.login(
|
App.Auth.login(
|
||||||
data:
|
data:
|
||||||
username: @params.login
|
username: @params.login
|
||||||
|
@ -70,7 +70,7 @@ class Index extends App.ControllerContent
|
||||||
success: @success
|
success: @success
|
||||||
error: @error
|
error: @error
|
||||||
)
|
)
|
||||||
# error: =>
|
# fail: =>
|
||||||
# @modalHide()
|
# @modalHide()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -311,7 +311,7 @@ class Table extends App.ControllerContent
|
||||||
|
|
||||||
ticket.load(ticket_update)
|
ticket.load(ticket_update)
|
||||||
ticket.save(
|
ticket.save(
|
||||||
success: (r) =>
|
done: (r) =>
|
||||||
@bulk_count_index++
|
@bulk_count_index++
|
||||||
|
|
||||||
# refresh view after all tickets are proceeded
|
# refresh view after all tickets are proceeded
|
||||||
|
@ -501,7 +501,7 @@ class Settings extends App.ControllerModal
|
||||||
@overview.view[@view_mode] = params['attributes']
|
@overview.view[@view_mode] = params['attributes']
|
||||||
|
|
||||||
@overview.save(
|
@overview.save(
|
||||||
success: =>
|
done: =>
|
||||||
if @reload_needed
|
if @reload_needed
|
||||||
@overview.trigger('local:refetch')
|
@overview.trigger('local:refetch')
|
||||||
else
|
else
|
||||||
|
|
|
@ -530,17 +530,17 @@ class Edit extends App.Controller
|
||||||
return
|
return
|
||||||
|
|
||||||
ticket.save(
|
ticket.save(
|
||||||
success: (r) =>
|
done: (r) =>
|
||||||
|
|
||||||
# reset form after save
|
# reset form after save
|
||||||
if article
|
if article
|
||||||
article.save(
|
article.save(
|
||||||
success: (r) =>
|
done: (r) =>
|
||||||
@ui.fetch( ticket.id, true )
|
@ui.fetch( ticket.id, true )
|
||||||
|
|
||||||
# reset form after save
|
# reset form after save
|
||||||
App.TaskManager.update( @task_key, { 'state': {} })
|
App.TaskManager.update( @task_key, { 'state': {} })
|
||||||
error: (r) =>
|
fail: (r) =>
|
||||||
@log 'error', 'update article', r
|
@log 'error', 'update article', r
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
|
@ -80,10 +80,10 @@ class App.WidgetTemplate extends App.ControllerDrox
|
||||||
else
|
else
|
||||||
ui = @
|
ui = @
|
||||||
template.save(
|
template.save(
|
||||||
success: ->
|
done: ->
|
||||||
ui.template_id = @.id
|
ui.template_id = @.id
|
||||||
ui.render()
|
ui.render()
|
||||||
|
|
||||||
error: =>
|
fail: =>
|
||||||
@log 'error', 'save failed!'
|
@log 'error', 'save failed!'
|
||||||
)
|
)
|
||||||
|
|
|
@ -295,12 +295,12 @@ class App.WidgetTextModuleOld extends App.Controller
|
||||||
else
|
else
|
||||||
ui = @
|
ui = @
|
||||||
text_module.save(
|
text_module.save(
|
||||||
success: ->
|
done: ->
|
||||||
ui.el.find('#text_module_name').val('')
|
ui.el.find('#text_module_name').val('')
|
||||||
ui.renderTable()
|
ui.renderTable()
|
||||||
ui.log 'save success!'
|
ui.log 'save success!'
|
||||||
|
|
||||||
error: ->
|
fail: ->
|
||||||
ui.log 'save failed!'
|
ui.log 'save failed!'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ class _taskManagerSingleton extends App.Controller
|
||||||
# save new task and update task collection
|
# save new task and update task collection
|
||||||
ui = @
|
ui = @
|
||||||
task.save(
|
task.save(
|
||||||
success: ->
|
done: ->
|
||||||
for taskPosition of ui.allTasks
|
for taskPosition of ui.allTasks
|
||||||
if ui.allTasks[taskPosition] && ui.allTasks[taskPosition]['key'] is @key
|
if ui.allTasks[taskPosition] && ui.allTasks[taskPosition]['key'] is @key
|
||||||
task = @attributes()
|
task = @attributes()
|
||||||
|
@ -338,10 +338,10 @@ class _taskManagerSingleton extends App.Controller
|
||||||
if taskUpdate.isOnline()
|
if taskUpdate.isOnline()
|
||||||
ui = @
|
ui = @
|
||||||
taskUpdate.save(
|
taskUpdate.save(
|
||||||
success: ->
|
done: ->
|
||||||
if ui.tasksToUpdate[ @key ] is 'inProgress'
|
if ui.tasksToUpdate[ @key ] is 'inProgress'
|
||||||
delete ui.tasksToUpdate[ @key ]
|
delete ui.tasksToUpdate[ @key ]
|
||||||
error: ->
|
fail: ->
|
||||||
ui.log 'error', "can't update task", @
|
ui.log 'error', "can't update task", @
|
||||||
if ui.tasksToUpdate[ @key ] is 'inProgress'
|
if ui.tasksToUpdate[ @key ] is 'inProgress'
|
||||||
delete ui.tasksToUpdate[ @key ]
|
delete ui.tasksToUpdate[ @key ]
|
||||||
|
|
275
app/assets/javascripts/app/lib/spine/ajax.coffee
Executable file
275
app/assets/javascripts/app/lib/spine/ajax.coffee
Executable file
|
@ -0,0 +1,275 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
$ = Spine.$
|
||||||
|
Model = Spine.Model
|
||||||
|
Queue = $({})
|
||||||
|
|
||||||
|
Ajax =
|
||||||
|
getURL: (object) ->
|
||||||
|
if object.className?
|
||||||
|
@generateURL(object)
|
||||||
|
else
|
||||||
|
@generateURL(object, encodeURIComponent(object.id))
|
||||||
|
|
||||||
|
getCollectionURL: (object) ->
|
||||||
|
@generateURL(object)
|
||||||
|
|
||||||
|
getScope: (object) ->
|
||||||
|
object.scope?() or object.scope
|
||||||
|
|
||||||
|
getCollection: (object) ->
|
||||||
|
if object.url isnt object.generateURL
|
||||||
|
if typeof object.url is 'function'
|
||||||
|
object.url()
|
||||||
|
else
|
||||||
|
object.url
|
||||||
|
else if object.className?
|
||||||
|
object.className.toLowerCase() + 's'
|
||||||
|
|
||||||
|
generateURL: (object, args...) ->
|
||||||
|
collection = Ajax.getCollection(object) or Ajax.getCollection(object.constructor)
|
||||||
|
scope = Ajax.getScope(object) or Ajax.getScope(object.constructor)
|
||||||
|
args.unshift(collection)
|
||||||
|
args.unshift(scope)
|
||||||
|
# construct and clean url
|
||||||
|
path = args.join('/')
|
||||||
|
path = path.replace /(\/\/)/g, "/"
|
||||||
|
path = path.replace /^\/|\/$/g, ""
|
||||||
|
# handle relative urls vs those that use a host
|
||||||
|
if path.indexOf("../") isnt 0
|
||||||
|
Model.host + "/" + path
|
||||||
|
else
|
||||||
|
path
|
||||||
|
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
disable: (callback) ->
|
||||||
|
if @enabled
|
||||||
|
@enabled = false
|
||||||
|
try
|
||||||
|
do callback
|
||||||
|
catch e
|
||||||
|
throw e
|
||||||
|
finally
|
||||||
|
@enabled = true
|
||||||
|
else
|
||||||
|
do callback
|
||||||
|
|
||||||
|
queue: (request) ->
|
||||||
|
if request then Queue.queue(request) else Queue.queue()
|
||||||
|
|
||||||
|
clearQueue: ->
|
||||||
|
@queue []
|
||||||
|
|
||||||
|
class Base
|
||||||
|
defaults:
|
||||||
|
dataType: 'json'
|
||||||
|
processData: false
|
||||||
|
headers: {'X-Requested-With': 'XMLHttpRequest'}
|
||||||
|
|
||||||
|
queue: Ajax.queue
|
||||||
|
|
||||||
|
ajax: (params, defaults) ->
|
||||||
|
$.ajax @ajaxSettings(params, defaults)
|
||||||
|
|
||||||
|
ajaxQueue: (params, defaults, record) ->
|
||||||
|
jqXHR = null
|
||||||
|
deferred = $.Deferred()
|
||||||
|
promise = deferred.promise()
|
||||||
|
return promise unless Ajax.enabled
|
||||||
|
settings = @ajaxSettings(params, defaults)
|
||||||
|
# prefer setting if exists else default is to parallelize 'GET' requests
|
||||||
|
parallel = if settings.parallel isnt undefined then settings.parallel else (settings.type is 'GET')
|
||||||
|
request = (next) ->
|
||||||
|
if record?.id?
|
||||||
|
# for existing singleton, model id may have been updated
|
||||||
|
# after request has been queued
|
||||||
|
settings.url ?= Ajax.getURL(record)
|
||||||
|
settings.data?.id = record.id
|
||||||
|
# 2 reasons not to stringify: if already a string, or if intend to have ajax processData
|
||||||
|
if typeof settings.data isnt 'string' and settings.processData isnt true
|
||||||
|
settings.data = JSON.stringify(settings.data)
|
||||||
|
jqXHR = $.ajax(settings)
|
||||||
|
.done(deferred.resolve)
|
||||||
|
.fail(deferred.reject)
|
||||||
|
.then(next, next)
|
||||||
|
if parallel
|
||||||
|
Queue.dequeue()
|
||||||
|
|
||||||
|
promise.abort = (statusText) ->
|
||||||
|
return jqXHR.abort(statusText) if jqXHR
|
||||||
|
index = $.inArray(request, @queue())
|
||||||
|
@queue().splice(index, 1) if index > -1
|
||||||
|
deferred.rejectWith(
|
||||||
|
settings.context or settings,
|
||||||
|
[promise, statusText, '']
|
||||||
|
)
|
||||||
|
promise
|
||||||
|
|
||||||
|
@queue request
|
||||||
|
promise
|
||||||
|
|
||||||
|
ajaxSettings: (params, defaults) ->
|
||||||
|
$.extend({}, @defaults, defaults, params)
|
||||||
|
|
||||||
|
class Collection extends Base
|
||||||
|
constructor: (@model) ->
|
||||||
|
|
||||||
|
find: (id, params, options = {}) ->
|
||||||
|
record = new @model(id: id)
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'GET'
|
||||||
|
url: options.url or Ajax.getURL(record)
|
||||||
|
parallel: options.parallel
|
||||||
|
}
|
||||||
|
).done(@recordsResponse)
|
||||||
|
.fail(@failResponse)
|
||||||
|
|
||||||
|
all: (params, options = {}) ->
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'GET'
|
||||||
|
url: options.url or Ajax.getURL(@model)
|
||||||
|
parallel: options.parallel
|
||||||
|
}
|
||||||
|
).done(@recordsResponse)
|
||||||
|
.fail(@failResponse)
|
||||||
|
|
||||||
|
fetch: (params = {}, options = {}) ->
|
||||||
|
if id = params.id
|
||||||
|
delete params.id
|
||||||
|
@find(id, params, options).done (record) =>
|
||||||
|
@model.refresh(record, options)
|
||||||
|
else
|
||||||
|
@all(params, options).done (records) =>
|
||||||
|
@model.refresh(records, options)
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
recordsResponse: (data, status, xhr) =>
|
||||||
|
@model.trigger('ajaxSuccess', null, status, xhr)
|
||||||
|
|
||||||
|
failResponse: (xhr, statusText, error) =>
|
||||||
|
@model.trigger('ajaxError', null, xhr, statusText, error)
|
||||||
|
|
||||||
|
class Singleton extends Base
|
||||||
|
constructor: (@record) ->
|
||||||
|
@model = @record.constructor
|
||||||
|
|
||||||
|
reload: (params, options = {}) ->
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'GET'
|
||||||
|
url: options.url
|
||||||
|
parallel: options.parallel
|
||||||
|
}, @record
|
||||||
|
).done(@recordResponse(options))
|
||||||
|
.fail(@failResponse(options))
|
||||||
|
|
||||||
|
create: (params, options = {}) ->
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'POST'
|
||||||
|
contentType: 'application/json'
|
||||||
|
data: @record.toJSON()
|
||||||
|
url: options.url or Ajax.getCollectionURL(@record)
|
||||||
|
parallel: options.parallel
|
||||||
|
}
|
||||||
|
).done(@recordResponse(options))
|
||||||
|
.fail(@failResponse(options))
|
||||||
|
|
||||||
|
update: (params, options = {}) ->
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'PUT'
|
||||||
|
contentType: 'application/json'
|
||||||
|
data: @record.toJSON()
|
||||||
|
url: options.url
|
||||||
|
parallel: options.parallel
|
||||||
|
}, @record
|
||||||
|
).done(@recordResponse(options))
|
||||||
|
.fail(@failResponse(options))
|
||||||
|
|
||||||
|
destroy: (params, options = {}) ->
|
||||||
|
@ajaxQueue(
|
||||||
|
params, {
|
||||||
|
type: 'DELETE'
|
||||||
|
url: options.url
|
||||||
|
parallel: options.parallel
|
||||||
|
}, @record
|
||||||
|
).done(@recordResponse(options))
|
||||||
|
.fail(@failResponse(options))
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
recordResponse: (options = {}) =>
|
||||||
|
(data, status, xhr) =>
|
||||||
|
|
||||||
|
Ajax.disable =>
|
||||||
|
unless Spine.isBlank(data) or @record.destroyed
|
||||||
|
# ID change, need to do some shifting
|
||||||
|
if data.id and @record.id isnt data.id
|
||||||
|
@record.changeID(data.id)
|
||||||
|
# Update with latest data
|
||||||
|
@record.refresh(data)
|
||||||
|
|
||||||
|
@record.trigger('ajaxSuccess', data, status, xhr)
|
||||||
|
options.done?.apply(@record)
|
||||||
|
|
||||||
|
failResponse: (options = {}) =>
|
||||||
|
(xhr, statusText, error) =>
|
||||||
|
@record.trigger('ajaxError', xhr, statusText, error)
|
||||||
|
options.fail?.apply(@record)
|
||||||
|
|
||||||
|
# Ajax endpoint
|
||||||
|
Model.host = ''
|
||||||
|
|
||||||
|
GenerateURL =
|
||||||
|
include: (args...) ->
|
||||||
|
args.unshift(encodeURIComponent(@id))
|
||||||
|
Ajax.generateURL(@, args...)
|
||||||
|
extend: (args...) ->
|
||||||
|
Ajax.generateURL(@, args...)
|
||||||
|
|
||||||
|
Include =
|
||||||
|
ajax: -> new Singleton(this)
|
||||||
|
|
||||||
|
generateURL: GenerateURL.include
|
||||||
|
|
||||||
|
url: GenerateURL.include
|
||||||
|
|
||||||
|
Extend =
|
||||||
|
ajax: -> new Collection(this)
|
||||||
|
|
||||||
|
generateURL: GenerateURL.extend
|
||||||
|
|
||||||
|
url: GenerateURL.extend
|
||||||
|
|
||||||
|
Model.Ajax =
|
||||||
|
extended: ->
|
||||||
|
@fetch @ajaxFetch
|
||||||
|
@change @ajaxChange
|
||||||
|
@extend Extend
|
||||||
|
@include Include
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
ajaxFetch: ->
|
||||||
|
@ajax().fetch(arguments...)
|
||||||
|
|
||||||
|
ajaxChange: (record, type, options = {}) ->
|
||||||
|
return if options.ajax is false
|
||||||
|
record.ajax()[type](options.ajax, options)
|
||||||
|
|
||||||
|
Model.Ajax.Methods =
|
||||||
|
extended: ->
|
||||||
|
@extend Extend
|
||||||
|
@include Include
|
||||||
|
|
||||||
|
# Globals
|
||||||
|
Ajax.defaults = Base::defaults
|
||||||
|
Ajax.Base = Base
|
||||||
|
Ajax.Singleton = Singleton
|
||||||
|
Ajax.Collection = Collection
|
||||||
|
Spine.Ajax = Ajax
|
||||||
|
module?.exports = Ajax
|
|
@ -1,386 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var $, Ajax, Base, Collection, Extend, Include, Model, Queue, Singleton, Spine,
|
|
||||||
__slice = [].slice,
|
|
||||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
|
||||||
__hasProp = {}.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
$ = Spine.$;
|
|
||||||
|
|
||||||
Model = Spine.Model;
|
|
||||||
|
|
||||||
Queue = $({});
|
|
||||||
|
|
||||||
Ajax = {
|
|
||||||
getURL: function(object) {
|
|
||||||
return (typeof object.url === "function" ? object.url() : void 0) || object.url;
|
|
||||||
},
|
|
||||||
getCollectionURL: function(object) {
|
|
||||||
if (object) {
|
|
||||||
if (typeof object.url === "function") {
|
|
||||||
return this.generateURL(object);
|
|
||||||
} else {
|
|
||||||
return object.url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getScope: function(object) {
|
|
||||||
return (typeof object.scope === "function" ? object.scope() : void 0) || object.scope;
|
|
||||||
},
|
|
||||||
generateURL: function() {
|
|
||||||
var args, collection, object, path, scope;
|
|
||||||
object = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
|
||||||
if (object.className) {
|
|
||||||
collection = object.className.toLowerCase() + 's';
|
|
||||||
scope = Ajax.getScope(object);
|
|
||||||
} else {
|
|
||||||
if (typeof object.constructor.url === 'string') {
|
|
||||||
collection = object.constructor.url;
|
|
||||||
} else {
|
|
||||||
collection = object.constructor.className.toLowerCase() + 's';
|
|
||||||
}
|
|
||||||
scope = Ajax.getScope(object) || Ajax.getScope(object.constructor);
|
|
||||||
}
|
|
||||||
args.unshift(collection);
|
|
||||||
args.unshift(scope);
|
|
||||||
path = args.join('/');
|
|
||||||
path = path.replace(/(\/\/)/g, "/");
|
|
||||||
path = path.replace(/^\/|\/$/g, "");
|
|
||||||
if (path.indexOf("../") !== 0) {
|
|
||||||
return Model.host + "/" + path;
|
|
||||||
} else {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
disable: function(callback) {
|
|
||||||
var e;
|
|
||||||
if (this.enabled) {
|
|
||||||
this.enabled = false;
|
|
||||||
try {
|
|
||||||
return callback();
|
|
||||||
} catch (_error) {
|
|
||||||
e = _error;
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
this.enabled = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
queue: function(request) {
|
|
||||||
if (request) {
|
|
||||||
return Queue.queue(request);
|
|
||||||
} else {
|
|
||||||
return Queue.queue();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearQueue: function() {
|
|
||||||
return this.queue([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Base = (function() {
|
|
||||||
function Base() {}
|
|
||||||
|
|
||||||
Base.prototype.defaults = {
|
|
||||||
dataType: 'json',
|
|
||||||
processData: false,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Base.prototype.queue = Ajax.queue;
|
|
||||||
|
|
||||||
Base.prototype.ajax = function(params, defaults) {
|
|
||||||
return $.ajax(this.ajaxSettings(params, defaults));
|
|
||||||
};
|
|
||||||
|
|
||||||
Base.prototype.ajaxQueue = function(params, defaults, record) {
|
|
||||||
var deferred, jqXHR, promise, request, settings;
|
|
||||||
jqXHR = null;
|
|
||||||
deferred = $.Deferred();
|
|
||||||
promise = deferred.promise();
|
|
||||||
if (!Ajax.enabled) {
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
settings = this.ajaxSettings(params, defaults);
|
|
||||||
request = function(next) {
|
|
||||||
var _ref;
|
|
||||||
if ((record != null ? record.id : void 0) != null) {
|
|
||||||
if (settings.url == null) {
|
|
||||||
settings.url = Ajax.getURL(record);
|
|
||||||
}
|
|
||||||
if ((_ref = settings.data) != null) {
|
|
||||||
_ref.id = record.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof settings.data !== 'string' && settings.processData !== true) {
|
|
||||||
settings.data = JSON.stringify(settings.data);
|
|
||||||
}
|
|
||||||
return jqXHR = $.ajax(settings).done(deferred.resolve).fail(deferred.reject).then(next, next);
|
|
||||||
};
|
|
||||||
promise.abort = function(statusText) {
|
|
||||||
var index;
|
|
||||||
if (jqXHR) {
|
|
||||||
return jqXHR.abort(statusText);
|
|
||||||
}
|
|
||||||
index = $.inArray(request, this.queue());
|
|
||||||
if (index > -1) {
|
|
||||||
this.queue().splice(index, 1);
|
|
||||||
}
|
|
||||||
deferred.rejectWith(settings.context || settings, [promise, statusText, '']);
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
this.queue(request);
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
Base.prototype.ajaxSettings = function(params, defaults) {
|
|
||||||
return $.extend({}, this.defaults, defaults, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Base;
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
||||||
Collection = (function(_super) {
|
|
||||||
__extends(Collection, _super);
|
|
||||||
|
|
||||||
function Collection(model) {
|
|
||||||
this.model = model;
|
|
||||||
this.failResponse = __bind(this.failResponse, this);
|
|
||||||
this.recordsResponse = __bind(this.recordsResponse, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection.prototype.find = function(id, params, options) {
|
|
||||||
var record;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
record = new this.model({
|
|
||||||
id: id
|
|
||||||
});
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'GET',
|
|
||||||
url: options.url || Ajax.getURL(record)
|
|
||||||
}).done(this.recordsResponse).fail(this.failResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.all = function(params, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'GET',
|
|
||||||
url: options.url || Ajax.getURL(this.model)
|
|
||||||
}).done(this.recordsResponse).fail(this.failResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.fetch = function(params, options) {
|
|
||||||
var id,
|
|
||||||
_this = this;
|
|
||||||
if (params == null) {
|
|
||||||
params = {};
|
|
||||||
}
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
if (id = params.id) {
|
|
||||||
delete params.id;
|
|
||||||
return this.find(id, params, options).done(function(record) {
|
|
||||||
return _this.model.refresh(record, options);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return this.all(params, options).done(function(records) {
|
|
||||||
return _this.model.refresh(records, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.recordsResponse = function(data, status, xhr) {
|
|
||||||
return this.model.trigger('ajaxSuccess', null, status, xhr);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.failResponse = function(xhr, statusText, error) {
|
|
||||||
return this.model.trigger('ajaxError', null, xhr, statusText, error);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Collection;
|
|
||||||
|
|
||||||
})(Base);
|
|
||||||
|
|
||||||
Singleton = (function(_super) {
|
|
||||||
__extends(Singleton, _super);
|
|
||||||
|
|
||||||
function Singleton(record) {
|
|
||||||
this.record = record;
|
|
||||||
this.failResponse = __bind(this.failResponse, this);
|
|
||||||
this.recordResponse = __bind(this.recordResponse, this);
|
|
||||||
this.model = this.record.constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
Singleton.prototype.reload = function(params, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'GET',
|
|
||||||
url: options.url
|
|
||||||
}, this.record).done(this.recordResponse(options)).fail(this.failResponse(options));
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.create = function(params, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: this.record.toJSON(),
|
|
||||||
url: options.url || Ajax.getCollectionURL(this.record)
|
|
||||||
}).done(this.recordResponse(options)).fail(this.failResponse(options));
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.update = function(params, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'PUT',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: this.record.toJSON(),
|
|
||||||
url: options.url
|
|
||||||
}, this.record).done(this.recordResponse(options)).fail(this.failResponse(options));
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.destroy = function(params, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return this.ajaxQueue(params, {
|
|
||||||
type: 'DELETE',
|
|
||||||
url: options.url
|
|
||||||
}, this.record).done(this.recordResponse(options)).fail(this.failResponse(options));
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.recordResponse = function(options) {
|
|
||||||
var _this = this;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return function(data, status, xhr) {
|
|
||||||
var _ref, _ref1;
|
|
||||||
Ajax.disable(function() {
|
|
||||||
if (!(Spine.isBlank(data) || _this.record.destroyed)) {
|
|
||||||
if (data.id && _this.record.id !== data.id) {
|
|
||||||
_this.record.changeID(data.id);
|
|
||||||
}
|
|
||||||
return _this.record.refresh(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_this.record.trigger('ajaxSuccess', data, status, xhr);
|
|
||||||
if ((_ref = options.success) != null) {
|
|
||||||
_ref.apply(_this.record);
|
|
||||||
}
|
|
||||||
return (_ref1 = options.done) != null ? _ref1.apply(_this.record) : void 0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.failResponse = function(options) {
|
|
||||||
var _this = this;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
return function(xhr, statusText, error) {
|
|
||||||
var _ref, _ref1;
|
|
||||||
_this.record.trigger('ajaxError', xhr, statusText, error);
|
|
||||||
if ((_ref = options.error) != null) {
|
|
||||||
_ref.apply(_this.record);
|
|
||||||
}
|
|
||||||
return (_ref1 = options.fail) != null ? _ref1.apply(_this.record) : void 0;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return Singleton;
|
|
||||||
|
|
||||||
})(Base);
|
|
||||||
|
|
||||||
Model.host = '';
|
|
||||||
|
|
||||||
Include = {
|
|
||||||
ajax: function() {
|
|
||||||
return new Singleton(this);
|
|
||||||
},
|
|
||||||
url: function() {
|
|
||||||
var args;
|
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
args.unshift(encodeURIComponent(this.id));
|
|
||||||
return Ajax.generateURL.apply(Ajax, [this].concat(__slice.call(args)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Extend = {
|
|
||||||
ajax: function() {
|
|
||||||
return new Collection(this);
|
|
||||||
},
|
|
||||||
url: function() {
|
|
||||||
var args;
|
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
return Ajax.generateURL.apply(Ajax, [this].concat(__slice.call(args)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Model.Ajax = {
|
|
||||||
extended: function() {
|
|
||||||
this.fetch(this.ajaxFetch);
|
|
||||||
this.change(this.ajaxChange);
|
|
||||||
this.extend(Extend);
|
|
||||||
return this.include(Include);
|
|
||||||
},
|
|
||||||
ajaxFetch: function() {
|
|
||||||
var _ref;
|
|
||||||
return (_ref = this.ajax()).fetch.apply(_ref, arguments);
|
|
||||||
},
|
|
||||||
ajaxChange: function(record, type, options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
if (options.ajax === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return record.ajax()[type](options.ajax, options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Model.Ajax.Methods = {
|
|
||||||
extended: function() {
|
|
||||||
this.extend(Extend);
|
|
||||||
return this.include(Include);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ajax.defaults = Base.prototype.defaults;
|
|
||||||
|
|
||||||
Ajax.Base = Base;
|
|
||||||
|
|
||||||
Ajax.Singleton = Singleton;
|
|
||||||
|
|
||||||
Ajax.Collection = Collection;
|
|
||||||
|
|
||||||
Spine.Ajax = Ajax;
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Ajax;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=ajax.map
|
|
||||||
*/
|
|
File diff suppressed because one or more lines are too long
95
app/assets/javascripts/app/lib/spine/bindings.coffee
Executable file
95
app/assets/javascripts/app/lib/spine/bindings.coffee
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
BindingsClass =
|
||||||
|
|
||||||
|
model: 'model'
|
||||||
|
|
||||||
|
bindings: {}
|
||||||
|
|
||||||
|
|
||||||
|
class ValueSetter
|
||||||
|
|
||||||
|
constructor: (@context) ->
|
||||||
|
|
||||||
|
setValue: (element, value, setter) ->
|
||||||
|
if typeof setter is 'string'
|
||||||
|
setter = @context.proxy(@context[setter])
|
||||||
|
setter = setter || (e, v) => @_standardSetter e, v
|
||||||
|
setter element, value
|
||||||
|
|
||||||
|
getValue: (element, getter) ->
|
||||||
|
if typeof getter is 'string'
|
||||||
|
getter = @context.proxy(@context[getter])
|
||||||
|
getter = getter || (e, v) => @_standardGetter e, v
|
||||||
|
getter element
|
||||||
|
|
||||||
|
_standardGetter: (element) ->
|
||||||
|
self = @
|
||||||
|
self["_#{element.attr("type")}Get"]?(element) || element.val()
|
||||||
|
|
||||||
|
_standardSetter: (element, value) ->
|
||||||
|
self = @
|
||||||
|
element.each ->
|
||||||
|
el = $(this)
|
||||||
|
self["_#{el.attr("type")}Set"]?(el, value) || el.val(value)
|
||||||
|
|
||||||
|
_checkboxSet: (element, value) ->
|
||||||
|
if value
|
||||||
|
element.prop("checked", "checked")
|
||||||
|
else
|
||||||
|
element.prop("checked", "")
|
||||||
|
|
||||||
|
_checkboxGet: (element) ->
|
||||||
|
element.is(":checked")
|
||||||
|
|
||||||
|
BindingsInstance =
|
||||||
|
|
||||||
|
getModel: ->
|
||||||
|
@[@modelVar]
|
||||||
|
|
||||||
|
setModel: (model) ->
|
||||||
|
@[@modelVar] = model
|
||||||
|
|
||||||
|
walkBindings: (fn) ->
|
||||||
|
for selector, field of @bindings
|
||||||
|
fn selector, field
|
||||||
|
|
||||||
|
applyBindings: ->
|
||||||
|
@valueSetter = new ValueSetter @
|
||||||
|
@walkBindings (selector, field) =>
|
||||||
|
if not field.direction or field.direction is 'model'
|
||||||
|
@_bindModelToEl @getModel(), field, selector
|
||||||
|
if not field.direction or field.direction is 'element'
|
||||||
|
@_bindElToModel @getModel(), field, selector
|
||||||
|
|
||||||
|
_getField: (value) ->
|
||||||
|
if typeof value is 'string'
|
||||||
|
value
|
||||||
|
else
|
||||||
|
value.field
|
||||||
|
|
||||||
|
_forceModelBindings: (model) ->
|
||||||
|
@walkBindings (selector, field) =>
|
||||||
|
@valueSetter.setValue @$(selector), model[@_getField(field)], field.setter
|
||||||
|
|
||||||
|
changeBindingSource: (model) ->
|
||||||
|
@getModel().unbind 'change'
|
||||||
|
@walkBindings (selector) =>
|
||||||
|
selector = false if selector is 'self'
|
||||||
|
@el.off 'change', selector
|
||||||
|
@setModel model
|
||||||
|
@_forceModelBindings model
|
||||||
|
do @applyBindings
|
||||||
|
|
||||||
|
_bindModelToEl: (model, field, selector) ->
|
||||||
|
self = @
|
||||||
|
selector = false if selector is 'self'
|
||||||
|
@el.on 'change', selector, ->
|
||||||
|
model[self._getField(field)] = self.valueSetter.getValue $(this), field.getter
|
||||||
|
|
||||||
|
_bindElToModel: (model, field, selector) ->
|
||||||
|
model.bind 'change', =>
|
||||||
|
@valueSetter.setValue @$(selector), model[@_getField(field)], field.setter
|
||||||
|
|
||||||
|
Spine.Bindings =
|
||||||
|
extended: ->
|
||||||
|
@extend BindingsClass
|
||||||
|
@include BindingsInstance
|
47
app/assets/javascripts/app/lib/spine/list.coffee
Executable file
47
app/assets/javascripts/app/lib/spine/list.coffee
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
$ = Spine.$
|
||||||
|
|
||||||
|
class Spine.List extends Spine.Controller
|
||||||
|
events:
|
||||||
|
'click .item': 'click'
|
||||||
|
|
||||||
|
selectFirst: false
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
@bind 'change', @change
|
||||||
|
|
||||||
|
template: ->
|
||||||
|
throw Error 'Override template'
|
||||||
|
|
||||||
|
change: (item) =>
|
||||||
|
@current = item
|
||||||
|
|
||||||
|
unless @current
|
||||||
|
@children().removeClass('active')
|
||||||
|
return
|
||||||
|
|
||||||
|
@children().removeClass('active')
|
||||||
|
for item, idx in @items when item is @current
|
||||||
|
index = idx
|
||||||
|
break
|
||||||
|
|
||||||
|
$(@children().get(index)).addClass('active')
|
||||||
|
|
||||||
|
render: (items) ->
|
||||||
|
@items = items if items
|
||||||
|
@html @template(@items)
|
||||||
|
@change @current
|
||||||
|
if @selectFirst
|
||||||
|
unless @children('.active').length
|
||||||
|
@children(':first').click()
|
||||||
|
|
||||||
|
children: (sel) ->
|
||||||
|
@el.children(sel)
|
||||||
|
|
||||||
|
click: (e) ->
|
||||||
|
item = @items[$(e.currentTarget).index()]
|
||||||
|
@trigger('change', item)
|
||||||
|
true
|
||||||
|
|
||||||
|
module?.exports = Spine.List
|
|
@ -1,77 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var $, Spine,
|
|
||||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
|
||||||
__hasProp = {}.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
$ = Spine.$;
|
|
||||||
|
|
||||||
Spine.List = (function(_super) {
|
|
||||||
__extends(List, _super);
|
|
||||||
|
|
||||||
List.prototype.events = {
|
|
||||||
'click .item': 'click'
|
|
||||||
};
|
|
||||||
|
|
||||||
List.prototype.selectFirst = false;
|
|
||||||
|
|
||||||
function List() {
|
|
||||||
this.change = __bind(this.change, this);
|
|
||||||
List.__super__.constructor.apply(this, arguments);
|
|
||||||
this.bind('change', this.change);
|
|
||||||
}
|
|
||||||
|
|
||||||
List.prototype.template = function() {
|
|
||||||
throw 'Override template';
|
|
||||||
};
|
|
||||||
|
|
||||||
List.prototype.change = function(item) {
|
|
||||||
this.current = item;
|
|
||||||
if (!this.current) {
|
|
||||||
this.children().removeClass('active');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.children().removeClass('active');
|
|
||||||
return $(this.children().get(this.items.indexOf(this.current))).addClass('active');
|
|
||||||
};
|
|
||||||
|
|
||||||
List.prototype.render = function(items) {
|
|
||||||
if (items) {
|
|
||||||
this.items = items;
|
|
||||||
}
|
|
||||||
this.html(this.template(this.items));
|
|
||||||
this.change(this.current);
|
|
||||||
if (this.selectFirst) {
|
|
||||||
if (!this.children('.active').length) {
|
|
||||||
return this.children(':first').click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
List.prototype.children = function(sel) {
|
|
||||||
return this.el.children(sel);
|
|
||||||
};
|
|
||||||
|
|
||||||
List.prototype.click = function(e) {
|
|
||||||
var item;
|
|
||||||
item = this.items[$(e.currentTarget).index()];
|
|
||||||
this.trigger('change', item);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return List;
|
|
||||||
|
|
||||||
})(Spine.Controller);
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Spine.List;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=list.map
|
|
||||||
*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"file": "list.js",
|
|
||||||
"sourceRoot": "..",
|
|
||||||
"sources": [
|
|
||||||
"src/list.coffee"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"mappings": ";AAAA;CAAA,KAAA,EAAA;KAAA;;oSAAA;;CAAA,CAAA,CAAS,CAAC,CAAV,EAAmB;;CAAnB,CACA,CAAS,EAAK;;CADd,CAGM,GAAK;CACT;;CAAA,EACE,GADF;CACE,CAAe,IAAf,CAAA,MAAA;CADF,KAAA;;CAAA,EAGa,EAHb,MAGA;;CAEa,EAAA,CAAA,UAAA;CACX,sCAAA;CAAA,KAAA,GAAA,8BAAA;CAAA,CACgB,EAAf,EAAD,EAAA;CAPF,IAKa;;CALb,EASU,KAAV,CAAU;CACR,WAAM,OAAN;CAVF,IASU;;CATV,EAYQ,CAAA,EAAR,GAAS;CACP,EAAW,CAAV,EAAD,CAAA;AAEO,CAAP,GAAA,EAAA,CAAA;CACE,GAAC,IAAD,GAAA;CACA,aAAA;QAJF;CAAA,GAMC,EAAD,EAAA,GAAA;CACA,EAAE,CAAC,CAAqB,EAAN,CAAhB,KAAF;CApBF,IAYQ;;CAZR,EAsBQ,EAAA,CAAR,GAAS;CACP,GAAkB,CAAlB,CAAA;CAAA,EAAS,CAAR,CAAD,GAAA;QAAA;CAAA,GACC,CAAK,CAAN,EAAM;CADN,GAEC,EAAD,CAAA;CACA,GAAG,EAAH,KAAA;AACS,CAAP,GAAA,EAAA,EAAA,CAAO;CACJ,GAAA,CAAD,GAAA,SAAA;UAFJ;QAJM;CAtBR,IAsBQ;;CAtBR,EA8BU,KAAV,CAAW;CACR,CAAE,CAAH,CAAC,IAAD,KAAA;CA/BF,IA8BU;;CA9BV,EAiCO,EAAP,IAAQ;CACN,GAAA,MAAA;CAAA,EAAO,CAAP,CAAc,CAAd,OAAc;CAAd,CACmB,EAAlB,EAAD,CAAA,CAAA;CAFK,YAGL;CApCF,IAiCO;;CAjCP;;CADuB,IAAK;;;CAuCtB,EAAU,CAAlB,CAAuB,CAAjB,CAAN;IA1CA;CAAA"
|
|
||||||
}
|
|
17
app/assets/javascripts/app/lib/spine/local.coffee
Executable file
17
app/assets/javascripts/app/lib/spine/local.coffee
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
|
||||||
|
Spine.Model.Local =
|
||||||
|
extended: ->
|
||||||
|
@change @saveLocal
|
||||||
|
@fetch @loadLocal
|
||||||
|
|
||||||
|
saveLocal: ->
|
||||||
|
result = JSON.stringify(@)
|
||||||
|
localStorage[@className] = result
|
||||||
|
|
||||||
|
loadLocal: (options = {})->
|
||||||
|
options.clear = true unless options.hasOwnProperty('clear')
|
||||||
|
result = localStorage[@className]
|
||||||
|
@refresh(result or [], options)
|
||||||
|
|
||||||
|
module?.exports = Spine.Model.Local
|
|
@ -1,38 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var Spine;
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
Spine.Model.Local = {
|
|
||||||
extended: function() {
|
|
||||||
this.change(this.saveLocal);
|
|
||||||
return this.fetch(this.loadLocal);
|
|
||||||
},
|
|
||||||
saveLocal: function() {
|
|
||||||
var result;
|
|
||||||
result = JSON.stringify(this);
|
|
||||||
return localStorage[this.className] = result;
|
|
||||||
},
|
|
||||||
loadLocal: function(options) {
|
|
||||||
var result;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
if (!options.hasOwnProperty('clear')) {
|
|
||||||
options.clear = true;
|
|
||||||
}
|
|
||||||
result = localStorage[this.className];
|
|
||||||
return this.refresh(result || [], options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Spine.Model.Local;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=local.map
|
|
||||||
*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"file": "local.js",
|
|
||||||
"sourceRoot": "..",
|
|
||||||
"sources": [
|
|
||||||
"src/local.coffee"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"mappings": ";AAAA;CAAA,IAAA,CAAA;;CAAA,CAAA,CAAQ,CAAC,CAAT,EAAkB;;CAAlB,CAEA,CACE,EADG;CACH,CAAU,CAAA,CAAV,IAAA,CAAU;CACR,GAAC,EAAD,GAAA;CACC,GAAA,CAAD,IAAA,IAAA;CAFF,IAAU;CAAV,CAIW,CAAA,CAAX,KAAA;CACE,KAAA,IAAA;CAAA,EAAS,CAAI,EAAb,GAAS;CACI,EAAc,CAAb,KAAD,GAAA,CAAb;CANF,IAIW;CAJX,CAQW,CAAA,CAAX,GAAW,EAAX;CACE,KAAA,IAAA;;GADoB,KAAV;QACV;AAA4B,CAA5B,GAAA,EAAA,CAAmC,OAAP;CAA5B,EAAgB,CAAhB,CAAA,EAAO,CAAP;QAAA;CAAA,EACS,CAAc,EAAvB,GAAsB,GAAA;CACrB,CAAD,EAAC,EAAQ,CAAT,MAAA;CAXF,IAQW;CAXb,GAAA;;;CAgBQ,EAAU,CAAlB,CAAuB,CAAjB,CAAN;IAhBA;CAAA"
|
|
||||||
}
|
|
83
app/assets/javascripts/app/lib/spine/manager.coffee
Executable file
83
app/assets/javascripts/app/lib/spine/manager.coffee
Executable file
|
@ -0,0 +1,83 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
$ = Spine.$
|
||||||
|
|
||||||
|
class Spine.Manager extends Spine.Module
|
||||||
|
@include Spine.Events
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
@controllers = []
|
||||||
|
@bind 'change', @change
|
||||||
|
@add(arguments...)
|
||||||
|
|
||||||
|
add: (controllers...) ->
|
||||||
|
@addOne(cont) for cont in controllers
|
||||||
|
|
||||||
|
addOne: (controller) ->
|
||||||
|
controller.bind 'active', (args...) =>
|
||||||
|
@trigger('change', controller, args...)
|
||||||
|
controller.bind 'release', =>
|
||||||
|
@controllers = (c for c in @controllers when c isnt controller)
|
||||||
|
|
||||||
|
@controllers.push(controller)
|
||||||
|
|
||||||
|
deactivate: ->
|
||||||
|
@trigger('change', false, arguments...)
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
change: (current, args...) ->
|
||||||
|
for cont in @controllers when cont isnt current
|
||||||
|
cont.deactivate(args...)
|
||||||
|
|
||||||
|
current.activate(args...) if current
|
||||||
|
|
||||||
|
Spine.Controller.include
|
||||||
|
active: (args...) ->
|
||||||
|
if typeof args[0] is 'function'
|
||||||
|
@bind('active', args[0])
|
||||||
|
else
|
||||||
|
args.unshift('active')
|
||||||
|
@trigger(args...)
|
||||||
|
@
|
||||||
|
|
||||||
|
isActive: ->
|
||||||
|
@el.hasClass('active')
|
||||||
|
|
||||||
|
activate: ->
|
||||||
|
@el.addClass('active')
|
||||||
|
@
|
||||||
|
|
||||||
|
deactivate: ->
|
||||||
|
@el.removeClass('active')
|
||||||
|
@
|
||||||
|
|
||||||
|
class Spine.Stack extends Spine.Controller
|
||||||
|
controllers: {}
|
||||||
|
routes: {}
|
||||||
|
|
||||||
|
className: 'spine stack'
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
super
|
||||||
|
|
||||||
|
@manager = new Spine.Manager
|
||||||
|
|
||||||
|
for key, value of @controllers
|
||||||
|
throw Error "'@#{ key }' already assigned - choose a different name" if @[key]?
|
||||||
|
@[key] = new value(stack: @)
|
||||||
|
@add(@[key])
|
||||||
|
|
||||||
|
for key, value of @routes
|
||||||
|
do (key, value) =>
|
||||||
|
callback = value if typeof value is 'function'
|
||||||
|
callback or= => @[value].active(arguments...)
|
||||||
|
@route(key, callback)
|
||||||
|
|
||||||
|
@[@default].active() if @default
|
||||||
|
|
||||||
|
add: (controller) ->
|
||||||
|
@manager.add(controller)
|
||||||
|
@append(controller)
|
||||||
|
|
||||||
|
module?.exports = Spine.Manager
|
||||||
|
module?.exports.Stack = Spine.Stack
|
|
@ -1,162 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var $, Spine,
|
|
||||||
__hasProp = {}.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
||||||
__slice = [].slice;
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
$ = Spine.$;
|
|
||||||
|
|
||||||
Spine.Manager = (function(_super) {
|
|
||||||
__extends(Manager, _super);
|
|
||||||
|
|
||||||
Manager.include(Spine.Events);
|
|
||||||
|
|
||||||
function Manager() {
|
|
||||||
this.controllers = [];
|
|
||||||
this.bind('change', this.change);
|
|
||||||
this.add.apply(this, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
Manager.prototype.add = function() {
|
|
||||||
var cont, controllers, _i, _len, _results;
|
|
||||||
controllers = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
_results = [];
|
|
||||||
for (_i = 0, _len = controllers.length; _i < _len; _i++) {
|
|
||||||
cont = controllers[_i];
|
|
||||||
_results.push(this.addOne(cont));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
};
|
|
||||||
|
|
||||||
Manager.prototype.addOne = function(controller) {
|
|
||||||
var _this = this;
|
|
||||||
controller.bind('active', function() {
|
|
||||||
var args;
|
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
return _this.trigger.apply(_this, ['change', controller].concat(__slice.call(args)));
|
|
||||||
});
|
|
||||||
controller.bind('release', function() {
|
|
||||||
return _this.controllers.splice(_this.controllers.indexOf(controller), 1);
|
|
||||||
});
|
|
||||||
return this.controllers.push(controller);
|
|
||||||
};
|
|
||||||
|
|
||||||
Manager.prototype.deactivate = function() {
|
|
||||||
return this.trigger.apply(this, ['change', false].concat(__slice.call(arguments)));
|
|
||||||
};
|
|
||||||
|
|
||||||
Manager.prototype.change = function() {
|
|
||||||
var args, cont, current, _i, _len, _ref;
|
|
||||||
current = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
|
||||||
_ref = this.controllers;
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
cont = _ref[_i];
|
|
||||||
if (cont !== current) {
|
|
||||||
cont.deactivate.apply(cont, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (current) {
|
|
||||||
return current.activate.apply(current, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Manager;
|
|
||||||
|
|
||||||
})(Spine.Module);
|
|
||||||
|
|
||||||
Spine.Controller.include({
|
|
||||||
active: function() {
|
|
||||||
var args;
|
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
if (typeof args[0] === 'function') {
|
|
||||||
this.bind('active', args[0]);
|
|
||||||
} else {
|
|
||||||
args.unshift('active');
|
|
||||||
this.trigger.apply(this, args);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
isActive: function() {
|
|
||||||
return this.el.hasClass('active');
|
|
||||||
},
|
|
||||||
activate: function() {
|
|
||||||
this.el.addClass('active');
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
deactivate: function() {
|
|
||||||
this.el.removeClass('active');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Spine.Stack = (function(_super) {
|
|
||||||
__extends(Stack, _super);
|
|
||||||
|
|
||||||
Stack.prototype.controllers = {};
|
|
||||||
|
|
||||||
Stack.prototype.routes = {};
|
|
||||||
|
|
||||||
Stack.prototype.className = 'spine stack';
|
|
||||||
|
|
||||||
function Stack() {
|
|
||||||
var key, value, _fn, _ref, _ref1,
|
|
||||||
_this = this;
|
|
||||||
Stack.__super__.constructor.apply(this, arguments);
|
|
||||||
this.manager = new Spine.Manager;
|
|
||||||
_ref = this.controllers;
|
|
||||||
for (key in _ref) {
|
|
||||||
value = _ref[key];
|
|
||||||
if (this[key] != null) {
|
|
||||||
throw Error("'@" + key + "' already assigned - choose a different name");
|
|
||||||
}
|
|
||||||
this[key] = new value({
|
|
||||||
stack: this
|
|
||||||
});
|
|
||||||
this.add(this[key]);
|
|
||||||
}
|
|
||||||
_ref1 = this.routes;
|
|
||||||
_fn = function(key, value) {
|
|
||||||
var callback;
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
callback = value;
|
|
||||||
}
|
|
||||||
callback || (callback = function() {
|
|
||||||
var _ref2;
|
|
||||||
return (_ref2 = _this[value]).active.apply(_ref2, arguments);
|
|
||||||
});
|
|
||||||
return _this.route(key, callback);
|
|
||||||
};
|
|
||||||
for (key in _ref1) {
|
|
||||||
value = _ref1[key];
|
|
||||||
_fn(key, value);
|
|
||||||
}
|
|
||||||
if (this["default"]) {
|
|
||||||
this[this["default"]].active();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Stack.prototype.add = function(controller) {
|
|
||||||
this.manager.add(controller);
|
|
||||||
return this.append(controller);
|
|
||||||
};
|
|
||||||
|
|
||||||
return Stack;
|
|
||||||
|
|
||||||
})(Spine.Controller);
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Spine.Manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports.Stack = Spine.Stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=manager.map
|
|
||||||
*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"file": "manager.js",
|
|
||||||
"sourceRoot": "..",
|
|
||||||
"sources": [
|
|
||||||
"src/manager.coffee"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"mappings": "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"
|
|
||||||
}
|
|
137
app/assets/javascripts/app/lib/spine/relation.coffee
Executable file
137
app/assets/javascripts/app/lib/spine/relation.coffee
Executable file
|
@ -0,0 +1,137 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
isArray = Spine.isArray
|
||||||
|
require = @require or ((value) -> eval(value))
|
||||||
|
|
||||||
|
class Collection extends Spine.Module
|
||||||
|
constructor: (options = {}) ->
|
||||||
|
for key, value of options
|
||||||
|
@[key] = value
|
||||||
|
|
||||||
|
all: ->
|
||||||
|
@model.select (rec) => @associated(rec)
|
||||||
|
|
||||||
|
first: ->
|
||||||
|
@all()[0]
|
||||||
|
|
||||||
|
last: ->
|
||||||
|
values = @all()
|
||||||
|
values[values.length - 1]
|
||||||
|
|
||||||
|
count: ->
|
||||||
|
@all().length
|
||||||
|
|
||||||
|
find: (id, notFound = @model.notFound) ->
|
||||||
|
records = @select (rec) =>
|
||||||
|
"#{rec.id}" is "#{id}"
|
||||||
|
return records[0] or notFound?(id)
|
||||||
|
|
||||||
|
findAllByAttribute: (name, value) ->
|
||||||
|
@model.select (rec) =>
|
||||||
|
@associated(rec) and rec[name] is value
|
||||||
|
|
||||||
|
findByAttribute: (name, value) ->
|
||||||
|
@findAllByAttribute(name, value)[0]
|
||||||
|
|
||||||
|
select: (cb) ->
|
||||||
|
@model.select (rec) =>
|
||||||
|
@associated(rec) and cb(rec)
|
||||||
|
|
||||||
|
refresh: (values) ->
|
||||||
|
return this unless values?
|
||||||
|
for record in @all()
|
||||||
|
delete @model.irecords[record.id]
|
||||||
|
for match, i in @model.records when match.id is record.id
|
||||||
|
@model.records.splice(i, 1)
|
||||||
|
break
|
||||||
|
values = [values] unless isArray(values)
|
||||||
|
for record in values
|
||||||
|
record.newRecord = false
|
||||||
|
record[@fkey] = @record.id
|
||||||
|
@model.refresh values
|
||||||
|
this
|
||||||
|
|
||||||
|
create: (record, options) ->
|
||||||
|
record[@fkey] = @record.id
|
||||||
|
@model.create(record, options)
|
||||||
|
|
||||||
|
add: (record, options) ->
|
||||||
|
record.updateAttribute @fkey, @record.id, options
|
||||||
|
|
||||||
|
remove: (record, options) ->
|
||||||
|
record.updateAttribute @fkey, null, options
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
associated: (record) ->
|
||||||
|
record[@fkey] is @record.id
|
||||||
|
|
||||||
|
class Instance extends Spine.Module
|
||||||
|
constructor: (options = {}) ->
|
||||||
|
for key, value of options
|
||||||
|
@[key] = value
|
||||||
|
|
||||||
|
exists: ->
|
||||||
|
return if @record[@fkey] then @model.exists(@record[@fkey]) else false
|
||||||
|
|
||||||
|
find: ->
|
||||||
|
return @model.find(@record[@fkey])
|
||||||
|
|
||||||
|
update: (value) ->
|
||||||
|
return this unless value?
|
||||||
|
unless value instanceof @model
|
||||||
|
value = new @model(value)
|
||||||
|
value.save() if value.isNew()
|
||||||
|
@record[@fkey] = value and value.id
|
||||||
|
this
|
||||||
|
|
||||||
|
class Singleton extends Spine.Module
|
||||||
|
constructor: (options = {}) ->
|
||||||
|
for key, value of options
|
||||||
|
@[key] = value
|
||||||
|
|
||||||
|
find: ->
|
||||||
|
@record.id and @model.findByAttribute(@fkey, @record.id)
|
||||||
|
|
||||||
|
update: (value) ->
|
||||||
|
return this unless value?
|
||||||
|
unless value instanceof @model
|
||||||
|
value = @model.fromJSON(value)
|
||||||
|
|
||||||
|
value[@fkey] = @record.id
|
||||||
|
value.save()
|
||||||
|
this
|
||||||
|
|
||||||
|
singularize = (str) ->
|
||||||
|
str.replace(/s$/, '')
|
||||||
|
|
||||||
|
underscore = (str) ->
|
||||||
|
str.replace(/::/g, '/')
|
||||||
|
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
||||||
|
.replace(/([a-z\d])([A-Z])/g, '$1_$2')
|
||||||
|
.replace(/-/g, '_')
|
||||||
|
.toLowerCase()
|
||||||
|
|
||||||
|
association = (name, model, record, fkey, Ctor) ->
|
||||||
|
model = require(model) if typeof model is 'string'
|
||||||
|
new Ctor(name: name, model: model, record: record, fkey: fkey)
|
||||||
|
|
||||||
|
Spine.Model.extend
|
||||||
|
hasMany: (name, model, fkey) ->
|
||||||
|
fkey ?= "#{underscore(this.className)}_id"
|
||||||
|
@::[name] = (value) ->
|
||||||
|
association(name, model, @, fkey, Collection).refresh(value)
|
||||||
|
|
||||||
|
belongsTo: (name, model, fkey) ->
|
||||||
|
fkey ?= "#{underscore(singularize(name))}_id"
|
||||||
|
@::[name] = (value) ->
|
||||||
|
association(name, model, @, fkey, Instance).update(value).find()
|
||||||
|
@attributes.push(fkey)
|
||||||
|
|
||||||
|
hasOne: (name, model, fkey) ->
|
||||||
|
fkey ?= "#{underscore(@className)}_id"
|
||||||
|
@::[name] = (value) ->
|
||||||
|
association(name, model, @, fkey, Singleton).update(value).find()
|
||||||
|
|
||||||
|
Spine.Collection = Collection
|
||||||
|
Spine.Singleton = Singleton
|
||||||
|
Spine.Instance = Instance
|
|
@ -1,264 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var Collection, Instance, Singleton, Spine, association, isArray, require, singularize, underscore,
|
|
||||||
__hasProp = {}.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
isArray = Spine.isArray;
|
|
||||||
|
|
||||||
require = this.require || (function(value) {
|
|
||||||
return eval(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
Collection = (function(_super) {
|
|
||||||
__extends(Collection, _super);
|
|
||||||
|
|
||||||
function Collection(options) {
|
|
||||||
var key, value;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
for (key in options) {
|
|
||||||
value = options[key];
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection.prototype.all = function() {
|
|
||||||
var _this = this;
|
|
||||||
return this.model.select(function(rec) {
|
|
||||||
return _this.associated(rec);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.first = function() {
|
|
||||||
return this.all()[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.last = function() {
|
|
||||||
var values;
|
|
||||||
values = this.all();
|
|
||||||
return values[values.length - 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.count = function() {
|
|
||||||
return this.all().length;
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.find = function(id) {
|
|
||||||
var records,
|
|
||||||
_this = this;
|
|
||||||
records = this.select(function(rec) {
|
|
||||||
return ("" + rec.id) === ("" + id);
|
|
||||||
});
|
|
||||||
if (!records[0]) {
|
|
||||||
throw new Error("\"" + this.model.className + "\" model could not find a record for the ID \"" + id + "\"");
|
|
||||||
}
|
|
||||||
return records[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.findAllByAttribute = function(name, value) {
|
|
||||||
var _this = this;
|
|
||||||
return this.model.select(function(rec) {
|
|
||||||
return _this.associated(rec) && rec[name] === value;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.findByAttribute = function(name, value) {
|
|
||||||
return this.findAllByAttribute(name, value)[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.select = function(cb) {
|
|
||||||
var _this = this;
|
|
||||||
return this.model.select(function(rec) {
|
|
||||||
return _this.associated(rec) && cb(rec);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.refresh = function(values) {
|
|
||||||
var i, match, record, _i, _j, _k, _len, _len1, _len2, _ref, _ref1;
|
|
||||||
if (values == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
_ref = this.all();
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
record = _ref[_i];
|
|
||||||
delete this.model.irecords[record.id];
|
|
||||||
_ref1 = this.model.records;
|
|
||||||
for (i = _j = 0, _len1 = _ref1.length; _j < _len1; i = ++_j) {
|
|
||||||
match = _ref1[i];
|
|
||||||
if (!(match.id === record.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.model.records.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isArray(values)) {
|
|
||||||
values = [values];
|
|
||||||
}
|
|
||||||
for (_k = 0, _len2 = values.length; _k < _len2; _k++) {
|
|
||||||
record = values[_k];
|
|
||||||
record.newRecord = false;
|
|
||||||
record[this.fkey] = this.record.id;
|
|
||||||
}
|
|
||||||
this.model.refresh(values);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.create = function(record, options) {
|
|
||||||
record[this.fkey] = this.record.id;
|
|
||||||
return this.model.create(record, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.add = function(record, options) {
|
|
||||||
return record.updateAttribute(this.fkey, this.record.id, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.remove = function(record, options) {
|
|
||||||
return record.updateAttribute(this.fkey, null, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
Collection.prototype.associated = function(record) {
|
|
||||||
return record[this.fkey] === this.record.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Collection;
|
|
||||||
|
|
||||||
})(Spine.Module);
|
|
||||||
|
|
||||||
Instance = (function(_super) {
|
|
||||||
__extends(Instance, _super);
|
|
||||||
|
|
||||||
function Instance(options) {
|
|
||||||
var key, value;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
for (key in options) {
|
|
||||||
value = options[key];
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Instance.prototype.exists = function() {
|
|
||||||
if (this.record[this.fkey]) {
|
|
||||||
return this.model.exists(this.record[this.fkey]);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Instance.prototype.update = function(value) {
|
|
||||||
if (value == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (!(value instanceof this.model)) {
|
|
||||||
value = new this.model(value);
|
|
||||||
}
|
|
||||||
if (value.isNew()) {
|
|
||||||
value.save();
|
|
||||||
}
|
|
||||||
this.record[this.fkey] = value && value.id;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Instance;
|
|
||||||
|
|
||||||
})(Spine.Module);
|
|
||||||
|
|
||||||
Singleton = (function(_super) {
|
|
||||||
__extends(Singleton, _super);
|
|
||||||
|
|
||||||
function Singleton(options) {
|
|
||||||
var key, value;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
for (key in options) {
|
|
||||||
value = options[key];
|
|
||||||
this[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Singleton.prototype.find = function() {
|
|
||||||
return this.record.id && this.model.findByAttribute(this.fkey, this.record.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
Singleton.prototype.update = function(value) {
|
|
||||||
if (value == null) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
if (!(value instanceof this.model)) {
|
|
||||||
value = this.model.fromJSON(value);
|
|
||||||
}
|
|
||||||
value[this.fkey] = this.record.id;
|
|
||||||
value.save();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Singleton;
|
|
||||||
|
|
||||||
})(Spine.Module);
|
|
||||||
|
|
||||||
singularize = function(str) {
|
|
||||||
return str.replace(/s$/, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
underscore = function(str) {
|
|
||||||
return str.replace(/::/g, '/').replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2').replace(/([a-z\d])([A-Z])/g, '$1_$2').replace(/-/g, '_').toLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
association = function(name, model, record, fkey, Ctor) {
|
|
||||||
if (typeof model === 'string') {
|
|
||||||
model = require(model);
|
|
||||||
}
|
|
||||||
return new Ctor({
|
|
||||||
name: name,
|
|
||||||
model: model,
|
|
||||||
record: record,
|
|
||||||
fkey: fkey
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Spine.Model.extend({
|
|
||||||
hasMany: function(name, model, fkey) {
|
|
||||||
if (fkey == null) {
|
|
||||||
fkey = "" + (underscore(this.className)) + "_id";
|
|
||||||
}
|
|
||||||
return this.prototype[name] = function(value) {
|
|
||||||
return association(name, model, this, fkey, Collection).refresh(value);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
belongsTo: function(name, model, fkey) {
|
|
||||||
if (fkey == null) {
|
|
||||||
fkey = "" + (underscore(singularize(name))) + "_id";
|
|
||||||
}
|
|
||||||
this.prototype[name] = function(value) {
|
|
||||||
return association(name, model, this, fkey, Instance).update(value).exists();
|
|
||||||
};
|
|
||||||
return this.attributes.push(fkey);
|
|
||||||
},
|
|
||||||
hasOne: function(name, model, fkey) {
|
|
||||||
if (fkey == null) {
|
|
||||||
fkey = "" + (underscore(this.className)) + "_id";
|
|
||||||
}
|
|
||||||
return this.prototype[name] = function(value) {
|
|
||||||
return association(name, model, this, fkey, Singleton).update(value).find();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Spine.Collection = Collection;
|
|
||||||
|
|
||||||
Spine.Singleton = Singleton;
|
|
||||||
|
|
||||||
Spine.Instance = Instance;
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=relation.map
|
|
||||||
*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"file": "relation.js",
|
|
||||||
"sourceRoot": "..",
|
|
||||||
"sources": [
|
|
||||||
"src/relation.coffee"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"mappings": "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"
|
|
||||||
}
|
|
159
app/assets/javascripts/app/lib/spine/route.coffee
Executable file
159
app/assets/javascripts/app/lib/spine/route.coffee
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
Spine = @Spine or require('spine')
|
||||||
|
$ = Spine.$
|
||||||
|
|
||||||
|
hashStrip = /^#*/
|
||||||
|
namedParam = /:([\w\d]+)/g
|
||||||
|
splatParam = /\*([\w\d]+)/g
|
||||||
|
escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g
|
||||||
|
|
||||||
|
class Spine.Route extends Spine.Module
|
||||||
|
@extend Spine.Events
|
||||||
|
|
||||||
|
@historySupport: window.history?.pushState?
|
||||||
|
|
||||||
|
@routes: []
|
||||||
|
|
||||||
|
@options:
|
||||||
|
trigger: true
|
||||||
|
history: false
|
||||||
|
shim: false
|
||||||
|
replace: false
|
||||||
|
redirect: false
|
||||||
|
|
||||||
|
@add: (path, callback) ->
|
||||||
|
if (typeof path is 'object' and path not instanceof RegExp)
|
||||||
|
@add(key, value) for key, value of path
|
||||||
|
else
|
||||||
|
@routes.push(new @(path, callback))
|
||||||
|
|
||||||
|
@setup: (options = {}) ->
|
||||||
|
@options = $.extend({}, @options, options)
|
||||||
|
|
||||||
|
if (@options.history)
|
||||||
|
@history = @historySupport and @options.history
|
||||||
|
|
||||||
|
return if @options.shim
|
||||||
|
|
||||||
|
if @history
|
||||||
|
$(window).bind('popstate', @change)
|
||||||
|
else
|
||||||
|
$(window).bind('hashchange', @change)
|
||||||
|
@change()
|
||||||
|
|
||||||
|
@unbind: ->
|
||||||
|
return if @options.shim
|
||||||
|
|
||||||
|
if @history
|
||||||
|
$(window).unbind('popstate', @change)
|
||||||
|
else
|
||||||
|
$(window).unbind('hashchange', @change)
|
||||||
|
|
||||||
|
@navigate: (args...) ->
|
||||||
|
options = {}
|
||||||
|
|
||||||
|
lastArg = args[args.length - 1]
|
||||||
|
if typeof lastArg is 'object'
|
||||||
|
options = args.pop()
|
||||||
|
else if typeof lastArg is 'boolean'
|
||||||
|
options.trigger = args.pop()
|
||||||
|
|
||||||
|
options = $.extend({}, @options, options)
|
||||||
|
|
||||||
|
path = args.join('/')
|
||||||
|
return if @path is path
|
||||||
|
@path = path
|
||||||
|
|
||||||
|
@trigger('navigate', @path)
|
||||||
|
|
||||||
|
route = @matchRoute(@path, options) if options.trigger
|
||||||
|
|
||||||
|
return if options.shim
|
||||||
|
|
||||||
|
if !route
|
||||||
|
if typeof options.redirect is 'function'
|
||||||
|
return options.redirect.apply this, [@path, options]
|
||||||
|
else
|
||||||
|
if options.redirect is true
|
||||||
|
@redirect(@path)
|
||||||
|
|
||||||
|
if @history and options.replace
|
||||||
|
history.replaceState({}, document.title, @path)
|
||||||
|
else if @history
|
||||||
|
history.pushState({}, document.title, @path)
|
||||||
|
else
|
||||||
|
window.location.hash = @path
|
||||||
|
|
||||||
|
# Private
|
||||||
|
|
||||||
|
@getPath: ->
|
||||||
|
if @history
|
||||||
|
path = window.location.pathname
|
||||||
|
path = '/' + path if path.substr(0,1) isnt '/'
|
||||||
|
else
|
||||||
|
path = window.location.hash
|
||||||
|
path = path.replace(hashStrip, '')
|
||||||
|
path
|
||||||
|
|
||||||
|
@getHost: ->
|
||||||
|
"#{window.location.protocol}//#{window.location.host}"
|
||||||
|
|
||||||
|
@change: ->
|
||||||
|
path = @getPath()
|
||||||
|
return if path is @path
|
||||||
|
@path = path
|
||||||
|
@matchRoute(@path)
|
||||||
|
|
||||||
|
@matchRoute: (path, options) ->
|
||||||
|
for route in @routes when route.match(path, options)
|
||||||
|
@trigger('change', route, path)
|
||||||
|
return route
|
||||||
|
|
||||||
|
@redirect: (path) ->
|
||||||
|
window.location = path
|
||||||
|
|
||||||
|
constructor: (@path, @callback) ->
|
||||||
|
@names = []
|
||||||
|
|
||||||
|
if typeof path is 'string'
|
||||||
|
namedParam.lastIndex = 0
|
||||||
|
while (match = namedParam.exec(path)) != null
|
||||||
|
@names.push(match[1])
|
||||||
|
|
||||||
|
splatParam.lastIndex = 0
|
||||||
|
while (match = splatParam.exec(path)) != null
|
||||||
|
@names.push(match[1])
|
||||||
|
|
||||||
|
path = path.replace(escapeRegExp, '\\$&')
|
||||||
|
.replace(namedParam, '([^\/]*)')
|
||||||
|
.replace(splatParam, '(.*?)')
|
||||||
|
|
||||||
|
@route = new RegExp("^#{path}$")
|
||||||
|
else
|
||||||
|
@route = path
|
||||||
|
|
||||||
|
match: (path, options = {}) ->
|
||||||
|
match = @route.exec(path)
|
||||||
|
return false unless match
|
||||||
|
options.match = match
|
||||||
|
params = match.slice(1)
|
||||||
|
|
||||||
|
if @names.length
|
||||||
|
for param, i in params
|
||||||
|
options[@names[i]] = param
|
||||||
|
@.constructor.trigger('before', @)
|
||||||
|
@callback.call(null, options) isnt false
|
||||||
|
|
||||||
|
# Coffee-script bug
|
||||||
|
Spine.Route.change = Spine.Route.proxy(Spine.Route.change)
|
||||||
|
|
||||||
|
Spine.Controller.include
|
||||||
|
route: (path, callback) ->
|
||||||
|
Spine.Route.add(path, @proxy(callback))
|
||||||
|
|
||||||
|
routes: (routes) ->
|
||||||
|
@route(key, value) for key, value of routes
|
||||||
|
|
||||||
|
navigate: ->
|
||||||
|
Spine.Route.navigate.apply(Spine.Route, arguments)
|
||||||
|
|
||||||
|
module?.exports = Spine.Route
|
|
@ -1,242 +0,0 @@
|
||||||
// Generated by CoffeeScript 1.6.3
|
|
||||||
(function() {
|
|
||||||
var $, Spine, escapeRegExp, hashStrip, namedParam, splatParam,
|
|
||||||
__hasProp = {}.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
|
||||||
__slice = [].slice;
|
|
||||||
|
|
||||||
Spine = this.Spine || require('spine');
|
|
||||||
|
|
||||||
$ = Spine.$;
|
|
||||||
|
|
||||||
hashStrip = /^#*/;
|
|
||||||
|
|
||||||
namedParam = /:([\w\d]+)/g;
|
|
||||||
|
|
||||||
splatParam = /\*([\w\d]+)/g;
|
|
||||||
|
|
||||||
escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
|
|
||||||
|
|
||||||
Spine.Route = (function(_super) {
|
|
||||||
var _ref;
|
|
||||||
|
|
||||||
__extends(Route, _super);
|
|
||||||
|
|
||||||
Route.extend(Spine.Events);
|
|
||||||
|
|
||||||
Route.historySupport = ((_ref = window.history) != null ? _ref.pushState : void 0) != null;
|
|
||||||
|
|
||||||
Route.routes = [];
|
|
||||||
|
|
||||||
Route.options = {
|
|
||||||
trigger: true,
|
|
||||||
history: false,
|
|
||||||
shim: false,
|
|
||||||
replace: false,
|
|
||||||
redirect: false
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.add = function(path, callback) {
|
|
||||||
var key, value, _results;
|
|
||||||
if (typeof path === 'object' && !(path instanceof RegExp)) {
|
|
||||||
_results = [];
|
|
||||||
for (key in path) {
|
|
||||||
value = path[key];
|
|
||||||
_results.push(this.add(key, value));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
} else {
|
|
||||||
return this.routes.push(new this(path, callback));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.setup = function(options) {
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
this.options = $.extend({}, this.options, options);
|
|
||||||
if (this.options.history) {
|
|
||||||
this.history = this.historySupport && this.options.history;
|
|
||||||
}
|
|
||||||
if (this.options.shim) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.history) {
|
|
||||||
$(window).bind('popstate', this.change);
|
|
||||||
} else {
|
|
||||||
$(window).bind('hashchange', this.change);
|
|
||||||
}
|
|
||||||
return this.change();
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.unbind = function() {
|
|
||||||
if (this.options.shim) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.history) {
|
|
||||||
return $(window).unbind('popstate', this.change);
|
|
||||||
} else {
|
|
||||||
return $(window).unbind('hashchange', this.change);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.navigate = function() {
|
|
||||||
var args, lastArg, options, path, route;
|
|
||||||
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
|
||||||
options = {};
|
|
||||||
lastArg = args[args.length - 1];
|
|
||||||
if (typeof lastArg === 'object') {
|
|
||||||
options = args.pop();
|
|
||||||
} else if (typeof lastArg === 'boolean') {
|
|
||||||
options.trigger = args.pop();
|
|
||||||
}
|
|
||||||
options = $.extend({}, this.options, options);
|
|
||||||
path = args.join('/');
|
|
||||||
if (this.path === path) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.path = path;
|
|
||||||
this.trigger('navigate', this.path);
|
|
||||||
if (options.trigger) {
|
|
||||||
route = this.matchRoute(this.path, options);
|
|
||||||
}
|
|
||||||
if (options.shim) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!route) {
|
|
||||||
if (typeof options.redirect === 'function') {
|
|
||||||
return options.redirect.apply(this, [this.path, options]);
|
|
||||||
} else {
|
|
||||||
if (options.redirect === true) {
|
|
||||||
this.redirect(this.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.history && options.replace) {
|
|
||||||
return history.replaceState({}, document.title, this.path);
|
|
||||||
} else if (this.history) {
|
|
||||||
return history.pushState({}, document.title, this.path);
|
|
||||||
} else {
|
|
||||||
return window.location.hash = this.path;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.getPath = function() {
|
|
||||||
var path;
|
|
||||||
if (this.history) {
|
|
||||||
path = window.location.pathname;
|
|
||||||
if (path.substr(0, 1) !== '/') {
|
|
||||||
path = '/' + path;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path = window.location.hash;
|
|
||||||
path = path.replace(hashStrip, '');
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.getHost = function() {
|
|
||||||
return "" + window.location.protocol + "//" + window.location.host;
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.change = function() {
|
|
||||||
var path;
|
|
||||||
path = this.getPath();
|
|
||||||
if (path === this.path) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.path = path;
|
|
||||||
return this.matchRoute(this.path);
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.matchRoute = function(path, options) {
|
|
||||||
var route, _i, _len, _ref1;
|
|
||||||
_ref1 = this.routes;
|
|
||||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
|
||||||
route = _ref1[_i];
|
|
||||||
if (!(route.match(path, options))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.trigger('change', route, path);
|
|
||||||
return route;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Route.redirect = function(path) {
|
|
||||||
return window.location = path;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Route(path, callback) {
|
|
||||||
var match;
|
|
||||||
this.path = path;
|
|
||||||
this.callback = callback;
|
|
||||||
this.names = [];
|
|
||||||
if (typeof path === 'string') {
|
|
||||||
namedParam.lastIndex = 0;
|
|
||||||
while ((match = namedParam.exec(path)) !== null) {
|
|
||||||
this.names.push(match[1]);
|
|
||||||
}
|
|
||||||
splatParam.lastIndex = 0;
|
|
||||||
while ((match = splatParam.exec(path)) !== null) {
|
|
||||||
this.names.push(match[1]);
|
|
||||||
}
|
|
||||||
path = path.replace(escapeRegExp, '\\$&').replace(namedParam, '([^\/]*)').replace(splatParam, '(.*?)');
|
|
||||||
this.route = new RegExp("^" + path + "$");
|
|
||||||
} else {
|
|
||||||
this.route = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Route.prototype.match = function(path, options) {
|
|
||||||
var i, match, param, params, _i, _len;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
match = this.route.exec(path);
|
|
||||||
if (!match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
options.match = match;
|
|
||||||
params = match.slice(1);
|
|
||||||
if (this.names.length) {
|
|
||||||
for (i = _i = 0, _len = params.length; _i < _len; i = ++_i) {
|
|
||||||
param = params[i];
|
|
||||||
options[this.names[i]] = param;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.callback.call(null, options) !== false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Route;
|
|
||||||
|
|
||||||
})(Spine.Module);
|
|
||||||
|
|
||||||
Spine.Route.change = Spine.Route.proxy(Spine.Route.change);
|
|
||||||
|
|
||||||
Spine.Controller.include({
|
|
||||||
route: function(path, callback) {
|
|
||||||
return Spine.Route.add(path, this.proxy(callback));
|
|
||||||
},
|
|
||||||
routes: function(routes) {
|
|
||||||
var key, value, _results;
|
|
||||||
_results = [];
|
|
||||||
for (key in routes) {
|
|
||||||
value = routes[key];
|
|
||||||
_results.push(this.route(key, value));
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
},
|
|
||||||
navigate: function() {
|
|
||||||
return Spine.Route.navigate.apply(Spine.Route, arguments);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Spine.Route;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
|
|
||||||
/*
|
|
||||||
//@ sourceMappingURL=route.map
|
|
||||||
*/
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"file": "route.js",
|
|
||||||
"sourceRoot": "..",
|
|
||||||
"sources": [
|
|
||||||
"src/route.coffee"
|
|
||||||
],
|
|
||||||
"names": [],
|
|
||||||
"mappings": "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"
|
|
||||||
}
|
|
659
app/assets/javascripts/app/lib/spine/spine.coffee
Executable file
659
app/assets/javascripts/app/lib/spine/spine.coffee
Executable file
|
@ -0,0 +1,659 @@
|
||||||
|
###
|
||||||
|
Spine.js MVC library
|
||||||
|
Released under the MIT License
|
||||||
|
###
|
||||||
|
|
||||||
|
Events =
|
||||||
|
bind: (ev, callback) ->
|
||||||
|
evs = ev.split(' ')
|
||||||
|
@_callbacks = {} unless @hasOwnProperty('_callbacks') and @_callbacks
|
||||||
|
for name in evs
|
||||||
|
@_callbacks[name] or= []
|
||||||
|
@_callbacks[name].push(callback)
|
||||||
|
this
|
||||||
|
|
||||||
|
one: (ev, callback) ->
|
||||||
|
@bind ev, handler = ->
|
||||||
|
@unbind(ev, handler)
|
||||||
|
callback.apply(this, arguments)
|
||||||
|
|
||||||
|
trigger: (args...) ->
|
||||||
|
ev = args.shift()
|
||||||
|
list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
|
||||||
|
return unless list
|
||||||
|
for callback in list
|
||||||
|
if callback.apply(this, args) is false
|
||||||
|
break
|
||||||
|
true
|
||||||
|
|
||||||
|
listenTo: (obj, ev, callback) ->
|
||||||
|
obj.bind(ev, callback)
|
||||||
|
@listeningTo or= []
|
||||||
|
@listeningTo.push {obj, ev, callback}
|
||||||
|
this
|
||||||
|
|
||||||
|
listenToOnce: (obj, ev, callback) ->
|
||||||
|
listeningToOnce = @listeningToOnce or= []
|
||||||
|
obj.bind ev, handler = ->
|
||||||
|
idx = -1
|
||||||
|
for lt, i in listeningToOnce when lt.obj is obj
|
||||||
|
idx = i if lt.ev is ev and lt.callback is callback
|
||||||
|
obj.unbind(ev, handler)
|
||||||
|
listeningToOnce.splice(idx, 1) unless idx is -1
|
||||||
|
callback.apply(this, arguments)
|
||||||
|
listeningToOnce.push {obj, ev, callback, handler}
|
||||||
|
this
|
||||||
|
|
||||||
|
stopListening: (obj, events, callback) ->
|
||||||
|
if arguments.length is 0
|
||||||
|
for listeningTo in [@listeningTo, @listeningToOnce]
|
||||||
|
continue unless listeningTo
|
||||||
|
for lt in listeningTo
|
||||||
|
lt.obj.unbind(lt.ev, lt.handler or lt.callback)
|
||||||
|
@listeningTo = undefined
|
||||||
|
@listeningToOnce = undefined
|
||||||
|
|
||||||
|
else if obj
|
||||||
|
for listeningTo in [@listeningTo, @listeningToOnce]
|
||||||
|
continue unless listeningTo
|
||||||
|
events = if events then events.split(' ') else [undefined]
|
||||||
|
for ev in events
|
||||||
|
for idx in [listeningTo.length-1..0]
|
||||||
|
lt = listeningTo[idx]
|
||||||
|
continue if callback and (lt.handler or lt.callback) isnt callback
|
||||||
|
if (not ev) or (ev is lt.ev)
|
||||||
|
lt.obj.unbind(lt.ev, lt.handler or lt.callback)
|
||||||
|
listeningTo.splice(idx, 1) unless idx is -1
|
||||||
|
else if ev
|
||||||
|
evts = lt.ev.split(' ')
|
||||||
|
if ev in evts
|
||||||
|
evts = (e for e in evts when e isnt ev)
|
||||||
|
lt.ev = $.trim(evts.join(' '))
|
||||||
|
lt.obj.unbind(ev, lt.handler or lt.callback)
|
||||||
|
|
||||||
|
unbind: (ev, callback) ->
|
||||||
|
if arguments.length is 0
|
||||||
|
@_callbacks = {}
|
||||||
|
return this
|
||||||
|
return this unless ev
|
||||||
|
evs = ev.split(' ')
|
||||||
|
for name in evs
|
||||||
|
list = @_callbacks?[name]
|
||||||
|
continue unless list
|
||||||
|
unless callback
|
||||||
|
delete @_callbacks[name]
|
||||||
|
continue
|
||||||
|
for cb, i in list when (cb is callback)
|
||||||
|
list = list.slice()
|
||||||
|
list.splice(i, 1)
|
||||||
|
@_callbacks[name] = list
|
||||||
|
break
|
||||||
|
this
|
||||||
|
|
||||||
|
Events.on = Events.bind
|
||||||
|
Events.off = Events.unbind
|
||||||
|
|
||||||
|
Log =
|
||||||
|
trace: true
|
||||||
|
|
||||||
|
logPrefix: '(App)'
|
||||||
|
|
||||||
|
log: (args...) ->
|
||||||
|
return unless @trace
|
||||||
|
if @logPrefix then args.unshift(@logPrefix)
|
||||||
|
console?.log?(args...)
|
||||||
|
this
|
||||||
|
|
||||||
|
moduleKeywords = ['included', 'extended']
|
||||||
|
|
||||||
|
class Module
|
||||||
|
@include: (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
|
||||||
|
|
||||||
|
@extend: (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
|
||||||
|
|
||||||
|
@proxy: (func) ->
|
||||||
|
=> func.apply(this, arguments)
|
||||||
|
|
||||||
|
proxy: (func) ->
|
||||||
|
=> func.apply(this, arguments)
|
||||||
|
|
||||||
|
constructor: ->
|
||||||
|
@init?(arguments...)
|
||||||
|
|
||||||
|
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, notFound = @notFound) ->
|
||||||
|
record = @irecords[id]?.clone()
|
||||||
|
return record or notFound?(id)
|
||||||
|
|
||||||
|
@notFound: (id) -> return null
|
||||||
|
|
||||||
|
@exists: (id) ->
|
||||||
|
return if @irecords[id] then true else false
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
@slice: (begin = 0, end)->
|
||||||
|
@cloneArray(@records.slice(begin, end))
|
||||||
|
|
||||||
|
@first: (end = 1)->
|
||||||
|
if end > 1
|
||||||
|
@cloneArray(@records.slice(0, end))
|
||||||
|
else
|
||||||
|
@records[0]?.clone()
|
||||||
|
|
||||||
|
@last: (begin)->
|
||||||
|
if typeof begin is 'number'
|
||||||
|
@cloneArray(@records.slice(-begin))
|
||||||
|
else
|
||||||
|
@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
|
||||||
|
if @constructor.uuid? and typeof @constructor.uuid is 'function'
|
||||||
|
@cid = @constructor.uuid()
|
||||||
|
@id = @cid unless @id
|
||||||
|
else
|
||||||
|
@cid = atts?.cid or @constructor.uid('c-')
|
||||||
|
@load atts if atts
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 this
|
||||||
|
delete @[key] if key in @constructor.attributes
|
||||||
|
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] unless @cid is @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
|
||||||
|
|
||||||
|
class Controller extends Module
|
||||||
|
@include Events
|
||||||
|
@include Log
|
||||||
|
|
||||||
|
eventSplitter: /^(\S+)\s*(.*)$/
|
||||||
|
tag: 'div'
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
@options = options
|
||||||
|
|
||||||
|
for key, value of @options
|
||||||
|
@[key] = value
|
||||||
|
|
||||||
|
@el = document.createElement(@tag) unless @el
|
||||||
|
@el = $(@el)
|
||||||
|
@$el = @el
|
||||||
|
|
||||||
|
@el.addClass(@className) if @className
|
||||||
|
@el.attr(@attributes) if @attributes
|
||||||
|
|
||||||
|
@events = @constructor.events unless @events
|
||||||
|
@elements = @constructor.elements unless @elements
|
||||||
|
|
||||||
|
context = @
|
||||||
|
while parent_prototype = context.constructor.__super__
|
||||||
|
@events = $.extend({}, parent_prototype.events, @events) if parent_prototype.events
|
||||||
|
@elements = $.extend({}, parent_prototype.elements, @elements) if parent_prototype.elements
|
||||||
|
context = parent_prototype
|
||||||
|
|
||||||
|
@delegateEvents(@events) if @events
|
||||||
|
@refreshElements() if @elements
|
||||||
|
|
||||||
|
super
|
||||||
|
|
||||||
|
release: =>
|
||||||
|
@trigger 'release', this
|
||||||
|
# no need to unDelegateEvents since remove will end up handling that
|
||||||
|
@el.remove()
|
||||||
|
@unbind()
|
||||||
|
@stopListening()
|
||||||
|
|
||||||
|
$: (selector) -> $(selector, @el)
|
||||||
|
|
||||||
|
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(@eventSplitter)
|
||||||
|
eventName = match[1]
|
||||||
|
selector = match[2]
|
||||||
|
|
||||||
|
if selector is ''
|
||||||
|
@el.bind(eventName, method)
|
||||||
|
else
|
||||||
|
@el.on(eventName, selector, method)
|
||||||
|
|
||||||
|
refreshElements: ->
|
||||||
|
for key, value of @elements
|
||||||
|
@[value] = @$(key)
|
||||||
|
|
||||||
|
delay: (func, timeout) ->
|
||||||
|
setTimeout(@proxy(func), timeout || 0)
|
||||||
|
|
||||||
|
# keep controllers elements obj in sync with it contents
|
||||||
|
|
||||||
|
html: (element) ->
|
||||||
|
@el.html(element.el or element)
|
||||||
|
@refreshElements()
|
||||||
|
@el
|
||||||
|
|
||||||
|
append: (elements...) ->
|
||||||
|
elements = (e.el or e for e in elements)
|
||||||
|
@el.append(elements...)
|
||||||
|
@refreshElements()
|
||||||
|
@el
|
||||||
|
|
||||||
|
appendTo: (element) ->
|
||||||
|
@el.appendTo(element.el or element)
|
||||||
|
@refreshElements()
|
||||||
|
@el
|
||||||
|
|
||||||
|
prepend: (elements...) ->
|
||||||
|
elements = (e.el or e for e in elements)
|
||||||
|
@el.prepend(elements...)
|
||||||
|
@refreshElements()
|
||||||
|
@el
|
||||||
|
|
||||||
|
replace: (element) ->
|
||||||
|
element = element.el or element
|
||||||
|
element = $.trim(element) if typeof element is "string"
|
||||||
|
# parseHTML is incompatible with Zepto
|
||||||
|
[previous, @el] = [@el, $($.parseHTML(element)?[0] or element)]
|
||||||
|
previous.replaceWith(@el)
|
||||||
|
@delegateEvents(@events)
|
||||||
|
@refreshElements()
|
||||||
|
@el
|
||||||
|
|
||||||
|
# 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 = @Spine = {}
|
||||||
|
module?.exports = Spine
|
||||||
|
|
||||||
|
Spine.version = '1.3.0'
|
||||||
|
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
|
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
@ -1,66 +0,0 @@
|
||||||
(function() {
|
|
||||||
var $,
|
|
||||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
|
||||||
__hasProp = Object.prototype.hasOwnProperty,
|
|
||||||
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
|
|
||||||
|
|
||||||
if (typeof Spine === "undefined" || Spine === null) Spine = require('spine');
|
|
||||||
|
|
||||||
$ = Spine.$;
|
|
||||||
|
|
||||||
Spine.Tabs = (function(_super) {
|
|
||||||
|
|
||||||
__extends(Tabs, _super);
|
|
||||||
|
|
||||||
Tabs.prototype.events = {
|
|
||||||
'click [data-name]': 'click'
|
|
||||||
};
|
|
||||||
|
|
||||||
function Tabs() {
|
|
||||||
this.change = __bind(this.change, this); Tabs.__super__.constructor.apply(this, arguments);
|
|
||||||
this.bind('change', this.change);
|
|
||||||
}
|
|
||||||
|
|
||||||
Tabs.prototype.change = function(name) {
|
|
||||||
if (!name) return;
|
|
||||||
this.current = name;
|
|
||||||
this.children().removeClass('active');
|
|
||||||
return this.children("[data-name=" + this.current + "]").addClass('active');
|
|
||||||
};
|
|
||||||
|
|
||||||
Tabs.prototype.render = function() {
|
|
||||||
this.change(this.current);
|
|
||||||
if (!(this.children('.active').length || this.current)) {
|
|
||||||
return this.children(':first').click();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Tabs.prototype.children = function(sel) {
|
|
||||||
return this.el.children(sel);
|
|
||||||
};
|
|
||||||
|
|
||||||
Tabs.prototype.click = function(e) {
|
|
||||||
var name;
|
|
||||||
name = $(e.currentTarget).attr('data-name');
|
|
||||||
return this.trigger('change', name);
|
|
||||||
};
|
|
||||||
|
|
||||||
Tabs.prototype.connect = function(tabName, controller) {
|
|
||||||
var _this = this;
|
|
||||||
this.bind('change', function(name) {
|
|
||||||
if (name === tabName) return controller.active();
|
|
||||||
});
|
|
||||||
return controller.bind('active', function() {
|
|
||||||
return _this.change(tabName);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return Tabs;
|
|
||||||
|
|
||||||
})(Spine.Controller);
|
|
||||||
|
|
||||||
if (typeof module !== "undefined" && module !== null) {
|
|
||||||
module.exports = Spine.Tabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).call(this);
|
|
|
@ -1,22 +0,0 @@
|
||||||
(function() {
|
|
||||||
var $;
|
|
||||||
|
|
||||||
$ = typeof jQuery !== "undefined" && jQuery !== null ? jQuery : require("jqueryify");
|
|
||||||
|
|
||||||
$.fn.item = function() {
|
|
||||||
var item;
|
|
||||||
item = $(this);
|
|
||||||
item = item.data("item") || (typeof item.tmplItem === "function" ? item.tmplItem().data : void 0);
|
|
||||||
if (item != null) if (typeof item.reload === "function") item.reload();
|
|
||||||
return item;
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.forItem = function(item) {
|
|
||||||
return this.filter(function() {
|
|
||||||
var compare;
|
|
||||||
compare = $(this).item();
|
|
||||||
return (typeof item.eql === "function" ? item.eql(compare) : void 0) || item === compare;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
}).call(this);
|
|
Loading…
Reference in a new issue