wed
Version:
Wed is a schema-aware editor for XML documents.
204 lines • 8.37 kB
JavaScript
/**
* Listens to changes on a tree and updates the GUI tree in response to changes.
* @author Louis-Dominique Dubeau
* @license MPL 2.0
* @copyright Mangalam Research Center for Buddhist Languages
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
define(["require", "exports", "jquery", "./convert", "./dloc", "./domtypeguards", "./domutil", "./tree-updater", "./util"], function (require, exports, jquery_1, convert, dloc_1, domtypeguards_1, domutil_1, tree_updater_1, util) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
jquery_1 = __importDefault(jquery_1);
convert = __importStar(convert);
util = __importStar(util);
/**
* Updates a GUI tree so that its data nodes (those nodes that are not
* decorations) mirror a data tree.
*/
class GUIUpdater extends tree_updater_1.TreeUpdater {
/**
* @param guiTree The DOM tree to update.
*
* @param treeUpdater A tree updater that updates the data tree. It serves as
* a source of modification events which the object being created will listen
* on.
*/
constructor(guiTree, treeUpdater) {
super(guiTree);
this.treeUpdater = treeUpdater;
this.haveTooltips =
guiTree.ownerDocument.getElementsByClassName("wed-has-tooltip");
this.treeUpdater.events.subscribe((ev) => {
switch (ev.name) {
case "InsertNodeAt":
this._insertNodeAtHandler(ev);
break;
case "SetTextNodeValue":
this._setTextNodeValueHandler(ev);
break;
case "BeforeDeleteNode":
this._beforeDeleteNodeHandler(ev);
break;
case "SetAttributeNS":
this._setAttributeNSHandler(ev);
break;
default:
// Do nothing...
}
});
}
/**
* Handles "InsertNodeAt" events.
*
* @param ev The event.
*/
_insertNodeAtHandler(ev) {
const guiCaret = this.fromDataLocation(ev.parent, ev.index);
if (guiCaret === null) {
throw new Error("cannot find gui tree position");
}
const clone = convert.toHTMLTree(this.tree.ownerDocument, ev.node);
if (domtypeguards_1.isElement(ev.node)) {
// If ev.node is an element, then the clone is an element too.
domutil_1.linkTrees(ev.node, clone);
}
this.insertNodeAt(guiCaret, clone);
}
/**
* Handles "SetTextNodeValue" events.
*
* @param ev The event.
*/
_setTextNodeValueHandler(ev) {
const guiCaret = this.fromDataLocation(ev.node, 0);
if (guiCaret === null) {
throw new Error("cannot find gui tree position");
}
this.setTextNodeValue(guiCaret.node, ev.value);
}
/**
* Handles "BeforeDeleteNode" events.
*
* @param ev The event.
*/
_beforeDeleteNodeHandler(ev) {
const dataNode = ev.node;
let toRemove;
let element = false;
switch (dataNode.nodeType) {
case Node.TEXT_NODE:
const guiCaret = this.fromDataLocation(dataNode, 0);
if (guiCaret === null) {
throw new Error("cannot find gui tree position");
}
toRemove = guiCaret.node;
break;
case Node.ELEMENT_NODE:
toRemove = jquery_1.default.data(dataNode, "wed_mirror_node");
element = true;
break;
default:
}
this.deleteNode(toRemove);
// We have to do this **after** we delete the node.
if (element) {
domutil_1.unlinkTree(dataNode);
domutil_1.unlinkTree(toRemove);
}
}
/**
* Handles "SetAttributeNS" events.
*
* @param ev The event.
*/
_setAttributeNSHandler(ev) {
const guiCaret = this.fromDataLocation(ev.node, 0);
if (guiCaret === null) {
throw new Error("cannot find gui tree position");
}
this.setAttributeNS(guiCaret.node, "", util.encodeAttrName(ev.attribute), ev.newValue);
}
fromDataLocation(loc, offset) {
let node;
if (loc instanceof dloc_1.DLoc) {
node = loc.node;
offset = loc.offset;
}
else {
node = loc;
if (offset === undefined) {
throw new Error("must specify an offset");
}
}
let guiNode = this.pathToNode(this.treeUpdater.nodeToPath(node));
if (guiNode === null) {
return null;
}
if (domtypeguards_1.isText(node)) {
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, offset);
}
if (domutil_1.isAttr(node)) {
// The check for the node type is to avoid getting a location inside a
// placeholder.
if (domtypeguards_1.isText(guiNode.firstChild)) {
guiNode = guiNode.firstChild;
}
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, offset);
}
if (offset === 0) {
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, 0);
}
if (offset >= node.childNodes.length) {
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, guiNode.childNodes.length);
}
const guiChild = this.pathToNode(this.treeUpdater.nodeToPath(node.childNodes[offset]));
if (guiChild === null) {
// This happens if for instance node has X children but the
// corresponding node in tree has X-1 children.
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiNode, guiNode.childNodes.length);
}
return dloc_1.DLoc.mustMakeDLoc(this.tree, guiChild);
}
/**
* Check whether a tooltip should be destroyed when the element is removed
* from the tree. This function checks whether the element or any descendant
* has a tooltip.
*
* @param el An element to check.
*
*/
removeTooltips(el) {
for (const hasTooltip of Array.from(this.haveTooltips)) {
if (!el.contains(hasTooltip)) {
continue;
}
const tt = jquery_1.default.data(hasTooltip, "bs.tooltip");
if (tt != null) {
tt.destroy();
}
// We don't remove the wed-has-tooltip class. Generally, the elements
// that have tooltips and are removed from the GUI tree won't be added
// to the tree again. If they are added again, they'll most likely get
// a new tooltip so removing the class does not gain us much because
// it will be added again.
//
// If we *were* to remove the class, then the collection would change
// as we go through it.
}
}
}
exports.GUIUpdater = GUIUpdater;
});
// LocalWords: domutil jquery pathToNode nodeToPath jQuery deleteNode Dubeau
// LocalWords: insertNodeAt MPL Mangalam gui setTextNodeValue TreeUpdater ev
// LocalWords: BeforeDeleteNode SetAttributeNS
//# sourceMappingURL=gui-updater.js.map