(function (root, factory) { if (typeof define === "function" && define.amd) { define([], factory(root)); } else if (typeof exports === "object") { module.exports = factory(root); } else { root.formSaver = factory(root); } })( typeof global !== "undefined" ? global : this.window || this.global, function (root) { "use strict"; // // Variables // var formSaver = {}; // Object for public APIs var supports = "querySelector" in document && "addEventListener" in root && "localStorage" in root && "classList" in document.createElement("_"); // Feature test var settings, forms; // Default settings var defaults = { selectorStatus: "[data-form-status]", selectorSave: "[data-form-save]", selectorDelete: "[data-form-delete]", selectorIgnore: "[data-form-no-save]", deleteClear: true, saveMessage: "Saved!", deleteMessage: "Deleted!", saveClass: "", deleteClass: "", initClass: "js-form-saver", callbackSave: function () {}, callbackDelete: function () {}, callbackLoad: function () {}, }; // // Methods // /** * A simple forEach() implementation for Arrays, Objects and NodeLists. * @private * @author Todd Motto * @link https://github.com/toddmotto/foreach * @param {Array|Object|NodeList} collection Collection of items to iterate * @param {Function} callback Callback function for each iteration * @param {Array|Object|NodeList} scope Object/NodeList/Array that forEach is iterating over (aka `this`) */ var forEach = function (collection, callback, scope) { if (Object.prototype.toString.call(collection) === "[object Object]") { for (var prop in collection) { if (Object.prototype.hasOwnProperty.call(collection, prop)) { callback.call(scope, collection[prop], prop, collection); } } } else { for (var i = 0, len = collection.length; i < len; i++) { callback.call(scope, collection[i], i, collection); } } }; /** * Merge two or more objects. Returns a new object. * @private * @param {Boolean} deep If true, do a deep (or recursive) merge [optional] * @param {Object} objects The objects to merge together * @returns {Object} Merged values of defaults and options */ var extend = function () { // Variables var extended = {}; var deep = false; var i = 0; var length = arguments.length; // Check if a deep merge if (Object.prototype.toString.call(arguments[0]) === "[object Boolean]") { deep = arguments[0]; i++; } // Loop through each object and conduct a merge for (; i < length; i++) { var obj = arguments[i]; merge_object(obj); } return extended; }; /** * Get the closest matching element up the DOM tree. * @private * @param {Element} elem Starting element * @param {String} selector Selector to match against * @return {Boolean|Element} Returns null if not match found */ var getClosest = function (elem, selector) { // Element.matches() polyfill if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) { var matches = ( this.document || this.ownerDocument ).querySelectorAll(s), i = matches.length; while (--i >= 0 && matches.item(i) !== this) {} return i > -1; }; } // Get closest match for (; elem && elem !== document; elem = elem.parentNode) { if (elem.matches(selector)) return elem; } return null; }; /** * Convert data-options attribute into an object of key/value pairs * @private * @param {String} options Link-specific options as a data attribute string * @returns {Object} */ var getDataOptions = function (options) { return !options || !(typeof JSON === "object" && typeof JSON.parse === "function") ? {} : JSON.parse(options); }; /** * Save form data to localStorage * @public * @param {Element} btn Button that triggers form save * @param {Element} form The form to save * @param {Object} options * @param {Event} event */ formSaver.saveForm = function (btn, formID, options, event) { // Defaults and settings var overrides = getDataOptions( btn ? btn.getAttribute("data-options") : null ); var settings = extend(settings || defaults, options || {}, overrides); // Merge user options with defaults // Selectors and variables var form = document.querySelector(formID); var formSaverID = "formSaver-" + form.id; var formSaverData = {}; var formFields = form.elements; var formStatus = form.querySelectorAll(settings.selectorStatus); /** * Convert field data into an array * @private * @param {Element} field Form field to convert */ var prepareField = function (field) { if (!getClosest(field, settings.selectorIgnore)) { if ( field.type.toLowerCase() === "radio" || field.type.toLowerCase() === "checkbox" ) { if (field.checked === true) { formSaverData[field.name + field.value] = "on"; } } else if ( field.type.toLowerCase() !== "hidden" && field.type.toLowerCase() !== "submit" ) { if (field.value && field.value !== "") { formSaverData[field.name] = field.value; } } } }; /** * Display status message * @private * @param {Element} status The element that displays the status message * @param {String} saveMessage The message to display on save * @param {String} saveClass The class to apply to the save message wrappers */ var displayStatus = function (status, saveMessage, saveClass) { status.innerHTML = saveClass === "" ? "