diff --git a/lib/sisyphus-rails/form_tag_helper.rb b/lib/sisyphus-rails/form_tag_helper.rb
index 866f498..5a98156 100644
--- a/lib/sisyphus-rails/form_tag_helper.rb
+++ b/lib/sisyphus-rails/form_tag_helper.rb
@@ -6,7 +6,7 @@ module ActionView
buf = ActiveSupport::SafeBuffer.new
if options.has_key?(:id) && Sisyphus::process
- buf.safe_concat("")
+ buf.safe_concat("")
end
buf << form_tag_without_sisyphus(url_for_options, options, &block)
@@ -15,4 +15,4 @@ module ActionView
alias_method_chain :form_tag, :sisyphus
end
end
-end
\ No newline at end of file
+end
diff --git a/sisyphus-rails.gemspec b/sisyphus-rails.gemspec
index a40d7da..e700930 100644
--- a/sisyphus-rails.gemspec
+++ b/sisyphus-rails.gemspec
@@ -16,6 +16,9 @@ Gem::Specification.new do |gem|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
- gem.add_dependency "railties", "~> 3.1"
- gem.add_dependency "jquery-rails"
+ gem.requirements << 'requires jQuery 1.4+ to be included before Sisyphus'
+ gem.requirements << 'requires jQuery 1.8+ to be included if you require jStorage'
+
+ gem.add_dependency "rails", ">= 3.1.0"
end
+
diff --git a/vendor/assets/javascripts/sisyphus.js b/vendor/assets/javascripts/sisyphus.js
index 1c5b652..8509bc5 100644
--- a/vendor/assets/javascripts/sisyphus.js
+++ b/vendor/assets/javascripts/sisyphus.js
@@ -1 +1,459 @@
-(function(a){a.sisyphus=function(){return Sisyphus.getInstance()},a.fn.sisyphus=function(a){var b=Sisyphus.getInstance();return b.setOptions(a),b.protect(this),b};var b={};b.isAvailable=function(){if(typeof a.jStorage=="object")return!0;try{return localStorage.getItem}catch(b){return!1}},b.set=function(b,c){if(typeof a.jStorage=="object")a.jStorage.set(b,c+"");else try{localStorage.setItem(b,c+"")}catch(d){}},b.get=function(b){if(typeof a.jStorage=="object"){var c=a.jStorage.get(b);return c?c.toString():c}return localStorage.getItem(b)},b.remove=function(b){typeof a.jStorage=="object"?a.jStorage.deleteKey(b):localStorage.removeItem(b)},Sisyphus=function(){function d(){return{setInitialOptions:function(c){var d={excludeFields:[],customKeyPrefix:"",timeout:0,autoRelease:!0,name:null,onSave:function(){},onBeforeRestore:function(){},onRestore:function(){},onRelease:function(){}};this.options=this.options||a.extend(d,c),this.browserStorage=b},setOptions:function(b){this.options=this.options||this.setInitialOptions(b),this.options=a.extend(this.options,b)},protect:function(b){b=b||{};var d=this;this.targets=this.targets||[],this.href=this.options.name||location.hostname+location.pathname+location.search+location.hash,this.targets=a.merge(this.targets,b),this.targets=a.unique(this.targets),this.targets=a(this.targets);if(!this.browserStorage.isAvailable())return!1;d.restoreAllData(),this.options.autoRelease&&d.bindReleaseData(),c.started||(d.bindSaveData(),c.started=!0)},bindSaveData:function(){var b=this;b.options.timeout&&b.saveDataByTimeout(),b.targets.each(function(){var c=a(this).attr("id"),d=a(this).find(":input").not(":submit").not(":reset").not(":button").not(":file");d.each(function(){if(a.inArray(this,b.options.excludeFields)!==-1)return!0;var d=a(this),e=b.href+c+d.attr("name")+b.options.customKeyPrefix;d.is(":text")||d.is("textarea")?b.options.timeout||b.bindSaveDataImmediately(d,e):b.bindSaveDataOnChange(d,e)})})},saveAllData:function(){var b=this;b.targets.each(function(){var c=a(this).attr("id"),d=a(this).find(":input").not(":submit").not(":reset").not(":button").not(":file");d.each(function(){var d=a(this);if(a.inArray(this,b.options.excludeFields)!==-1||d.attr("name")===undefined)return!0;var e=b.href+c+d.attr("name")+b.options.customKeyPrefix,f=d.val();d.is(":checkbox")?(d.attr("name").indexOf("[")!==-1?(f=[],a("[name='"+d.attr("name")+"']:checked").each(function(){f.push(a(this).val())})):f=d.is(":checked"),b.saveToBrowserStorage(e,f,!1)):d.is(":radio")?d.is(":checked")&&(f=d.val(),b.saveToBrowserStorage(e,f,!1)):b.saveToBrowserStorage(e,f,!1)})}),a.isFunction(b.options.onSave)&&b.options.onSave.call()},restoreAllData:function(){var b=this,c=!1;a.isFunction(b.options.onBeforeRestore)&&b.options.onBeforeRestore.call(),b.targets.each(function(){var d=a(this),e=d.attr("id"),f=d.find(":input").not(":submit").not(":reset").not(":button").not(":file");f.each(function(){if(a.inArray(this,b.options.excludeFields)!==-1)return!0;var d=a(this),f=b.href+e+d.attr("name")+b.options.customKeyPrefix,g=b.browserStorage.get(f);g&&(b.restoreFieldsData(d,g),c=!0)})}),c&&a.isFunction(b.options.onRestore)&&b.options.onRestore.call()},restoreFieldsData:function(a,b){if(a.attr("name")===undefined)return!1;a.is(":checkbox")&&b!=="false"&&a.attr("name").indexOf("[")===-1?a.attr("checked","checked"):a.is(":radio")?a.val()===b&&a.attr("checked","checked"):a.attr("name").indexOf("[")===-1?a.val(b):(b=b.split(","),a.val(b))},bindSaveDataImmediately:function(b,c){var d=this;a.browser.msie===null?b.get(0).oninput=function(){d.saveToBrowserStorage(c,b.val())}:b.get(0).onpropertychange=function(){d.saveToBrowserStorage(c,b.val())}},saveToBrowserStorage:function(b,c,d){d=d===null?!0:d,this.browserStorage.set(b,c),d&&c!==""&&a.isFunction(this.options.onSave)&&this.options.onSave.call()},bindSaveDataOnChange:function(a,b){var c=this;a.change(function(){c.saveAllData()})},saveDataByTimeout:function(){var a=this,b=a.targets;setTimeout(function(b){function c(){a.saveAllData(),setTimeout(c,a.options.timeout*1e3)}return c}(b),a.options.timeout*1e3)},bindReleaseData:function(){var b=this;b.targets.each(function(c){var d=a(this),e=d.find(":input").not(":submit").not(":reset").not(":button").not(":file"),f=d.attr("id");a(this).bind("submit reset",function(){b.releaseData(f,e)})})},manuallyReleaseData:function(){var b=this;b.targets.each(function(c){var d=a(this),e=d.find(":input").not(":submit").not(":reset").not(":button").not(":file"),f=d.attr("id");b.releaseData(f,e)})},releaseData:function(b,c){var d=!1,e=this;c.each(function(){if(a.inArray(this,e.options.excludeFields)!==-1)return!0;var c=a(this),f=e.href+b+c.attr("name")+e.options.customKeyPrefix;e.browserStorage.remove(f),d=!0}),d&&a.isFunction(e.options.onRelease)&&e.options.onRelease.call()}}}var c={instantiated:null,started:null};return{getInstance:function(){return c.instantiated||(c.instantiated=d(),c.instantiated.setInitialOptions()),c.instantiated},free:function(){return c={},null}}}()})(jQuery)
\ No newline at end of file
+/**
+ * Plugin developed to save html forms data to LocalStorage to restore them after browser crashes, tabs closings
+ * and other disasters.
+ *
+ * @author Alexander Kaupanin
+ */
+
+//$.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 );
+ }
+ } );
+ } );
+ },
+
+ /**
+ * 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;
+ 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;
+ 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;
+ }
+ };
+ } )();
+} )( jQuery );
\ No newline at end of file