Skip to content

Instantly share code, notes, and snippets.

@DimitarChristoff
Created July 19, 2012 15:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DimitarChristoff/3144651 to your computer and use it in GitHub Desktop.
Save DimitarChristoff/3144651 to your computer and use it in GitHub Desktop.
(function(exports) {
var Epitome = {};
typeof define == "function" && define.amd ? define("epitome", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
})(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome,
eq = Epitome.isEqual = function(a, b, stack) {
stack = stack || [];
if (a === b) return a !== 0 || 1 / a == 1 / b;
if (a == null || b == null) return a === b;
var typeA = typeOf(a),
typeB = typeOf(b);
if (typeA != typeB) return !1;
switch (typeA) {
case "string":
return a == String(b);
case "number":
return a != +a ? b != +b : a == 0 ? 1 / a == 1 / b : a == +b;
case "date":
case "boolean":
return +a == +b;
case "regexp":
return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase
}
if (typeof a != "object" || typeof b != "object") return !1;
var length = stack.length;
while (length--) if (stack[length] == a) return !0;
stack.push(a);
var size = 0,
result = !0;
if (typeA == "array") {
size = a.length, result = size == b.length;
if (result) while (size--) if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break
} else {
if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return !1;
for (var key in a) if (a.hasOwnProperty(key)) {
size++;
if (!(result = b.hasOwnProperty(key) && eq(a[key], b[key], stack))) break
}
if (result) {
for (key in b) if (b.hasOwnProperty(key) && !(size--)) break;
result = !size
}
}
return stack.pop(), result
};
typeof define == "function" && define.amd ? define("epitome-isequal", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome;
Epitome.Model = new Class({
Implements: [Options, Events],
_attributes: {},
properties: {
id: {
get: function() {
var id = this._attributes.id || (this._attributes.id = String.uniqueID());
return this.cid || (this.cid = id), id
}
}
},
options: {
defaults: {}
},
collections: [],
initialize: function(obj, options) {
return options && options.defaults && (this.options.defaults = Object.merge(this.options.defaults, options.defaults)), obj = obj && typeOf(obj) === "object" ? obj : {}, this.set(Object.merge(this.options.defaults, obj)), this.setOptions(options), this.fireEvent("ready")
},
set: function() {
this.propertiesChanged = [], this._set.apply(this, arguments), this.propertiesChanged.length && this.fireEvent("change", this.get(this.propertiesChanged))
},
_set: function(key, value) {
return !key || typeof value == "undefined" ? this : this.properties[key] && this.properties[key].set ? this.properties[key].set.call(this, value) : this._attributes[key] && Epitome.isEqual(this._attributes[key], value) ? this : (value === null ? delete this._attributes[key] : this._attributes[key] = value, this.fireEvent("change:" + key, value), this.propertiesChanged.push(key), this)
}.overloadSetter(),
get: function(key) {
return key && this.properties[key] && this.properties[key].get ? this.properties[key].get.call(this) : key && typeof this._attributes[key] != "undefined" ? this._attributes[key] : null
}.overloadGetter(),
unset: function() {
var keys = Array.prototype.slice.apply(arguments),
obj = {},
len = keys.length;
return len ? (Array.each(Array.flatten(keys), function(key) {
obj[key] = null
}), this.set(obj), this) : this
},
toJSON: function() {
return Object.clone(this._attributes)
},
empty: function() {
var keys = Object.keys(this.toJSON()),
self = this;
this.fireEvent("change", [keys]), Array.each(keys, function(key) {
self.fireEvent("change:" + key, null)
}, this), this._attributes = {}, this.fireEvent("empty")
},
destroy: function() {
this._attributes = {}, this.fireEvent("destroy")
}
}), typeof define == "function" && define.amd ? define("epitome-model", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome-model") : exports.Epitome,
Model = Epitome.Model,
syncPseudo = "sync:",
methodMap = {
create: "POST",
read: "GET",
update: "PUT",
delete_: "DELETE"
};
Model.Sync = new Class({
Extends: Model,
properties: {
id: {
get: function() {
var id = this._attributes.id || (this._attributes.id = String.uniqueID());
return this.cid || (this.cid = id), id
}
},
urlRoot: {
set: function(value) {
this.urlRoot = value, delete this._attributes.urlRoot
},
get: function() {
var base = this.urlRoot || this.options.urlRoot || "no-urlRoot-set";
return base.charAt(base.length - 1) != "/" && (base += "/"), base
}
}
},
options: {
emulateREST: !1
},
initialize: function(obj, options) {
this.setupSync(), this.parent(obj, options)
},
sync: function(method, model) {
var options = {};
method = method && methodMap[method] ? methodMap[method] : methodMap.read, options.method = method;
if (method == methodMap.create || method == methodMap.update) options.data = model || this.toJSON();
return options.url = this.get("urlRoot") + this.get("id") + "/", this.request.setOptions(options), this.request[method](model), this
},
setupSync: function() {
var self = this,
rid = 0,
incrementRequestId = function() {
rid++
};
return this.getRequestId = function() {
return rid + 1
}, this.request = new Request.JSON({
link: "chain",
url: this.get("urlRoot"),
emulation: this.options.emulateREST,
onRequest: incrementRequestId,
onCancel: function() {
this.removeEvents(syncPseudo + rid)
},
onSuccess: function(responseObj) {
responseObj = self.parse && self.parse(responseObj), self.fireEvent(syncPseudo + rid, [responseObj]), self.fireEvent("sync", [responseObj, this.options.method, this.options.data])
},
onFailure: function() {
self.fireEvent(syncPseudo + "error", [this.options.method, this.options.url, this.options.data])
}
}), Object.each(methodMap, function(requestMethod, protoMethod) {
self[protoMethod] = function(model) {
this.sync(protoMethod, model)
}
}), this
},
_throwAwaySyncEvent: function(eventName, callback) {
eventName = eventName || syncPseudo + this.getRequestId();
var self = this,
throwAway = {};
return throwAway[eventName] = function(responseObj) {
responseObj && typeof responseObj == "object" && (self.set(responseObj), callback && callback.call(self, responseObj)), self.removeEvents(throwAway)
}, this.addEvents(throwAway)
}.protect(),
parse: function(resp) {
return resp
},
fetch: function() {
return this._throwAwaySyncEvent(syncPseudo + this.getRequestId(), function() {
this.fireEvent("fetch"), this.isNewModel = !1
}), this.read(), this
},
save: function(key, value) {
var method = ["update", "create"][+this.isNew()];
if (key) {
var ktype = typeOf(key),
canSet = ktype == "object" || ktype == "string" && typeof value != "undefined";
canSet && this._set.apply(this, arguments)
}
return this._throwAwaySyncEvent(syncPseudo + this.getRequestId(), function() {
this.fireEvent("save"), this.fireEvent(method)
}), this[method](), this.isNewModel = !1, this
},
destroy: function() {
this._throwAwaySyncEvent(syncPseudo + this.getRequestId(), function() {
this._attributes = {}, this.delete_(), this.fireEvent("destroy")
})
},
isNew: function() {
return typeof this.isNewModel == "undefined" && (this.isNewModel = !0), this.isNewModel
}
}), typeof define == "function" && define.amd ? define("epitome-model-sync", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome;
Epitome.Storage = function() {
var hasNativeStorage = typeof exports.localStorage == "object" && !! exports.localStorage.getItem,
localStorage = "localStorage",
sessionStorage = "sessionStorage",
setStorage = function(storageMethod) {
var s, privateKey = "epitome-" + storageMethod,
storage = {},
storagePrefix = "model";
if (hasNativeStorage) try {
storage = JSON.decode(exports[storageMethod].getItem(privateKey)) || storage
} catch (e) {
hasNativeStorage = !1
}
if (!hasNativeStorage) try {
s = JSON.decode(exports.name), s && typeof s == "object" && s[privateKey] && (storage = s[privateKey])
} catch (e) {
serializeWindowName()
}
var Methods = {
store: function(model) {
model = model || this.toJSON(), setItem([storagePrefix, this.get("id")].join(":"), model), this.fireEvent("store", model)
},
eliminate: function() {
return removeItem([storagePrefix, this.get("id")].join(":")), this.fireEvent("eliminate")
},
retrieve: function() {
var model = getItem([storagePrefix, this.get("id")].join(":")) || null;
return this.fireEvent("retrieve", model), model
}
},
getItem = function(item) {
return storage[item] || null
},
setItem = function(item, value) {
storage = JSON.decode(exports[storageMethod].getItem(privateKey)) || storage, storage[item] = value;
if (hasNativeStorage) try {
exports[storageMethod].setItem(privateKey, JSON.encode(storage))
} catch (e) {} else serializeWindowName();
return this
},
removeItem = function(item) {
delete storage[item];
if (hasNativeStorage) try {
exports[storageMethod].setItem(privateKey, JSON.encode(storage))
} catch (e) {} else serializeWindowName()
},
serializeWindowName = function() {
var obj = {},
s = JSON.decode(exports.name);
obj[privateKey] = storage, exports.name = JSON.encode(Object.merge(obj, s))
};
return function(storageName) {
return storageName && (storagePrefix = storageName), new Class(Object.clone(Methods))
}
};
return {
localStorage: setStorage(localStorage),
sessionStorage: setStorage(sessionStorage)
}
}(), typeof define == "function" && define.amd ? define("epitome-storage", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome,
methodMap = ["forEach", "each", "invoke", "filter", "map", "some", "indexOf", "contains", "getRandom", "getLast"];
Function.extend({
monitorModelEvents: function(listener, orig) {
var self = this;
return orig = orig || this, !listener || !listener.fireEvent ? this : function(type, args, delay) {
self.apply(orig, arguments), listener.getModelByCID(orig.cid) && listener.fireEvent(type, Array.flatten([orig, args]), delay)
}
}
});
var Collection = Epitome.Collection = new Class({
Implements: [Options, Events],
model: Epitome.Model,
_models: [],
initialize: function(models, options) {
return this.setOptions(options), models && this.setUp(models), this.id = this.options.id || String.uniqueID(), this.fireEvent("ready")
},
setUp: function(models) {
return models = Array.from(models), Array.each(models, this.addModel.bind(this)), this.addEvent("destroy", this.removeModel.bind(this)), this
},
addModel: function(model, replace) {
var exists;
return typeOf(model) == "object" && !instanceOf(model, this.model) && (model = new this.model(model)), model.cid = model.cid || model.get("id") || String.uniqueID(), exists = this.getModelByCID(model.cid), exists && replace !== !0 ? this.fireEvent("add:error", model) : (exists && replace === !0 && (this._models[this._models.indexOf(model)] = model), model.fireEvent = Function.monitorModelEvents.apply(model.fireEvent, [this, model]), this._models.push(model), model.collections.include(this), this.length = this._models.length, this.fireEvent("add", [model, model.cid]).fireEvent("reset", [model, model.cid]))
},
removeModel: function(models) {
var self = this;
return models = Array.from(models), Array.each(models, function(model) {
model.collections.erase(self), model.collections.length || delete model.fireEvent, Array.erase(self._models, model), self.length = self._models.length, self.fireEvent("remove", [model, model.cid])
}), this.fireEvent("reset", [models])
},
get: function(what) {
return this[what]
},
getModelByCID: function(cid) {
var last = null;
return this.some(function(el) {
return el.cid == cid && (last = el)
}), last
},
getModelById: function(id) {
var last = null;
return this.some(function(el) {
return el.get("id") == id && (last = el)
}), last
},
getModel: function(index) {
return this._models[index]
},
toJSON: function() {
var getJSON = function(model) {
return model.toJSON()
};
return Array.map(this._models, getJSON)
},
empty: function() {
return this.removeModel(this._models), this.fireEvent("empty")
},
sort: function(how) {
if (!how) return this._models.sort(), this.fireEvent("sort");
if (typeof how == "function") return this.model.sort(how), this.fireEvent("sort");
var type = "asc",
pseudos = how.split(":"),
key = pseudos[0],
c = function(a, b) {
return a < b ? -1 : a > b ? 1 : 0
};
return pseudos[1] && (type = pseudos[1]), this._models.sort(function(a, b) {
var ak = a.get(key),
bk = b.get(key),
cm = c(ak, bk),
map = {
asc: cm,
desc: -cm
};
return typeof map[type] == "undefined" && (type = "asc"), map[type]
}), this.fireEvent("sort")
},
reverse: function() {
return Array.reverse(this._models), this.fireEvent("sort")
}
});
Array.each(methodMap, function(method) {
Collection.implement(method, function() {
return Array.prototype[method].apply(this._models, arguments)
})
}), typeof define == "function" && define.amd ? define("epitome-collection", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome-collection") : exports.Epitome,
noUrl = "no-urlRoot-set",
eventPseudo = "fetch:";
Epitome.Collection.Sync = new Class({
Extends: Epitome.Collection,
options: {
urlRoot: noUrl
},
initialize: function(models, options) {
this.setupSync(), this.parent(models, options)
},
setupSync: function() {
var self = this,
rid = 0,
incrementRequestId = function() {
rid++
};
return this.getRequestId = function() {
return rid + 1
}, this.request = new Request.JSON({
link: "chain",
url: this.options.urlRoot,
emulation: this.options.emulateREST,
onRequest: incrementRequestId,
onCancel: function() {
this.removeEvents(eventPseudo + rid)
},
onSuccess: function(responseObj) {
responseObj = self.parse && self.parse(responseObj), self.fireEvent(eventPseudo + rid, [[responseObj]])
},
onFailure: function() {
self.fireEvent(eventPseudo + "error", [this.options.method, this.options.url, this.options.data])
}
}), this
},
parse: function(resp) {
return resp
},
fetch: function(refresh) {
return this._throwAwayEvent(function(models) {
refresh ? (this.empty(), Array.each(models, this.addModel.bind(this))) : this.processModels(models), this.fireEvent("fetch", [models])
}), this.request.get(), this
},
processModels: function(models) {
var self = this;
Array.each(models, function(model) {
var exists = model.id && self.getModelById(model.id);
exists ? exists.set(model) : self.addModel(model)
})
},
_throwAwayEvent: function(callback) {
var eventName = eventPseudo + this.getRequestId(),
self = this,
throwAway = {};
if (!callback || typeof callback != "function") return;
return throwAway[eventName] = function(responseObj) {
callback.apply(self, responseObj), self.removeEvents(throwAway)
}, this.addEvents(throwAway)
}.protect()
}), typeof define == "function" && define.amd ? define("epitome-collection-sync", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome;
Epitome.Template = new Class({
options: {
evaluate: /<%([\s\S]+?)%>/g,
normal: /<%=([\s\S]+?)%>/g,
noMatch: /.^/,
escaper: /\\|'|\r|\n|\t|\u2028|\u2029/g,
unescaper: /\\(\\|'|r|n|t|u2028|u2029)/g
},
Implements: [Options],
initialize: function(options) {
this.setOptions(options);
var unescaper = this.options.unescaper,
escapes = this.escapes = {
"\\": "\\",
"'": "'",
r: "\r",
n: "\n",
t: " ",
u2028: "\u2028",
u2029: "\u2029"
};
return Object.each(escapes, function(value, key) {
this[value] = key
}, escapes), this.unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape]
})
}, this
},
template: function(str, data) {
var o = this.options,
escapes = this.escapes,
unescape = this.unescape,
noMatch = o.noMatch,
escaper = o.escaper,
template, source = ["var __p=[],print=function(){__p.push.apply(__p,arguments);};", "with(obj||{}){__p.push('", str.replace(escaper, function(match) {
return "\\" + escapes[match]
}).replace(o.normal || noMatch, function(match, code) {
return "',\nobj['" + unescape(code) + "'],\n'"
}).replace(o.evaluate || noMatch, function(match, code) {
return "');\n" + unescape(code) + "\n;__p.push('"
}), "');\n}\nreturn __p.join('');"].join(""),
render = new Function("obj", "_", source);
return data ? render(data) : (template = function(data) {
return render.call(this, data)
}, template.source = "function(obj){\n" + source + "\n}", template)
}
}), typeof define == "function" && define.amd ? define("epitome-template", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome-template") : exports.Epitome;
Epitome.View = new Class({
Implements: [Options, Events],
element: null,
collection: null,
model: null,
options: {
template: "",
events: {}
},
initialize: function(options) {
return options && options.collection && (this.setCollection(options.collection), delete options.collection), options && options.model && (this.setModel(options.model), delete options.model), this.setOptions(options), this.options.element && (this.setElement(this.options.element, this.options.events), delete this.options.element), this.fireEvent("ready")
},
setElement: function(el, events) {
return this.element && this.detachEvents() && this.destroy(), this.element = document.id(el), events && this.attachEvents(events), this
},
setCollection: function(collection) {
var self = this,
eventProxy = function(type) {
return function() {
self.fireEvent(type + ":collection", arguments)
}
};
return instanceOf(collection, Epitome.Collection) && (this.collection = collection, this.collection.addEvents({
change: eventProxy("change"),
fetch: eventProxy("fetch"),
add: eventProxy("add"),
remove: eventProxy("remove"),
sort: eventProxy("sort"),
reset: eventProxy("reset")
})), this
},
setModel: function(model) {
var self = this,
eventProxy = function(type) {
return function() {
self.fireEvent(type + ":model", arguments)
}
};
return instanceOf(model, Epitome.Model) && (this.model = model, this.model.addEvents({
change: eventProxy("change"),
destroy: eventProxy("destroy"),
empty: eventProxy("empty")
})), this
},
attachEvents: function(events) {
var self = this;
return Object.each(events, function(method, type) {
self.element.addEvent(type, function(e) {
self.fireEvent(method, arguments)
})
}), this.element.store("attachedEvents", events), this
},
detachEvents: function() {
var events = this.element.retrieve("attachedEvents");
return events && this.element.removeEvents(events).eliminate("attachedEvents"), this
},
template: function(data, template) {
template = template || this.options.template;
var compiler = this.Template || (this.Template = new Epitome.Template);
return compiler.template(template, data)
},
render: function() {
return this.fireEvent("render")
},
empty: function(soft) {
return soft ? this.element.empty() : this.element.set("html", ""), this.fireEvent("empty")
},
dispose: function() {
return this.element.dispose(), this.fireEvent("dispose")
},
destroy: function() {
return this.element.destroy(), this.fireEvent("destroy")
}
}), typeof define == "function" && define.amd ? define("epitome-view", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this), function(exports) {
var Epitome = typeof require == "function" ? require("./epitome") : exports.Epitome,
hc = "hashchange",
hcSupported = "on" + hc in window,
eventHosts = [window, document],
timer, getQueryString = function(queryString) {
var result = {},
re = /([^&=]+)=([^&]*)/g,
m;
while (m = re.exec(queryString)) result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
return result
};
Element.Events.hashchange = {
onAdd: function() {
var hash = location.hash,
check = function() {
if (hash == location.hash) return;
hash = location.hash, eventHosts.invoke("fireEvent", hc, hash.indexOf("#") == 0 ? hash.substr(1) : hash)
};
hcSupported && (window.onhashchange = check) || (timer = check.periodical(100))
},
onRemove: function() {
hcSupported && (window.onhashchange = null) || clearInterval(timer)
}
}, Epitome.Router = new Class({
Implements: [Options, Events],
options: {
triggerOnLoad: !0
},
routes: {},
boundEvents: {},
initialize: function(options) {
var self = this;
this.setOptions(options), this.options.routes && (this.routes = this.options.routes), window.addEvent(hc, function(e) {
var hash = location.hash,
path = hash.split("?")[0],
query = hash.split("?")[1] || "",
notfound = !0,
route;
for (route in self.routes) {
var keys = [],
regex = self.normalize(route, keys, !0, !1),
found = regex.exec(path),
routeEvent = !1;
if (found) {
notfound = !1, self.req = found[0];
var args = found.slice(1),
param = {};
Array.each(args, function(a, i) {
typeof keys[i] != "undefined" && (param[keys[i].name] = a)
}), self.route = route, self.param = param || {}, self.query = query && getQueryString(query), routeEvent = self.routes[route], self.fireEvent("before", routeEvent), routeEvent && self.$events[routeEvent] ? (self.fireEvent(routeEvent + ":before"), self.fireEvent(routeEvent, Object.values(self.param))) : self.fireEvent("error", ["Route", routeEvent, "is undefined"].join(" ")), self.fireEvent("after", routeEvent), routeEvent && self.fireEvent(routeEvent + ":after");
break
}
}
notfound && self.fireEvent("undefined")
}), this.fireEvent("ready"), this.options.triggerOnLoad && window.fireEvent(hc)
},
navigate: function(route, trigger) {
location.hash == route && trigger ? window.fireEvent(hc) : location.hash = route
},
normalize: function(path, keys, sensitive, strict) {
return path instanceof RegExp ? path : (path = path.concat(strict ? "" : "/?").replace(/\/\(/g, "(?:/").replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional) {
return keys.push({
name: key,
optional: !! optional
}), slash = slash || "", [optional ? "" : slash, "(?:", optional ? slash : "", (format || "") + (capture || format && "([^/.]+?)" || "([^/]+?)") + ")", optional || ""].join("")
}).replace(/([\/.])/g, "\\$1").replace(/\*/g, "(.*)"), new RegExp("^" + path + "$", sensitive ? "" : "i"))
},
addRoute: function(obj) {
return !obj || !obj.route || !obj.id || !obj.events ? this.fireEvent("error", "Please include route, id and events in the argument object when adding a route") : obj.id.length ? this.routes[obj.route] ? this.fireEvent("error", 'Route "{route}" or id "{id}" already exists, aborting'.substitute(obj)) : (this.routes[obj.route] = obj.id, this.addEvents(this.boundEvents[obj.route] = obj.events), this.fireEvent("route:add", obj)) : this.fireEvent("error", "Route id cannot be empty, aborting")
},
removeRoute: function(route) {
return !route || !this.routes[route] || !this.boundEvents[route] ? this.fireEvent("error", "Could not find route or route is not removable") : (this.removeEvents(this.boundEvents[route]), delete this.routes[route], delete this.boundEvents[route], this.fireEvent("route:remove", route))
}
}), typeof define == "function" && define.amd ? define("epitome-router", [], function() {
return Epitome
}) : typeof module == "object" ? module.exports = Epitome : exports.Epitome = Epitome
}(this)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment