2012-10-02 20:52:07 +00:00
|
|
|
/**
|
|
|
|
* Plugin developed to save html forms data to LocalStorage to restore them after browser crashes, tabs closings
|
|
|
|
* and other disasters.
|
|
|
|
*
|
|
|
|
* @author Alexander Kaupanin <kaupanin@gmail.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
//$.sisyphus().setOptions({timeout: 15})
|
|
|
|
( function( $ ) {
|
|
|
|
$.sisyphus = function() {
|
|
|
|
return Sisyphus.getInstance();
|
|
|
|
};
|
|
|
|
|
|
|
|
$.fn.sisyphus = function( options ) {
|
|
|
|
var sisyphus = Sisyphus.getInstance();
|
|
|
|
sisyphus.setOptions( options );
|
|
|
|
sisyphus.protect( this );
|
|
|
|
return sisyphus;
|
|
|
|
};
|
|
|
|
|
|
|
|
var browserStorage = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if local storage or other browser storage is available
|
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
browserStorage.isAvailable = function() {
|
|
|
|
if ( typeof $.jStorage === "object" ) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return localStorage.getItem;
|
|
|
|
} catch ( e ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set data to browser storage
|
|
|
|
*
|
|
|
|
* @param [String] key
|
|
|
|
* @param [String] value
|
|
|
|
*
|
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
browserStorage.set = function( key, value ) {
|
|
|
|
if ( typeof $.jStorage === "object" ) {
|
|
|
|
$.jStorage.set( key, value + "" );
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
localStorage.setItem( key, value + "" );
|
|
|
|
} catch (e) {
|
|
|
|
//QUOTA_EXCEEDED_ERR
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get data from browser storage by specified key
|
|
|
|
*
|
|
|
|
* @param [String] key
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
browserStorage.get = function( key ) {
|
|
|
|
if ( typeof $.jStorage === "object" ) {
|
|
|
|
var result = $.jStorage.get( key );
|
|
|
|
return result ? result.toString() : result;
|
|
|
|
} else {
|
|
|
|
return localStorage.getItem( key );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete data from browser storage by specified key
|
|
|
|
*
|
|
|
|
* @param [String] key
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
browserStorage.remove = function( key ) {
|
|
|
|
if ( typeof $.jStorage === "object" ) {
|
|
|
|
$.jStorage.deleteKey( key );
|
|
|
|
} else {
|
|
|
|
localStorage.removeItem( key );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Sisyphus = ( function() {
|
|
|
|
var params = {
|
|
|
|
instantiated: null,
|
|
|
|
started: null
|
|
|
|
};
|
|
|
|
|
|
|
|
function init () {
|
|
|
|
|
|
|
|
return {
|
|
|
|
/**
|
|
|
|
* Set plugin initial options
|
|
|
|
*
|
|
|
|
* @param [Object] options
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
setInitialOptions: function ( options ) {
|
|
|
|
var defaults = {
|
|
|
|
excludeFields: [],
|
|
|
|
customKeyPrefix: "",
|
|
|
|
timeout: 0,
|
|
|
|
autoRelease: true,
|
|
|
|
name: null,
|
|
|
|
onSave: function() {},
|
|
|
|
onBeforeRestore: function() {},
|
|
|
|
onRestore: function() {},
|
|
|
|
onRelease: function() {}
|
|
|
|
};
|
|
|
|
this.options = this.options || $.extend( defaults, options );
|
|
|
|
this.browserStorage = browserStorage;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set plugin options
|
|
|
|
*
|
|
|
|
* @param [Object] options
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
setOptions: function ( options ) {
|
|
|
|
this.options = this.options || this.setInitialOptions( options );
|
|
|
|
this.options = $.extend( this.options, options );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Protect specified forms, store it's fields data to local storage and restore them on page load
|
|
|
|
*
|
|
|
|
* @param [Object] targets forms object(s), result of jQuery selector
|
|
|
|
* @param Object options plugin options
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protect: function( targets ) {
|
|
|
|
targets = targets || {};
|
|
|
|
var self = this;
|
|
|
|
this.targets = this.targets || [];
|
|
|
|
this.href = this.options.name || location.hostname + location.pathname + location.search + location.hash;
|
|
|
|
|
|
|
|
this.targets = $.merge( this.targets, targets );
|
|
|
|
this.targets = $.unique( this.targets );
|
|
|
|
this.targets = $( this.targets );
|
|
|
|
if ( ! this.browserStorage.isAvailable() ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.restoreAllData();
|
|
|
|
if ( this.options.autoRelease ) {
|
|
|
|
self.bindReleaseData();
|
|
|
|
}
|
|
|
|
if ( ! params.started ) {
|
|
|
|
self.bindSaveData();
|
|
|
|
params.started = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind saving data
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
bindSaveData: function() {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if ( self.options.timeout ) {
|
|
|
|
self.saveDataByTimeout();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.targets.each( function() {
|
|
|
|
var targetFormId = $( this ).attr( "id" );
|
|
|
|
var fieldsToProtect = $( this ).find( ":input" ).not( ":submit" ).not( ":reset" ).not( ":button" ).not( ":file" );
|
|
|
|
|
|
|
|
fieldsToProtect.each( function() {
|
|
|
|
if ( $.inArray( this, self.options.excludeFields ) !== -1 ) {
|
|
|
|
// Returning non-false is the same as a continue statement in a for loop; it will skip immediately to the next iteration.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var field = $( this );
|
|
|
|
var prefix = self.href + targetFormId + field.attr( "name" ) + self.options.customKeyPrefix;
|
|
|
|
if ( field.is( ":text" ) || field.is( "textarea" ) ) {
|
|
|
|
if ( ! self.options.timeout ) {
|
|
|
|
self.bindSaveDataImmediately( field, prefix );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.bindSaveDataOnChange( field, prefix );
|
|
|
|
}
|
|
|
|
} );
|
2012-11-04 16:07:03 +00:00
|
|
|
|
|
|
|
if ( typeof window.CKEDITOR !== 'undefined' ) {
|
|
|
|
CKEDITOR.on( 'instanceReady', function(ev) {
|
|
|
|
if ( $.inArray( ev.editor.element.$, fieldsToProtect ) ) {
|
|
|
|
ev.editor.on( 'change', function(eev) {
|
|
|
|
eev.editor.updateElement();
|
|
|
|
var element = eev.editor.element.$;
|
|
|
|
|
|
|
|
if ( typeof element.oninput === 'undefined' ) {
|
|
|
|
element.onpropertychange();
|
|
|
|
} else {
|
|
|
|
element.oninput();
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
2012-10-02 20:52:07 +00:00
|
|
|
} );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save all protected forms data to Local Storage.
|
|
|
|
* Common method, necessary to not lead astray user firing 'data are saved' when select/checkbox/radio
|
|
|
|
* is changed and saved, while textfield data are saved only by timeout
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
saveAllData: function() {
|
|
|
|
var self = this;
|
2012-11-04 16:07:03 +00:00
|
|
|
|
2012-10-02 20:52:07 +00:00
|
|
|
self.targets.each( function() {
|
|
|
|
var targetFormId = $( this ).attr( "id" );
|
|
|
|
var fieldsToProtect = $( this ).find( ":input" ).not( ":submit" ).not( ":reset" ).not( ":button" ).not( ":file" );
|
|
|
|
|
|
|
|
fieldsToProtect.each( function() {
|
|
|
|
var field = $( this );
|
|
|
|
if ( $.inArray( this, self.options.excludeFields ) !== -1 || field.attr( "name" ) === undefined ) {
|
|
|
|
// Returning non-false is the same as a continue statement in a for loop; it will skip immediately to the next iteration.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var prefix = self.href + targetFormId + field.attr( "name" ) + self.options.customKeyPrefix;
|
|
|
|
var value = field.val();
|
|
|
|
|
|
|
|
if ( field.is(":checkbox") ) {
|
|
|
|
if ( field.attr( "name" ).indexOf( "[" ) !== -1 ) {
|
|
|
|
value = [];
|
|
|
|
$( "[name='" + field.attr( "name" ) +"']:checked" ).each( function() {
|
|
|
|
value.push( $( this ).val() );
|
|
|
|
} );
|
|
|
|
} else {
|
|
|
|
value = field.is( ":checked" );
|
|
|
|
}
|
|
|
|
self.saveToBrowserStorage( prefix, value, false );
|
|
|
|
} else if ( field.is( ":radio" ) ) {
|
|
|
|
if ( field.is( ":checked" ) ) {
|
|
|
|
value = field.val();
|
|
|
|
self.saveToBrowserStorage( prefix, value, false );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.saveToBrowserStorage( prefix, value, false );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
if ( $.isFunction( self.options.onSave ) ) {
|
|
|
|
self.options.onSave.call();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore forms data from Local Storage
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
restoreAllData: function() {
|
|
|
|
var self = this;
|
|
|
|
var restored = false;
|
|
|
|
|
|
|
|
if ( $.isFunction( self.options.onBeforeRestore ) ) {
|
|
|
|
self.options.onBeforeRestore.call();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.targets.each( function() {
|
|
|
|
var target = $( this );
|
|
|
|
var targetFormId = target.attr( "id" );
|
|
|
|
var fieldsToProtect = target.find( ":input" ).not( ":submit" ).not( ":reset" ).not( ":button" ).not( ":file" );
|
|
|
|
|
|
|
|
fieldsToProtect.each( function() {
|
|
|
|
|
|
|
|
if ( $.inArray( this, self.options.excludeFields ) !== -1 ) {
|
|
|
|
// Returning non-false is the same as a continue statement in a for loop; it will skip immediately to the next iteration.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var field = $( this );
|
|
|
|
var prefix = self.href + targetFormId + field.attr( "name" ) + self.options.customKeyPrefix;
|
|
|
|
var resque = self.browserStorage.get( prefix );
|
|
|
|
if ( resque ) {
|
|
|
|
self.restoreFieldsData( field, resque );
|
|
|
|
restored = true;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
|
|
|
|
if ( restored && $.isFunction( self.options.onRestore ) ) {
|
|
|
|
self.options.onRestore.call();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore form field data from local storage
|
|
|
|
*
|
|
|
|
* @param Object field jQuery form element object
|
|
|
|
* @param String resque previously stored fields data
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
restoreFieldsData: function( field, resque ) {
|
|
|
|
if ( field.attr( "name" ) === undefined ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ( field.is( ":checkbox" ) && resque !== "false" && field.attr( "name" ).indexOf( "[" ) === -1 ) {
|
|
|
|
field.attr( "checked", "checked" );
|
|
|
|
} else if ( field.is( ":radio" ) ) {
|
|
|
|
if ( field.val() === resque ) {
|
|
|
|
field.attr( "checked", "checked" );
|
|
|
|
}
|
|
|
|
} else if ( field.attr( "name" ).indexOf( "[" ) === -1 ) {
|
|
|
|
field.val( resque );
|
|
|
|
} else {
|
|
|
|
resque = resque.split( "," );
|
|
|
|
field.val( resque );
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind immediate saving (on typing/checking/changing) field data to local storage when user fills it
|
|
|
|
*
|
|
|
|
* @param Object field jQuery form element object
|
|
|
|
* @param String prefix prefix used as key to store data in local storage
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
bindSaveDataImmediately: function( field, prefix ) {
|
|
|
|
var self = this;
|
2012-11-04 16:07:03 +00:00
|
|
|
|
2012-10-02 20:52:07 +00:00
|
|
|
if ( typeof $.browser.msie === 'undefined' ) {
|
|
|
|
field.get(0).oninput = function() {
|
|
|
|
self.saveToBrowserStorage( prefix, field.val() );
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
field.get(0).onpropertychange = function() {
|
|
|
|
self.saveToBrowserStorage( prefix, field.val() );
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save data to Local Storage and fire callback if defined
|
|
|
|
*
|
|
|
|
* @param String key
|
|
|
|
* @param String value
|
|
|
|
* @param Boolean [true] fireCallback
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
saveToBrowserStorage: function( key, value, fireCallback ) {
|
|
|
|
// if fireCallback is undefined it should be true
|
|
|
|
fireCallback = fireCallback === null ? true : fireCallback;
|
|
|
|
this.browserStorage.set( key, value );
|
|
|
|
if ( fireCallback && value !== "" && $.isFunction( this.options.onSave ) ) {
|
|
|
|
this.options.onSave.call();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind saving field data on change
|
|
|
|
*
|
|
|
|
* @param Object field jQuery form element object
|
|
|
|
* @param String prefix prefix used as key to store data in local storage
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
bindSaveDataOnChange: function( field, prefix ) {
|
|
|
|
var self = this;
|
|
|
|
field.change( function() {
|
|
|
|
self.saveAllData();
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saving (by timeout) field data to local storage when user fills it
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
saveDataByTimeout: function() {
|
|
|
|
var self = this;
|
|
|
|
var targetForms = self.targets;
|
|
|
|
setTimeout( ( function( targetForms ) {
|
|
|
|
function timeout() {
|
|
|
|
self.saveAllData();
|
|
|
|
setTimeout( timeout, self.options.timeout * 1000 );
|
|
|
|
}
|
|
|
|
return timeout;
|
|
|
|
} )( targetForms ), self.options.timeout * 1000 );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind release form fields data from local storage on submit/reset form
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
bindReleaseData: function() {
|
|
|
|
var self = this;
|
|
|
|
self.targets.each( function( i ) {
|
|
|
|
var target = $( this );
|
|
|
|
var fieldsToProtect = target.find( ":input" ).not( ":submit" ).not( ":reset" ).not( ":button" ).not( ":file" );
|
|
|
|
var formId = target.attr( "id" );
|
|
|
|
$( this ).bind( "submit reset", function() {
|
|
|
|
self.releaseData( formId, fieldsToProtect );
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manually release form fields
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
manuallyReleaseData: function() {
|
|
|
|
var self = this;
|
|
|
|
self.targets.each( function( i ) {
|
|
|
|
var target = $( this );
|
|
|
|
var fieldsToProtect = target.find( ":input" ).not( ":submit" ).not( ":reset" ).not( ":button" ).not( ":file" );
|
|
|
|
var formId = target.attr( "id" );
|
|
|
|
self.releaseData( formId, fieldsToProtect );
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bind release form fields data from local storage on submit/resett form
|
|
|
|
*
|
|
|
|
* @param String targetFormId
|
|
|
|
* @param Object fieldsToProtect jQuery object contains form fields to protect
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
releaseData: function( targetFormId, fieldsToProtect ) {
|
|
|
|
var released = false;
|
|
|
|
var self = this;
|
|
|
|
fieldsToProtect.each( function() {
|
|
|
|
if ( $.inArray( this, self.options.excludeFields ) !== -1 ) {
|
|
|
|
// Returning non-false is the same as a continue statement in a for loop; it will skip immediately to the next iteration.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
var field = $( this );
|
|
|
|
var prefix = self.href + targetFormId + field.attr( "name" ) + self.options.customKeyPrefix;
|
|
|
|
self.browserStorage.remove( prefix );
|
|
|
|
released = true;
|
|
|
|
} );
|
|
|
|
|
|
|
|
if ( released && $.isFunction( self.options.onRelease ) ) {
|
|
|
|
self.options.onRelease.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
getInstance: function() {
|
|
|
|
if ( ! params.instantiated ) {
|
|
|
|
params.instantiated = init();
|
|
|
|
params.instantiated.setInitialOptions();
|
|
|
|
}
|
|
|
|
return params.instantiated;
|
|
|
|
},
|
|
|
|
|
|
|
|
free: function() {
|
|
|
|
params = {};
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} )();
|
2012-11-04 16:07:03 +00:00
|
|
|
} )( jQuery );
|