(function(){ var Spine; if (typeof exports !== "undefined") { Spine = exports; } else { Spine = this.Spine = {}; } Spine.version = "0.0.4"; var $ = Spine.$ = this.jQuery || this.Zepto || function(){ return arguments[0]; }; var makeArray = Spine.makeArray = function(args){ return Array.prototype.slice.call(args, 0); }; var isArray = Spine.isArray = function(value){ return Object.prototype.toString.call(value) == "[object Array]"; }; // Shim Array, as these functions aren't in IE if (typeof Array.prototype.indexOf === "undefined") Array.prototype.indexOf = function(value){ for ( var i = 0; i < this.length; i++ ) if ( this[ i ] === value ) return i; return -1; }; var Events = Spine.Events = { bind: function(ev, callback) { var evs = ev.split(" "); var calls = this._callbacks || (this._callbacks = {}); for (var i=0; i < evs.length; i++) (this._callbacks[evs[i]] || (this._callbacks[evs[i]] = [])).push(callback); return this; }, trigger: function() { var args = makeArray(arguments); var ev = args.shift(); var list, calls, i, l; if (!(calls = this._callbacks)) return false; if (!(list = this._callbacks[ev])) return false; for (i = 0, l = list.length; i < l; i++) if (list[i].apply(this, args) === false) break; return true; }, unbind: function(ev, callback){ if ( !ev ) { this._callbacks = {}; return this; } var list, calls, i, l; if (!(calls = this._callbacks)) return this; if (!(list = this._callbacks[ev])) return this; if ( !callback ) { delete this._callbacks[ev]; return this; } for (i = 0, l = list.length; i < l; i++) if (callback === list[i]) { list.splice(i, 1); break; } return this; } }; var Log = Spine.Log = { trace: true, logPrefix: "(App)", log: function(){ if ( !this.trace ) return; if (typeof console == "undefined") return; var args = makeArray(arguments); if (this.logPrefix) args.unshift(this.logPrefix); console.log.apply(console, args); return this; } }; // Classes (or prototypial inheritors) if (typeof Object.create !== "function") Object.create = function(o) { function F() {} F.prototype = o; return new F(); }; var moduleKeywords = ["included", "extended"]; var Class = Spine.Class = { inherited: function(){}, created: function(){}, prototype: { initialize: function(){}, init: function(){} }, create: function(include, extend){ var object = Object.create(this); object.parent = this; object.prototype = object.fn = Object.create(this.prototype); if (include) object.include(include); if (extend) object.extend(extend); object.created(); this.inherited(object); return object; }, init: function(){ var instance = Object.create(this.prototype); instance.parent = this; instance.initialize.apply(instance, arguments); instance.init.apply(instance, arguments); return instance; }, proxy: function(func){ var thisObject = this; return(function(){ return func.apply(thisObject, arguments); }); }, proxyAll: function(){ var functions = makeArray(arguments); for (var i=0; i < functions.length; i++) this[functions[i]] = this.proxy(this[functions[i]]); }, include: function(obj){ for(var key in obj) if (moduleKeywords.indexOf(key) == -1) this.fn[key] = obj[key]; var included = obj.included; if (included) included.apply(this); return this; }, extend: function(obj){ for(var key in obj) if (moduleKeywords.indexOf(key) == -1) this[key] = obj[key]; var extended = obj.extended; if (extended) extended.apply(this); return this; } }; Class.prototype.proxy = Class.proxy; Class.prototype.proxyAll = Class.proxyAll; Class.instancet = Class.init; Class.sub = Class.create; // Models Spine.guid = function(){ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }).toUpperCase(); }; var Model = Spine.Model = Class.create(); Model.extend(Events); Model.extend({ setup: function(name, atts){ var model = Model.sub(); if (name) model.name = name; if (atts) model.attributes = atts; return model; }, created: function(sub){ this.records = {}; this.attributes = this.attributes ? makeArray(this.attributes) : []; }, find: function(id){ var record = this.records[id]; if ( !record ) throw("Unknown record"); return record.clone(); }, exists: function(id){ try { return this.find(id); } catch (e) { return false; } }, refresh: function(values){ values = this.fromJSON(values); this.records = {}; for (var i=0, il = values.length; i < il; i++) { var record = values[i]; record.newRecord = false; record.id = record.id || Spine.guid(); this.records[record.id] = record; } this.trigger("refresh"); return this; }, // adeed by ayush createAll: function(values){ values = this.fromJSON(values); for (var i=0, il = values.length; i < il; i++) { var record = values[i]; record.newRecord = false; record.id = record.id || Spine.guid(); this.records[record.id] = record; } return this; }, select: function(callback){ var result = []; for (var key in this.records) if (callback(this.records[key])) result.push(this.records[key]); return this.cloneArray(result); }, findByAttribute: function(name, value){ for (var key in this.records) if (this.records[key][name] == value) return this.records[key].clone(); }, findAllByAttribute: function(name, value){ return(this.select(function(item){ return(item[name] == value); })); }, each: function(callback){ for (var key in this.records) callback(this.records[key]); }, all: function(){ return this.cloneArray(this.recordsValues()); }, first: function(){ var record = this.recordsValues()[0]; return(record && record.clone()); }, last: function(){ var values = this.recordsValues() var record = values[values.length - 1]; return(record && record.clone()); }, count: function(){ return this.recordsValues().length; }, deleteAll: function(){ for (var key in this.records) delete this.records[key]; }, destroyAll: function(){ for (var key in this.records) this.records[key].destroy(); }, update: function(id, atts){ this.find(id).updateAttributes(atts); }, create: function(atts){ var record = this.init(atts); return record.save(); }, destroy: function(id){ this.find(id).destroy(); }, sync: function(callback){ this.bind("change", callback); }, fetch: function(callbackOrParams){ typeof(callbackOrParams) == "function" ? this.bind("fetch", callbackOrParams) : this.trigger("fetch", callbackOrParams); }, toJSON: function(){ return this.recordsValues(); }, fromJSON: function(objects){ if ( !objects ) return; if ( typeof objects == "string" ) objects = JSON.parse(objects) if ( isArray(objects) ) { var results = []; for (var i=0; i < objects.length; i++) results.push(this.init(objects[i])); return results; } else { return this.init(objects); } }, // Private recordsValues: function(){ var result = []; for (var key in this.records) result.push(this.records[key]); return result; }, cloneArray: function(array){ var result = []; for (var i=0; i < array.length; i++) result.push(array[i].clone()); return result; }, // added by ayush logAll: function() { for(var i = 0; i < this.all().length; i++) { var e = this.all()[i]; if(window.console) console.log(e.toString()); } } }); Model.include({ model: true, newRecord: true, init: function(atts){ if (atts) this.load(atts); this.trigger("init", this); }, isNew: function(){ return this.newRecord; }, isValid: function(){ return(!this.validate()); }, validate: function(){ }, load: function(atts){ for(var name in atts) this[name] = atts[name]; }, attributes: function(){ var result = {}; for (var i=0; i < this.parent.attributes.length; i++) { var attr = this.parent.attributes[i]; result[attr] = this[attr]; } result.id = this.id; return result; }, eql: function(rec){ return(rec && rec.id === this.id && rec.parent === this.parent); }, save: function(){ var error = this.validate(); if ( error ) { this.trigger("error", this, error); return false; } this.trigger("beforeSave", this); this.newRecord ? this.create() : this.update(); this.trigger("save", this); return this; }, updateAttribute: function(name, value){ this[name] = value; return this.save(); }, updateAttributes: function(atts){ this.load(atts); return this.save(); }, destroy: function(){ this.trigger("beforeDestroy", this); delete this.parent.records[this.id]; this.destroyed = true; this.trigger("destroy", this); this.trigger("change", this, "destroy"); }, dup: function(){ var result = this.parent.init(this.attributes()); result.newRecord = this.newRecord; return result; }, clone: function(){ return Object.create(this); }, reload: function(){ if ( this.newRecord ) return this; var original = this.parent.find(this.id); this.load(original.attributes()); return original; }, toJSON: function(){ return(this.attributes()); }, exists: function(){ return(this.id && this.id in this.parent.records); }, // Private update: function(){ this.trigger("beforeUpdate", this); var records = this.parent.records; records[this.id].load(this.attributes()); var clone = records[this.id].clone(); this.trigger("update", clone); this.trigger("change", clone, "update"); }, create: function(){ this.trigger("beforeCreate", this); if ( !this.id ) this.id = Spine.guid(); this.newRecord = false; var records = this.parent.records; records[this.id] = this.dup(); var clone = records[this.id].clone(); this.trigger("create", clone); this.trigger("change", clone, "create"); }, bind: function(events, callback){ return this.parent.bind(events, this.proxy(function(record){ if ( record && this.eql(record) ) callback.apply(this, arguments); })); }, trigger: function(){ return this.parent.trigger.apply(this.parent, arguments); } }); // Controllers var eventSplitter = /^(\w+)\s*(.*)$/; var Controller = Spine.Controller = Class.create({ tag: "div", initialize: function(options){ this.options = options; for (var key in this.options) this[key] = this.options[key]; if (!this.el) this.el = document.createElement(this.tag); this.el = $(this.el); if ( !this.events ) this.events = this.parent.events; if ( !this.elements ) this.elements = this.parent.elements; if (this.events) this.delegateEvents(); if (this.elements) this.refreshElements(); if (this.proxied) this.proxyAll.apply(this, this.proxied); }, $: function(selector){ return $(selector, this.el); }, delegateEvents: function(){ for (var key in this.events) { var methodName = this.events[key]; var method = this.proxy(this[methodName]); var match = key.match(eventSplitter); var eventName = match[1], selector = match[2]; if (selector === '') { this.el.bind(eventName, method); } else { this.el.delegate(selector, eventName, method); } } }, refreshElements: function(){ for (var key in this.elements) { this[this.elements[key]] = this.$(key); } }, delay: function(func, timeout){ setTimeout(this.proxy(func), timeout || 0); } }); Controller.include(Events); Controller.include(Log); Spine.App = Class.create(); Spine.App.extend(Events); Controller.fn.App = Spine.App; })();