UNPKG

@jinntec/fore

Version:

Fore - declarative user interfaces in plain HTML

1,112 lines (970 loc) 36.5 kB
import { addClass, removeClass, isEmptyTextNode, containsOnlyText, newElement, drawOptions, pauseEvent, drawAttrRow, } from './helpers.js'; import { Fore } from '../fore.js'; function isAttributeShown(name, sourceNode) { if (name === 'style') return false; return true; // return name === 'id' || name === 'ref' || name === 'event'; } class ADI { constructor(rootElement, instance) { this.sourceNodeByInspectorNodeLookup = new Map(); this.uiView = null; this.menuView = null; this.domView = null; this.attrView = null; this.optsView = null; /** * The current active element. Note this is the element in the DOM view */ this.activeElement = null; this.vertResizing = false; this.horizResizing = false; this.pathScrolling = null; this.elemLookup = false; this.styleBackup = ''; this.xPos = 0; this.delegatedEvents = []; this.options = { align: 'right', // NOTE: left is not supported in this version split: 50, minSplit: 30, visible: true, saving: false, transparent: true, omitEmptyText: true, makeVisible: true, foldText: true, nodeTypes: [Node.ELEMENT_NODE, Node.TEXT_NODE, Node.COMMENT_NODE, Node.DOCUMENT_NODE], }; if (instance === '#document') { this.instanceId = '#document'; this.document = window.document; this.isInstanceViewer = false; } else { this.instanceId = instance.id; if (!instance || instance.localName !== 'fx-instance') { console.error('No instance found!'); } this.document = instance.getInstanceData(); this.isInstanceViewer = true; this.options.foldText = false; } this.drawUI(rootElement); this.registerEvents(); // We're updating here, but we're doing that again later, when the UI is read (the 'ready' event fires) this.drawDOM(this.document, this.domView.querySelector('.adi-tree-view'), true); document.addEventListener('execute-action', e => this.processExecuteAction); } // Returns selected element or null getSelected() { if (!this.activeElement) { return null; } let elem = document; elem = this.sourceNodeByInspectorNodeLookup.get(this.activeElement); /* document.dispatchEvent( new CustomEvent('path-touched', { composed: true, bubbles: true, detail: {path: elem.modelItem.path}, }), ); */ return elem; } // Loads user defined options stored in HTML5 storage (if available) loadOptions() { let userOptions = {}; userOptions = JSON.parse(window.localStorage.getItem('ADI.options')) || {}; // merge with defaults for (const opt of Object.keys(userOptions)) { this.options[opt] = userOptions[opt]; } } // Saves user defined options into the HTML5 storage (if available) saveOptions() { if (this.options.saving) { window.localStorage.setItem('ADI.options', JSON.stringify(this.options)); } } // Checks if a node has some child nodes and if at least on of them is of a supported type hasRequiredNodes(node) { if (typeof node !== 'object') { throw new Error( `hasRequiredNodes: Expected argument node of type object, ${typeof node} given.`, ); } if (node.hasChildNodes()) { for (let i = 0, len = node.childNodes.length; i < len; i += 1) { const child = node.childNodes[i]; if (this.options.nodeTypes.includes(child.nodeType)) { return true; } } } return false; } // Creates a starting markup for a new DOM tree view node newTreeNode(sourceNode) { if (typeof sourceNode !== 'object') { throw new Error( `newTreeNode: Expected argument node of type object, ${typeof sourceNode} given.`, ); } const withChildren = this.hasRequiredNodes(sourceNode); let omit = false; let adiNode = sourceNode.nodeName.startsWith('FX-') ? `adi-node ${sourceNode.nodeName.toLowerCase()}` : ''; if (sourceNode.nodeName.startsWith('FX-')) { adiNode = `adi-node ${sourceNode.nodeName.toLowerCase()}`; adiNode += Fore.isActionElement(sourceNode.nodeName) ? ' action' : ''; } const elem = newElement('li', { class: adiNode, }); // do not show ADI DOM nodes in the DOM view if (sourceNode === this.uiView) { return null; } // generate UI for elements with children if (withChildren) { elem.appendChild(newElement('span', { class: 'adi-trigger' })); } // we can omit empty text nodes if allowed in options if (this.options.omitEmptyText && sourceNode.nodeType === Node.TEXT_NODE) { omit = isEmptyTextNode(sourceNode); } if (!omit) { const tagStart = newElement('span'); this.sourceNodeByInspectorNodeLookup.set(tagStart, sourceNode); this.sourceNodeByInspectorNodeLookup.set(sourceNode, tagStart); let tagEnd = null; if (containsOnlyText(sourceNode)) { if (sourceNode.nodeType === Node.COMMENT_NODE) { addClass(tagStart, 'adi-comment-node'); if (typeof tagStart.innerText === 'string') { tagStart.innerText = `<!-- ${sourceNode.textContent} -->`; } } else { addClass(tagStart, 'adi-text-node'); tagStart.textContent = sourceNode.textContent; } } else { addClass(tagStart, 'adi-normal-node'); if (sourceNode.nodeType !== Node.DOCUMENT_NODE) { // tagStart.textContent = '<' + node.nodeName.toLowerCase() + '>'; /* let attrString = `<${sourceNode.nodeName.toLowerCase()} `; if(sourceNode.attributes){ Array.from(sourceNode.attributes).forEach(attr => { attrString += `${attr.nodeName}="${attr.nodeValue}" `; }); console.log('ATTRSTRING',attrString); } if (sourceNode.nodeName === 'FX-BIND') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`; } else if (sourceNode.nodeName === 'FX-INSERT') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`; } else if (sourceNode.nodeName === 'FX-INSTANCE') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} id="${sourceNode.id}">`; } else if (sourceNode.nodeName === 'FX-CONTROL') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`; } else if (sourceNode.nodeName === 'FX-SEND') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} submission="${sourceNode.getAttribute('submission')}">`; } else if (sourceNode.nodeName === 'FX-SETVALUE') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} ref="${sourceNode.getAttribute('ref')}">`; } else if (sourceNode.nodeName === 'FX-SUBMISSION') { tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()} id="${sourceNode.getAttribute('id')}">`; } else { */ const attrString = Array.from(sourceNode.attributes) .filter(attr => this.isInstanceViewer ? true : isAttributeShown(attr.name, sourceNode), ) .map(attr => `${attr.name}="${attr.value}"`) .join(' '); tagStart.textContent = `<${sourceNode.nodeName.toLowerCase()}${ attrString ? ` ${attrString}` : '' }>`; // } if (withChildren) { tagEnd = newElement('span'); addClass(tagEnd, 'adi-end-node'); tagEnd.textContent = `</${sourceNode.nodeName.toLowerCase()}>`; } } else { tagStart.textContent = sourceNode.nodeName.toLowerCase(); } } elem.appendChild(tagStart); /* const icon = document.createElement('span'); icon.textContent = 'x'; icon.classList.add(('icon')) const icon2 = document.createElement('span'); icon2.textContent = '<-'; icon2.classList.add(('icon')) elem.appendChild(icon); elem.appendChild(icon2); */ if (sourceNode.nodeName.startsWith('FX-')) { tagStart.classList.add('fore-node'); tagStart.classList.add(sourceNode.nodeName.toLowerCase()); } if (tagEnd) { elem.appendChild(tagEnd); if (sourceNode.nodeName.startsWith('FX-')) { tagEnd.classList.add('fore-node'); } } return elem; } return null; } // Renders the DOM Tree view drawDOM(root, elem, isRoot) { if (typeof root !== 'object') { throw new Error(`drawDOM: Expected argument root of type object, ${typeof root} given.`); } let newNode = null; let isOpen = true; const adiNode = elem.nodeName.startsWith('FX-') ? `adi-node ${node.nodeName.toLowerCase()}` : ''; if (isRoot && this.options.nodeTypes.indexOf(root.nodeType) !== -1) { elem.innerHTML = ''; newNode = this.newTreeNode(root); if (this.hasRequiredNodes(root)) { newNode.appendChild(newElement('ul', { 'data-open': true, class: adiNode })); addClass(newNode.querySelector('.adi-trigger'), 'opened'); } elem.appendChild(newNode); elem = elem.querySelector('ul'); } // recursive DOM traversal for (let i = 0, len = root.childNodes.length; i < len; i += 1) { const node = root.childNodes[i]; const withChildren = this.hasRequiredNodes(node); if (this.options.nodeTypes.indexOf(node.nodeType) !== -1) { newNode = this.newTreeNode(node); if (newNode) { if (withChildren) { if (this.options.foldText) { isOpen = !containsOnlyText(node, true); } else { isOpen = true; } if (node.nodeName === 'HEAD') isOpen = false; if (node.nodeName === 'SELECT') isOpen = false; if (node.nodeName === 'FX-INSTANCE') isOpen = false; /* if(this.options.closedElements.includes(node.nodeName.toLowerCase())){ isOpen = false; } */ if (node.nodeType === Node.DOCUMENT_NODE) { newNode.appendChild(newElement('ul', { 'data-open': isOpen, class: adiNode })); } else { newNode.insertBefore( newElement('ul', { 'data-open': isOpen, class: adiNode }), newNode.lastChild, ); } addClass(newNode.querySelector('.adi-trigger'), isOpen ? 'opened' : 'closed'); } elem.appendChild(newNode); if (this.getSelected() === node) { const span = newNode.querySelector('span.adi-normal-node'); span?.classList?.add('adi-active-node'); this.activeElement = span; newNode.scrollIntoView({ block: 'nearest', behavior: 'instant' }); } if (withChildren) { this.drawDOM(node, newNode.querySelector('ul'), false); } } } } } // Show/hide the options view toggleOptions() { if (this.optsView.className.indexOf('adi-hidden') !== -1) { removeClass(this.optsView, 'adi-hidden'); } else { addClass(this.optsView, 'adi-hidden'); this.attrView.querySelector('.adi-content').innerHTML = ''; this.refreshUI(); this.drawDOM(document, this.domView.querySelector('.adi-tree-view'), true); if (this.options.saving) { this.saveOptions(); } else { window.localStorage.removeItem('ADI.options'); } } } // Renders the UI drawUI(rootElement) { this.uiView = newElement('div', { id: 'adi-wrapper', class: this.options.transparent ? 'transparent' : '', }); this.domView = newElement('div', { id: 'adi-dom-view' }); const domViewContent = newElement('div', { class: 'adi-content', id: 'detailsView' }); // this.attrView.appendChild(newElement('fx-fore', {src: './lab/inspector-view.html'})); // const horizSplit = newElement('div', {id: 'adi-horiz-split'}); const domTree = newElement('ul', { class: 'adi-tree-view' }); const domPathWrap = newElement('div', { class: 'adi-path-wrap' }); const domPathScrollLeft = newElement('span', { class: 'adi-path-left' }); const domPathScrollRight = newElement('span', { class: 'adi-path-right' }); this.menuView = newElement('div', { id: 'adi-panel' }); const naviButtons = newElement('div', { class: 'adi-menu-wrap' }); const naviConfig = newElement('a', { class: 'adi-menu-config', title: 'Settings' }); const naviLookup = newElement('a', { class: 'adi-menu-lookup', title: 'Lookup tool' }); // this.horizSplit = horizSplit; this.optsView = drawOptions(); // put UI together domViewContent.appendChild(domTree); this.domView.appendChild(this.menuView); this.domView.appendChild(domViewContent); domPathWrap.appendChild(domPathScrollLeft); domPathWrap.appendChild(domPathScrollRight); naviButtons.appendChild(naviLookup); naviButtons.appendChild(naviConfig); this.menuView.appendChild(domPathWrap); this.menuView.appendChild(naviButtons); // this.uiView.appendChild(this.menuView); this.uiView.appendChild(this.optsView); this.uiView.appendChild(this.domView); if (!this.isInstanceViewer) { this.attrView = newElement('div', { id: 'adi-attr-view' }); const attrViewContent = newElement('div', { class: 'adi-content' }); this.attrView.appendChild(attrViewContent); this.uiView.appendChild(this.attrView); } // this.uiView.appendChild(horizSplit); // wrapper.appendChild(naviWrap); // cache UI object and append to the DOM rootElement.appendChild(this.uiView); // document.querySelector('#inspector').appendChild(wrapper); this.refreshUI(true); } // Refreshes the global UI refreshUI(refreshOpts) { if (this.uiView === null) { return; } // load options if requested (e.g. before the first UI refresh) if (refreshOpts) { this.loadOptions(); } // Options view refresh if (refreshOpts) { this.optsView.querySelector('[data-opt="transparent"]').checked = this.options.transparent; this.optsView.querySelector('[data-opt="saving"]').checked = this.options.saving; this.optsView.querySelector('[data-opt="omitEmptyText"]').checked = this.options.omitEmptyText; this.optsView.querySelector('[data-opt="makeVisible"]').checked = this.options.makeVisible; this.optsView.querySelector('[data-opt="foldText"]').checked = this.options.foldText; this.optsView.querySelector('[data-opt="nodeTypes-3"]').checked = this.options.nodeTypes.indexOf(3) !== -1; this.optsView.querySelector('[data-opt="nodeTypes-8"]').checked = this.options.nodeTypes.indexOf(8) !== -1; // this.optsView.querySelector('[data-opt="nodeTypes-1"]').checked = this.options.nodeTypes.indexOf(1) !== -1; // this.optsView.querySelector('[data-opt="nodeTypes-9"]').checked = this.options.nodeTypes.indexOf(9) !== -1; } // UI appearance refresh this.uiView.className = this.options.transparent ? 'transparent' : ''; // this.uiView.style.display = this.options.visible ? 'grid' : 'none'; // this.domView.style.height = `${this.options.split}%`; // this.attrView.style.height = `${100 - this.options.split}%`; this.domView.querySelector('.adi-content').style.height = `${this.domView.clientHeight}px`; if (!this.isInstanceViewer) { this.attrView.querySelector('.adi-content').style.height = `${ this.attrView.clientHeight - this.menuView.clientHeight }px`; } addClass(this.uiView, this.options.align); } // UI visibility toggle handler toggleVisibilityUI() { if (this.uiView === null) { return; } this.uiView.style.display = this.options.visible ? 'none' : 'block'; this.options.visible = !this.options.visible; this.saveOptions(); } // Renders the attribute view drawAttrs(elem) { if (this.isInstanceViewer) { return; } const content = this.attrView.querySelector('.adi-content'); // prepare attributes content.innerHTML = ''; const header = document.createElement('header'); header.innerText = 'Attributes'; content.appendChild(header); // todo: hook element-def.json in here /* if (elem.nodeName.startsWith('FX-')) { console.log('got a fore element'); const {properties} = elem.constructor; Object.keys(properties).forEach(propertyName => { const property = properties[propertyName]; if (!property || property.hidden) { return; } const row = content.appendChild(newElement('span', {class: 'adi-attr'})); switch (property.type) { case 'referencedNode': { row.innerHTML = `<label>${propertyName}: <button>${elem[propertyName]?.nodeName}</button></label>`; const button = row.querySelector('button'); button.addEventListener( 'click', () => this.handleLookup({detail: {target: elem[propertyName]}}) ); break; } case Number: { row.innerHTML = `<label>${propertyName}: <input type="number" data-attr="${propertyName}" value="${elem[propertyName]}"></label>`; break; } case String: { if (property.valueSpace) { row.innerHTML = `<label>${propertyName}: <select data-attr="${propertyName}" value="${elem[propertyName]}">${property.valueSpace.map(value => `<option>${value}</option>`)}</label>`; break; } row.innerHTML = `<label>${propertyName}: <input type="text" data-attr="${propertyName}" value="${elem[propertyName]}"></label>`; break; } case Boolean: { if (property.valueSpace) { row.innerHTML = `<label>${propertyName}: <input type="checkbox" data-attr="${propertyName}" value="${elem[propertyName]}"></input></label>`; } break; } case Object: { try { row.innerHTML = `<label>${propertyName}: <code>${JSON.stringify(elem[propertyName])}</code></label>`; } catch (err) { row.innerHTML = `<label>${propertyName}: <code>Unserializable</code></label>`; } break; } case Map: { try { row.innerHTML = `<label>${propertyName}: <code>${JSON.stringify(elem[propertyName])}</code></label>`; } catch (err) { row.innerHTML = `<label>${propertyName}: <code>Unserializable</code></label>`; } break; } default: { row.innerHTML = `<label>${propertyName}: Unknown type ${property.type}</label>`; } } }); } else { */ [...elem.attributes].forEach(attr => { if (attr.name !== 'style') { content.appendChild(drawAttrRow(attr.name, attr.value)); } }); // } } // Handles attribute changes changeAttribute(e) { const target = e ? e.target : window.event.srcElement; const attr = target.getAttribute('data-attr'); const val = target.value; const elem = this.getSelected(); // remove attribute if the new value is empty if (val === '') { elem.removeAttribute(attr); } else { elem.setAttribute(attr, val); } } // Handles option changes changeOption(e) { const target = e ? e.target : window.event.srcElement; const data = target.getAttribute('data-opt'); const val = target.checked; if (data.indexOf('nodeTypes') !== -1) { const type = parseInt(data.match(/\d+/)[0], 10); if (val) { this.options.nodeTypes.push(type); } else { this.options.nodeTypes.splice(this.options.nodeTypes.indexOf(type), 1); } } else { this.options[data] = val; } } // Key events processing processKey(e) { e = e || window.event; const code = e.keyCode || e.which; switch (code) { case 272: // ctrl + alt + d this.toggleVisibilityUI(); break; default: break; } } // Vertical splitter resize handler verticalResize(e) { if (!this.vertResizing) { return; } e = e || window.event; document.documentElement.style.cursor = 'e-resize'; const nWidth = this.options.width + this.xPos - e.clientX; if (nWidth >= this.options.minWidth) { this.options.width = nWidth; this.xPos = e.clientX; this.refreshUI(); this.saveOptions(); } } // Horizontal splitter resize handler horizontalResize(e) { if (!this.horizResizing) { return; } e = e || window.event; document.documentElement.style.cursor = 'n-resize'; const nSplit = Math.floor((e.clientY / this.uiView.clientHeight) * 100); if (nSplit >= this.options.minSplit && nSplit <= 100 - this.options.minSplit) { this.options.split = nSplit; this.refreshUI(); this.saveOptions(); } } processExecuteAction(e) { this.refreshUI(); } // Handles active element selection handleActive(e) { let target = e ? e.detail?.target || e.target : window.event.srcElement; const active = this.domView.querySelector('.adi-active-node'); if (active) { removeClass(active, 'adi-active-node'); } // clicked on normal-node or end-node? if (!target || target.nodeType === Node.DOCUMENT_NODE) return; if (target && target.classList && target.classList.contains('adi-end-node')) { target = target.parentNode.querySelector('.adi-normal-node'); } this.activeElement = target; addClass(target, 'adi-active-node'); /* e.target.dispatchEvent( new CustomEvent('handle-active', { composed: true, bubbles: true, detail: {active: this.activeElement, selected: this.getSelected()}, }), ); */ // make it visible (scroll) if (this.options.makeVisible) { const wrap = this.domView.querySelector('.adi-content'); wrap.scrollIntoView({ block: 'center', behavior: 'instant' }); } const selected = this.getSelected(); this.drawAttrs(selected); if (selected && typeof selected.getModelItem === 'function' && selected.getModelItem()?.node) { let selectedElement = selected.modelItem.node; if (selectedElement?.nodeType === Node.ATTRIBUTE_NODE) { selectedElement = selectedElement.ownerElement; } window.document.dispatchEvent( new CustomEvent('log-active-element', { detail: { target: selectedElement } }), ); } // window.document.dispatchEvent(new CustomEvent('log-active-element', {detail: {target: selected}})); } // Highlights an element on page highlightElement(event) { // console.log('highlight',e); let sourceNode = event ? event.target : window.event.srcElement; if (sourceNode.classList.contains('adi-end-node')) { sourceNode = sourceNode.parentNode.querySelector('.adi-normal-node'); } const inspectorNode = this.sourceNodeByInspectorNodeLookup.get(sourceNode); if (!inspectorNode || inspectorNode.ownerDocument !== window.document) { // Not in HTML: ignore return; } if (inspectorNode) { if (event.type === 'mouseover') { this.styleBackup = inspectorNode.getAttribute('style') || ''; inspectorNode.setAttribute('style', `outline: 2px solid blue; ${this.styleBackup}`); } else if (this.styleBackup === '') { inspectorNode.removeAttribute('style'); } else { inspectorNode.setAttribute('style', this.styleBackup); } } } // Handles element lookup on page handleLookup(e) { const target = e ? e.detail?.target || e.target : window.event.srcElement; if (!this.document.contains(target)) { // Targetted at somewhere else!!! return; } if (target.nodeType === Node.DOCUMENT_NODE) { // Targetted at the document node. Nothing to highlight return; } if (target.className.indexOf('adi-menu-lookup') !== -1) { // enable/disable interactive lookup if (this.elemLookup) { removeClass(target, 'adi-active'); this.elemLookup = false; this.removeEvent(document.body, 'mouseover', this.handleLookup, true); this.removeEvent(document.body, 'mouseout', this.handleLookup, true); this.removeEvent(document.body, 'click', this.handleLookup, true); return; } addClass(target, 'adi-active'); this.elemLookup = true; this.addEventDelegate( document.body, 'mouseover', this.handleLookup, false, '*', true, 'adi-wrapper', ); this.addEventDelegate( document.body, 'mouseout', this.handleLookup, false, '*', true, 'adi-wrapper', ); this.addEventDelegate( document.body, 'click', this.handleLookup, false, '*', true, 'adi-wrapper', ); return; } // handle lookup events if (e.type === 'mouseover') { this.styleBackup = target.getAttribute('style') || ''; target.setAttribute('style', `outline: 1px dashed red; ${this.styleBackup}`); return; } if (e.type === 'mouseout') { target.setAttribute('style', this.styleBackup); return; } this.elemLookup = false; removeClass(this.menuView.querySelector('.adi-menu-lookup'), 'adi-active'); target.setAttribute('style', this.styleBackup); this.removeEvent(document.body, 'mouseover', this.handleLookup, true); this.removeEvent(document.body, 'mouseout', this.handleLookup, true); this.removeEvent(document.body, 'click', this.handleLookup, true); pauseEvent(e); // find corresponding node in the DOM view const active = this.sourceNodeByInspectorNodeLookup.get(target); // activate it if (!active) return; if (active) { active.click(); } // open the whole path in DOM view if (!active.parentNode) return; let node = active.parentNode; let tmp; if (node.querySelector('ul')) { node.querySelector('ul').setAttribute('data-open', 'true'); } while (node !== this.domView.querySelector('.adi-content')) { if (node.className.indexOf('adi-node') !== -1) { tmp = node.querySelector('.adi-trigger'); if (tmp) { removeClass(tmp, 'closed'); addClass(tmp, 'opened'); } node = node.parentNode; // ul node node.setAttribute('data-open', 'true'); } node = node.parentNode; } // make it visible (scroll) if (this.options.makeVisible) { active.scrollIntoView({ behavior: 'instant', block: 'nearest', inline: 'nearest' }); } target.scrollIntoView({ behavior: 'instant', block: 'nearest', inline: 'nearest' }); } // Simple cross-browser event handler that enables simple event delegation. // Note that the selector must be a string and no nesting is supported. // Selector is expected to be in one of formats listed below and works for all children // in the particular element. // Store parameter enables storing the reference to custom event handler. // Exclude parameter will exclude the particular element and all of its children, this works // only for id selectors. // Selector formats: tag name ("div"), class name (".my-class"), id ("#my-id") and any ("*"). addEventDelegate(elem, evt, fn, capture, selector, store, exclude) { // custom event handler is registered const handler = e => { // check if target corresponds to the selector const target = e ? e.target : window.event.srcElement; const sel = selector.substr(1); let delegate = false; if (exclude) { let node = target; while (node !== document) { if (node.id === exclude) { return; } node = node.parentNode; } } // should the event be delegated? if (selector.indexOf('#') === 0) { // ID delegate = target.id === sel; } else if (selector.indexOf('.') === 0) { // class delegate = target.className.indexOf(sel) !== -1; } else if (selector === '*') { // any delegate = true; } else { // tag name delegate = target.nodeName.toLowerCase() === selector; } // delegate the event handling if (delegate) { fn(e); } }; // save the reference if (store) { this.delegatedEvents.push({ handle: handler, elem, fn, evt, }); } elem.addEventListener(evt, handler, capture); } // Simple cross-browser event removing removeEvent(elem, evt, fn, wasDelegated) { if (typeof elem !== 'object') { throw new Error(`addEvent: Expected argument elem of type object, ${typeof elem} given.`); } // try to find stored delegated event let stored = null; if (wasDelegated) { for (let i = 0, len = this.delegatedEvents.length; i < len; i += 1) { stored = this.delegatedEvents[i]; if (stored.elem === elem && stored.evt === evt && stored.fn === fn) { fn = stored.handle; this.delegatedEvents.splice(i, 1); break; } } } // elem.detachEvent(`on${evt}`, fn); } // Event registration registerEvents() { // events for splitters /* this.horizSplit.addEventListener( 'mousedown', e => { e = e || window.event; pauseEvent(e); this.horizResizing = true; }, false, ); */ const redrawUi = () => { if (this.instanceId !== '#document') { const instance = window.document.querySelector(`#${this.instanceId}`); this.document = instance.getInstanceData(); } this.drawDOM(this.document, this.domView.querySelector('.adi-tree-view'), true); }; // Update UI when something with instances changed document.addEventListener('instance-loaded', redrawUi); // Update UI when some value changes document.addEventListener('value-changed', redrawUi); // Update UI when we're done loading and all repeats are done document.addEventListener('ready', redrawUi); document.addEventListener( 'mouseup', () => { document.documentElement.style.cursor = 'default'; this.vertResizing = false; this.horizResizing = false; }, false, ); document.addEventListener('mousemove', event => this.verticalResize(event), false); document.addEventListener('mousemove', event => this.horizontalResize(event), false); // window resize window.addEventListener('resize', event => this.refreshUI(event), false); // keypress events document.addEventListener('keypress', event => this.processKey(event), false); // fore action events document.addEventListener('log-active-element', event => this.handleLookup(event), false); // Dom view folding handler const handleFolding = e => { const target = e ? e.target : window.event.srcElement; const ul = target.parentNode.querySelector('ul'); if (ul.getAttribute('data-open') === 'true') { removeClass(target, 'opened'); addClass(target, 'closed'); ul.setAttribute('data-open', 'false'); } else { removeClass(target, 'closed'); addClass(target, 'opened'); ul.setAttribute('data-open', 'true'); } }; // dom tree view folding this.addEventDelegate(this.domView, 'click', handleFolding, false, '.adi-trigger'); // active element this.addEventDelegate( this.domView, 'click', event => this.handleActive(event), false, '.adi-normal-node', ); this.addEventDelegate( this.domView, 'click', event => this.handleActive(event), false, '.adi-end-node', ); // matching tag highlighting this.addEventDelegate( this.domView, 'mouseover', e => { const target = e ? e.target : window.event.srcElement; addClass(target.parentNode.querySelector('.adi-normal-node'), 'hover'); }, false, '.adi-end-node', ); this.addEventDelegate( this.domView, 'mouseout', e => { const target = e ? e.target : window.event.srcElement; removeClass(target.parentNode.querySelector('.adi-normal-node'), 'hover'); }, false, '.adi-end-node', ); // page element highlighting this.addEventDelegate( this.domView, 'mouseover', event => this.highlightElement(event), false, '.adi-end-node', ); this.addEventDelegate( this.domView, 'mouseover', event => this.highlightElement(event), false, '.adi-normal-node', ); this.addEventDelegate( this.domView, 'mouseout', event => this.highlightElement(event), false, '.adi-end-node', ); this.addEventDelegate( this.domView, 'mouseout', event => this.highlightElement(event), false, '.adi-normal-node', ); // element lookup this.menuView .querySelector('.adi-menu-lookup') .addEventListener('click', event => this.handleLookup(event), false); document.addEventListener('handle-active', e => { if (e.detail.selected === this.getSelected()) { // We caused this. ignore return; } const { selected } = e.detail; const target = this.sourceNodeByInspectorNodeLookup.get(selected); // make it visible (scroll) if (this.options.makeVisible) { const wrap = this.domView.querySelector('.adi-content'); if (target.offsetTop >= wrap.clientHeight || target.offsetTop <= wrap.scrollTop) { wrap.scrollTop = target.offsetTop - Math.floor(wrap.clientHeight / 2); } } this.drawAttrs(this.getSelected()); }); document.addEventListener('execute-action', e => this.processExecuteAction(event), { capture: true, }); // options events this.addEventDelegate( this.optsView, 'change', event => this.changeOption(event), false, 'input', ); this.addEventDelegate( this.optsView, 'click', event => this.toggleOptions(event), false, '.adi-opt-close', ); this.menuView .querySelector('.adi-menu-config') .addEventListener('click', event => this.toggleOptions(event), false); // attributes events if (!this.isInstanceViewer) { this.addEventDelegate(this.attrView, 'change', this.changeAttribute, false, 'input'); } } } export default ADI;