Comparing version 1.0.0 to 1.1.0
{ | ||
"name": "persisto", | ||
"description": "Persist Javascript objects to localStorage and remote servers.", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"main": [ | ||
@@ -6,0 +6,0 @@ "dist/persisto.js" |
@@ -1,2 +0,9 @@ | ||
# 1.0.0-0 / Unreleased | ||
# 1.1.1-0 / Unreleased | ||
* | ||
# 1.1.0 / 2016-08-14 | ||
* [CHANGE] rename `.init` option to `.defaults` | ||
* [CHANGE] rename `.debug` option to `.debugLevel` | ||
# 1.0.0 / 2016-08-11 | ||
* [ADD] .set() creates intermediate objects if missing | ||
@@ -3,0 +10,0 @@ * [ADD] .writeToForm() and .readFromForm() accept inputs with nested names |
/*! | ||
* persisto.js | ||
* | ||
* Persistent, editable objects for Javascript. | ||
* Persistent Javascript objects and web forms using Web Storage. | ||
* | ||
@@ -9,4 +9,4 @@ * Copyright (c) 2016, Martin Wendt (http://wwWendt.de) | ||
* | ||
* @version 1.0.0 | ||
* @date 2016-08-11T21:56 | ||
* @version 1.1.0 | ||
* @date 2016-08-14T10:00 | ||
*/ | ||
@@ -41,15 +41,21 @@ | ||
if ( typeof namespace !== "string" ) { $.error("Missing required argument: namespace"); } | ||
/* jshint ignore:start */ // disable warning 'PersistentObject is not defined' | ||
if ( !(this instanceof PersistentObject) ) { $.error("Must use 'new' keyword"); } | ||
/* jshint ignore:end */ | ||
if ( typeof namespace !== "string" ) { | ||
$.error(this + ": Missing required argument: namespace"); | ||
} | ||
this.opts = $.extend({ | ||
remote: null, // URL for GET/PUT, ajax options, or callback | ||
init: {}, // default value if no data is found in localStorage | ||
defaults: {}, // default value if no data is found in localStorage | ||
commitDelay: 500, // commit changes after 0.5 seconds of inactivity | ||
createParents: true, // set() creates missing intermediate parent objects for children | ||
maxCommitDelay: 3000, // commit changes max. 3 seconds after first change | ||
pushDelay: 5000, // push commits after 5 seconds of inactivity | ||
maxPushDelay: 30000, // push commits max. 30 seconds after first change | ||
debug: 2, // 0:quiet, 1:normal, 2:verbose | ||
createParents: true, // set() creates intermediate parent objects for children | ||
storage: window.localStorage, | ||
// form: {}, | ||
// Default debugLevel is set to 1 by `grunt build`: | ||
debugLevel: 1, // 0:quiet, 1:normal, 2:verbose | ||
// Events | ||
@@ -64,8 +70,6 @@ change: $.noop, | ||
}, opts); | ||
this.debugLevel = 1; // Set to 1 by 'grunt build' | ||
this._checkTimer = null; | ||
this.namespace = namespace; | ||
this.storage = this.opts.storage; | ||
this._data = this.opts.init; | ||
this._data = this.opts.defaults; | ||
@@ -98,3 +102,3 @@ this.offline = undefined; | ||
// self._data = JSON.parse(prevValue); | ||
self._data = $.extend({}, self.opts.init, JSON.parse(prevValue)); | ||
self._data = $.extend({}, self.opts.defaults, JSON.parse(prevValue)); | ||
} else { | ||
@@ -107,4 +111,4 @@ console.warn(self + ": could not init from remote; falling back default."); | ||
this.update(); | ||
// We still extend from opts.init, in case some fields where missing | ||
this._data = $.extend({}, this.opts.init, this._data); | ||
// We still extend from opts.defaults, in case some fields where missing | ||
this._data = $.extend({}, this.opts.defaults, this._data); | ||
// this.debug("init from storage", this._data); | ||
@@ -122,3 +126,3 @@ dfd.resolve(); | ||
/** @type {string} */ | ||
version: "1.0.0", // Set to semver by 'grunt release' | ||
version: "1.1.0", // Set to semver by 'grunt release' | ||
@@ -146,3 +150,3 @@ /* Trigger commit/push according to current settings. */ | ||
} else { | ||
this.debug("_invalidate recursive()"); | ||
this.debug("_invalidate() recursive"); | ||
} | ||
@@ -156,3 +160,3 @@ | ||
this.debug("_invalidate: force commit", | ||
this.debug("_invalidate(): force commit", | ||
((now - prevChange) >= this.opts.commitDelay), | ||
@@ -173,3 +177,3 @@ ((now - this.uncommittedSince) >= this.opts.maxCommitDelay)); | ||
this.debug("_invalidate: force push", ((now - prevChange) >= this.opts.pushDelay), | ||
this.debug("_invalidate(): force push", ((now - prevChange) >= this.opts.pushDelay), | ||
((now - this.unpushedSince) >= this.opts.maxPushDelay)); | ||
@@ -188,3 +192,3 @@ this.push(); | ||
// this.debug("Defer update:", nextCheck - now) | ||
this.debug("_invalidate defer (" + hint + ") by " + (nextCheck - now) + "ms" ); | ||
this.debug("_invalidate(" + hint + ") defer by " + (nextCheck - now) + "ms" ); | ||
this._checkTimer = setTimeout(function() { | ||
@@ -208,9 +212,9 @@ self._checkTimer = null; // no need to call clearTimeout in the handler... | ||
}, | ||
/* */ | ||
/* Return readable string representation for this instance. */ | ||
toString: function() { | ||
return "PersistentObject('" + this.namespace + "')"; | ||
}, | ||
/* */ | ||
/* Log to console if opts.debugLevel >= 2 */ | ||
debug: function() { | ||
if ( this.opts.debug >= 1 ) { | ||
if ( this.opts.debugLevel >= 2 ) { | ||
Array.prototype.unshift.call(arguments, this.toString()); | ||
@@ -220,2 +224,9 @@ console.log.apply(window.console, arguments); | ||
}, | ||
/* Log to console if opts.debugLevel >= 1 */ | ||
log: function() { | ||
if ( this.opts.debugLevel >= 1 ) { | ||
Array.prototype.unshift.call(arguments, this.toString()); | ||
console.log.apply(window.console, arguments); | ||
} | ||
}, | ||
/** Return true if there are uncommited or unpushed modifications. */ | ||
@@ -248,6 +259,4 @@ isDirty: function() { | ||
if ( cur === undefined && i < (parts.length - 1) ) { | ||
$.error("The property '" + key + | ||
"' could not be accessed because parent '" + | ||
parts.slice(0, i + 1).join(".") + | ||
"' does not exist"); | ||
$.error(this + ": Property '" + key + "' could not be accessed because parent '" + | ||
parts.slice(0, i + 1).join(".") + "' does not exist"); | ||
} | ||
@@ -273,9 +282,7 @@ } | ||
if ( this.opts.createParents ) { | ||
this.debug("Creating intermediate parent '" + parts[i] + "'"); | ||
cur = parent[parts[i]] = {}; | ||
this.debug("Creating intermediate property '" + parts[i] + "'"); | ||
} else { | ||
$.error("The property '" + key + | ||
"' could not be set because parent '" + | ||
parts.slice(0, i + 1).join(".") + | ||
"' does not exist"); | ||
$.error(this + ": Property '" + key + "' could not be set because parent '" + | ||
parts.slice(0, i + 1).join(".") + "' does not exist"); | ||
} | ||
@@ -319,7 +326,7 @@ } | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".update"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".update"); } | ||
var data = this.storage.getItem(this.namespace); | ||
data = JSON.parse(data); | ||
this._update(data); | ||
if ( this.opts.debug >= 2 && console.time ) { console.timeEnd(this + ".update"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.timeEnd(this + ".update"); } | ||
}, | ||
@@ -332,3 +339,3 @@ /** Write data to localStorage. */ | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".commit"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".commit"); } | ||
// try { data = JSON.stringify(this._data); } catch(e) { } | ||
@@ -341,3 +348,3 @@ data = JSON.stringify(this._data); | ||
// this.lastCommit = _getNow(); | ||
if ( this.opts.debug >= 2 && console.time ) { console.timeEnd(this + ".commit"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.timeEnd(this + ".commit"); } | ||
return data; | ||
@@ -352,3 +359,3 @@ }, | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".pull"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".pull"); } | ||
this.phase = "pull"; | ||
@@ -373,3 +380,3 @@ // return $.get(this.opts.remote, function(objData) { | ||
self.phase = null; | ||
if ( self.opts.debug >= 2 && console.time ) { console.timeEnd(self + ".pull"); } | ||
if ( self.opts.debugLevel >= 2 && console.time ) { console.timeEnd(self + ".pull"); } | ||
}); | ||
@@ -385,5 +392,5 @@ }, | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(self + ".push"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(self + ".push"); } | ||
this.phase = "push"; | ||
if ( !this.opts.remote ) { $.error(this + ": missing remote option"); } | ||
if ( !this.opts.remote ) { $.error(this + ": Missing remote option"); } | ||
return $.ajax({ | ||
@@ -402,3 +409,3 @@ type: "PUT", | ||
self.phase = null; | ||
if ( self.opts.debug >= 2 && console.time ) { console.timeEnd(self + ".push"); } | ||
if ( self.opts.debugLevel >= 2 && console.time ) { console.timeEnd(self + ".push"); } | ||
}); | ||
@@ -423,4 +430,4 @@ }, | ||
if ( self._data[name] === undefined ) { | ||
self.debug("readFromForm: add field '" + name + "'"); | ||
self._data[name] = null; | ||
self.debug("readFromForm: add field '" + name + "'"); | ||
} | ||
@@ -427,0 +434,0 @@ }); |
@@ -1,4 +0,4 @@ | ||
/*! Persistent, editable objects for Javascript. - v1.0.0 - 2016-08-11 | https://github.com/mar10/persisto | Copyright (c) 2016 Martin Wendt; Licensed MIT */ | ||
/*! Persistent Javascript objects and web forms using Web Storage. - v1.1.0 - 2016-08-14 | https://github.com/mar10/persisto | Copyright (c) 2016 Martin Wendt; Licensed MIT */ | ||
!function(a,b,c,d){"use strict";function e(){return(new Date).getTime()}var f=9007199254740991;b.PersistentObject=function(c,f){var g,h=this,i=a.Deferred(),j=e();"string"!=typeof c&&a.error("Missing required argument: namespace"),this.opts=a.extend({remote:null,init:{},commitDelay:500,maxCommitDelay:3e3,pushDelay:5e3,maxPushDelay:3e4,debug:2,createParents:!0,storage:b.localStorage,change:a.noop,commit:a.noop,conflict:a.noop,error:a.noop,pull:a.noop,push:a.noop,update:a.noop},f),this.debugLevel = 1,this._checkTimer=null,this.namespace=c,this.storage=this.opts.storage,this._data=this.opts.init,this.offline=d,this.phase=null,this.uncommittedSince=null,this.unpushedSince=null,this.lastUpdate=0,this.lastPull=0,this.commitCount=0,this.pushCount=0,this.lastModified=j,this.ready=i.promise,g=this.storage?this.storage.getItem(this.namespace):null,this.opts.remote?this.pull().done(function(){h.offline=!1,i.resolve()}).fail(function(){h.offline=!0,null!=g?(console.warn(h+": could not init from remote; falling back to storage."),h._data=a.extend({},h.opts.init,JSON.parse(g))):console.warn(h+": could not init from remote; falling back default."),i.resolve()}):null!=g?(this.update(),this._data=a.extend({},this.opts.init,this._data),i.resolve()):i.resolve()},b.PersistentObject.prototype={version:"1.0.0",_invalidate:function(a,b){var c=this,d=this.lastModified,g=e(),h=0,i=0,j=0;this._checkTimer&&(clearTimeout(this._checkTimer),this._checkTimer=null),b?this.debug("_invalidate recursive()"):(this.lastModified=g,this.uncommittedSince||(this.uncommittedSince=g),this.unpushedSince||(this.unpushedSince=g),this.opts.change(a)),this.storage&&(g-d>=this.opts.commitDelay||g-this.uncommittedSince>=this.opts.maxCommitDelay?(this.debug("_invalidate: force commit",g-d>=this.opts.commitDelay,g-this.uncommittedSince>=this.opts.maxCommitDelay),this.commit()):h=Math.min(g+this.opts.commitDelay+1,this.uncommittedSince+this.opts.maxCommitDelay+1)),this.opts.remote&&(g-d>=this.opts.pushDelay||g-this.unpushedSince>=this.opts.maxPushDelay?(this.debug("_invalidate: force push",g-d>=this.opts.pushDelay,g-this.unpushedSince>=this.opts.maxPushDelay),this.push()):i=Math.min(g+this.opts.pushDelay+1,this.unpushedSince+this.opts.maxPushDelay+1)),(h||i)&&(j=Math.min(h||f,i||f),this.debug("_invalidate defer ("+a+") by "+(j-g)+"ms"),this._checkTimer=setTimeout(function(){c._checkTimer=null,c._invalidate.call(c,null,!0)},j-g))},_update:function(a){this.uncommittedSince&&(console.warn("Updating an uncommitted object."),this.conflict(a,this._data)===!1)||(this._data=a,this.lastUpdate=e())},toString:function(){return"PersistentObject('"+this.namespace+"')"},debug:function(){this.opts.debug>=1&&(Array.prototype.unshift.call(arguments,this.toString()),console.log.apply(b.console,arguments))},isDirty:function(){return!!(this.storage&&this.uncommittedSince||this.opts.remote&&this.unpushedSince)},isReady:function(){return"pending"!==this.ready.state},get:function(b){var c,e=this._data,f=(""+b).replace(/\[(\w+)\]/g,".$1").replace(/^\./,"").split(".");for(c=0;c<f.length;c++)e=e[f[c]],e===d&&c<f.length-1&&a.error("The property '"+b+"' could not be accessed because parent '"+f.slice(0,c+1).join(".")+"' does not exist");return e},_setOrRemove:function(b,c,e){var f,g,h=this._data,i=(""+b).replace(/\[(\w+)\]/g,".$1").replace(/^\./,"").split("."),j=i.pop();for(f=0;f<i.length;f++)g=h,h=g[i[f]],h===d&&(this.opts.createParents?(h=g[i[f]]={},this.debug("Creating intermediate property '"+i[f]+"'")):a.error("The property '"+b+"' could not be set because parent '"+i.slice(0,f+1).join(".")+"' does not exist"));h[j]!==c&&(e===!0?(delete h[j],this._invalidate("remove")):(h[j]=c,this._invalidate("set")))},set:function(a,b){return this._setOrRemove(a,b,!1)},remove:function(a){return this._setOrRemove(a,d,!0)},reset:function(a){this._data=a||{},this._invalidate("reset")},setDirty:function(a){a===!1||this._invalidate("explicit")},update:function(){this.phase&&a.error(this+": Trying to update while '"+this.phase+"' is pending."),this.opts.debug>=2&&console.time&&console.time(this+".update");var b=this.storage.getItem(this.namespace);b=JSON.parse(b),this._update(b),this.opts.debug>=2&&console.time&&console.timeEnd(this+".update")},commit:function(){var b;return this.phase&&a.error(this+": Trying to commit while '"+this.phase+"' is pending."),this.opts.debug>=2&&console.time&&console.time(this+".commit"),b=JSON.stringify(this._data),this.storage.setItem(this.namespace,b),this.uncommittedSince=null,this.commitCount+=1,this.opts.debug>=2&&console.time&&console.timeEnd(this+".commit"),b},pull:function(){var b=this;return this.phase&&a.error(this+": Trying to pull while '"+this.phase+"' is pending."),this.opts.debug>=2&&console.time&&console.time(this+".pull"),this.phase="pull",a.ajax({type:"GET",url:this.opts.remote}).done(function(c){var d=c;a.isArray(c)||a.isPlainObject(c)?d=JSON.stringify(c):c=JSON.parse(c),b.storage.setItem(b.namespace,d),b._update(c),b.lastPull=e()}).fail(function(){b.opts.error(arguments)}).always(function(){b.phase=null,b.opts.debug>=2&&console.time&&console.timeEnd(b+".pull")})},push:function(){var b=this,c=this.commit();return this.phase&&a.error(this+": Trying to push while '"+this.phase+"' is pending."),this.opts.debug>=2&&console.time&&console.time(b+".push"),this.phase="push",this.opts.remote||a.error(this+": missing remote option"),a.ajax({type:"PUT",url:this.opts.remote,data:c}).done(function(){b.unpushedSince=null,b.pushCount+=1}).fail(function(){b.opts.error(arguments)}).always(function(){b.phase=null,b.opts.debug>=2&&console.time&&console.timeEnd(b+".push")})},readFromForm:function(b,c){var e=this,f=a(b),g=a.extend({addNew:!1,coerce:!0,trim:!0},c);g.addNew&&f.find("[name]").each(function(){var b=a(this).attr("name");e._data[b]===d&&(e._data[b]=null,e.debug("readFromForm: add field '"+b+"'"))}),a.each(this._data,function(b,c){var d,h=f.find("[name='"+b+"']"),i=h.attr("type");return h.length?("radio"===i?d=h.filter(":checked").val():"checkbox"===i&&1===h.length?d=!!h.filter(":checked").length:"checkbox"===i&&h.length>1?(d=[],h.filter(":checked").each(function(){d.push(a(this).val())})):(d=h.val(),g.trim&&"string"==typeof d&&(d=a.trim(d))),void e.set(b,d)):void e.debug("readFromForm: field not found: '"+b+"'")})},writeToForm:function(b,c){var d=a(b);a.each(this._data,function(b,c){var e=d.find("[name='"+b+"']"),f=e.attr("type");e.length&&("radio"===f?e.filter("[value='"+c+"']").prop("checked",!0):"checkbox"===f?1===e.length?e.prop("checked",!!c):e.each(function(){a(this).prop("checked",a.isArray(c)?a.inArray(this.value,c)>=0:this.value===c)}):e.is("select")?e.find("option").each(function(){a(this).prop("selected",a.isArray(c)?a.inArray(this.value,c)>=0:this.value===c)}):e.val(c))})}}}(jQuery,window,document); | ||
!function(a,b,c,d){"use strict";function e(){return(new Date).getTime()}var f=9007199254740991;b.PersistentObject=function(c,f){var g,h=this,i=a.Deferred(),j=e();this instanceof PersistentObject||a.error("Must use 'new' keyword"),"string"!=typeof c&&a.error(this+": Missing required argument: namespace"),this.opts=a.extend({remote:null,defaults:{},commitDelay:500,createParents:!0,maxCommitDelay:3e3,pushDelay:5e3,maxPushDelay:3e4,storage:b.localStorage,debugLevel: 1,change:a.noop,commit:a.noop,conflict:a.noop,error:a.noop,pull:a.noop,push:a.noop,update:a.noop},f),this._checkTimer=null,this.namespace=c,this.storage=this.opts.storage,this._data=this.opts.defaults,this.offline=d,this.phase=null,this.uncommittedSince=null,this.unpushedSince=null,this.lastUpdate=0,this.lastPull=0,this.commitCount=0,this.pushCount=0,this.lastModified=j,this.ready=i.promise,g=this.storage?this.storage.getItem(this.namespace):null,this.opts.remote?this.pull().done(function(){h.offline=!1,i.resolve()}).fail(function(){h.offline=!0,null!=g?(console.warn(h+": could not init from remote; falling back to storage."),h._data=a.extend({},h.opts.defaults,JSON.parse(g))):console.warn(h+": could not init from remote; falling back default."),i.resolve()}):null!=g?(this.update(),this._data=a.extend({},this.opts.defaults,this._data),i.resolve()):i.resolve()},b.PersistentObject.prototype={version:"1.1.0",_invalidate:function(a,b){var c=this,d=this.lastModified,g=e(),h=0,i=0,j=0;this._checkTimer&&(clearTimeout(this._checkTimer),this._checkTimer=null),b?this.debug("_invalidate() recursive"):(this.lastModified=g,this.uncommittedSince||(this.uncommittedSince=g),this.unpushedSince||(this.unpushedSince=g),this.opts.change(a)),this.storage&&(g-d>=this.opts.commitDelay||g-this.uncommittedSince>=this.opts.maxCommitDelay?(this.debug("_invalidate(): force commit",g-d>=this.opts.commitDelay,g-this.uncommittedSince>=this.opts.maxCommitDelay),this.commit()):h=Math.min(g+this.opts.commitDelay+1,this.uncommittedSince+this.opts.maxCommitDelay+1)),this.opts.remote&&(g-d>=this.opts.pushDelay||g-this.unpushedSince>=this.opts.maxPushDelay?(this.debug("_invalidate(): force push",g-d>=this.opts.pushDelay,g-this.unpushedSince>=this.opts.maxPushDelay),this.push()):i=Math.min(g+this.opts.pushDelay+1,this.unpushedSince+this.opts.maxPushDelay+1)),(h||i)&&(j=Math.min(h||f,i||f),this.debug("_invalidate("+a+") defer by "+(j-g)+"ms"),this._checkTimer=setTimeout(function(){c._checkTimer=null,c._invalidate.call(c,null,!0)},j-g))},_update:function(a){this.uncommittedSince&&(console.warn("Updating an uncommitted object."),this.conflict(a,this._data)===!1)||(this._data=a,this.lastUpdate=e())},toString:function(){return"PersistentObject('"+this.namespace+"')"},debug:function(){this.opts.debugLevel>=2&&(Array.prototype.unshift.call(arguments,this.toString()),console.log.apply(b.console,arguments))},log:function(){this.opts.debugLevel>=1&&(Array.prototype.unshift.call(arguments,this.toString()),console.log.apply(b.console,arguments))},isDirty:function(){return!!(this.storage&&this.uncommittedSince||this.opts.remote&&this.unpushedSince)},isReady:function(){return"pending"!==this.ready.state},get:function(b){var c,e=this._data,f=(""+b).replace(/\[(\w+)\]/g,".$1").replace(/^\./,"").split(".");for(c=0;c<f.length;c++)e=e[f[c]],e===d&&c<f.length-1&&a.error(this+": Property '"+b+"' could not be accessed because parent '"+f.slice(0,c+1).join(".")+"' does not exist");return e},_setOrRemove:function(b,c,e){var f,g,h=this._data,i=(""+b).replace(/\[(\w+)\]/g,".$1").replace(/^\./,"").split("."),j=i.pop();for(f=0;f<i.length;f++)g=h,h=g[i[f]],h===d&&(this.opts.createParents?(this.debug("Creating intermediate parent '"+i[f]+"'"),h=g[i[f]]={}):a.error(this+": Property '"+b+"' could not be set because parent '"+i.slice(0,f+1).join(".")+"' does not exist"));h[j]!==c&&(e===!0?(delete h[j],this._invalidate("remove")):(h[j]=c,this._invalidate("set")))},set:function(a,b){return this._setOrRemove(a,b,!1)},remove:function(a){return this._setOrRemove(a,d,!0)},reset:function(a){this._data=a||{},this._invalidate("reset")},setDirty:function(a){a===!1||this._invalidate("explicit")},update:function(){this.phase&&a.error(this+": Trying to update while '"+this.phase+"' is pending."),this.opts.debugLevel>=2&&console.time&&console.time(this+".update");var b=this.storage.getItem(this.namespace);b=JSON.parse(b),this._update(b),this.opts.debugLevel>=2&&console.time&&console.timeEnd(this+".update")},commit:function(){var b;return this.phase&&a.error(this+": Trying to commit while '"+this.phase+"' is pending."),this.opts.debugLevel>=2&&console.time&&console.time(this+".commit"),b=JSON.stringify(this._data),this.storage.setItem(this.namespace,b),this.uncommittedSince=null,this.commitCount+=1,this.opts.debugLevel>=2&&console.time&&console.timeEnd(this+".commit"),b},pull:function(){var b=this;return this.phase&&a.error(this+": Trying to pull while '"+this.phase+"' is pending."),this.opts.debugLevel>=2&&console.time&&console.time(this+".pull"),this.phase="pull",a.ajax({type:"GET",url:this.opts.remote}).done(function(c){var d=c;a.isArray(c)||a.isPlainObject(c)?d=JSON.stringify(c):c=JSON.parse(c),b.storage.setItem(b.namespace,d),b._update(c),b.lastPull=e()}).fail(function(){b.opts.error(arguments)}).always(function(){b.phase=null,b.opts.debugLevel>=2&&console.time&&console.timeEnd(b+".pull")})},push:function(){var b=this,c=this.commit();return this.phase&&a.error(this+": Trying to push while '"+this.phase+"' is pending."),this.opts.debugLevel>=2&&console.time&&console.time(b+".push"),this.phase="push",this.opts.remote||a.error(this+": Missing remote option"),a.ajax({type:"PUT",url:this.opts.remote,data:c}).done(function(){b.unpushedSince=null,b.pushCount+=1}).fail(function(){b.opts.error(arguments)}).always(function(){b.phase=null,b.opts.debugLevel>=2&&console.time&&console.timeEnd(b+".push")})},readFromForm:function(b,c){var e=this,f=a(b),g=a.extend({addNew:!1,coerce:!0,trim:!0},c);g.addNew&&f.find("[name]").each(function(){var b=a(this).attr("name");e._data[b]===d&&(e.debug("readFromForm: add field '"+b+"'"),e._data[b]=null)}),a.each(this._data,function(b,c){var d,h=f.find("[name='"+b+"']"),i=h.attr("type");return h.length?("radio"===i?d=h.filter(":checked").val():"checkbox"===i&&1===h.length?d=!!h.filter(":checked").length:"checkbox"===i&&h.length>1?(d=[],h.filter(":checked").each(function(){d.push(a(this).val())})):(d=h.val(),g.trim&&"string"==typeof d&&(d=a.trim(d))),void e.set(b,d)):void e.debug("readFromForm: field not found: '"+b+"'")})},writeToForm:function(b,c){var d=a(b);a.each(this._data,function(b,c){var e=d.find("[name='"+b+"']"),f=e.attr("type");e.length&&("radio"===f?e.filter("[value='"+c+"']").prop("checked",!0):"checkbox"===f?1===e.length?e.prop("checked",!!c):e.each(function(){a(this).prop("checked",a.isArray(c)?a.inArray(this.value,c)>=0:this.value===c)}):e.is("select")?e.find("option").each(function(){a(this).prop("selected",a.isArray(c)?a.inArray(this.value,c)>=0:this.value===c)}):e.val(c))})}}}(jQuery,window,document); | ||
//# sourceMappingURL=persisto.min.js.map |
{ | ||
"name": "persisto", | ||
"title": "Persistent, editable objects for Javascript.", | ||
"title": "Persistent Javascript objects and web forms using Web Storage.", | ||
"description": "Persist Javascript objects to localStorage and remote servers.", | ||
"filename": "dist/persisto.min.js", | ||
"main": "dist/persisto.js", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"homepage": "https://github.com/mar10/persisto", | ||
@@ -9,0 +9,0 @@ "author": { |
# persisto [![GitHub version](https://badge.fury.io/gh/mar10%2Fpersisto.svg)](https://github.com/mar10/persisto/releases/latest) [![Build Status](https://travis-ci.org/mar10/persisto.svg?branch=master)](https://travis-ci.org/mar10/persisto) | ||
> Persistent, editable objects for Javascript. | ||
> Persistent Javascript objects and web forms using Web Storage. | ||
@@ -32,5 +32,14 @@ Features | ||
[Download the latest persisto.js](https://github.com/mar10/persisto/releases) | ||
or include directly [from CDN](https://www.jsdelivr.com/projects/persisto): | ||
```html | ||
<script src="//cdn.jsdelivr.net/persisto/1/persisto.min.js"></script> | ||
``` | ||
then instantiate a `PersistentObject`: | ||
```js | ||
var store = PersistentObject("mySettings", { | ||
init: { | ||
defaults: { | ||
theme: "default" | ||
@@ -43,3 +52,3 @@ } | ||
present. Otherwise, `store` is initialized to the default values that we | ||
passed with the `.init` option. | ||
passed with the `.defaults` option. | ||
@@ -60,3 +69,8 @@ We can access data using `set`, `get`, `remove`, `reset`: | ||
**More:** | ||
* Try the [online example](http://plnkr.co/edit/qcDmvN?p=preview). | ||
* Run the [unit tests](http://rawgit.com/mar10/persisto/master/test/unit/test-core.html). | ||
## Synchronize Data with HTML Forms | ||
@@ -71,3 +85,3 @@ | ||
var settingsStore = PersistentObject("mySettings", { | ||
init: { | ||
defaults: { | ||
nickname: "anonymous", | ||
@@ -82,6 +96,6 @@ theme: "default" | ||
// Allow users to edit and save settings: | ||
$("#settingsForm").submit(function(e){ | ||
$("#settingsForm").submit(function(event){ | ||
// ... maybe some validations here ... | ||
settingsStore.readFromForm(this); | ||
e.preventDefault(); | ||
event.preventDefault(); | ||
}); | ||
@@ -137,12 +151,28 @@ ``` | ||
Arrays are only a special form of plain Javascript objects, so we can store and | ||
access them like this: | ||
access them as top level type like this: | ||
```js | ||
var store = PersistentObject("mySettings", { | ||
init: ["a", "b", "c"] | ||
defaults: ["a", "b", "c"] | ||
}); | ||
store.get("[0]"); // 'a' | ||
store.set("[1]", "b2"); | ||
``` | ||
However if we use child properties, it is even easier: | ||
```js | ||
var store = PersistentObject("mySettings", { | ||
defaults: { | ||
values: ["a", "b", "c"] | ||
} | ||
}); | ||
store.get("values")[0]; // 'a' | ||
store.get("values[0]"); // 'a' | ||
S.each(store.get("values"), function(idx, obj) { ... }); | ||
store.set("values[1]", "b2"); | ||
``` | ||
### Performance and Direct Access | ||
@@ -156,3 +186,3 @@ | ||
```js | ||
store._data.owner = {name: "joe", age: 42}); | ||
store._data.owner = { name: "joe", age: 42 }; | ||
store._data.owner.role = "manager"; | ||
@@ -167,3 +197,3 @@ delete store._data.owner.age; | ||
By default, changed values will be commited to webStorage after a small delay | ||
(see `.commitDelay` option). This allows to collate sequences of mutliple changes | ||
(see `.commitDelay` option). This allows to collate sequences of multiple changes | ||
into one single write command. | ||
@@ -199,3 +229,3 @@ | ||
$.ready, | ||
// PersistentObjects must be pulled | ||
// PersistentObject must be pulled | ||
store.ready | ||
@@ -230,9 +260,9 @@ | ||
</dd> | ||
<dt>debug</dt> | ||
<dt>debugLevel</dt> | ||
<dd> | ||
Type: <code>int</code>, | ||
default: <code>2</code><br> | ||
default: <code>1</code><br> | ||
Verbosity level: 0:quiet, 1:normal, 2:verbose. | ||
</dd> | ||
<dt>init</dt> | ||
<dt>defaults</dt> | ||
<dd> | ||
@@ -239,0 +269,0 @@ Type: <code>object</code>, |
/*! | ||
* persisto.js | ||
* | ||
* Persistent, editable objects for Javascript. | ||
* Persistent Javascript objects and web forms using Web Storage. | ||
* | ||
@@ -40,15 +40,21 @@ * Copyright (c) 2016, Martin Wendt (http://wwWendt.de) | ||
if ( typeof namespace !== "string" ) { $.error("Missing required argument: namespace"); } | ||
/* jshint ignore:start */ // disable warning 'PersistentObject is not defined' | ||
if ( !(this instanceof PersistentObject) ) { $.error("Must use 'new' keyword"); } | ||
/* jshint ignore:end */ | ||
if ( typeof namespace !== "string" ) { | ||
$.error(this + ": Missing required argument: namespace"); | ||
} | ||
this.opts = $.extend({ | ||
remote: null, // URL for GET/PUT, ajax options, or callback | ||
init: {}, // default value if no data is found in localStorage | ||
defaults: {}, // default value if no data is found in localStorage | ||
commitDelay: 500, // commit changes after 0.5 seconds of inactivity | ||
createParents: true, // set() creates missing intermediate parent objects for children | ||
maxCommitDelay: 3000, // commit changes max. 3 seconds after first change | ||
pushDelay: 5000, // push commits after 5 seconds of inactivity | ||
maxPushDelay: 30000, // push commits max. 30 seconds after first change | ||
debug: 2, // 0:quiet, 1:normal, 2:verbose | ||
createParents: true, // set() creates intermediate parent objects for children | ||
storage: window.localStorage, | ||
// form: {}, | ||
// Default debugLevel is set to 1 by `grunt build`: | ||
debugLevel: 2, // 0:quiet, 1:normal, 2:verbose | ||
// Events | ||
@@ -63,8 +69,6 @@ change: $.noop, | ||
}, opts); | ||
this.debugLevel = 2; // Set to 1 by 'grunt build' | ||
this._checkTimer = null; | ||
this.namespace = namespace; | ||
this.storage = this.opts.storage; | ||
this._data = this.opts.init; | ||
this._data = this.opts.defaults; | ||
@@ -97,3 +101,3 @@ this.offline = undefined; | ||
// self._data = JSON.parse(prevValue); | ||
self._data = $.extend({}, self.opts.init, JSON.parse(prevValue)); | ||
self._data = $.extend({}, self.opts.defaults, JSON.parse(prevValue)); | ||
} else { | ||
@@ -106,4 +110,4 @@ console.warn(self + ": could not init from remote; falling back default."); | ||
this.update(); | ||
// We still extend from opts.init, in case some fields where missing | ||
this._data = $.extend({}, this.opts.init, this._data); | ||
// We still extend from opts.defaults, in case some fields where missing | ||
this._data = $.extend({}, this.opts.defaults, this._data); | ||
// this.debug("init from storage", this._data); | ||
@@ -144,3 +148,3 @@ dfd.resolve(); | ||
} else { | ||
this.debug("_invalidate recursive()"); | ||
this.debug("_invalidate() recursive"); | ||
} | ||
@@ -154,3 +158,3 @@ | ||
this.debug("_invalidate: force commit", | ||
this.debug("_invalidate(): force commit", | ||
((now - prevChange) >= this.opts.commitDelay), | ||
@@ -171,3 +175,3 @@ ((now - this.uncommittedSince) >= this.opts.maxCommitDelay)); | ||
this.debug("_invalidate: force push", ((now - prevChange) >= this.opts.pushDelay), | ||
this.debug("_invalidate(): force push", ((now - prevChange) >= this.opts.pushDelay), | ||
((now - this.unpushedSince) >= this.opts.maxPushDelay)); | ||
@@ -186,3 +190,3 @@ this.push(); | ||
// this.debug("Defer update:", nextCheck - now) | ||
this.debug("_invalidate defer (" + hint + ") by " + (nextCheck - now) + "ms" ); | ||
this.debug("_invalidate(" + hint + ") defer by " + (nextCheck - now) + "ms" ); | ||
this._checkTimer = setTimeout(function() { | ||
@@ -206,9 +210,9 @@ self._checkTimer = null; // no need to call clearTimeout in the handler... | ||
}, | ||
/* */ | ||
/* Return readable string representation for this instance. */ | ||
toString: function() { | ||
return "PersistentObject('" + this.namespace + "')"; | ||
}, | ||
/* */ | ||
/* Log to console if opts.debugLevel >= 2 */ | ||
debug: function() { | ||
if ( this.opts.debug >= 1 ) { | ||
if ( this.opts.debugLevel >= 2 ) { | ||
Array.prototype.unshift.call(arguments, this.toString()); | ||
@@ -218,2 +222,9 @@ console.log.apply(window.console, arguments); | ||
}, | ||
/* Log to console if opts.debugLevel >= 1 */ | ||
log: function() { | ||
if ( this.opts.debugLevel >= 1 ) { | ||
Array.prototype.unshift.call(arguments, this.toString()); | ||
console.log.apply(window.console, arguments); | ||
} | ||
}, | ||
/** Return true if there are uncommited or unpushed modifications. */ | ||
@@ -246,6 +257,4 @@ isDirty: function() { | ||
if ( cur === undefined && i < (parts.length - 1) ) { | ||
$.error("The property '" + key + | ||
"' could not be accessed because parent '" + | ||
parts.slice(0, i + 1).join(".") + | ||
"' does not exist"); | ||
$.error(this + ": Property '" + key + "' could not be accessed because parent '" + | ||
parts.slice(0, i + 1).join(".") + "' does not exist"); | ||
} | ||
@@ -271,9 +280,7 @@ } | ||
if ( this.opts.createParents ) { | ||
this.debug("Creating intermediate parent '" + parts[i] + "'"); | ||
cur = parent[parts[i]] = {}; | ||
this.debug("Creating intermediate property '" + parts[i] + "'"); | ||
} else { | ||
$.error("The property '" + key + | ||
"' could not be set because parent '" + | ||
parts.slice(0, i + 1).join(".") + | ||
"' does not exist"); | ||
$.error(this + ": Property '" + key + "' could not be set because parent '" + | ||
parts.slice(0, i + 1).join(".") + "' does not exist"); | ||
} | ||
@@ -317,7 +324,7 @@ } | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".update"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".update"); } | ||
var data = this.storage.getItem(this.namespace); | ||
data = JSON.parse(data); | ||
this._update(data); | ||
if ( this.opts.debug >= 2 && console.time ) { console.timeEnd(this + ".update"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.timeEnd(this + ".update"); } | ||
}, | ||
@@ -330,3 +337,3 @@ /** Write data to localStorage. */ | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".commit"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".commit"); } | ||
// try { data = JSON.stringify(this._data); } catch(e) { } | ||
@@ -339,3 +346,3 @@ data = JSON.stringify(this._data); | ||
// this.lastCommit = _getNow(); | ||
if ( this.opts.debug >= 2 && console.time ) { console.timeEnd(this + ".commit"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.timeEnd(this + ".commit"); } | ||
return data; | ||
@@ -350,3 +357,3 @@ }, | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(this + ".pull"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(this + ".pull"); } | ||
this.phase = "pull"; | ||
@@ -371,3 +378,3 @@ // return $.get(this.opts.remote, function(objData) { | ||
self.phase = null; | ||
if ( self.opts.debug >= 2 && console.time ) { console.timeEnd(self + ".pull"); } | ||
if ( self.opts.debugLevel >= 2 && console.time ) { console.timeEnd(self + ".pull"); } | ||
}); | ||
@@ -383,5 +390,5 @@ }, | ||
} | ||
if ( this.opts.debug >= 2 && console.time ) { console.time(self + ".push"); } | ||
if ( this.opts.debugLevel >= 2 && console.time ) { console.time(self + ".push"); } | ||
this.phase = "push"; | ||
if ( !this.opts.remote ) { $.error(this + ": missing remote option"); } | ||
if ( !this.opts.remote ) { $.error(this + ": Missing remote option"); } | ||
return $.ajax({ | ||
@@ -400,3 +407,3 @@ type: "PUT", | ||
self.phase = null; | ||
if ( self.opts.debug >= 2 && console.time ) { console.timeEnd(self + ".push"); } | ||
if ( self.opts.debugLevel >= 2 && console.time ) { console.timeEnd(self + ".push"); } | ||
}); | ||
@@ -421,4 +428,4 @@ }, | ||
if ( self._data[name] === undefined ) { | ||
self.debug("readFromForm: add field '" + name + "'"); | ||
self._data[name] = null; | ||
self.debug("readFromForm: add field '" + name + "'"); | ||
} | ||
@@ -425,0 +432,0 @@ }); |
@@ -22,6 +22,16 @@ ;(function($, window, document, undefined) { | ||
QUnit.test( "Access core data (storage = null)", function( assert ) { | ||
assert.expect(19); | ||
assert.expect(21); | ||
assert.equal( window.localStorage.length, 0, "localStorage is clean" ); | ||
assert.throws(function(){ | ||
var _dummy = PersistentObject(); | ||
}, /Must use 'new' keyword/, | ||
"Fail if 'new' keyword is missing"); | ||
assert.throws(function(){ | ||
var _dummy = new PersistentObject(); | ||
}, /Missing required argument: namespace/, | ||
"Fail if 'namespace' argument is missing"); | ||
var store = new PersistentObject("foo", { | ||
@@ -59,3 +69,3 @@ storage: null, // don't commit/update local storage | ||
}, | ||
/The property 'bar.qux.undefined.boo' could not be accessed because parent 'bar.qux.undefined' does not exist/, | ||
/PersistentObject\('foo'\): Property 'bar.qux.undefined.boo' could not be accessed because parent 'bar.qux.undefined' does not exist/, | ||
"get() raises error if parent does not exist"); | ||
@@ -79,3 +89,3 @@ | ||
}, | ||
/The property 'bar.undefined2.test' could not be set because parent 'bar.undefined2' does not exist/, | ||
/PersistentObject\('foo'\): Property 'bar.undefined2.test' could not be set because parent 'bar.undefined2' does not exist/, | ||
"set() raises error if createParents is false"); | ||
@@ -137,3 +147,3 @@ | ||
remote: null, // don't pull/push remote endpoint | ||
init: { | ||
defaults: { | ||
title: "foo", | ||
@@ -182,3 +192,3 @@ details: "bar\nbaz", | ||
store = new PersistentObject("foo", { | ||
init: { | ||
defaults: { | ||
title: "qux", | ||
@@ -230,3 +240,3 @@ details: "qux", | ||
var i, v, | ||
store = new PersistentObject("foo", {storage: null, debug: 0}), | ||
store = new PersistentObject("foo", {storage: null, debugLevel: 0}), | ||
count = 100000; | ||
@@ -290,3 +300,3 @@ | ||
var i, v, | ||
store = new PersistentObject("foo", {storage: window.sessionStorage, debug: 0}), | ||
store = new PersistentObject("foo", {storage: window.sessionStorage, debugLevel: 0}), | ||
count = 10000; | ||
@@ -350,3 +360,3 @@ | ||
var i, v, | ||
store = new PersistentObject("foo", {storage: window.sessionStorage, commitDelay: 0, debug: 0}), | ||
store = new PersistentObject("foo", {storage: window.sessionStorage, commitDelay: 0, debugLevel: 0}), | ||
count = 1000; | ||
@@ -353,0 +363,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
165690
24
1609
414