Files
P42_UI/app/libs/Windoz/WindozController.js
2025-10-14 16:31:07 +00:00

377 lines
13 KiB
JavaScript
Executable File

/**
* @category MyEic
* @subcategory Libraries
* @extends Controller
*/
class WindozController 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('<div></div>');
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 settingsMarkup = ''
if(options.withSettings){
settingsMarkup=`
<div eicdropdown>
<button eicbutton data-id="${view._sparcId}" basic primary rounded xsmall class="icon-cog settings" title="settings"></button>
<menu eicmenu data-output="settingsMenu"></menu>
</div>
`
}
let content = ui.create(`<div class="window">
<header class="cols-2 right">
<h1>${options.title || ''}</h1>
<div class="controls">
${settingsMarkup}
<button eicbutton data-id="${view._sparcId}" basic primary rounded xsmall class="icon-copy shrink" title="shrink"></button>
<button eicbutton data-id="${view._sparcId}" basic primary rounded xsmall class="icon-square-o expand" title="expand"></button>
<button eicbutton data-id="${view._sparcId}" basic primary rounded xsmall class="icon-cancel close" title="close"></button>
</div>
</header>
<section></section>
<div class="handle" data-side="nw"></div>
<div class="handle" data-side="n"></div>
<div class="handle" data-side="ne"></div>
<div class="handle" data-side="e"></div>
<div class="handle" data-side="se"></div>
<div class="handle" data-side="s"></div>
<div class="handle" data-side="sw"></div>
<div class="handle" data-side="w"></div>
</div>`);
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 WindozDialogContent();
view.el = message;
}
let dialog = WindozController.createDialog(view);
function commit(result) {
WindozController.closeDialog(view._sparcId);
resolve(result);
}
function abort(result) {
WindozController.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(`<div eicdialog sparc-id="${dialog._sparcId}">
<article eiccard ${ options.closable || ''}>
${ dialog.options.title ? `<header><h1>${dialog.options.title}</h1>${dialog.options.subtitle ? `<h2>${dialog.options.subtitle}</h2>`: '' }</header>`: ''}
<section></section>
<footer></footer>
</article>
</div>`);
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('WindozController', WindozController);