if(!app.helpers) app.helpers = {} /** * Mixing add-in methods to your view instance. * All of this should not be a helper, but inherited this from EICDomContent, but not my framework anymore. * @category MyEic */ app.helpers.activeAttributes = { /** * setupTriggers adds all click (data-trigger) and change (data-change) handlers. * handlers should have the signatue : onXyz(component, event), will spit a warning if the handler doesn't exist. * setupTriggers is re-entrant: it can be called again after refreshing part of the view * @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) ) */ _triggersRegister: { 'click': new WeakMap(), 'change': new WeakMap()}, setupTriggers(components = []){ // Should inherit this from EICDomContent, but not my framework anymore. for(let component of components.filter(component => component.el.hasAttribute('data-trigger'))) { //components with or without click property if(typeof this[component.el.dataset.trigger] !== 'function') { console.warn(`data-trigger without corresponding method : ${component.el.dataset.trigger}`) continue } if(component.click) component.click = this[component.el.dataset.trigger].bind(this, component) else { const oldTrigger = this._triggersRegister.click.get(component.el) if(oldTrigger) component.el.removeEventListener('click', oldTrigger) const newTrigger = this[component.el.dataset.trigger].bind(this, component) this._triggersRegister.click.set(component.el, newTrigger) component.el.addEventListener('click', newTrigger) } } if(this.el){ // for views and other content-based classes, add triggers on simple non-component elements for(const el of this.el.querySelectorAll('[data-trigger]:not([data-eicui-id])')){ if(typeof this[el.dataset.trigger] !== 'function') { console.warn(`data-trigger without corresponding method : ${el.dataset.trigger}`) continue } const oldTrigger = this._triggersRegister.click.get(el) if(oldTrigger) el.removeEventListener('click', oldTrigger) const newTrigger = this[el.dataset.trigger].bind(this) this._triggersRegister.click.set(el, newTrigger) el.addEventListener('click', newTrigger) } } for(let component of components.filter(component => component.el.hasAttribute('data-change'))) { //components with or without click property if(typeof this[component.el.dataset.change] !== 'function') { console.warn(`data-change without corresponding method : ${component.el.dataset.change}`) continue } const oldTrigger = this._triggersRegister.change.get(component.el) if(oldTrigger) { component.el.removeEventListener('change', oldTrigger) component.el.removeEventListener('keyup', oldTrigger) } const newTrigger = this[component.el.dataset.change].bind(this, component) this._triggersRegister.change.set(component.el, newTrigger) component.el.addEventListener("change", newTrigger) if(component.el.type=='text') component.el.addEventListener("keyup", newTrigger) } if(this.el){ // for views and other content-based classes, add triggers on simple non-component elements for(const el of this.el.querySelectorAll('[data-change]:not([data-eicui-id])')){ if(typeof this[el.dataset.change] !== 'function') { console.warn(`data-change without corresponding method : ${el.dataset.change}`) continue } const oldTrigger = this._triggersRegister.change.get(el) if(oldTrigger) { el.removeEventListener('change', oldTrigger) el.removeEventListener('keyup', oldTrigger) } const newTrigger = this[el.dataset.change].bind(this) this._triggersRegister.change.set(el, newTrigger) el.addEventListener('change', newTrigger) if(el.type=='text') el.addEventListener('keyup', newTrigger) } } }, /** * setupRefs will populate : * => this.components: an attribute 'data-ref="toto"' will create the reference this.components.toto to the EICUI component * => this.outputs: an attribute 'data-output="titi"' will create the reference this.outputs.toto to the dom node (used by this.output() ) * => this.asyncComponents: an attribute 'data-async="tintin"' will create the reference this.asyncComponents.tintin (used by setAsyncLoading() ) * => this.asyncElements: an attribute 'data-async="haddock"' will create the reference this.asyncElements.haddock (used by setAsyncLoading() ) * setupRefs is re-entrant: it can be called again after refreshing part of the view * @param {eicui-components []} components : the view's components (usually result of ui.eicfy(this.el) ) */ setupRefs(components=[]){ if(!this.components) this.components = {} for(let component of components.filter(component => component.el.hasAttribute('data-ref'))) { this.components[component.el.dataset.ref] = component } if(!this.asyncComponents) this.asyncComponents = {} for(let component of components.filter(component => component.el.hasAttribute('data-async'))) { this.asyncComponents[component.el.dataset.async] = component } if(!this.asyncElements) this.asyncElements = {} for(let el of this.el.querySelectorAll('[data-async]')) { if('eicuiId' in el.dataset) continue this.asyncElements[el.dataset.async] = el } if(!this.outputs) this.outputs = {} for(let el of this.el.querySelectorAll('[data-output]')) { this.outputs[el.dataset.output] = el } }, /** * output (singular) : this.output('mydiv', '
Some markup
') places markup in a data-output node * @param {*} name * @param {*} markup */ output(name, markup){ if(name in this.outputs) this.outputs[name].innerHTML = markup else console.warn(`Output ${name} not found !`) }, /** * setAsyncLoading Turns on all or some of the loaders * @param {boolean} value : true to show loading, false to hide * @param {string[]} names : (optional) array of names to show/hide, (names given via the data-async attributes) * If no names given, will show/hide all asyncs of the view * Small counter so it can handle several async requests which are overlapping and using on the same loader. * Ex1 overlapping completion: loader=true & REQ1-start, loader=true & REQ2-start, REQ1-end & loader=false, REQ2-end & loader=false ] * Ex2 inverted completion: loader=true & REQ1-start, loader=true & REQ2-start, REQ2-end & loader=false, REQ1-end & loader=false ] */ setAsyncLoading(value, names=null){ if(!this.asyncComponents) this.asyncComponents = {} if(!this.asyncComponentsCnt) this.asyncComponentsCnt = {} if(!names) names = [ ...Object.keys(this.asyncComponents), ...Object.keys(this.asyncElements)] for(let name of names){ if(name in this.asyncComponentsCnt){ this.asyncComponentsCnt[name] = value ? this.asyncComponentsCnt[name]+1 : this.asyncComponentsCnt[name]-1 } else { this.asyncComponentsCnt[name] = value ? 1 : 0 } if(name in this.asyncComponents) this.asyncComponents[name].loading = (this.asyncComponentsCnt[name]>0) if(name in this.asyncElements) { const el = this.asyncElements[name].querySelector('.icon-spinner.spin') if(el) el.remove() if((this.asyncComponentsCnt[name]>0)){ this.asyncElements[name].prepend(Object.assign(document.createElement('div'), { className: 'icon-spinner spin', style: 'width:1em;height:1em;margin:1em;position: absolute;z-index: 9999;' })) } } } }, /** * Manage spin loader * @param {*} element * @param {*} loadingText * @returns */ showLoading(element, loadingText = '') { if (!element) return element.classList.add('loading') if (element.tagName === 'BUTTON' || element.type === 'button') { element.disabled = true element.setAttribute('data-original-content', element.innerHTML); element.innerHTML = loadingText; } else if (element.hasAttribute('contenteditable')) { element.setAttribute('data-original-content', element.innerHTML); element.innerHTML = `${loadingText}` element.setAttribute('contenteditable', 'false') } }, hideLoading(element, defaultIcon = null) { if (!element) return; element.classList.remove('loading') if (element.tagName === 'BUTTON' || element.type === 'button') { element.disabled = false; element.innerHTML = element.getAttribute('data-original-content') || defaultIcon || '' } else if (element.hasAttribute('contenteditable')) { element.innerHTML = element.getAttribute('data-original-content') || '' element.setAttribute('contenteditable', 'true') } }, }