/// /// import getCheckedValue from './getCheckedValue'; import IsJsonString from './isJsonString'; import makeid from './makeid'; import { currentPathname } from './url'; import { isEmpty } from './utils'; /** * Local Storage key */ const storageKey: string = currentPathname.replace(/\/$/, '') + '/formField'; let formFieldBuild: Record | Array; const formSaved = localStorage.getItem(storageKey.toString()); if (!formSaved) { formFieldBuild = []; } else { formFieldBuild = JSON.parse(formSaved); } /** * Element Indexer */ const formField = formFieldBuild; const uniqueid = makeid(5); /** * Element Counter */ let Count = -1; // declare jquery plugin if (typeof jQuery != 'undefined') { (function ($) { $.fn.getIDName = function () { // get attr id or name if ($(this).attr('aria-autovalue')) { // seeder auto value $(this).val(uniqueid).trigger('change'); } // return JqueryFormSaver.get_identifier(this); return $(this).attr('name') || $(this).attr('id'); }; $.fn.has_attr = function (name: string) { const attr = $(this).attr(name); // For some browsers, `attr` is undefined; for others, // `attr` is false. Check for both. return typeof attr !== 'undefined' && attr !== false; }; $.fn.smartForm = function () { Count++; new JqueryFormSaver($(this).get(0)); }; $.arrive = function (target, callback) { if (target) { $(target).bind('DOMNodeInserted', callback); } else { if (typeof callback == 'function') { $(document).bind('DOMNodeInserted', callback); } else if (typeof target == 'function') { $(document).bind('DOMNodeInserted', target); } } }; })(jQuery); } class JqueryFormSaver { /** * Get Offsets Element * @param el * @returns */ offset(el: IEHtml | Element | HTMLElement) { return el.getBoundingClientRect(); } /** * jQuery event listener */ jquery_listener() { const self = this; // bind to new elements $(document).bind('DOMNodeInserted', function () { switch ($(this).prop('tagName')) { case 'SELECT': case 'INPUT': case 'TEXTAREA': self.restore($(this).get(0)); break; } }); // @todo detach from removed elements $(document).bind('DOMNodeRemoved', function () { const t = $(this); const allowed = !t.attr('no-save') && t.attr('formsaver-integrity'); if (allowed) { switch (t.prop('tagName')) { case 'SELECT': case 'INPUT': case 'TEXTAREA': t.off('change'); break; } } }); // @todo save value to localstorage $(document).on('change', 'select, input, textarea', function (_e) { self.save(this); }); // @todo validate formsaver $(document).on('focus', 'input,textarea,select', function () { const t = $(this); t.getIDName(); const aria = t.attr('formsaver-integrity'); if (aria && aria != uniqueid) { console.log('aria id invalid'); t.smartForm(); t.attr('formsaver-integrity', uniqueid); } }); } /** * Pure javascript event listener */ vanilla_listener(el: IEHtml | Element | HTMLElement, callback: EventListenerOrEventListenerObject) { if (el.addEventListener) { el.addEventListener('change', callback); } else if (el.attachEvent) { el.attachEvent('onchange', callback); } } /** * Is element has attribute ? * @param el * @param name * @returns */ hasAttribute(el: HTMLElement, name: string) { return el.nodeType === 1 && el.hasAttribute(name); } private convertElement(el: IEHtml | Element | HTMLElement) { if (this.is_jquery() && el instanceof jQuery) { el = el.get(0); } // eslint-disable-next-line @typescript-eslint/no-unused-vars const nodeValid = el.nodeType === 1; return el; } // is ignored? isIgnored(el: IEHtml | Element | HTMLElement, debug = false) { const ignored = el.hasAttribute('no-save'); // || el.classList.contains('no-save'); if (debug) { const id = el.id || el.getAttribute('name') || this.get_identifier(el) || 'unidentified element'; console.log(`id="${id}" is ignored (${ignored})`); } return ignored; } /** * Restore form value * @param el * @param debug * @returns */ restore(el: IEHtml | Element | HTMLElement, debug = false) { el = this.convertElement(el); Count++; // skip no save (ignore) if (this.isIgnored(el, debug)) { if ('value' in el) el.value = ''; return; } el.setAttribute('formsaver-integrity', uniqueid); let item: any; const key = this.get_identifier(el); const type = el.getAttribute('type'); //console.log(`restoring ${key} ${type}`); // begin restoration if (key.length > 0) { // checkbox input button if (type === 'checkbox') { item = JSON.parse(localStorage.getItem(key)); if (item === null) { return; } if (debug) console.log(`restore value checkbox[${key}] ${item}`); el.checked = item; return; } // radio input button else if (type === 'radio') { let value: any = localStorage.getItem(key); if (IsJsonString(value)) { value = JSON.parse(value); } const ele = document.getElementsByName(el.getAttribute('name')); for (let i = 0; i < ele.length; i++) ele[i].checked = false; setTimeout(function () { if (value && typeof value == 'object' && Object.prototype.hasOwnProperty.call(value, 'index')) { //ele.item(value.index).checked = true; ele[value.index].checked = true; if (debug) console.log('restoring checkbox', value); } }, 1000); //item = value === "on"; //el.checked = item; return; } // input text number, textarea, or select else { item = localStorage.getItem(key); if (item === null || !item.toString().length) { return; } // @todo check if element isn't ignored if (!this.isIgnored(el, debug)) { el.value = item; // select2 if (this.is_select2(el)) { console.log(`restoring ${el.getAttribute('id')} which Initialized with select2`); if (typeof jQuery !== 'undefined') $(el).val(item).trigger('change'); } } } if (debug) console.log('load', type, key, item); } } /** * Save values form * @param el * @returns */ save(el: IEHtml | Element | HTMLElement, debug = false) { el = this.convertElement(el); const key = this.get_identifier(el); const item = el.value; const allowed = !el.hasAttribute('no-save') && el.hasAttribute('formsaver-integrity') && el.hasAttribute('name'); const type = el.getAttribute('type'); if (debug) console.log(`${el.tagName} ${key} ${allowed}`); // nothing to do if we don't have an identifier or saving isn't allowed if (!key || !allowed) return; // handle checkbox always (save checked state) if (type == 'checkbox') { localStorage.setItem(key, (el.checked == true).toString()); if (debug) console.log('save checkbox button ', this.offset(el)); return; } // handle radio group: delete group entries first, then save selected if (type == 'radio') { const ele = document.getElementsByName(el.getAttribute('name')); const getVal = getCheckedValue(ele); const self = this; for (let checkboxIndex = 0; checkboxIndex < ele.length; checkboxIndex++) { if (Object.prototype.hasOwnProperty.call(ele, checkboxIndex)) { const element = ele[checkboxIndex]; self.delete(element, debug); } } setTimeout(function () { localStorage.setItem(key, JSON.stringify(getVal)); if (debug) console.log('save radio button ', getVal); }, 1000); return; } // text / textarea / select: if cleared, remove stored value; otherwise save if (isEmpty(item)) { localStorage.removeItem(key); if (debug) console.log('removed empty value', key); return; } localStorage.setItem(key, item.toString()); } delete(el: IEHtml | Element | HTMLElement, debug = false) { el = this.convertElement(el); const key = this.get_identifier(el); if (debug) console.log(`deleting ${key}`); localStorage.removeItem(key); } /** * Is Select2 Initialized ? * @param el * @returns */ is_select2(el: IEHtml | Element | HTMLElement) { return this.is_jquery() && $(el).data('select2'); } /** * Is jQuery loaded? * @returns */ is_jquery() { return typeof jQuery != 'undefined'; } get_identifier(el: IEHtml | Element | HTMLElement) { el = this.convertElement(el); const attrNotExist = function (attrname: string) { let ID: string; if (!(Count in formField)) { // @todo if id line not exists in list, create new one ID = makeid(5); el.setAttribute(attrname, ID); (formField)[Count] = ID; localStorage.setItem(storageKey.toString(), JSON.stringify(formField)); } else { // @todo if id line exists in list, restore it ID = (formField)[Count]; el.setAttribute(attrname, ID); } /** * Increase index offset */ Count++; return ID; }; const attrEmpty = function (attrname: string) { const ID = makeid(5); el.setAttribute(attrname, ID); (formField)[Count] = ID; localStorage.setItem(storageKey.toString(), JSON.stringify(formField)); return ID; }; let attrn: string = null, attre: string = null; // @todo auto create id on field if not exists if (!el.hasAttribute('id')) { attrn = attrNotExist('id'); } else if (el.getAttribute('id') == 'null') { attre = attrEmpty('id'); } // @todo auto create name attribute on field if not exists if (!el.hasAttribute('name')) { if (typeof attrn != 'string') { attrNotExist('name'); } else { el.setAttribute('name', attrn); } } else if (el.getAttribute('name') == 'null') { if (typeof attre != 'string') { attrEmpty('name'); } else { el.setAttribute('name', attre); } } return currentPathname + el.getAttribute('id'); } constructor(el: IEHtml | Element | HTMLElement, options?: { debug?: boolean; method?: 'vanilla' | 'jquery' }) { const defaultOpt = { debug: false, method: 'vanilla' }; const self = this; if (typeof options != 'object') options = {}; options = Object.assign(defaultOpt, options); //console.log(`init debug ${options.debug}`, el); if (typeof options.debug == 'undefined') { options.debug = false; console.log(`change debug to false`); } this.restore(el, options.debug); if (options.method == 'jquery' && this.is_jquery()) { this.jquery_listener(); } else { console.log('vanilla listener started'); this.vanilla_listener(el, function () { // console.log(arguments); self.save(el, options.debug); }); } } } /// modify this to tell typescript compiler export default JqueryFormSaver;