Moved to spine.js files.
This commit is contained in:
parent
95d2644b0f
commit
940811a377
19 changed files with 1860 additions and 1222 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
|
290
app/assets/javascripts/app/lib/spine/ajax.js
Executable file
290
app/assets/javascripts/app/lib/spine/ajax.js
Executable 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);
|
|
@ -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
|
|
70
app/assets/javascripts/app/lib/spine/list.js
Executable file
70
app/assets/javascripts/app/lib/spine/list.js
Executable 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);
|
|
@ -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
|
|
28
app/assets/javascripts/app/lib/spine/local.js
Executable file
28
app/assets/javascripts/app/lib/spine/local.js
Executable 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);
|
|
@ -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
|
|
154
app/assets/javascripts/app/lib/spine/manager.js
Executable file
154
app/assets/javascripts/app/lib/spine/manager.js
Executable 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);
|
|
@ -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()
|
|
222
app/assets/javascripts/app/lib/spine/relation.js
Executable file
222
app/assets/javascripts/app/lib/spine/relation.js
Executable 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);
|
|
@ -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
|
|
197
app/assets/javascripts/app/lib/spine/route.js
Executable file
197
app/assets/javascripts/app/lib/spine/route.js
Executable 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);
|
|
@ -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
|
|
808
app/assets/javascripts/app/lib/spine/spine.js
Executable file
808
app/assets/javascripts/app/lib/spine/spine.js
Executable 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);
|
|
@ -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
|
|
66
app/assets/javascripts/app/lib/spine/tabs.js
Executable file
66
app/assets/javascripts/app/lib/spine/tabs.js
Executable 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);
|
|
@ -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
|
|
||||||
|
|
22
app/assets/javascripts/app/lib/spine/tmpl.js
Executable file
22
app/assets/javascripts/app/lib/spine/tmpl.js
Executable 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);
|
Loading…
Reference in a new issue