text: /*
    Garlic.js allows you to automatically persist your forms' text field values locally,
    until the form is submitted. This way, your users don't lose any precious data if they
    accidentally close their tab or browser.
  
    author: Guillaume Potier - @guillaumepotier
  */
  
  !function ($) {
  
    "use strict";
    /*global localStorage */
    /*global document */
  
    /* STORAGE PUBLIC CLASS DEFINITION
     * =============================== */
    var Storage = function ( options ) {
      this.defined = 'undefined' !== typeof localStorage;
  
      // https://github.com/Modernizr/Modernizr/blob/5eea7e2a213edc9e83a47b6414d0250468d83471/feature-detects/storage/localstorage.js#L40
      var key = 'garlic:' + document.domain + '>test';
      try {
        localStorage.setItem(key, key);
        localStorage.removeItem(key);
      } catch (e) {
        this.defined = false;
      }
    };
  
    Storage.prototype = {
  
      constructor: Storage
  
      , get: function ( key, placeholder ) {
        var value = localStorage.getItem( key );
  
        if (value) {
          try{
            value = JSON.parse(value);
          } catch (e) {
            // ignore, fallback for older versions
          }
  
          return value;
  
        } else if ('undefined' !== typeof placeholder) {
          return placeholder;
  
        } else {
          return null;
        }
      }
  
      , has: function ( key ) {
        return localStorage.getItem( key ) ? true : false;
      }
  
      , set: function ( key, value, fn ) {
  
        // if value is null, remove storage if exists
        if ( '' === value || (value instanceof Array && value.length === 0)) {
          this.destroy( key );
        } else {
          value = JSON.stringify(value);
          localStorage.setItem( key , value );
        }
  
        return 'function' === typeof fn ? fn() : true;
      }
  
      , destroy: function ( key, fn ) {
        localStorage.removeItem( key );
        return 'function' === typeof fn ? fn() : true;
      }
  
      , clean: function ( fn ) {
        for ( var i = localStorage.length - 1; i >= 0; i-- ) {
          if ( 'undefined' === typeof Array.indexOf && -1 !== localStorage.key(i).indexOf( 'garlic:' ) ) {
            localStorage.removeItem( localStorage.key(i) );
          }
        }
  
        return 'function' === typeof fn ? fn() : true;
      }
  
      , clear: function ( fn ) {
        localStorage.clear();
        return 'function' === typeof fn ? fn() : true;
      }
    }
  
   /* GARLIC PUBLIC CLASS DEFINITION
    * =============================== */
  
    var Garlic = function ( element, storage, options ) {
      this.init( 'garlic', element, storage, options );
    };
  
    Garlic.prototype = {
  
      constructor: Garlic
  
      /* init data, bind jQuery on() actions */
      , init: function ( type, element, storage, options ) {
        this.type = type;
        this.$element = $( element );
        this.options = this.getOptions( options );
        this.storage = storage;
        this.path = this.options.getPath( this.$element ) || this.getPath();
        this.parentForm = this.$element.closest( 'form' );
        this.$element.addClass('garlic-auto-save');
        this.expiresFlag = !this.options.expires ? false : ( this.$element.data( 'expires' ) ? this.path : this.getPath( this.parentForm ) ) + '_flag' ;
  
        // bind garlic events
        this.$element.on( this.options.events.join( '.' + this.type + ' ') , false, $.proxy( this.persist, this ) );
  
        if ( this.options.destroy ) {
          $( this.parentForm ).on( 'submit reset' , false, $.proxy( this.destroy, this ) );
        }
  
        // retrieve garlic persisted data
        this.retrieve();
      }
  
      , getOptions: function ( options ) {
        return $.extend( {}, $.fn[this.type].defaults, options, this.$element.data() );
      }
  
      /* temporary store data / state in localStorage */
      , persist: function () {
  
        // some binded events are redundant (change & paste for example), persist only once by field val
        if ( this.val === this.getVal() ) {
          return;
        }
  
        this.val = this.getVal();
  
        // if auto-expires is enabled, set the expiration date for future auto-deletion
        if ( this.options.expires ) {
          this.storage.set( this.expiresFlag , ( new Date().getTime() + this.options.expires * 1000 ).toString() );
        }
        
        // Allow changing the value to any other string
        var prePersistValue = this.options.prePersist(this.$element, this.val);
        if (typeof prePersistValue == 'string') {
            this.val = prePersistValue;
        }
  
        this.storage.set(this.path , this.val);
        this.options.onPersist(this.$element, this.val);
      }
  
      , getVal: function () {
        return !this.$element.is( 'input[type=checkbox]' ) ? this.$element.val() : ( this.$element.prop( 'checked' ) ? 'checked' : 'unchecked' );
      }
  
      /* retrieve localStorage data / state and update elem accordingly */
      , retrieve: function () {
        if ( this.storage.has( this.path ) ) {
  
          // if data expired, destroy it!
          if ( this.options.expires ) {
            var date = new Date().getTime();
            if ( this.storage.get( this.expiresFlag ) < date.toString() ) {
              this.storage.destroy( this.path );
              return;
            } else {
              this.$element.attr( 'expires-in',  Math.floor( ( parseInt( this.storage.get( this.expiresFlag ) ) - date ) / 1000 ) );
            }
          }
          
          var currentValue = this.$element.val();
          var storedValue = this.storage.get( this.path );
  
          // Allow not restoring the value in case value was `false`
          var storedValue = this.options.preRetrieve(this.$element, currentValue, storedValue);
          if (typeof storedValue == 'boolean' && storedValue == false) {
              return;
          }
  
          // if conflictManager enabled, manage fields with already provided data, different from the one stored
          if ( this.options.conflictManager.enabled && this.detectConflict() ) {
            return this.conflictManager();
          }
  
          // input[type=checkbox] and input[type=radio] have a special checked / unchecked behavior
          if ( this.$element.is( 'input[type=radio], input[type=checkbox]' ) ) {
  
            // for checkboxes and radios
            if ( 'checked' === storedValue || this.$element.val() === storedValue ) {
              return this.$element.prop( 'checked', true );
  
            // only needed for checkboxes
            } else if ( 'unchecked' === storedValue ) {
              this.$element.prop( 'checked', false );
            }
  
            return;
          }
  
          // for input[type=text], select and textarea, just set val()
          this.$element.val( storedValue );
  
          // trigger an input event given the value has been changed
          this.$element.trigger( 'input' );
  
          // trigger custom user function when data is retrieved
          this.options.onRetrieve( this.$element, storedValue );
  
          return;
        }
      }
  
      /* there is a conflict when initial data / state differs from persisted data / state */
      , detectConflict: function() {
        var self = this;
  
        // radio buttons and checkboxes are yet not supported
        if ( this.$element.is( 'input[type=checkbox], input[type=radio]' ) ) {
          return false;
        }
  
        // there is a default not null value and we have a different one stored
        if ( this.$element.val() && this.storage.get( this.path ) !== this.$element.val() ) {
  
          // for select elements, we need to check if there is a default checked value
          if ( this.$element.is( 'select' ) ) {
            var selectConflictDetected = false;
  
            // foreach each options except first one, always considered as selected, seeking for a default selected one
            this.$element.find( 'option' ).each( function () {
              if ( $( this ).index() !== 0 && $( this ).attr( 'selected' ) && $( this ).val() !== self.storage.get( this.path ) ) {
                selectConflictDetected = true;
                return;
              }
            });
  
            return selectConflictDetected;
          }
  
          return true;
        }
  
        return false;
      }
  
      /* manage here the conflict, show default value depending on options.garlicPriority value */
      , conflictManager: function () {
  
        // user can define here a custom function that could stop Garlic default behavior, if returns false
        if ( 'function' === typeof this.options.conflictManager.onConflictDetected
          && !this.options.conflictManager.onConflictDetected( this.$element, this.storage.get( this.path ) ) ) {
          return false;
        }
  
        if ( this.options.conflictManager.garlicPriority ) {
          this.$element.data( 'swap-data', this.$element.val() );
          this.$element.data( 'swap-state', 'garlic' );
          this.$element.val( this.storage.get( this.path ) );
        } else {
          this.$element.data( 'swap-data', this.storage.get( this.path ) );
          this.$element.data( 'swap-state', 'default' );
        }
  
        this.swapHandler();
        this.$element.addClass( 'garlic-conflict-detected' );
        this.$element.closest( 'input[type=submit]' ).attr( 'disabled', true );
      }
  
      /* manage swap user interface */
      , swapHandler: function () {
        var swapChoiceElem = $( this.options.conflictManager.template );
        this.$element.after( swapChoiceElem.text( this.options.conflictManager.message ) );
        swapChoiceElem.on( 'click', false, $.proxy( this.swap, this ) );
      }
  
      /* swap data / states for conflicted elements */
      , swap: function () {
        var val = this.$element.data( 'swap-data' );
        this.$element.data( 'swap-state', 'garlic' === this.$element.data( 'swap-state' ) ? 'default' : 'garlic' );
        this.$element.data( 'swap-data', this.$element.val());
        $( this.$element ).val( val );
        this.options.onSwap(this.$element, this.$element.data( 'swap-data'), val);
      }
  
      /* delete localStorage persistance only */
      , destroy: function () {
        this.storage.destroy( this.path );
      }
  
      /* remove data / reset state AND delete localStorage */
      , remove: function () {
        this.destroy();
  
        if ( this.$element.is( 'input[type=radio], input[type=checkbox]' ) ) {
          $( this.$element ).attr( 'checked', false );
          return;
        }
  
        this.$element.val( '' );
      }
  
      /* retuns an unique identifier for form elements, depending on their behaviors:
         * radio buttons: domain > pathname > form.<attr.name>[:eq(x)] > input.<attr.name>
            no eq(); must be all stored under the same field name inside the same form
  
         * checkbokes: domain > pathname > form.<attr.name>[:eq(x)] > [fieldset, div, span..] > input.<attr.name>[:eq(y)]
            cuz' they have the same name, must detect their exact position in the form. detect the exact hierarchy in DOM elements
  
         * other inputs: domain > pathname > form.<attr.name>[:eq(x)] > input.<attr.name>[:eq(y)]
            we just need the element name / eq() inside a given form
      */
      , getPath: function ( elem ) {
  
        if ( 'undefined' === typeof elem ) {
          elem = this.$element;
        }
  
        if ( this.options.getPath( elem ) ) {
          return this.options.getPath( elem );
        }
  
        // Requires one element.
        if ( elem.length != 1 ) {
          return false;
        }
  
        var path = ''
          , fullPath = elem.is( 'input[type=checkbox]' )
          , node = elem;
  
        while ( node.length ) {
          var realNode = node[0]
            , name = realNode.nodeName;
  
          if ( !name ) {
            break;
          }
  
          name = name.toLowerCase();
  
          var parent = node.parent()
            , siblings = parent.children( name );
  
          // don't need to pollute path with select, fieldsets, divs and other noisy elements,
          // exept for checkboxes that need exact path, cuz have same name and sometimes same eq()!
          if ( !$( realNode ).is( 'form, input, select, textarea' ) && !fullPath ) {
            node = parent;
            continue;
          }
  
          // set input type as name + name attr if exists
          name += $( realNode ).attr( 'name' ) ? '.' + $( realNode ).attr( 'name' ) : '';
  
          // if has sibilings, get eq(), exept for radio buttons
          if ( siblings.length > 1 && !$( realNode ).is( 'input[type=radio]' ) ) {
            name += ':eq(' + siblings.index( realNode ) + ')';
          }
  
          path = name + ( path ? '>' + path : '' );
  
          // break once we came up to form:eq(x), no need to go further
          if ( 'form' == realNode.nodeName.toLowerCase() ) {
            break;
          }
  
          node = parent;
        }
  
        return 'garlic:' + document.domain + ( this.options.domain ? '*' : window.location.pathname ) + '>' + path;
      }
  
      , getStorage: function () {
        return this.storage;
      }
    }
  
    /* GARLIC PLUGIN DEFINITION
    * ========================= */
  
    $.fn.garlic = function ( option, fn ) {
      var options = $.extend(true, {}, $.fn.garlic.defaults, option, this.data() )
        , storage = new Storage()
        , returnValue = false;
  
      // this plugin heavily rely on local Storage. If there is no localStorage or data-storage=false, no need to go further
      if ( !storage.defined ) {
        return false;
      }
  
      function bind ( self ) {
        var $this = $( self )
          , data = $this.data( 'garlic' )
          , fieldOptions = $.extend( {}, options, $this.data() );
  
        // don't bind an elem with data-storage=false
        if ( 'undefined' !== typeof fieldOptions.storage && !fieldOptions.storage ) {
          return;
        }
  
        // don't bind a password type field
        if ( 'password' === $( self ).attr( 'type' ) ) {
          return;
        }
  
        // if data never binded, bind it right now!
        if ( !data ) {
          $this.data( 'garlic', ( data = new Garlic( self, storage, fieldOptions ) ) );
        }
  
        // here is our garlic public function accessor, currently does not support args
        if ( 'string' === typeof option && 'function' === typeof data[option] ) {
          return data[option]();
        }
      }
  
      // loop through every elemt we want to garlic
      this.each(function () {
  
        // if a form elem is given, bind all its input children
        if ( $( this ).is( 'form' ) ) {
          $( this ).find( options.inputs ).each( function () {
            if ( $( this ).is( options.excluded ) ) { return; }
            returnValue = bind( $( this ) );
          });
  
        // if it is a Garlic supported single element, bind it too
        // add here a return instance, cuz' we could call public methods on single elems with data[option]() above
        } else if ( $( this ).is( options.inputs ) ) {
          if ( $( this ).is( options.excluded ) ) { return; }
          returnValue = bind( $( this ) );
        }
      });
  
      return 'function' === typeof fn ? fn() : returnValue;
    }
  
    /* GARLIC CONFIGS & OPTIONS
    * ========================= */
    $.fn.garlic.Constructor = Garlic;
  
    $.fn.garlic.defaults = {
        destroy: true                                                                                         // Remove or not localstorage on submit & clear
      , inputs: 'input, textarea, select'                                                                     // Default supported inputs.
      , excluded: 'input[type="file"], input[type="hidden"], input[type="submit"], input[type="reset"]'                            // Default ignored inputs.
      , events: [ 'DOMAttrModified', 'textInput', 'input', 'change', 'click', 'keypress', 'paste', 'focus' ]  // Events list that trigger a localStorage
      , domain: false                                                                                         // Store et retrieve forms data accross all domain, not just on
      , expires: false                                                                                        // false for no expiration, otherwise (int) in seconds for auto-expiration
      , conflictManager: {
          enabled: false                                                                                      // Manage default data and persisted data. If false, persisted data will always replace default ones
        , garlicPriority: true                                                                                // If form have default data, garlic persisted data will be shown first
        , template: '<span class="garlic-swap"></span>'                                                       // Template used to swap between values if conflict detected
        , message: 'This is your saved data. Click here to see default one'                                   // Default message for swapping data / state
        , onConflictDetected: function ( $item, storedVal ) { return true; }                                  // This function will be triggered if a conflict is detected on an item. Return true if you want Garlic behavior, return false if you want to override it
      }
     , getPath: function ( $item ) {}                                                                         // Set your own key-storing strategy per field
     , preRetrieve: function ( $item, currentValue, storedVal ) { return storedVal; }                         // This function will be triggered before retrieve, and allows to override setting the field's value restored from local storage
     , onRetrieve: function ( $item, storedVal ) {}                                                           // This function will be triggered each time Garlic find an retrieve a local stored data for a field
     , prePersist: function ( $item, storedVal ) { return false; }                                            // This function will be triggered before Garlic, and allows to override the value stored to local storage
     , onPersist: function ( $item, storedVal ) {}                                                            // This function will be triggered each time Garlic stores a field to local storage
     , onSwap: function ( $item, prevValue, curValue ) {}                                                     // This function will be triggered each time Garlic swap values with conflict manager
    }
  
  
    /* GARLIC DATA-API
    * =============== */
    $( window ).on( 'load', function () {
      $( '[data-persist="garlic"]' ).each( function () {
        $(this).garlic();
      })
    });
  
  // This plugin works with jQuery or Zepto (with data extension builded for Zepto. See changelog 0.0.6)
  }(window.jQuery || window.Zepto);
  