mirror of https://github.com/keeweb/keeweb.git
212 lines
5.9 KiB
JavaScript
212 lines
5.9 KiB
JavaScript
import morphdom from 'morphdom';
|
|
import EventEmitter from 'events';
|
|
import { Tip } from 'util/ui/tip';
|
|
import { KeyHandler } from 'comp/browser/key-handler';
|
|
import { Logger } from 'util/logger';
|
|
|
|
class View extends EventEmitter {
|
|
parent = undefined;
|
|
template = undefined;
|
|
events = {};
|
|
model = undefined;
|
|
options = {};
|
|
views = {};
|
|
hidden = false;
|
|
removed = false;
|
|
boundEvents = [];
|
|
debugLogger = localStorage.debugViews ? new Logger('view', this.constructor.name) : undefined;
|
|
|
|
constructor(model, options = {}) {
|
|
super();
|
|
|
|
this.model = model;
|
|
this.options = options;
|
|
|
|
this.setMaxListeners(100);
|
|
}
|
|
|
|
render(templateData) {
|
|
if (this.removed) {
|
|
return;
|
|
}
|
|
|
|
let ts;
|
|
if (this.debugLogger) {
|
|
this.debugLogger.debug('Render start');
|
|
ts = this.debugLogger.ts();
|
|
}
|
|
|
|
if (this.el) {
|
|
Tip.destroyTips(this.el);
|
|
}
|
|
|
|
this.unbindEvents();
|
|
this.renderElement(templateData);
|
|
this.bindEvents();
|
|
|
|
Tip.createTips(this.el);
|
|
|
|
this.debugLogger && this.debugLogger.debug('Render finished', this.debugLogger.ts(ts));
|
|
|
|
return this;
|
|
}
|
|
|
|
renderElement(templateData) {
|
|
const html = this.template(templateData);
|
|
if (this.el) {
|
|
const mountRoot = this.options.ownParent ? this.el.firstChild : this.el;
|
|
morphdom(mountRoot, html);
|
|
} else {
|
|
let parent = this.options.parent || this.parent;
|
|
if (parent) {
|
|
if (typeof parent === 'string') {
|
|
parent = document.querySelector(parent);
|
|
}
|
|
if (!parent) {
|
|
throw new Error(`Error rendering ${this.constructor.name}: parent not found`);
|
|
}
|
|
if (this.options.replace) {
|
|
Tip.destroyTips(parent);
|
|
parent.innerHTML = '';
|
|
}
|
|
const el = document.createElement('div');
|
|
el.innerHTML = html;
|
|
const root = el.firstChild;
|
|
if (this.options.ownParent) {
|
|
if (root) {
|
|
parent.appendChild(root);
|
|
}
|
|
this.el = parent;
|
|
} else {
|
|
this.el = root;
|
|
parent.appendChild(this.el);
|
|
}
|
|
} else {
|
|
throw new Error(
|
|
`Error rendering ${this.constructor.name}: I don't know how to insert the view`
|
|
);
|
|
}
|
|
this.$el = $(this.el); // legacy
|
|
}
|
|
}
|
|
|
|
bindEvents() {
|
|
for (const [eventDef, method] of Object.entries(this.events)) {
|
|
const spaceIx = eventDef.indexOf(' ');
|
|
let event, targets;
|
|
if (spaceIx > 0) {
|
|
event = eventDef.substr(0, spaceIx);
|
|
const selector = eventDef.substr(spaceIx + 1);
|
|
targets = this.el.querySelectorAll(selector);
|
|
} else {
|
|
event = eventDef;
|
|
targets = [this.el];
|
|
}
|
|
for (const target of targets) {
|
|
const listener = e => {
|
|
this.debugLogger && this.debugLogger.debug('Listener', method);
|
|
this[method](e);
|
|
};
|
|
target.addEventListener(event, listener);
|
|
this.boundEvents.push({ target, event, listener });
|
|
}
|
|
}
|
|
}
|
|
|
|
unbindEvents() {
|
|
for (const boundEvent of this.boundEvents) {
|
|
const { target, event, listener } = boundEvent;
|
|
target.removeEventListener(event, listener);
|
|
}
|
|
}
|
|
|
|
remove() {
|
|
this.emit('remove');
|
|
|
|
this.removeInnerViews();
|
|
Tip.hideTips(this.el);
|
|
this.el.remove();
|
|
this.removed = true;
|
|
|
|
this.debugLogger && this.debugLogger.debug('Remove');
|
|
}
|
|
|
|
removeInnerViews() {
|
|
if (this.views) {
|
|
for (const view of Object.values(this.views)) {
|
|
if (view) {
|
|
if (view instanceof Array) {
|
|
view.forEach(v => v.remove());
|
|
} else {
|
|
view.remove();
|
|
}
|
|
}
|
|
}
|
|
this.views = {};
|
|
}
|
|
}
|
|
|
|
listenTo(model, event, callback) {
|
|
const boundCallback = callback.bind(this);
|
|
model.on(event, boundCallback);
|
|
this.once('remove', () => model.off(event, boundCallback));
|
|
}
|
|
|
|
stopListening(model, event, callback) {
|
|
// TODO: remove a pending callback
|
|
model.off(event, callback);
|
|
}
|
|
|
|
hide() {
|
|
Tip.hideTips(this.el);
|
|
return this.toggle(false);
|
|
}
|
|
|
|
show() {
|
|
return this.toggle(true);
|
|
}
|
|
|
|
toggle(visible) {
|
|
this.debugLogger && this.debugLogger.debug(visible ? 'Show' : 'Hide');
|
|
if (visible === undefined) {
|
|
visible = this.hidden;
|
|
}
|
|
this.hidden = !visible;
|
|
this.emit(visible ? 'show' : 'hide');
|
|
if (this.el) {
|
|
this.el.classList.toggle('show', !!visible);
|
|
this.el.classList.toggle('hide', !visible);
|
|
if (!visible) {
|
|
Tip.hideTips(this.el);
|
|
}
|
|
}
|
|
}
|
|
|
|
isHidden() {
|
|
return this.hidden;
|
|
}
|
|
|
|
isVisible() {
|
|
return !this.hidden;
|
|
}
|
|
|
|
afterPaint(callback) {
|
|
requestAnimationFrame(() => requestAnimationFrame(callback));
|
|
}
|
|
|
|
onKey(key, handler, shortcut, modal, noPrevent) {
|
|
KeyHandler.onKey(key, handler, this, shortcut, modal, noPrevent);
|
|
this.once('remove', () => KeyHandler.offKey(key, handler, this));
|
|
}
|
|
|
|
off(event, listener) {
|
|
if (listener === undefined) {
|
|
return super.removeAllListeners(event);
|
|
} else {
|
|
return super.off(event, listener);
|
|
}
|
|
}
|
|
}
|
|
|
|
export { View };
|