Moved to spine.js files.

This commit is contained in:
Martin Edenhofer 2012-05-22 22:02:17 +02:00
parent 95d2644b0f
commit 940811a377
19 changed files with 1860 additions and 1222 deletions

View file

@ -2,9 +2,9 @@
#= require ./lib/jquery-1.7.2.min.js #= require ./lib/jquery-1.7.2.min.js
#= require ./lib/ui/jquery-ui-1.8.18.custom.min.js #= require ./lib/ui/jquery-ui-1.8.18.custom.min.js
#= require ./lib/spine/spine.coffee #= require ./lib/spine/spine.js
#= require ./lib/spine/ajax.coffee #= require ./lib/spine/ajax.js
#= require ./lib/spine/route.coffee #= require ./lib/spine/route.js
#= require ./lib/bootstrap-dropdown.js #= require ./lib/bootstrap-dropdown.js
#= require ./lib/bootstrap-tooltip.js #= require ./lib/bootstrap-tooltip.js

View file

@ -1,206 +0,0 @@
Spine ?= require('spine')
$ = Spine.$
Model = Spine.Model
Ajax =
getURL: (object) ->
object and object.url?() or object.url
enabled: true
pending: false
requests: []
disable: (callback) ->
if @enabled
@enabled = false
do callback
@enabled = true
else
do callback
requestNext: ->
next = @requests.shift()
if next
@request(next)
else
@pending = false
request: (callback) ->
(do callback).complete(=> do @requestNext)
queue: (callback) ->
return unless @enabled
if @pending
@requests.push(callback)
else
@pending = true
@request(callback)
callback
class Base
defaults:
contentType: 'application/json'
dataType: 'json'
processData: false
headers: {'X-Requested-With': 'XMLHttpRequest'}
ajax: (params, defaults) ->
$.ajax($.extend({}, @defaults, defaults, params))
queue: (callback) ->
Ajax.queue(callback)
class Collection extends Base
constructor: (@model) ->
find: (id, params) ->
record = new @model(id: id)
@ajax(
params,
type: 'GET',
url: Ajax.getURL(record)
).success(@recordsResponse)
.error(@errorResponse)
all: (params) ->
@ajax(
params,
type: 'GET',
url: Ajax.getURL(@model)
).success(@recordsResponse)
.error(@errorResponse)
fetch: (params = {}, options = {}) ->
if id = params.id
delete params.id
@find(id, params).success (record) =>
@model.refresh(record, options)
else
@all(params).success (records) =>
@model.refresh(records, options)
# Private
recordsResponse: (data, status, xhr) =>
@model.trigger('ajaxSuccess', null, status, xhr)
errorResponse: (xhr, statusText, error) =>
@model.trigger('ajaxError', null, xhr, statusText, error)
class Singleton extends Base
constructor: (@record) ->
@model = @record.constructor
reload: (params, options) ->
@queue =>
@ajax(
params,
type: 'GET'
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
create: (params, options) ->
@queue =>
@ajax(
params,
type: 'POST'
data: JSON.stringify(@record)
url: Ajax.getURL(@model)
).success(@recordResponse(options))
.error(@errorResponse(options))
update: (params, options) ->
@queue =>
@ajax(
params,
type: 'PUT'
data: JSON.stringify(@record)
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
destroy: (params, options) ->
@queue =>
@ajax(
params,
type: 'DELETE'
url: Ajax.getURL(@record)
).success(@recordResponse(options))
.error(@errorResponse(options))
# Private
recordResponse: (options = {}) =>
(data, status, xhr) =>
if Spine.isBlank(data)
data = false
else
data = @model.fromJSON(data)
Ajax.disable =>
if data
# 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.updateAttributes(data.attributes())
@record.trigger('ajaxSuccess', data, status, xhr)
# 2012-02-23 me
# options.success?.apply(@record)
options.success?(@record)
errorResponse: (options = {}) =>
(xhr, statusText, error) =>
@record.trigger('ajaxError', xhr, statusText, error)
options.error?.apply(@record)
# Ajax endpoint
Model.host = ''
Include =
ajax: -> new Singleton(this)
url: (args...) ->
url = Ajax.getURL(@constructor)
url += '/' unless url.charAt(url.length - 1) is '/'
url += encodeURIComponent(@id)
args.unshift(url)
args.join('/')
Extend =
ajax: -> new Collection(this)
url: (args...) ->
args.unshift(@className.toLowerCase() + 's')
args.unshift(Model.host)
args.join('/')
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
Spine.Ajax = Ajax
module?.exports = Ajax

View file

@ -0,0 +1,290 @@
(function() {
var $, Ajax, Base, Collection, Extend, Include, Model, Singleton,
__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; },
__slice = Array.prototype.slice;
if (typeof Spine === "undefined" || Spine === null) Spine = require('spine');
$ = Spine.$;
Model = Spine.Model;
Ajax = {
getURL: function(object) {
return object && (typeof object.url === "function" ? object.url() : void 0) || object.url;
},
enabled: true,
pending: false,
requests: [],
disable: function(callback) {
if (this.enabled) {
this.enabled = false;
callback();
return this.enabled = true;
} else {
return callback();
}
},
requestNext: function() {
var next;
next = this.requests.shift();
if (next) {
return this.request(next);
} else {
return this.pending = false;
}
},
request: function(callback) {
var _this = this;
return (callback()).complete(function() {
return _this.requestNext();
});
},
queue: function(callback) {
if (!this.enabled) return;
if (this.pending) {
this.requests.push(callback);
} else {
this.pending = true;
this.request(callback);
}
return callback;
}
};
Base = (function() {
function Base() {}
Base.prototype.defaults = {
contentType: 'application/json',
dataType: 'json',
processData: false,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
};
Base.prototype.ajax = function(params, defaults) {
return $.ajax($.extend({}, this.defaults, defaults, params));
};
Base.prototype.queue = function(callback) {
return Ajax.queue(callback);
};
return Base;
})();
Collection = (function(_super) {
__extends(Collection, _super);
function Collection(model) {
this.model = model;
this.errorResponse = __bind(this.errorResponse, this);
this.recordsResponse = __bind(this.recordsResponse, this);
}
Collection.prototype.find = function(id, params) {
var record;
record = new this.model({
id: id
});
return this.ajax(params, {
type: 'GET',
url: Ajax.getURL(record)
}).success(this.recordsResponse).error(this.errorResponse);
};
Collection.prototype.all = function(params) {
return this.ajax(params, {
type: 'GET',
url: Ajax.getURL(this.model)
}).success(this.recordsResponse).error(this.errorResponse);
};
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).success(function(record) {
return _this.model.refresh(record, options);
});
} else {
return this.all(params).success(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.errorResponse = 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.errorResponse = __bind(this.errorResponse, this);
this.recordResponse = __bind(this.recordResponse, this);
this.model = this.record.constructor;
}
Singleton.prototype.reload = function(params, options) {
var _this = this;
return this.queue(function() {
return _this.ajax(params, {
type: 'GET',
url: Ajax.getURL(_this.record)
}).success(_this.recordResponse(options)).error(_this.errorResponse(options));
});
};
Singleton.prototype.create = function(params, options) {
var _this = this;
return this.queue(function() {
return _this.ajax(params, {
type: 'POST',
data: JSON.stringify(_this.record),
url: Ajax.getURL(_this.model)
}).success(_this.recordResponse(options)).error(_this.errorResponse(options));
});
};
Singleton.prototype.update = function(params, options) {
var _this = this;
return this.queue(function() {
return _this.ajax(params, {
type: 'PUT',
data: JSON.stringify(_this.record),
url: Ajax.getURL(_this.record)
}).success(_this.recordResponse(options)).error(_this.errorResponse(options));
});
};
Singleton.prototype.destroy = function(params, options) {
var _this = this;
return this.queue(function() {
return _this.ajax(params, {
type: 'DELETE',
url: Ajax.getURL(_this.record)
}).success(_this.recordResponse(options)).error(_this.errorResponse(options));
});
};
Singleton.prototype.recordResponse = function(options) {
var _this = this;
if (options == null) options = {};
return function(data, status, xhr) {
var _ref;
if (Spine.isBlank(data)) {
data = false;
} else {
data = _this.model.fromJSON(data);
}
Ajax.disable(function() {
if (data) {
if (data.id && _this.record.id !== data.id) {
_this.record.changeID(data.id);
}
return _this.record.updateAttributes(data.attributes());
}
});
_this.record.trigger('ajaxSuccess', data, status, xhr);
return (_ref = options.success) != null ? _ref.apply(_this.record) : void 0;
};
};
Singleton.prototype.errorResponse = function(options) {
var _this = this;
if (options == null) options = {};
return function(xhr, statusText, error) {
var _ref;
_this.record.trigger('ajaxError', xhr, statusText, error);
return (_ref = options.error) != null ? _ref.apply(_this.record) : void 0;
};
};
return Singleton;
})(Base);
Model.host = '';
Include = {
ajax: function() {
return new Singleton(this);
},
url: function() {
var args, url;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
url = Ajax.getURL(this.constructor);
if (url.charAt(url.length - 1) !== '/') url += '/';
url += encodeURIComponent(this.id);
args.unshift(url);
return args.join('/');
}
};
Extend = {
ajax: function() {
return new Collection(this);
},
url: function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
args.unshift(this.className.toLowerCase() + 's');
args.unshift(Model.host);
return args.join('/');
}
};
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;
Spine.Ajax = Ajax;
if (typeof module !== "undefined" && module !== null) module.exports = Ajax;
}).call(this);

View file

@ -1,42 +0,0 @@
Spine ?= require('spine')
$ = Spine.$
class Spine.List extends Spine.Controller
events:
'click .item': 'click'
selectFirst: false
constructor: ->
super
@bind 'change', @change
template: -> arguments[0]
change: (item) =>
@current = item
unless @current
@children().removeClass('active')
return
@children().removeClass('active')
@children().forItem(@current).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 = $(e.currentTarget).item()
@trigger('change', item)
true
module?.exports = Spine.List

View file

@ -0,0 +1,70 @@
(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.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() {
return arguments[0];
};
List.prototype.change = function(item) {
this.current = item;
if (!this.current) {
this.children().removeClass('active');
return;
}
this.children().removeClass('active');
return this.children().forItem(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 = $(e.currentTarget).item();
this.trigger('change', item);
return true;
};
return List;
})(Spine.Controller);
if (typeof module !== "undefined" && module !== null) {
module.exports = Spine.List;
}
}).call(this);

View file

@ -1,16 +0,0 @@
Spine ?= require('spine')
Spine.Model.Local =
extended: ->
@change @saveLocal
@fetch @loadLocal
saveLocal: ->
result = JSON.stringify(@)
localStorage[@className] = result
loadLocal: ->
result = localStorage[@className]
@refresh(result or [], clear: true)
module?.exports = Spine.Model.Local

View file

@ -0,0 +1,28 @@
(function() {
if (typeof Spine === "undefined" || Spine === null) 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() {
var result;
result = localStorage[this.className];
return this.refresh(result || [], {
clear: true
});
}
};
if (typeof module !== "undefined" && module !== null) {
module.exports = Spine.Model.Local;
}
}).call(this);

View file

@ -1,85 +0,0 @@
Spine ?= 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.splice(@controllers.indexOf(controller), 1)
@controllers.push(controller)
deactivate: ->
@trigger('change', false, arguments...)
# Private
change: (current, args...) ->
for cont in @controllers
if cont is current
cont.activate(args...)
else
cont.deactivate(args...)
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
@manager.bind 'change', (controller, args...) =>
@active(args...) if controller
for key, value of @controllers
@[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

View file

@ -0,0 +1,154 @@
(function() {
var $,
__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; },
__slice = Array.prototype.slice;
if (typeof Spine === "undefined" || Spine === null) 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, _results;
current = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
_ref = this.controllers;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
cont = _ref[_i];
if (cont === current) {
_results.push(cont.activate.apply(cont, args));
} else {
_results.push(cont.deactivate.apply(cont, args));
}
}
return _results;
};
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, _ref2,
_this = this;
Stack.__super__.constructor.apply(this, arguments);
this.manager = new Spine.Manager;
this.manager.bind('change', function() {
var args, controller;
controller = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (controller) return _this.active.apply(_this, args);
});
_ref = this.controllers;
for (key in _ref) {
value = _ref[key];
this[key] = new value({
stack: this
});
this.add(this[key]);
}
_ref2 = this.routes;
_fn = function(key, value) {
var callback;
if (typeof value === 'function') callback = value;
callback || (callback = function() {
var _ref3;
return (_ref3 = _this[value]).active.apply(_ref3, arguments);
});
return _this.route(key, callback);
};
for (key in _ref2) {
value = _ref2[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;
}
}).call(this);

View file

@ -1,144 +0,0 @@
Spine ?= require('spine')
isArray = Spine.isArray
require ?= ((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]
find: (id) ->
records = @select (rec) =>
rec.id + '' is id + ''
throw('Unknown record') unless records[0]
records[0]
findAllByAttribute: (name, value) ->
@model.select (rec) =>
rec[name] is value
findByAttribute: (name, value) ->
@findAllByAttribute(name, value)[0]
select: (cb) ->
@model.select (rec) =>
@associated(rec) and cb(rec)
refresh: (values) ->
delete @model.records[record.id] for record in @all()
records = @model.fromJSON(values)
records = [records] unless isArray(records)
for record in records
record.newRecord = false
record[@fkey] = @record.id
@model.records[record.id] = record
@model.trigger('refresh', records)
create: (record) ->
record[@fkey] = @record.id
@model.create(record)
# Private
associated: (record) ->
record[@fkey] is @record.id
class Instance extends Spine.Module
constructor: (options = {}) ->
for key, value of options
@[key] = value
exists: ->
@record[@fkey] and @model.exists(@record[@fkey])
update: (value) ->
unless value instanceof @model
value = new @model(value)
value.save() if value.isNew()
@record[@fkey] = value and value.id
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) ->
unless value instanceof @model
value = @model.fromJSON(value)
value[@fkey] = @record.id
value.save()
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()
Spine.Model.extend
hasMany: (name, model, fkey) ->
fkey ?= "#{underscore(this.className)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Collection(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).refresh(value) if value?
association(@)
belongsTo: (name, model, fkey) ->
fkey ?= "#{singularize(name)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Instance(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).exists()
@attributes.push(fkey)
hasOne: (name, model, fkey) ->
fkey ?= "#{underscore(@className)}_id"
association = (record) ->
model = require(model) if typeof model is 'string'
new Singleton(
name: name, model: model,
record: record, fkey: fkey
)
@::[name] = (value) ->
association(@).update(value) if value?
association(@).find()

View file

@ -0,0 +1,222 @@
(function() {
var Collection, Instance, Singleton, isArray, singularize, underscore,
__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');
isArray = Spine.isArray;
if (typeof require === "undefined" || require === null) {
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.find = function(id) {
var records,
_this = this;
records = this.select(function(rec) {
return rec.id + '' === id + '';
});
if (!records[0]) throw 'Unknown record';
return records[0];
};
Collection.prototype.findAllByAttribute = function(name, value) {
var _this = this;
return this.model.select(function(rec) {
return 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 record, records, _i, _j, _len, _len2, _ref;
_ref = this.all();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
record = _ref[_i];
delete this.model.records[record.id];
}
records = this.model.fromJSON(values);
if (!isArray(records)) records = [records];
for (_j = 0, _len2 = records.length; _j < _len2; _j++) {
record = records[_j];
record.newRecord = false;
record[this.fkey] = this.record.id;
this.model.records[record.id] = record;
}
return this.model.trigger('refresh', records);
};
Collection.prototype.create = function(record) {
record[this.fkey] = this.record.id;
return this.model.create(record);
};
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() {
return this.record[this.fkey] && this.model.exists(this.record[this.fkey]);
};
Instance.prototype.update = function(value) {
if (!(value instanceof this.model)) value = new this.model(value);
if (value.isNew()) value.save();
return this.record[this.fkey] = value && value.id;
};
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 instanceof this.model)) value = this.model.fromJSON(value);
value[this.fkey] = this.record.id;
return value.save();
};
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();
};
Spine.Model.extend({
hasMany: function(name, model, fkey) {
var association;
if (fkey == null) fkey = "" + (underscore(this.className)) + "_id";
association = function(record) {
if (typeof model === 'string') model = require(model);
return new Collection({
name: name,
model: model,
record: record,
fkey: fkey
});
};
return this.prototype[name] = function(value) {
if (value != null) association(this).refresh(value);
return association(this);
};
},
belongsTo: function(name, model, fkey) {
var association;
if (fkey == null) fkey = "" + (singularize(name)) + "_id";
association = function(record) {
if (typeof model === 'string') model = require(model);
return new Instance({
name: name,
model: model,
record: record,
fkey: fkey
});
};
this.prototype[name] = function(value) {
if (value != null) association(this).update(value);
return association(this).exists();
};
return this.attributes.push(fkey);
},
hasOne: function(name, model, fkey) {
var association;
if (fkey == null) fkey = "" + (underscore(this.className)) + "_id";
association = function(record) {
if (typeof model === 'string') model = require(model);
return new Singleton({
name: name,
model: model,
record: record,
fkey: fkey
});
};
return this.prototype[name] = function(value) {
if (value != null) association(this).update(value);
return association(this).find();
};
}
});
}).call(this);

View file

@ -1,147 +0,0 @@
Spine ?= 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
@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 && @options.history
return if @options.shim
if @history
$(window).bind('popstate', @change)
else
$(window).bind('hashchange', @change)
@change()
@unbind: ->
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)
@matchRoute(@path, options) if options.trigger
return if options.shim
if @history
history.pushState(
{},
document.title,
@path
)
else
window.location.hash = @path
# Private
@getPath: ->
path = window.location.pathname
if path.substr(0,1) isnt '/'
path = '/' + path
path
@getHash: -> window.location.hash
@getFragment: -> @getHash().replace(hashStrip, '')
@getHost: ->
(document.location + '').replace(@getPath() + @getHash(), '')
@change: ->
# change me 2012-02-15
# path = if @getFragment() isnt '' then @getFragment() else @getPath()
path = if @getFragment() isnt 'workaround' then @getFragment() else @getPath()
return if path is @path
@path = path
@matchRoute(@path)
@matchRoute: (path, options) ->
for route in @routes
if route.match(path, options)
@trigger('change', route, path)
return route
constructor: (@path, @callback) ->
@names = []
if typeof path is 'string'
namedParam.lastIndex = 0
while (match = namedParam.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
@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

@ -0,0 +1,197 @@
(function() {
var $, escapeRegExp, hashStrip, namedParam, splatParam,
__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; },
__slice = Array.prototype.slice;
if (typeof Spine === "undefined" || Spine === null) 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
};
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.history) {
return $(window).unbind('popstate', this.change);
} else {
return $(window).unbind('hashchange', this.change);
}
};
Route.navigate = function() {
var args, lastArg, options, path;
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) this.matchRoute(this.path, options);
if (options.shim) return;
if (this.history) {
return history.pushState({}, document.title, this.path);
} else {
return window.location.hash = this.path;
}
};
Route.getPath = function() {
var path;
path = window.location.pathname;
if (path.substr(0, 1) !== '/') path = '/' + path;
return path;
};
Route.getHash = function() {
return window.location.hash;
};
Route.getFragment = function() {
return this.getHash().replace(hashStrip, '');
};
Route.getHost = function() {
return (document.location + '').replace(this.getPath() + this.getHash(), '');
};
Route.change = function() {
var path;
path = this.getFragment() !== '' ? this.getFragment() : this.getPath();
if (path === this.path) return;
this.path = path;
return this.matchRoute(this.path);
};
Route.matchRoute = function(path, options) {
var route, _i, _len, _ref2;
_ref2 = this.routes;
for (_i = 0, _len = _ref2.length; _i < _len; _i++) {
route = _ref2[_i];
if (route.match(path, options)) {
this.trigger('change', route, path);
return route;
}
}
};
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]);
}
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, _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 = 0, _len = params.length; i < _len; 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);

View file

@ -1,528 +0,0 @@
Events =
bind: (ev, callback) ->
evs = ev.split(' ')
calls = @hasOwnProperty('_callbacks') and @_callbacks or= {}
for name in evs
calls[name] or= []
calls[name].push(callback)
this
one: (ev, callback) ->
@bind ev, ->
@unbind(ev, arguments.callee)
callback.apply(@, arguments)
trigger: (args...) ->
ev = args.shift()
list = @hasOwnProperty('_callbacks') and @_callbacks?[ev]
return unless list
for callback in list
if callback.apply(@, args) is false
break
true
unbind: (ev, callback) ->
unless ev
@_callbacks = {}
return this
list = @_callbacks?[ev]
return this unless list
unless callback
delete @_callbacks[ev]
return this
for cb, i in list when cb is callback
list = list.slice()
list.splice(i, 1)
@_callbacks[ev] = list
break
this
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('include(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@::[key] = value
obj.included?.apply(@)
this
@extend: (obj) ->
throw('extend(obj) requires obj') unless obj
for key, value of obj when key not in moduleKeywords
@[key] = value
obj.extended?.apply(@)
this
@proxy: (func) ->
=> func.apply(@, arguments)
proxy: (func) ->
=> func.apply(@, arguments)
constructor: ->
@init?(arguments...)
class Model extends Module
@extend Events
@records: {}
@crecords: {}
@attributes: []
@configure: (name, attributes...) ->
@className = name
@records = {}
@crecords = {}
@attributes = attributes if attributes.length
@attributes and= makeArray(@attributes)
@attributes or= []
@unbind()
this
@toString: -> "#{@className}(#{@attributes.join(", ")})"
@find: (id) ->
record = @records[id]
if !record and ("#{id}").match(/c-\d+/)
return @findCID(id)
throw('Unknown record'+id+@className) unless record
record.clone()
@findCID: (cid) ->
record = @crecords[cid]
throw('Unknown record') unless record
record.clone()
@exists: (id) ->
try
return @find(id)
catch e
return false
@refresh: (values, options = {}) ->
if options.clear
@records = {}
@crecords = {}
records = @fromJSON(values)
records = [records] unless isArray(records)
for record in records
record.id or= record.cid
@records[record.id] = record
@crecords[record.cid] = record
@trigger('refresh', not options.clear and @cloneArray(records))
this
@select: (callback) ->
result = (record for id, record of @records when callback(record))
@cloneArray(result)
@findByAttribute: (name, value) ->
for id, record of @records
if record[name] is value
return record.clone()
null
@findAllByAttribute: (name, value) ->
@select (item) ->
item[name] is value
@each: (callback) ->
for key, value of @records
callback(value.clone())
@all: ->
@cloneArray(@recordsValues())
@first: ->
record = @recordsValues()[0]
record?.clone()
@last: ->
values = @recordsValues()
record = values[values.length - 1]
record?.clone()
@count: ->
@recordsValues().length
@deleteAll: ->
for key, value of @records
delete @records[key]
@destroyAll: ->
for key, value of @records
@records[key].destroy()
@update: (id, atts, 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', callbackOrParams)
@fetch: (callbackOrParams) ->
if typeof callbackOrParams is 'function'
@bind('fetch', callbackOrParams)
else
@trigger('fetch', callbackOrParams)
@toJSON: ->
@recordsValues()
@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...)
# Private
@recordsValues: ->
result = []
for key, value of @records
result.push(value)
result
@cloneArray: (array) ->
(value.clone() for value in array)
@idCounter: 0
@uid: ->
@idCounter++
# Instance
constructor: (atts) ->
super
@load atts if atts
@cid or= 'c-' + @constructor.uid()
isNew: ->
not @exists()
isValid: ->
not @validate()
validate: ->
load: (atts) ->
for key, value of atts
if 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.id and rec.id is @id) or rec.cid is @cid))
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)
@trigger('save', options)
record
updateAttribute: (name, value) ->
@[name] = value
@save()
updateAttributes: (atts, options) ->
@load(atts)
@save(options)
changeID: (id) ->
records = @constructor.records
records[id] = records[@id]
delete records[@id]
@id = id
@save()
destroy: (options = {}) ->
@trigger('beforeDestroy', options)
delete @constructor.records[@id]
delete @constructor.crecords[@cid]
@destroyed = true
@trigger('destroy', options)
@trigger('change', 'destroy', options)
@unbind()
this
dup: (newRecord) ->
result = new @constructor(@attributes())
if newRecord is false
result.cid = @cid
else
delete result.id
result
clone: ->
Object.create(@)
reload: ->
return this if @isNew()
original = @constructor.find(@id)
@load(original.attributes())
original
toJSON: ->
@attributes()
toString: ->
"<#{@constructor.className} (#{JSON.stringify(@)})>"
fromForm: (form) ->
result = {}
for key in $(form).serializeArray()
result[key.name] = key.value
@load(result)
exists: ->
@id && @id of @constructor.records
# Private
update: (options) ->
@trigger('beforeUpdate', options)
records = @constructor.records
records[@id].load @attributes()
clone = records[@id].clone()
clone.trigger('update', options)
clone.trigger('change', 'update', options)
clone
create: (options) ->
@trigger('beforeCreate', options)
@id = @cid unless @id
record = @dup(false)
@constructor.records[@id] = record
@constructor.crecords[@cid] = record
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(@, arguments)
@constructor.bind 'unbind', unbinder = (record) =>
if record && @eql(record)
@constructor.unbind(events, binder)
@constructor.unbind('unbind', unbinder)
binder
one: (events, callback) ->
binder = @bind events, =>
@constructor.unbind(events, binder)
callback.apply(@)
trigger: (args...) ->
args.splice(1, 0, @)
@constructor.trigger(args...)
unbind: ->
@trigger('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.addClass(@className) if @className
@el.attr(@attributes) if @attributes
@release -> @el.remove()
@events = @constructor.events unless @events
@elements = @constructor.elements unless @elements
@delegateEvents() if @events
@refreshElements() if @elements
super
release: (callback) =>
if typeof callback is 'function'
@bind 'release', callback
else
@trigger 'release'
$: (selector) -> $(selector, @el)
delegateEvents: ->
for key, method of @events
unless typeof(method) is 'function'
method = @proxy(@[method])
match = key.match(@eventSplitter)
eventName = match[1]
selector = match[2]
if selector is ''
@el.bind(eventName, method)
else
@el.delegate(selector, eventName, method)
refreshElements: ->
for key, value of @elements
@[value] = @$(key)
delay: (func, timeout) ->
setTimeout(@proxy(func), timeout || 0)
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) ->
[previous, @el] = [@el, $(element.el or element)]
previous.replaceWith(@el)
@delegateEvents()
@refreshElements()
@el
# Utilities & Shims
$ = window?.jQuery or window?.Zepto or (element) -> element
unless typeof Object.create is 'function'
Object.create = (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.prototype.slice.call(args, 0)
# Globals
Spine = @Spine = {}
module?.exports = Spine
Spine.version = '1.0.6'
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
Module.init = Controller.init = Model.init = (a1, a2, a3, a4, a5) ->
new this(a1, a2, a3, a4, a5)
Spine.Class = Module

View file

@ -0,0 +1,808 @@
(function() {
var $, Controller, Events, Log, Model, Module, Spine, isArray, isBlank, makeArray, moduleKeywords,
__slice = Array.prototype.slice,
__indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__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; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Events = {
bind: function(ev, callback) {
var calls, evs, name, _i, _len;
evs = ev.split(' ');
calls = this.hasOwnProperty('_callbacks') && this._callbacks || (this._callbacks = {});
for (_i = 0, _len = evs.length; _i < _len; _i++) {
name = evs[_i];
calls[name] || (calls[name] = []);
calls[name].push(callback);
}
return this;
},
one: function(ev, callback) {
return this.bind(ev, function() {
this.unbind(ev, arguments.callee);
return callback.apply(this, arguments);
});
},
trigger: function() {
var args, callback, ev, list, _i, _len, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
ev = args.shift();
list = this.hasOwnProperty('_callbacks') && ((_ref = this._callbacks) != null ? _ref[ev] : void 0);
if (!list) return;
for (_i = 0, _len = list.length; _i < _len; _i++) {
callback = list[_i];
if (callback.apply(this, args) === false) break;
}
return true;
},
unbind: function(ev, callback) {
var cb, i, list, _len, _ref;
if (!ev) {
this._callbacks = {};
return this;
}
list = (_ref = this._callbacks) != null ? _ref[ev] : void 0;
if (!list) return this;
if (!callback) {
delete this._callbacks[ev];
return this;
}
for (i = 0, _len = list.length; i < _len; i++) {
cb = list[i];
if (!(cb === callback)) continue;
list = list.slice();
list.splice(i, 1);
this._callbacks[ev] = list;
break;
}
return this;
}
};
Log = {
trace: true,
logPrefix: '(App)',
log: function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (!this.trace) return;
if (this.logPrefix) args.unshift(this.logPrefix);
if (typeof console !== "undefined" && console !== null) {
if (typeof console.log === "function") console.log.apply(console, args);
}
return this;
}
};
moduleKeywords = ['included', 'extended'];
Module = (function() {
Module.include = function(obj) {
var key, value, _ref;
if (!obj) throw 'include(obj) requires obj';
for (key in obj) {
value = obj[key];
if (__indexOf.call(moduleKeywords, key) < 0) this.prototype[key] = value;
}
if ((_ref = obj.included) != null) _ref.apply(this);
return this;
};
Module.extend = function(obj) {
var key, value, _ref;
if (!obj) throw 'extend(obj) requires obj';
for (key in obj) {
value = obj[key];
if (__indexOf.call(moduleKeywords, key) < 0) this[key] = value;
}
if ((_ref = obj.extended) != null) _ref.apply(this);
return this;
};
Module.proxy = function(func) {
var _this = this;
return function() {
return func.apply(_this, arguments);
};
};
Module.prototype.proxy = function(func) {
var _this = this;
return function() {
return func.apply(_this, arguments);
};
};
function Module() {
if (typeof this.init === "function") this.init.apply(this, arguments);
}
return Module;
})();
Model = (function(_super) {
__extends(Model, _super);
Model.extend(Events);
Model.records = {};
Model.crecords = {};
Model.attributes = [];
Model.configure = function() {
var attributes, name;
name = arguments[0], attributes = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
this.className = name;
this.records = {};
this.crecords = {};
if (attributes.length) this.attributes = attributes;
this.attributes && (this.attributes = makeArray(this.attributes));
this.attributes || (this.attributes = []);
this.unbind();
return this;
};
Model.toString = function() {
return "" + this.className + "(" + (this.attributes.join(", ")) + ")";
};
Model.find = function(id) {
var record;
record = this.records[id];
if (!record && ("" + id).match(/c-\d+/)) return this.findCID(id);
if (!record) throw 'Unknown record';
return record.clone();
};
Model.findCID = function(cid) {
var record;
record = this.crecords[cid];
if (!record) throw 'Unknown record';
return record.clone();
};
Model.exists = function(id) {
try {
return this.find(id);
} catch (e) {
return false;
}
};
Model.refresh = function(values, options) {
var record, records, _i, _len;
if (options == null) options = {};
if (options.clear) {
this.records = {};
this.crecords = {};
}
records = this.fromJSON(values);
if (!isArray(records)) records = [records];
for (_i = 0, _len = records.length; _i < _len; _i++) {
record = records[_i];
record.id || (record.id = record.cid);
this.records[record.id] = record;
this.crecords[record.cid] = record;
}
this.trigger('refresh', !options.clear && this.cloneArray(records));
return this;
};
Model.select = function(callback) {
var id, record, result;
result = (function() {
var _ref, _results;
_ref = this.records;
_results = [];
for (id in _ref) {
record = _ref[id];
if (callback(record)) _results.push(record);
}
return _results;
}).call(this);
return this.cloneArray(result);
};
Model.findByAttribute = function(name, value) {
var id, record, _ref;
_ref = this.records;
for (id in _ref) {
record = _ref[id];
if (record[name] === value) return record.clone();
}
return null;
};
Model.findAllByAttribute = function(name, value) {
return this.select(function(item) {
return item[name] === value;
});
};
Model.each = function(callback) {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(callback(value.clone()));
}
return _results;
};
Model.all = function() {
return this.cloneArray(this.recordsValues());
};
Model.first = function() {
var record;
record = this.recordsValues()[0];
return record != null ? record.clone() : void 0;
};
Model.last = function() {
var record, values;
values = this.recordsValues();
record = values[values.length - 1];
return record != null ? record.clone() : void 0;
};
Model.count = function() {
return this.recordsValues().length;
};
Model.deleteAll = function() {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(delete this.records[key]);
}
return _results;
};
Model.destroyAll = function() {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(this.records[key].destroy());
}
return _results;
};
Model.update = function(id, atts, options) {
return this.find(id).updateAttributes(atts, options);
};
Model.create = function(atts, options) {
var record;
record = new this(atts);
return record.save(options);
};
Model.destroy = function(id, options) {
return this.find(id).destroy(options);
};
Model.change = function(callbackOrParams) {
if (typeof callbackOrParams === 'function') {
return this.bind('change', callbackOrParams);
} else {
return this.trigger('change', callbackOrParams);
}
};
Model.fetch = function(callbackOrParams) {
if (typeof callbackOrParams === 'function') {
return this.bind('fetch', callbackOrParams);
} else {
return this.trigger('fetch', callbackOrParams);
}
};
Model.toJSON = function() {
return this.recordsValues();
};
Model.fromJSON = function(objects) {
var value, _i, _len, _results;
if (!objects) return;
if (typeof objects === 'string') objects = JSON.parse(objects);
if (isArray(objects)) {
_results = [];
for (_i = 0, _len = objects.length; _i < _len; _i++) {
value = objects[_i];
_results.push(new this(value));
}
return _results;
} else {
return new this(objects);
}
};
Model.fromForm = function() {
var _ref;
return (_ref = new this).fromForm.apply(_ref, arguments);
};
Model.recordsValues = function() {
var key, result, value, _ref;
result = [];
_ref = this.records;
for (key in _ref) {
value = _ref[key];
result.push(value);
}
return result;
};
Model.cloneArray = function(array) {
var value, _i, _len, _results;
_results = [];
for (_i = 0, _len = array.length; _i < _len; _i++) {
value = array[_i];
_results.push(value.clone());
}
return _results;
};
Model.idCounter = 0;
Model.uid = function() {
return this.idCounter++;
};
function Model(atts) {
Model.__super__.constructor.apply(this, arguments);
if (atts) this.load(atts);
this.cid || (this.cid = 'c-' + this.constructor.uid());
}
Model.prototype.isNew = function() {
return !this.exists();
};
Model.prototype.isValid = function() {
return !this.validate();
};
Model.prototype.validate = function() {};
Model.prototype.load = function(atts) {
var key, value;
for (key in atts) {
value = atts[key];
if (typeof this[key] === 'function') {
this[key](value);
} else {
this[key] = value;
}
}
return this;
};
Model.prototype.attributes = function() {
var key, result, _i, _len, _ref;
result = {};
_ref = this.constructor.attributes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
if (key in this) {
if (typeof this[key] === 'function') {
result[key] = this[key]();
} else {
result[key] = this[key];
}
}
}
if (this.id) result.id = this.id;
return result;
};
Model.prototype.eql = function(rec) {
return !!(rec && rec.constructor === this.constructor && (rec.id === this.id || rec.cid === this.cid));
};
Model.prototype.save = function(options) {
var error, record;
if (options == null) options = {};
if (options.validate !== false) {
error = this.validate();
if (error) {
this.trigger('error', error);
return false;
}
}
this.trigger('beforeSave', options);
record = this.isNew() ? this.create(options) : this.update(options);
this.trigger('save', options);
return record;
};
Model.prototype.updateAttribute = function(name, value) {
this[name] = value;
return this.save();
};
Model.prototype.updateAttributes = function(atts, options) {
this.load(atts);
return this.save(options);
};
Model.prototype.changeID = function(id) {
var records;
records = this.constructor.records;
records[id] = records[this.id];
delete records[this.id];
this.id = id;
return this.save();
};
Model.prototype.destroy = function(options) {
if (options == null) options = {};
this.trigger('beforeDestroy', options);
delete this.constructor.records[this.id];
delete this.constructor.crecords[this.cid];
this.destroyed = true;
this.trigger('destroy', options);
this.trigger('change', 'destroy', options);
this.unbind();
return this;
};
Model.prototype.dup = function(newRecord) {
var result;
result = new this.constructor(this.attributes());
if (newRecord === false) {
result.cid = this.cid;
} else {
delete result.id;
}
return result;
};
Model.prototype.clone = function() {
return Object.create(this);
};
Model.prototype.reload = function() {
var original;
if (this.isNew()) return this;
original = this.constructor.find(this.id);
this.load(original.attributes());
return original;
};
Model.prototype.toJSON = function() {
return this.attributes();
};
Model.prototype.toString = function() {
return "<" + this.constructor.className + " (" + (JSON.stringify(this)) + ")>";
};
Model.prototype.fromForm = function(form) {
var key, result, _i, _len, _ref;
result = {};
_ref = $(form).serializeArray();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
result[key.name] = key.value;
}
return this.load(result);
};
Model.prototype.exists = function() {
return this.id && this.id in this.constructor.records;
};
Model.prototype.update = function(options) {
var clone, records;
this.trigger('beforeUpdate', options);
records = this.constructor.records;
records[this.id].load(this.attributes());
clone = records[this.id].clone();
clone.trigger('update', options);
clone.trigger('change', 'update', options);
return clone;
};
Model.prototype.create = function(options) {
var clone, record;
this.trigger('beforeCreate', options);
if (!this.id) this.id = this.cid;
record = this.dup(false);
this.constructor.records[this.id] = record;
this.constructor.crecords[this.cid] = record;
clone = record.clone();
clone.trigger('create', options);
clone.trigger('change', 'create', options);
return clone;
};
Model.prototype.bind = function(events, callback) {
var binder, unbinder,
_this = this;
this.constructor.bind(events, binder = function(record) {
if (record && _this.eql(record)) return callback.apply(_this, arguments);
});
this.constructor.bind('unbind', unbinder = function(record) {
if (record && _this.eql(record)) {
_this.constructor.unbind(events, binder);
return _this.constructor.unbind('unbind', unbinder);
}
});
return binder;
};
Model.prototype.one = function(events, callback) {
var binder,
_this = this;
return binder = this.bind(events, function() {
_this.constructor.unbind(events, binder);
return callback.apply(_this);
});
};
Model.prototype.trigger = function() {
var args, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
args.splice(1, 0, this);
return (_ref = this.constructor).trigger.apply(_ref, args);
};
Model.prototype.unbind = function() {
return this.trigger('unbind');
};
return Model;
})(Module);
Controller = (function(_super) {
__extends(Controller, _super);
Controller.include(Events);
Controller.include(Log);
Controller.prototype.eventSplitter = /^(\S+)\s*(.*)$/;
Controller.prototype.tag = 'div';
function Controller(options) {
this.release = __bind(this.release, this);
var key, value, _ref;
this.options = options;
_ref = this.options;
for (key in _ref) {
value = _ref[key];
this[key] = value;
}
if (!this.el) this.el = document.createElement(this.tag);
this.el = $(this.el);
if (this.className) this.el.addClass(this.className);
if (this.attributes) this.el.attr(this.attributes);
this.release(function() {
return this.el.remove();
});
if (!this.events) this.events = this.constructor.events;
if (!this.elements) this.elements = this.constructor.elements;
if (this.events) this.delegateEvents();
if (this.elements) this.refreshElements();
Controller.__super__.constructor.apply(this, arguments);
}
Controller.prototype.release = function(callback) {
if (typeof callback === 'function') {
return this.bind('release', callback);
} else {
return this.trigger('release');
}
};
Controller.prototype.$ = function(selector) {
return $(selector, this.el);
};
Controller.prototype.delegateEvents = function() {
var eventName, key, match, method, selector, _ref, _results;
_ref = this.events;
_results = [];
for (key in _ref) {
method = _ref[key];
if (typeof method !== 'function') method = this.proxy(this[method]);
match = key.match(this.eventSplitter);
eventName = match[1];
selector = match[2];
if (selector === '') {
_results.push(this.el.bind(eventName, method));
} else {
_results.push(this.el.delegate(selector, eventName, method));
}
}
return _results;
};
Controller.prototype.refreshElements = function() {
var key, value, _ref, _results;
_ref = this.elements;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(this[value] = this.$(key));
}
return _results;
};
Controller.prototype.delay = function(func, timeout) {
return setTimeout(this.proxy(func), timeout || 0);
};
Controller.prototype.html = function(element) {
this.el.html(element.el || element);
this.refreshElements();
return this.el;
};
Controller.prototype.append = function() {
var e, elements, _ref;
elements = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
elements = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = elements.length; _i < _len; _i++) {
e = elements[_i];
_results.push(e.el || e);
}
return _results;
})();
(_ref = this.el).append.apply(_ref, elements);
this.refreshElements();
return this.el;
};
Controller.prototype.appendTo = function(element) {
this.el.appendTo(element.el || element);
this.refreshElements();
return this.el;
};
Controller.prototype.prepend = function() {
var e, elements, _ref;
elements = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
elements = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = elements.length; _i < _len; _i++) {
e = elements[_i];
_results.push(e.el || e);
}
return _results;
})();
(_ref = this.el).prepend.apply(_ref, elements);
this.refreshElements();
return this.el;
};
Controller.prototype.replace = function(element) {
var previous, _ref;
_ref = [this.el, $(element.el || element)], previous = _ref[0], this.el = _ref[1];
previous.replaceWith(this.el);
this.delegateEvents();
this.refreshElements();
return this.el;
};
return Controller;
})(Module);
$ = (typeof window !== "undefined" && window !== null ? window.jQuery : void 0) || (typeof window !== "undefined" && window !== null ? window.Zepto : void 0) || function(element) {
return element;
};
if (typeof Object.create !== 'function') {
Object.create = function(o) {
var Func;
Func = function() {};
Func.prototype = o;
return new Func();
};
}
isArray = function(value) {
return Object.prototype.toString.call(value) === '[object Array]';
};
isBlank = function(value) {
var key;
if (!value) return true;
for (key in value) {
return false;
}
return true;
};
makeArray = function(args) {
return Array.prototype.slice.call(args, 0);
};
Spine = this.Spine = {};
if (typeof module !== "undefined" && module !== null) module.exports = Spine;
Spine.version = '1.0.6';
Spine.isArray = isArray;
Spine.isBlank = isBlank;
Spine.$ = $;
Spine.Events = Events;
Spine.Log = Log;
Spine.Module = Module;
Spine.Controller = Controller;
Spine.Model = Model;
Module.extend.call(Spine, Events);
Module.create = Module.sub = Controller.create = Controller.sub = Model.sub = function(instances, statics) {
var result;
result = (function(_super) {
__extends(result, _super);
function result() {
result.__super__.constructor.apply(this, arguments);
}
return result;
})(this);
if (instances) result.include(instances);
if (statics) result.extend(statics);
if (typeof result.unbind === "function") result.unbind();
return result;
};
Model.setup = function(name, attributes) {
var Instance;
if (attributes == null) attributes = [];
Instance = (function(_super) {
__extends(Instance, _super);
function Instance() {
Instance.__super__.constructor.apply(this, arguments);
}
return Instance;
})(this);
Instance.configure.apply(Instance, [name].concat(__slice.call(attributes)));
return Instance;
};
Module.init = Controller.init = Model.init = function(a1, a2, a3, a4, a5) {
return new this(a1, a2, a3, a4, a5);
};
Spine.Class = Module;
}).call(this);

View file

@ -1,36 +0,0 @@
Spine ?= require('spine')
$ = Spine.$
class Spine.Tabs extends Spine.Controller
events:
'click [data-name]': 'click'
constructor: ->
super
@bind 'change', @change
change: (name) =>
return unless name
@current = name
@children().removeClass('active')
@children("[data-name=#{@current}]").addClass('active')
render: ->
@change @current
unless @children('.active').length or @current
@children(':first').click()
children: (sel) ->
@el.children(sel)
click: (e) ->
name = $(e.currentTarget).attr('data-name')
@trigger('change', name)
connect: (tabName, controller) ->
@bind 'change', (name) ->
controller.active() if name is tabName
controller.bind 'active', =>
@change tabName
module?.exports = Spine.Tabs

View file

@ -0,0 +1,66 @@
(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,15 +0,0 @@
# jQuery.tmpl.js utilities
$ = jQuery ? require("jqueryify")
$.fn.item = ->
item = $(@)
item = item.data("item") or item.tmplItem?().data
item?.reload?()
item
$.fn.forItem = (item) ->
@filter ->
compare = $(@).item()
return item.eql?(compare) or item is compare

View file

@ -0,0 +1,22 @@
(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);