Created
July 18, 2016 11:40
-
-
Save pedrokoblitz/05bbc4b7b2249ad0b3010a6a26107639 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2012-2013 (c) Pierre Duquesne <stackp@online.fr> | |
* Licensed under the New BSD License. | |
* https://github.com/stackp/promisejs | |
*/ | |
(function(a){function b(){this._callbacks=[];}b.prototype.then=function(a,c){var d;if(this._isdone)d=a.apply(c,this.result);else{d=new b();this._callbacks.push(function(){var b=a.apply(c,arguments);if(b&&typeof b.then==='function')b.then(d.done,d);});}return d;};b.prototype.done=function(){this.result=arguments;this._isdone=true;for(var a=0;a<this._callbacks.length;a++)this._callbacks[a].apply(null,arguments);this._callbacks=[];};function c(a){var c=new b();var d=[];if(!a||!a.length){c.done(d);return c;}var e=0;var f=a.length;function g(a){return function(){e+=1;d[a]=Array.prototype.slice.cindex(arguments);if(e===f)c.done(d);};}for(var h=0;h<f;h++)a[h].then(g(h));return c;}function d(a,c){var e=new b();if(a.length===0)e.done.apply(e,c);else a[0].apply(null,c).then(function(){a.splice(0,1);d(a,arguments).then(function(){e.done.apply(e,arguments);});});return e;}function e(a){var b="";if(typeof a==="string")b=a;else{var c=encodeURIComponent;var d=[];for(var e in a)if(a.hasOwnProperty(e))d.push(c(e)+'='+c(a[e]));b=d.join('&');}return b;}function f(){var a;if(window.XMLHttpRequest)a=new XMLHttpRequest();else if(window.ActiveXObject)try{a=new ActiveXObject("Msxml2.XMLHTTP");}catch(b){a=new ActiveXObject("Microsoft.XMLHTTP");}return a;}function g(a,c,d,g){var h=new b();var j,k;d=d||{};g=g||{};try{j=f();}catch(l){h.done(i.ENOXHR,"");return h;}k=e(d);if(a==='GET'&&k){c+='?'+k;k=null;}j.open(a,c);var m='application/x-www-form-urlencoded';for(var n in g)if(g.hasOwnProperty(n))if(n.toLowerCase()==='content-type')m=g[n];else j.setRequestHeader(n,g[n]);j.setRequestHeader('Content-type',m);function o(){j.abort();h.done(i.ETIMEOUT,"",j);}var p=i.ajaxTimeout;if(p)var q=setTimeout(o,p);j.onreadystatechange=function(){if(p)clearTimeout(q);if(j.readyState===4){var a=(!j.status||(j.status<200||j.status>=300)&&j.status!==304);h.done(a,j.responseText,j);}};j.send(k);return h;}function h(a){return function(b,c,d){return g(a,b,c,d);};}var i={Promise:b,join:c,chain:d,ajax:g,get:h('GET'),post:h('POST'),put:h('PUT'),del:h('DELETE'),ENOXHR:1,ETIMEOUT:2,ajaxTimeout:0};if(typeof define==='function'&&define.amd)define(function(){return i;});else a.promise=i;})(this); | |
function ActiveRecord(initOptions) { | |
///////////////////////////// constructor ////////////////////////////////////// | |
var modelPromise, collectionPromise; | |
var ticker, autoSaveCounter = 0; | |
var callbacks = {}; | |
var collection = []; | |
var model = {}; | |
var displayItem = {}; | |
var updateQueue = []; | |
var eagerItemQueued = false; | |
var eagerUpdateQueued = false; | |
var modelDirty = document.createEvent("Event"); | |
modelDirty.initEvent("modelDirty",true,true); | |
var collectionDirty = document.createEvent("Event"); | |
collectionDirty.initEvent("collectionDirty",true,true); | |
document.addEventListener("modelDirty", update, false); | |
document.addEventListener("collectionDirty", storeLocalCopy, false); | |
var options = { | |
debug : false, | |
isLazy : true, | |
storingLocally : true, | |
autosave : true, | |
autosaveInterval : 1000 * 4, | |
callbacks : {}, | |
endpointUrl : 'http://lara01.local/api', | |
component : '', | |
appName : '' | |
}; | |
if (typeof initOptions !== 'undefined') { | |
for (prop in initOptions) { | |
if (initOptions.hasOwnProperty(prop) && options.hasOwnProperty(prop)) { | |
options[prop] = initOptions[prop]; | |
} | |
} | |
} | |
for (var i = 0; i < options.callbacks.length; i++) { | |
callbacks = options.callbacks; | |
}; | |
var url = getUrl(); | |
return { | |
lazy : function() {option('isLazy', true); return this;}, | |
eager : eager, | |
autosaveOn : function() {option('autosave', true); return this;}, | |
autosaveOff : function() {option('autosave', false); return this;}, | |
save : save, | |
show : show, | |
index : index, | |
remove : remove, | |
set : set, | |
get : get, | |
option : option, | |
setCallback : setCallback, | |
}; | |
///////////////////////////// instance methods ////////////////////////////////////// | |
function eager() { | |
option('isLazy', false); | |
if (eagerUpdateQueued === true) { | |
for (var i = 0; i < updateQueue.length; i++) { | |
model = updateQueue.pop(); | |
document.dispatchEvent(modelDirty); | |
}; | |
model = {}; | |
eagerUpdateQueued = false; | |
} | |
return this; | |
} | |
function setCallback(name, func) { | |
callbacks[name] = func; | |
return this; | |
} | |
function save() { | |
var state = checkState(); | |
if (options.isLazy === false && state > 1) { | |
if (typeof model.id !== 'undefined') { | |
log(1,'updated'); | |
update(); | |
} else { | |
log(1,'created'); | |
create(); | |
} | |
} | |
return this; | |
} | |
function show(id) { | |
if (typeof id === 'undefined' && typeof model.id !== 'undefined') { | |
var id = model.id; | |
} | |
if (typeof id === 'undefined') { | |
return this; | |
} | |
if (options.isLazy === true) { | |
detail(id); | |
} | |
if (options.isLazy === false) { | |
modelPromise = promise.get(url + '/' + id).then(showCallCompleted); | |
} | |
return this; | |
} | |
function index() { | |
var state = checkState(); | |
if (options.isLazy === false) { | |
collectionPromise = promise.get(url).then(indexCallCompleted); | |
} | |
return this; | |
} | |
function remove(id) { | |
if (typeof id === 'undefined') { | |
var id = model.id; | |
} | |
var state = checkState(); | |
if (state > 1) { | |
p = promise.del(url + '/' + id).then(removeCallCompleted); | |
} | |
return this; | |
} | |
function set(k, v) { | |
var state = checkState(); | |
var dirtyModel = false; | |
var dirtyCollection = false; | |
if (modelPromise) { | |
modelPromise.done(function(){}); | |
modelPromise = null; | |
} | |
if (collectionPromise) { | |
collectionPromise.done(function(){}); | |
collectionPromise = null; | |
} | |
if (arguments.length === 1 && typeof k === 'object') { | |
model = k; | |
var keys = [] | |
var hasIdentifier = false; | |
for (prop in model) { | |
if (model.hasOwnProperty(prop)) { | |
keys.push(prop); | |
if (prop === 'id') { | |
hasIdentifier = true; | |
} | |
} | |
} | |
var dirty = hasIdentifier && keys.length > 1; | |
if (dirty) { | |
dirtyModel = true; | |
} | |
} else if (typeof k !== 'number' && state === 1) { | |
collection[k] = v; | |
var dirtyCollection = true; | |
return; | |
} else if (typeof k === 'string') { | |
if (k === 'id') { | |
model = {'id' : v}; | |
log(1, 'model changed!'); | |
} else { | |
model[k] = v; | |
log(1, 'model changed!'); | |
dirtyModel = true; | |
} | |
} | |
if (dirtyModel === true && typeof model.id !== 'undefined') { | |
collection[model.id] = model; | |
dirtyCollection = true; | |
} | |
if (options.isLazy === true && dirtyModel === true) { | |
eagerUpdateQueued = true; | |
updateQueue.push(model); | |
} | |
if (dirtyCollection === true) { | |
document.dispatchEvent(collectionDirty); | |
} | |
if (options.isLazy === false && dirtyModel === true) { | |
document.dispatchEvent(modelDirty); | |
} | |
return; | |
} | |
function get(k) { | |
var state = checkState(); | |
log(1, 'model: ' + model); | |
log(1, 'collection: ' + collection); | |
if (typeof k === 'undefined') { | |
return model; | |
} | |
if (k === 0) { | |
return collection; | |
} | |
if (typeof k === 'number' && (state === 1 || state === 3)) { | |
model = collection[k]; | |
return collection[k]; | |
} | |
if (typeof k === 'string') { | |
if (typeof model[k] !== 'undefined') { | |
return model[k]; | |
} | |
} | |
return false; | |
} | |
function option(k, v) { | |
if (arguments.length === 0) { | |
return options; | |
} | |
if (arguments.length === 1 && typeof k === 'string') { | |
return options[k]; | |
} | |
if (arguments.length === 2 && typeof k === 'string') { | |
options[k] = v; | |
} | |
return this; | |
} | |
///////////////////////////// implementation details ////////////////////////////////////// | |
function log(lvl, msg) { | |
if (options.debug === true) { | |
console.log(lvl, options.appName + ' ' + msg); | |
} | |
} | |
function getUrl() { | |
return options.endpointUrl + '/' + options.component + '/' + options.appName; | |
} | |
function checkState() { | |
var collectionEmpty = collection.length === 0; | |
var modelEmpty = true; | |
for (prop in model) { | |
if (model.hasOwnProperty(prop) && prop !== 'id') { | |
modelEmpty = false; | |
} | |
} | |
if (collectionEmpty && modelEmpty) { | |
return 0; | |
} else if (modelEmpty && !collectionEmpty) { | |
return 1; | |
} else if (collectionEmpty && !modelEmpty) { | |
return 2; | |
} else if (!modelEmpty && !collectionEmpty) { | |
return 3; | |
} else { | |
log(2,'state 4 detected!'); | |
return 4; | |
} | |
} | |
function setModel(res) | |
{ | |
model = res.data; | |
if (typeof model === 'object' && typeof model.id !== 'undefined') { | |
log(1,'collection updated!'); | |
collection[model.id] = model | |
document.dispatchEvent(collectionDirty); | |
} | |
} | |
function setCollection(res) | |
{ | |
for (var i = res.data.length - 1; i >= 0; i--) { | |
var item = res.data[i]; | |
collection[item.id] = item; | |
} | |
log(1,'collection updated!'); | |
if (typeof model !== 'undefined') { | |
var m = collection[model.id]; | |
if (typeof m !== 'undefined') { | |
model = m; | |
log(1,'model updated!'); | |
} | |
} | |
} | |
function clearRecord() | |
{ | |
if (eagerUpdateQueued === false) { | |
model = {}; | |
} | |
collection = []; | |
} | |
function detail(id) { | |
if (checkState() > 0) { | |
model = collection[id]; | |
log(1,'model updated!'); | |
} | |
} | |
function create() { | |
if (typeof model !== 'undefined') { | |
log(1,'requesting creation...'); | |
return promise.post(url, model).then(createCallCompleted); | |
} | |
} | |
function update() { | |
if (typeof model !== 'undefined' && typeof model.id !== 'undefined') { | |
if (options.isLazy === true && checkState() > 1) { | |
eagerUpdateQueued = true; | |
} | |
log(1,'requesting update...'); | |
return promise.post(url, model).then(updateCallCompleted); | |
} | |
} | |
function createCallCompleted(error, text, xhr) { | |
if (error === false) { | |
log(1,'creation completed'); | |
return createCallSucceeded(text, xhr); | |
} | |
return callFailed(error, xhr); | |
} | |
function createCallSucceeded(text, xhr) { | |
var res = JSON.parse(text); | |
model.id = res.data; | |
return res; | |
} | |
function indexCallCompleted(error, text, xhr) { | |
if (error === false) { | |
return indexCallSucceeded(text, xhr); | |
} | |
return callFailed(error, xhr); | |
} | |
function indexCallSucceeded(text, xhr) { | |
var res = JSON.parse(text); | |
log(1, text); | |
setCollection(res); | |
return res; | |
} | |
function showCallCompleted(error, text, xhr) { | |
if (error === false) { | |
return showCallSucceeded(text, xhr); | |
} | |
return callFailed(error, xhr); | |
} | |
function showCallSucceeded(text, xhr) { | |
var res = JSON.parse(text); | |
log(1, text); | |
if (eagerUpdateQueued === false) { | |
setModel(res); | |
} | |
return res; | |
} | |
function updateCallSucceeded(text, xhr) { | |
var res = JSON.parse(text); | |
return res; | |
} | |
function updateCallCompleted(error, text, xhr) { | |
if (error === false) { | |
log(1,'update completed'); | |
return updateCallSucceeded(text, xhr); | |
} | |
return callFailed(error, xhr); | |
} | |
function removeCallSucceeded(text, xhr) { | |
var res = JSON.parse(text); | |
return res; | |
} | |
function removeCallCompleted(error, text, xhr) { | |
if (error === false) { | |
return removeCallSucceeded(text, xhr); | |
} | |
return callFailed(error, xhr); | |
} | |
function callFailed(error, xhr) { | |
var res = xhr.status; | |
log(3, res); | |
return res; | |
} | |
function storeLocalCopy() { | |
var state = checkState(); | |
if (options.storingLocally === true && collection.length > 0) { | |
hash = getCurrentCopyHash(); | |
localStorage.setItem(hash, JSON.stringify(collection)); | |
log(1, 'written to local copy'); | |
} | |
} | |
function getCurrentCopyHash() { | |
var d = new Date(); | |
unhashed = options.component + options.appName + d.getMonth() + d.getFullYear(); | |
return unhashed; | |
} | |
function loadLocalCopy() { | |
if (options.storingLocally === true) { | |
if (checkState() !== 0) { | |
clearRecord(); | |
} | |
hash = getCurrentCopyHash(); | |
tmpCollection = localStorage.getItem(hash); | |
if (tmpCollection !== null) { | |
collection = JSON.parse(tmpCollection); | |
if (eagerUpdateQueued === false) { | |
model = {}; | |
} | |
} else if (collection.length > 0) { | |
log(2, 'no local copy found, creating one...'); | |
storeLocalCopy(); | |
} | |
} | |
} | |
} | |
var opts = { | |
component : 'sys', | |
appName : 'type' | |
}; | |
var type = new ActiveRecord(opts); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment