js/controllers/PopoverViewController.js
import ViewController from '~/controllers/ViewController';
import ActionControllerDelegate from '~/delegate/ActionControllerDelegate';
import KeyManager from '~/models/KeyManager';
export const c = 'popvc__untrigger';
/**
* Controls a popover view. This has a trigger and a target. When the trigger is
* pressed, this displays the target.
*/
export default class PopoverViewController extends ViewController {
/**
* Creates a popover view with a given trigger + target.
* @param {?HTMLElement} root The root element to bind to.
* @param {HTMLElement} trigger binds `onclick` as a trigger to this node.
* @param {Template} template will display this view on trigger.
* @param {?HTMLElement} [untrigger=document] element to untrigger.
*/
constructor(root, trigger, template, untrigger = document) {
const instance = template.unique();
super(instance);
/**
* State is `true` when opening. `false` when closing
* @type {ActionControllerDelegate}
*/
this.delegate = new ActionControllerDelegate();
this._trigger = trigger;
this._template = template;
this._node = instance;
this._isActive = false;
this._untriggerTimeout = null;
this._animationTime = this._node.dataset.animationTime || 0; // in ms
this._parent = template.getParent(document.body);
this._node.classList.add("template");
if (this._node.parentNode === null) {
this._parent.appendChild(this._node);
}
this._keyBinding = null;
const untriggers = this._node.getElementsByClassName('popvc__untrigger');
for (const localUntrigger of untriggers) {
this.bindUntrigger(localUntrigger);
}
// Setup hide trigger
untrigger?.addEventListener("click", (event) => {
if (this._isActive) {
let target = event.target;
// Target is not in DOM
if (!(document.body.contains(event.target) || event.target === document.body)) { return }
// Target is outside of popover
if (!this._node.contains(untrigger) && (
this._node.contains(target) ||
this._trigger.contains(target)
)) { return }
this.untrigger();
}
});
this.bindTrigger(trigger);
}
/**
* Adds a new trigger node.
* @param {string|HTMLElement} trigger - A new trigger to add
*/
bindTrigger(trigger) {
if (typeof trigger === 'string') {
trigger = document.getElementById(trigger);
}
trigger.addEventListener("click", () => {
this.trigger();
}, false);
}
/**
* Binds an untrigger node.
* @param {string|HTMLElement} untrigger - A new untrigger to add
*/
bindUntrigger(untrigger) {
if (typeof untrigger === 'string') {
untrigger = document.getElementById(untrigger);
}
untrigger.addEventListener("click", () => {
this.untrigger();
}, false);
}
/**
* Sets into an active state
*/
trigger() {
this._template.willLoad();
this.delegate.didSetStateTo(this, true);
this._isActive = true;
if (this._untriggerTimeout) {
clearTimeout(this._untriggerTimeout);
}
this._node.classList.remove("template");
window.setTimeout(() => {
this._node.classList.add("template--active");
});
this._trigger.classList.add("state-active");
this._node.focus();
this._keyBinding = KeyManager.shared.register('Escape', () => {
this.untrigger();
});
this._template.didLoad();
}
/**
* Sets into inactive state.
*/
untrigger() {
this._template.willUnload();
this.delegate.didSetStateTo(this, false);
this._isActive = false;
this._trigger.classList.remove("state-active");
this._node.classList.remove("template--active");
this._untriggerTimeout = setTimeout(() => {
this._untriggerTimeout = null;
this._node.classList.add("template");
}, this._animationTime);
this._keyBinding?.();
this._keyBinding = null;
this._template.didUnload();
}
}