/** * @category MyEic * @subcategory Libraries * @extends Controller */ class EICController extends Controller { constructor() { super(); this.view2url = {}; } getViewByClass(className) { let content = Controller._contents.find(o => o.view._className == className) return content ? content.view: null; } getViewByURL(url) { let content = Controller._contents.find(o => o.view._url == url) return content ? content.view: null; } loadContent(name, options, data) { options = options || {}; options.onContentLoaded = this.createContent; return super.loadView(name, options, data); } createContent(options, data, html) { let container = ui.create('
'); container.innerHTML = Controller.processTemplate(options.name, html, data); let view = new app.LoadedClasses[options.className](options); view._className = options.name.replace('.html', ''); view._controller = this; view.el = container; view.DOMContentLoaded(data); container.setAttribute('sparc-id', view._sparcId); return view; } loadWindow(name, options, data) { let url = app.Router.currentRoute.realUrl; options = options || {}; // if static, redirect to existing instance if(options.static) { /* MFA: Using stored real url instead of classname allows different instance of window based on parameter: eg: having route "/window/{id}", calling "/window/345" won't replace "/window/123" Makes me wonder: shouldn't all windows be static anyway then ? */ //let existing = this.getViewByClass(name); let existing = this.getViewByURL(url); if(existing) { this.focus(existing._sparcId, data); return; } } options.onContentLoaded = this.createWindow; super.loadView(name, options, data).then( view => { this.view2url[view._sparcId] = url; view._url = url } ); } createWindow(options, data, html) { if(!(options.className in app.LoadedClasses)){ console.error(`Missing view ${options.className} !\nLoad explicitely it from your controller, or as set it as a dependency...`); return null; } let view = new app.LoadedClasses[options.className](); view._className = options.name.replace('.html', ''); view._controller = this; Controller._contents.push({ view: view, type: 'window', expanded: options.expanded || false, visible: true, active: false }); let content = ui.create(`

${options.title || ''}

${(options.withSettings) ? '' :''}
`); if(options.expanded) content.setAttribute('expanded',''); if(options.windowStyle){ for(const k in options.windowStyle){ content.style[k] = options.windowStyle[k] } } let container = content.querySelector('section'); container.innerHTML = Controller.processTemplate(options.name, html, data); view.el = content; // setting up window controls let expand = content.querySelector('header button.expand'); expand.addEventListener('click', view.expand.bind(view)); let shrink = content.querySelector('header button.shrink'); shrink.addEventListener('click', view.shrink.bind(view)); let close = content.querySelector('header button.close'); close.addEventListener('click', this.onclose.bind(this)); content.addEventListener('click', this.onFocusRequest.bind(this)); content.addEventListener('expanded', this.onFocusRequest.bind(this)); content.addEventListener('shrinked', this.onFocusRequest.bind(this)); if(options.withSettings && (typeof(view.settings) == 'function')){ let settings = content.querySelector('header button.settings'); settings.addEventListener('click', view.settings.bind(view)); } content.setAttribute('sparc-id', view._sparcId); let parent = Controller._template.view.find('.app-workspace'); parent.appendChild(content); this.addThesaurus(view._sparcId, options.title || 'a window') view.DOMContentLoaded(data); view.initWindowEvents(); this.focus(view._sparcId); return view; } openDialog(view) { let promise = new Promise(function(resolve) { if(typeof view === 'string') { let message = view; view = new EICDialogContent(); view.el = message; } let dialog = EICController.createDialog(view); function commit(result) { EICController.closeDialog(view._sparcId); resolve(result); } function abort(result) { EICController.closeDialog(view._sparcId); resolve(result); } view.commit = commit; view.abort = abort; view.buttons.forEach(function(button) { dialog.querySelector('[eicdialog] > [eiccard] > footer').append(button.el); }); view.DOMContentFocused(); }); return promise; } /** * Used by a view that needs to change the current URL * Typically happens when the view allows you to switch to another instance of the business-object * handled by this controiller. Doinbg a history.replaceState in the view is not enough, because * the controller needs to keep track of his current URL (in view2url) for changing * the Browser URL bar when -later- changing window focus. * @param {string} newUrl */ changeUrl(view, newUrl) { history.replaceState(null, null, newUrl) this.view2url[view._sparcId] = newUrl view._url = newUrl } static createDialog(dialog, options) { options = options || {}; let container = ui.create(`
${ dialog.options.title ? `

${dialog.options.title}

${dialog.options.subtitle ? `

${dialog.options.subtitle}

`: '' }
`: ''}
`); ui.eicfy(container); container.querySelector('[eiccard] section').append(dialog.el); dialog.parentContainer = container; document.body.append(container) return container; } static closeDialog(id) { document.body.querySelector(`[sparc-id="${id}"]`).remove(); } addThesaurus(id, title) { let container = Controller._template.view.find('.app-content-thesaurus'); container.querySelectorAll('[eicchip]').forEach(el => el.setAttribute('secondary', '')) let chip = new Chip(null, {label: title, destroyable: true}); chip.el.setAttribute('data-id', id); chip.addEventListener('click', this.onSelectThesaurus.bind(this)); chip.addEventListener('destroy', this.onRemoveThesaurus.bind(this)); container.append(chip.el); } removeThesaurus(id) { let container = Controller._template.view.find('.app-content-thesaurus'); let chip = container.querySelector(`[data-id="${id}"]`) if(chip) chip.remove(); let remaining = container.querySelectorAll('[eicchip]'); if(remaining.length > 0) { remaining.item(remaining.length - 1).dispatchEvent(new MouseEvent('click')); } } close(id) { this.removeThesaurus(id); this.unloadView(Controller.getContentById(id)); if(this.view2url.hasOwnProperty(id)) delete(this.view2url[id]); } focus(id, data) { this.selectThesaurus(id); let curUrl = new URL(document.location.href).pathname if(this.view2url.hasOwnProperty(id)) { history.replaceState(null, null, this.view2url[id]); curUrl = this.view2url[id] } if(app.matomo) app.matomo.logAction('Focus window / '+curUrl) let next = Controller.getContentById(id); if(Controller._currentContent) { if(next.expanded) { this.blur(Controller._currentContent); Controller._contents.filter(item => item.type == 'window').forEach(item => this.desactivate(item)); Controller._contents.filter(item => !item.expanded).forEach(item => this.blur(item)); } else { Controller._contents.filter(item => !item.expanded && item.type == 'window').forEach(item => this.attach(item)); this.attach(Controller._currentContent); let altexpand = Controller._contents.filter(item => item.expanded && item.visible && item.type == 'window'); if(altexpand.length == 0) { let latest = Controller._contents.reverse().find(item => item.expanded); if(latest) { this.attach(latest); } } } } Controller._currentContent = next; this.activate(Controller._currentContent, data); Controller._currentContent.view.DOMContentResized(); } desactivate(content) { content.view.el.classList.remove('active'); content.active = false; if(content.expanded) this.blur(content); } activate(content, data) { Controller._contents.forEach(o => o.active = false); Controller._contents.forEach(o => o.view.el.classList.remove('active')); content.view.el.classList.add('active'); content.active = true; if(!content.visible) this.attach(content); content.view.DOMContentFocused(data); } attach(content) { //ui.show(content.view.el,!content.expanded ? 'grid': 'block'); ui.show(content.view.el, 'flex'); content.visible = true; } blur(content) { ui.hide(content.view.el); content.visible = false; content.active = false; content.view.DOMContentBlured(); } onclose(event) { event.preventDefault(); event.stopPropagation(); this.close(event.currentTarget.dataset.id) } onSelectThesaurus(event) { event.stopPropagation(); event.preventDefault(); this.focus(event.currentTarget.dataset.id); } selectThesaurus(id) { let container = Controller._template.view.find('.app-content-thesaurus'); container.querySelectorAll('[eicchip]').forEach(el => el.setAttribute('secondary', '')) container.querySelector(`[eicchip][data-id="${id}"]`).removeAttribute('secondary'); } onRemoveThesaurus(event) { event.stopPropagation(); event.preventDefault(); this.close(event.currentTarget.dataset.id); } onFocusRequest(event) { let view = event.currentTarget; while(!view.hasAttribute('sparc-id') && !view.classList.contains('window')) view = view.parentElement; let content = Controller.getContentById(view.getAttribute('sparc-id')); if(!content.active || content.expanded != content.view.expanded || (Controller._currentContent.view._sparcId != content.view._sparcId)) { content.expanded = content.view.expanded; this.focus(view.getAttribute('sparc-id')); } } static resize() { Controller._contents.forEach(item => item.view.DOMContentResized()) } } app.registerClass('EICController', EICController);