Skip to content

Instantly share code, notes, and snippets.

@pedrokoblitz
Created July 18, 2016 11:40
Show Gist options
  • Save pedrokoblitz/05bbc4b7b2249ad0b3010a6a26107639 to your computer and use it in GitHub Desktop.
Save pedrokoblitz/05bbc4b7b2249ad0b3010a6a26107639 to your computer and use it in GitHub Desktop.
/*
* 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