iron-localstorage.html 6.52 KB
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="../polymer/polymer.html">

<!--
Element access to Web Storage API (window.localStorage).

Keeps `value` property in sync with localStorage.

Value is saved as json by default.

### Usage:

`ls-sample` will automatically save changes to its value.

    <dom-module id="ls-sample">
      <iron-localstorage name="my-app-storage"
        value="{{cartoon}}"
        on-iron-localstorage-load-empty="initializeDefaultCartoon"
      ></iron-localstorage>
    </dom-module>

    <script>
      Polymer({
        is: 'ls-sample',
        properties: {
          cartoon: {
            type: Object
          }
        },
        // initializes default if nothing has been stored
        initializeDefaultCartoon: function() {
          this.cartoon = {
            name: "Mickey",
            hasEars: true
          }
        },
        // use path set api to propagate changes to localstorage
        makeModifications: function() {
          this.set('cartoon.name', "Minions");
          this.set('cartoon.hasEars', false);
        }
      });
    </script>

### Tech notes:

* * `value.*` is observed, and saved on modifications. You must use
    path change notifification methods such as `set()` to modify value
    for changes to be observed.

* * Set `auto-save-disabled` to prevent automatic saving.

* * Value is saved as JSON by default.

* * To delete a key, set value to null

* Element listens to StorageAPI `storage` event, and will reload upon receiving it.

* **Warning**: do not bind value to sub-properties until Polymer
[bug 1550](https://github.com/Polymer/polymer/issues/1550)
is resolved. Local storage will be blown away.
`<iron-localstorage value="{{foo.bar}}"` will cause **data loss**.

@demo demo/index.html
@hero hero.svg
-->
<dom-module id="iron-localstorage"></dom-module>
<script>

  Polymer({
    is: 'iron-localstorage',

    properties: {
      /**
       * localStorage item key
       */
      name: {
        type: String,
        value: ''
      },
      /**
       * The data associated with this storage.
       * If set to null item will be deleted.
       * @type {*}
       */
      value: {
        type: Object,
        notify: true
      },

      /**
       * If true: do not convert value to JSON on save/load
       */
      useRaw: {
        type: Boolean,
        value: false
      },

      /**
       * Value will not be saved automatically if true. You'll have to do it manually with `save()`
       */
      autoSaveDisabled: {
        type: Boolean,
        value: false
      },
      /**
       * Last error encountered while saving/loading items
       */
      errorMessage: {
        type: String,
        notify: true
      },

      /** True if value has been loaded */
      _loaded: {
        type: Boolean,
        value: false
      }
    },

    observers: [
      '_debounceReload(name,useRaw)',
      '_trySaveValue(autoSaveDisabled)',
      '_trySaveValue(value.*)'
    ],

    ready: function() {
      this._boundHandleStorage = this._handleStorage.bind(this);
    },

    attached: function() {
      window.addEventListener('storage', this._boundHandleStorage);
    },

    detached: function() {
      window.removeEventListener('storage', this._boundHandleStorage);
    },

    _handleStorage: function(ev) {
      if (ev.key == this.name) {
        this._load(true);
      }
    },

    _trySaveValue: function() {
      if (this._doNotSave) {
        return;
      }
      if (this._loaded && !this.autoSaveDisabled) {
        this.debounce('save', this.save);
      }
    },

    _debounceReload: function() {
      this.debounce('reload', this.reload);
    },

    /**
     * Loads the value again. Use if you modify
     * localStorage using DOM calls, and want to
     * keep this element in sync.
     */
    reload: function() {
      this._loaded = false;
      this._load();
    },

    /**
     * loads value from local storage
     * @param {boolean=} externalChange true if loading changes from a different window
     */
    _load: function(externalChange) {
      var v = window.localStorage.getItem(this.name);

      if (v === null) {
        this._loaded = true;
        this._doNotSave = true;  // guard for save watchers
        this.value = null;
        this._doNotSave = false;
        this.fire('iron-localstorage-load-empty', { externalChange: externalChange});
      } else {
        if (!this.useRaw) {
          try { // parse value as JSON
            v = JSON.parse(v);
          } catch(x) {
            this.errorMessage = "Could not parse local storage value";
            console.error("could not parse local storage value", v);
            v = null;
          }
        }
        this._loaded = true;
        this._doNotSave = true;
        this.value = v;
        this._doNotSave = false;
        this.fire('iron-localstorage-load', { externalChange: externalChange});
      }
    },

    /**
     * Saves the value to localStorage. Call to save if autoSaveDisabled is set.
     * If `value` is null or undefined, deletes localStorage.
     */
    save: function() {
      var v = this.useRaw ? this.value : JSON.stringify(this.value);
      try {
        if (this.value === null || this.value === undefined) {
          window.localStorage.removeItem(this.name);
        } else {
          window.localStorage.setItem(this.name, /** @type {string} */ (v));
        }
      }
      catch(ex) {
        // Happens in Safari incognito mode,
        this.errorMessage = ex.message;
        console.error("localStorage could not be saved. Safari incoginito mode?", ex);
      }
    }

    /**
     * Fired when value loads from localStorage.
     *
     * @event iron-localstorage-load
     * @param {{externalChange:boolean}} detail -
     *     externalChange: true if change occured in different window.
     */

    /**
     * Fired when loaded value does not exist.
     * Event handler can be used to initialize default value.
     *
     * @event iron-localstorage-load-empty
     * @param {{externalChange:boolean}} detail -
     *     externalChange: true if change occured in different window.
     */
  });

</script>