Updated to spine 1.3.0.

This commit is contained in:
Martin Edenhofer 2014-04-05 12:42:28 +02:00
parent 200952f691
commit 0f9aeb62df
37 changed files with 1509 additions and 2515 deletions

View file

@ -40,13 +40,13 @@ class App.ControllerGenericNew extends App.ControllerModal
# save object
ui = @
object.save(
success: ->
done: ->
if ui.callback
item = App[ ui.genericObject ].retrieve(@id)
ui.callback( item )
ui.modalHide()
error: ->
fail: ->
ui.log 'errors'
ui.modalHide()
)
@ -87,13 +87,13 @@ class App.ControllerGenericEdit extends App.ControllerModal
# save object
ui = @
@item.save(
success: ->
done: ->
if ui.callback
item = App[ ui.genericObject ].retrieve(@id)
ui.callback( item )
ui.modalHide()
error: =>
fail: =>
ui.log 'errors'
ui.modalHide()
)
@ -170,6 +170,7 @@ class App.ControllerGenericIndex extends App.Controller
objects: objects
overview: overview
attributes: attributes
groupBy: 'state'
)
binds = {}

View file

@ -125,9 +125,9 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
# save object
object.save(
success: =>
done: =>
@modalHide()
error: =>
fail: =>
@modalHide()
)
@ -214,9 +214,9 @@ class App.ChannelEmailAddressEdit extends App.ControllerModal
# save object
object.save(
success: =>
done: =>
@modalHide()
error: =>
fail: =>
@modalHide()
)
@ -301,9 +301,9 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
# save object
object.save(
success: =>
done: =>
@modalHide()
error: =>
fail: =>
@modalHide()
)
@ -425,9 +425,9 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
# save object
object.save(
success: =>
done: =>
@modalHide()
error: =>
fail: =>
@modalHide()
)

View file

@ -260,7 +260,7 @@ class Settings extends App.ControllerModal
@overview.view['d'] = params['attributes']
@overview.save(
success: =>
done: =>
if @reload_needed
@overview.trigger('local:refetch')
else

View file

@ -75,7 +75,7 @@ class App.SettingsAreaItem extends App.Controller
@setting['state'] = state
ui = @
@setting.save(
success: =>
done: =>
App.Event.trigger 'notify', {
type: 'success'

View file

@ -282,7 +282,7 @@ class App.TicketCreate extends App.Controller
@formDisable(e)
ui = @
object.save(
success: ->
done: ->
# notify UI
ui.notify
@ -306,8 +306,7 @@ class App.TicketCreate extends App.Controller
# if not, show start screen
ui.navigate "#"
error: ->
fail: ->
ui.log 'save failed!'
ui.formEnable(e)
)
@ -357,7 +356,7 @@ class UserNew extends App.ControllerModal
# save user
ui = @
user.save(
success: ->
done: ->
# force to reload object
callbackReload = (user) ->
@ -370,7 +369,7 @@ class UserNew extends App.ControllerModal
ui.modalHide()
App.User.retrieve( @id, callbackReload , true )
error: ->
fail: ->
ui.modalHide()
)

View file

@ -178,12 +178,12 @@ class Index extends App.ControllerContent
@formDisable(e)
ui = @
object.save(
success: ->
done: ->
# redirect to zoom
ui.navigate '#ticket/zoom/' + this.id
error: ->
fail: ->
ui.log 'CustomerTicketCreate', 'error', 'can not create'
ui.formEnable(e)
)

View file

@ -94,7 +94,7 @@ class Index extends App.ControllerContent
# save user
user.save(
success: (r) =>
done: (r) =>
if @master_user
@master_user = false
@ -123,7 +123,7 @@ class Index extends App.ControllerContent
# rerender page
@render()
error: (data) ->
fail: (data) ->
App.Event.trigger 'notify', {
type: 'error'

View file

@ -62,7 +62,7 @@ class Index extends App.ControllerContent
# save user
user.save(
success: (r) =>
done: (r) =>
App.Auth.login(
data:
username: @params.login
@ -70,7 +70,7 @@ class Index extends App.ControllerContent
success: @success
error: @error
)
# error: =>
# fail: =>
# @modalHide()
)

View file

@ -311,7 +311,7 @@ class Table extends App.ControllerContent
ticket.load(ticket_update)
ticket.save(
success: (r) =>
done: (r) =>
@bulk_count_index++
# refresh view after all tickets are proceeded
@ -501,7 +501,7 @@ class Settings extends App.ControllerModal
@overview.view[@view_mode] = params['attributes']
@overview.save(
success: =>
done: =>
if @reload_needed
@overview.trigger('local:refetch')
else

View file

@ -530,17 +530,17 @@ class Edit extends App.Controller
return
ticket.save(
success: (r) =>
done: (r) =>
# reset form after save
if article
article.save(
success: (r) =>
done: (r) =>
@ui.fetch( ticket.id, true )
# reset form after save
App.TaskManager.update( @task_key, { 'state': {} })
error: (r) =>
fail: (r) =>
@log 'error', 'update article', r
)
else

View file

@ -80,10 +80,10 @@ class App.WidgetTemplate extends App.ControllerDrox
else
ui = @
template.save(
success: ->
done: ->
ui.template_id = @.id
ui.render()
error: =>
fail: =>
@log 'error', 'save failed!'
)

View file

@ -295,12 +295,12 @@ class App.WidgetTextModuleOld extends App.Controller
else
ui = @
text_module.save(
success: ->
done: ->
ui.el.find('#text_module_name').val('')
ui.renderTable()
ui.log 'save success!'
error: ->
fail: ->
ui.log 'save failed!'
)

View file

@ -138,7 +138,7 @@ class _taskManagerSingleton extends App.Controller
# save new task and update task collection
ui = @
task.save(
success: ->
done: ->
for taskPosition of ui.allTasks
if ui.allTasks[taskPosition] && ui.allTasks[taskPosition]['key'] is @key
task = @attributes()
@ -338,10 +338,10 @@ class _taskManagerSingleton extends App.Controller
if taskUpdate.isOnline()
ui = @
taskUpdate.save(
success: ->
done: ->
if ui.tasksToUpdate[ @key ] is 'inProgress'
delete ui.tasksToUpdate[ @key ]
error: ->
fail: ->
ui.log 'error', "can't update task", @
if ui.tasksToUpdate[ @key ] is 'inProgress'
delete ui.tasksToUpdate[ @key ]

View 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

View file

@ -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

View 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

View 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

View file

@ -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
*/

View file

@ -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"
}

View 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

View file

@ -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
*/

View file

@ -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"
}

View 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

View file

@ -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
*/

View file

@ -1,10 +0,0 @@
{
"version": 3,
"file": "manager.js",
"sourceRoot": "..",
"sources": [
"src/manager.coffee"
],
"names": [],
"mappings": "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"
}

View 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

View file

@ -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
*/

View file

@ -1,10 +0,0 @@
{
"version": 3,
"file": "relation.js",
"sourceRoot": "..",
"sources": [
"src/relation.coffee"
],
"names": [],
"mappings": "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"
}

View 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

View file

@ -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
*/

View file

@ -1,10 +0,0 @@
{
"version": 3,
"file": "route.js",
"sourceRoot": "..",
"sources": [
"src/route.coffee"
],
"names": [],
"mappings": "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"
}

View 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

View file

@ -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);

View file

@ -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);