UNPKG

memoria-js

Version:

Extensible form storage for memorising user inputs with localStorage.

298 lines (221 loc) 9.92 kB
(function($window, $localStorage) { "use strict"; /** * @module Memoria * @submodule Element * @constructor */ $window.Memoria.Element = function MemoriaElement() {}; /** * @property prototype * @type {Object} */ $window.Memoria.Element.prototype = { /** * @property node * @type {Object} */ node: null, /** * @property form * @type {Object} */ form: null, /** * @property key * @type {Object} */ key: null, /** * @property observer * @type {Object} */ observer: null, /** * @property _attribute * @type {String} * @default "value" */ _attribute: 'value', /** * @method initialise * Responsible for throwing an exception notifying the developer that this class should be overloaded * by the super object, because the `initialise` method is where the developer specifies on which event * the input should respond to. * @return {void} */ initialise: function initialise() { var attribute = this.node.getAttribute('data-memoria-event'), prepositions = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseover', 'mouseleave', 'mouseout', 'mouseup', 'keydown', 'keyup', 'keypress', 'blur', 'change', 'focus', 'reset', 'select', 'submit'], eventNames = []; if (!attribute) { // We were unable to find the event name automatically. throw 'MEMORIA: Unable to automatically find the event name.'; } // Explode the event names by comma, because you can specify multiple events if necessary. eventNames = attribute.split(/,/); // Iterate over each of the event names for the current node. for (var eventIndex = 0, maxEvents = eventNames.length; eventIndex < maxEvents; eventIndex++) { var eventName = eventNames[eventIndex].trim(); // Determine if the `eventName` requires "on" being prepended. if (eventName.substr(0, 2) !== 'on' && prepositions.indexOf(eventName) !== -1) { eventName = 'on' + eventName; } this._attachEvent(eventName); } }, /** * @method attachObserver * @param observer {Object} * Responsible for adding an observer to the element for implementing hooks. * @return {void} */ attachObserver: function attachObserver(observer) { this.observer = observer; }, /** * @method _attachEvent * @param eventNames {Array} * Responsible for attaching the Memoria event to the given event name. * @private */ _attachEvent: function _attachEvent(eventNames) { if (!Array.isArray(eventNames)) { // Transform the string into an array if it isn't already. eventNames = [eventNames]; } // Iterate over each of the event types for the current node. for (var eventIndex = 0, maxEvents = eventNames.length; eventIndex < maxEvents; eventIndex++) { var eventName = eventNames[eventIndex]; if (typeof this.node[eventName] === 'undefined') { // Throw an exception if we're unable to find the specified event. throw 'MEMORIA: Unable to find the `' + eventName + '` event on `' + this.node.nodeName.toLowerCase() +'` node.'; } // Attach the event to the `_save` method. this.node[eventName] = this._save.bind(this); } }, /** * @method transferValue * Responsible for detecting if the `localStorage` object has the current set, and if it does * to populate the node with its value. * @return {void} */ transferValue: function transferValue() { var storage = this._getStorage(), key = this.key, value = storage[key.formName][key.nodeName]; if (!value) { // Value doesn't seem to be set in `localStorage`. return; } if (this.observer && typeof this.observer.setupElement === 'function') { // We have an observer and therefore we can let that tell us what to do with the value. this.observer.setupElement(this.node, value); } else { // Otherwise there is a value and so we'll update the node's value to reflect it! this.node[this._attribute] = value; } }, /** * @method _save * @param event {Object} * Responsible for saving the updated value into the `localStorage`. * @return {void} * @private */ _save: function _save(event) { if (!this.key.nodeName) { throw 'Memoria: Attempting to save value "' + this.node[this._attribute] + '" but node does not have a `name`.'; } var storage = this._getStorage(), key = this.key; if (this.observer && typeof this.observer.eventFired === 'function') { // We have an observer and therefore we'll retrieve the value from there instead. var value = this.observer.eventFired(event, this.node); if (typeof value === 'undefined') { // Throw an exception because the user didn't return a value when they should have. throw 'MEMORIA: You must return a value from the `eventFired` method.'; } // Save the value in `localStorage`. storage[key.formName][key.nodeName] = value; } else { // Create the namespace for the node and its related value. storage[key.formName][key.nodeName] = this.node[this._attribute]; } // Save the new form data into the "memoria" `localStorage` namespace. $localStorage.setItem('memoria', JSON.stringify(storage)); if (this.node.hasAttribute('data-memoria-name')) { // Find any nodes with the same name and reset them if we're using the pseudo-name. this._resetOthersWithSameName(); } }, /** * @method getStorage * Responsible for returning the parsed JSON data from the `localStorage` data. * @return {Object} * @private */ _getStorage: function _getStorage() { var storage = JSON.parse($localStorage.getItem('memoria')); if (typeof storage[this.key.formName] === 'undefined') { // Create the namespace for the form if it doesn't already exist. storage[this.key.formName] = {}; } return storage; }, /** * @method _getEventName * @param defaultEventName {String} * Responsible for first checking if the current node has a "data-memoria-event" set, and if it doesn't * then to return what was passed into the method * @return {String} * @private */ _getEventName: function _getEventName(defaultEventName) { return this.node.getAttribute('data-memoria-event') || defaultEventName; }, /** * @method _setAttribute * @param name {String} * By default, Memoria attempts to set the `value` of the element. However, for some elements * this is not the case, and therefore this method allows the overriding of that default assumption. * @private */ _setAttribute: function _setAttribute(name) { this._attribute = name; }, /** * @method _resetOthersWithSameName * Responsible for finding any nodes with the same name as the one we're currently trying to save, * and removing its value from the `localStorage`. * @private */ _resetOthersWithSameName: function __resetOthersWithSameName() { var storage = this._getStorage(), actualName = this.node.getAttribute('name'), nodes = this.form.querySelectorAll('*[name="' + actualName + '"]'); // Iterate over each node we've found with the same name. for (var nodeIndex = 0, maxNodes = nodes.length; nodeIndex < maxNodes; nodeIndex++) { var node = nodes[nodeIndex]; if (node === this.node || !node.hasAttribute('data-memoria-name')) { // We don't want to affect the current node, nor do we want to affect any nodes that // don't have the `data-memoria-name` attribute set. continue; } var name = node.getAttribute('data-memoria-name'); if (!storage[this.key.formName][name]) { // We've found an element with the same name, but it's not in the `localStorage` so it // doesn't affect anything. continue; } // Otherwise we'll delete our neighbour and update the `localStorage`! delete storage[this.key.formName][name]; $localStorage.setItem('memoria', JSON.stringify(storage)); } } }; })(window, window.localStorage);