File: /home/workzeni/agency-erp-05.workzenix.com/public/admin/assets/js/plugins/module.js
/**
* Check is item is object
*/
const isObject = (val) => Object.prototype.toString.call(val) === "[object Object]";
/**
* Check for valid JSON string
*/
const isJson = (str) => {
let t = !1;
try {
t = JSON.parse(str);
}
catch (e) {
return !1;
}
return !(null === t || (!Array.isArray(t) && !isObject(t))) && t;
};
/**
* Create DOM element node
*/
const createElement = (nodeName, attrs) => {
const dom = document.createElement(nodeName);
if (attrs && "object" == typeof attrs) {
for (const attr in attrs) {
if ("html" === attr) {
dom.innerHTML = attrs[attr];
}
else {
dom.setAttribute(attr, attrs[attr]);
}
}
}
return dom;
};
const objToText = (obj) => {
if (["#text", "#comment"].includes(obj.nodeName)) {
return obj.data;
}
if (obj.childNodes) {
return obj.childNodes.map((childNode) => objToText(childNode)).join("");
}
return "";
};
const escapeText = function (text) {
return text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
};
const visibleToColumnIndex = function (visibleIndex, columns) {
let counter = 0;
let columnIndex = 0;
while (counter < (visibleIndex + 1)) {
const columnSettings = columns[columnIndex];
if (!columnSettings.hidden) {
counter += 1;
}
columnIndex += 1;
}
return columnIndex - 1;
};
const columnToVisibleIndex = function (columnIndex, columns) {
let visibleIndex = columnIndex;
let counter = 0;
while (counter < columnIndex) {
const columnSettings = columns[counter];
if (columnSettings.hidden) {
visibleIndex -= 1;
}
counter++;
}
return visibleIndex;
};
function objToNode(objNode, insideSvg, options) {
var node;
if (objNode.nodeName === "#text") {
node = options.document.createTextNode(objNode.data);
}
else if (objNode.nodeName === "#comment") {
node = options.document.createComment(objNode.data);
}
else {
if (insideSvg) {
node = options.document.createElementNS("http://www.w3.org/2000/svg", objNode.nodeName);
}
else if (objNode.nodeName.toLowerCase() === "svg") {
node = options.document.createElementNS("http://www.w3.org/2000/svg", "svg");
insideSvg = true;
}
else {
node = options.document.createElement(objNode.nodeName);
}
if (objNode.attributes) {
Object.entries(objNode.attributes).forEach(function (_a) {
var key = _a[0], value = _a[1];
return node.setAttribute(key, value);
});
}
if (objNode.childNodes) {
node = node;
objNode.childNodes.forEach(function (childNode) {
return node.appendChild(objToNode(childNode, insideSvg, options));
});
}
if (options.valueDiffing) {
if (objNode.value &&
(node instanceof HTMLButtonElement ||
node instanceof HTMLDataElement ||
node instanceof HTMLInputElement ||
node instanceof HTMLLIElement ||
node instanceof HTMLMeterElement ||
node instanceof HTMLOptionElement ||
node instanceof HTMLProgressElement ||
node instanceof HTMLParamElement)) {
node.value = objNode.value;
}
if (objNode.checked && node instanceof HTMLInputElement) {
node.checked = objNode.checked;
}
if (objNode.selected && node instanceof HTMLOptionElement) {
node.selected = objNode.selected;
}
}
}
return node;
}
// ===== Apply a diff =====
var getFromRoute = function (node, route) {
route = route.slice();
while (route.length > 0) {
var c = route.splice(0, 1)[0];
node = node.childNodes[c];
}
return node;
};
function applyDiff(tree, diff, options // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const}
) {
var action = diff[options._const.action];
var route = diff[options._const.route];
var node;
if (![options._const.addElement, options._const.addTextElement].includes(action)) {
// For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item.
node = getFromRoute(tree, route);
}
var newNode;
var reference;
var nodeArray;
// pre-diff hook
var info = {
diff: diff,
node: node
};
if (options.preDiffApply(info)) {
return true;
}
switch (action) {
case options._const.addAttribute:
if (!node || !(node instanceof Element)) {
return false;
}
node.setAttribute(diff[options._const.name], diff[options._const.value]);
break;
case options._const.modifyAttribute:
if (!node || !(node instanceof Element)) {
return false;
}
node.setAttribute(diff[options._const.name], diff[options._const.newValue]);
if (node instanceof HTMLInputElement &&
diff[options._const.name] === "value") {
node.value = diff[options._const.newValue];
}
break;
case options._const.removeAttribute:
if (!node || !(node instanceof Element)) {
return false;
}
node.removeAttribute(diff[options._const.name]);
break;
case options._const.modifyTextElement:
if (!node || !(node instanceof Text)) {
return false;
}
options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]);
if (node.parentNode instanceof HTMLTextAreaElement) {
node.parentNode.value = diff[options._const.newValue];
}
break;
case options._const.modifyValue:
if (!node || typeof node.value === "undefined") {
return false;
}
node.value = diff[options._const.newValue];
break;
case options._const.modifyComment:
if (!node || !(node instanceof Comment)) {
return false;
}
options.textDiff(node, node.data, diff[options._const.oldValue], diff[options._const.newValue]);
break;
case options._const.modifyChecked:
if (!node || typeof node.checked === "undefined") {
return false;
}
node.checked = diff[options._const.newValue];
break;
case options._const.modifySelected:
if (!node || typeof node.selected === "undefined") {
return false;
}
node.selected = diff[options._const.newValue];
break;
case options._const.replaceElement: {
var insideSvg = diff[options._const.newValue].nodeName.toLowerCase() === "svg" ||
node.parentNode.namespaceURI === "http://www.w3.org/2000/svg";
node.parentNode.replaceChild(objToNode(diff[options._const.newValue], insideSvg, options), node);
break;
}
case options._const.relocateGroup:
nodeArray = Array.apply(void 0, new Array(diff[options._const.groupLength])).map(function () {
return node.removeChild(node.childNodes[diff[options._const.from]]);
});
nodeArray.forEach(function (childNode, index) {
if (index === 0) {
reference =
node.childNodes[diff[options._const.to]];
}
node.insertBefore(childNode, reference || null);
});
break;
case options._const.removeElement:
node.parentNode.removeChild(node);
break;
case options._const.addElement: {
var parentRoute = route.slice();
var c = parentRoute.splice(parentRoute.length - 1, 1)[0];
node = getFromRoute(tree, parentRoute);
if (!(node instanceof Element)) {
return false;
}
node.insertBefore(objToNode(diff[options._const.element], node.namespaceURI === "http://www.w3.org/2000/svg", options), node.childNodes[c] || null);
break;
}
case options._const.removeTextElement: {
if (!node || node.nodeType !== 3) {
return false;
}
var parentNode = node.parentNode;
parentNode.removeChild(node);
if (parentNode instanceof HTMLTextAreaElement) {
parentNode.value = "";
}
break;
}
case options._const.addTextElement: {
var parentRoute = route.slice();
var c = parentRoute.splice(parentRoute.length - 1, 1)[0];
newNode = options.document.createTextNode(diff[options._const.value]);
node = getFromRoute(tree, parentRoute);
if (!node.childNodes) {
return false;
}
node.insertBefore(newNode, node.childNodes[c] || null);
if (node.parentNode instanceof HTMLTextAreaElement) {
node.parentNode.value = diff[options._const.value];
}
break;
}
default:
console.log("unknown action");
}
// if a new node was created, we might be interested in its
// post diff hook
options.postDiffApply({
diff: info.diff,
node: info.node,
newNode: newNode
});
return true;
}
function applyDOM(tree, diffs, options) {
return diffs.every(function (diff) {
return applyDiff(tree, diff, options);
});
}
// ===== Undo a diff =====
function swap(obj, p1, p2) {
var tmp = obj[p1];
obj[p1] = obj[p2];
obj[p2] = tmp;
}
function undoDiff(tree, diff, options // {preDiffApply, postDiffApply, textDiff, valueDiffing, _const}
) {
switch (diff[options._const.action]) {
case options._const.addAttribute:
diff[options._const.action] = options._const.removeAttribute;
applyDiff(tree, diff, options);
break;
case options._const.modifyAttribute:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.removeAttribute:
diff[options._const.action] = options._const.addAttribute;
applyDiff(tree, diff, options);
break;
case options._const.modifyTextElement:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyValue:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyComment:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifyChecked:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.modifySelected:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.replaceElement:
swap(diff, options._const.oldValue, options._const.newValue);
applyDiff(tree, diff, options);
break;
case options._const.relocateGroup:
swap(diff, options._const.from, options._const.to);
applyDiff(tree, diff, options);
break;
case options._const.removeElement:
diff[options._const.action] = options._const.addElement;
applyDiff(tree, diff, options);
break;
case options._const.addElement:
diff[options._const.action] = options._const.removeElement;
applyDiff(tree, diff, options);
break;
case options._const.removeTextElement:
diff[options._const.action] = options._const.addTextElement;
applyDiff(tree, diff, options);
break;
case options._const.addTextElement:
diff[options._const.action] = options._const.removeTextElement;
applyDiff(tree, diff, options);
break;
default:
console.log("unknown action");
}
}
function undoDOM(tree, diffs, options) {
diffs = diffs.slice();
diffs.reverse();
diffs.forEach(function (diff) {
undoDiff(tree, diff, options);
});
}
var elementDescriptors = function (el) {
var output = [];
output.push(el.nodeName);
if (el.nodeName !== "#text" && el.nodeName !== "#comment") {
el = el;
if (el.attributes) {
if (el.attributes["class"]) {
output.push("".concat(el.nodeName, ".").concat(el.attributes["class"].replace(/ /g, ".")));
}
if (el.attributes.id) {
output.push("".concat(el.nodeName, "#").concat(el.attributes.id));
}
}
}
return output;
};
var findUniqueDescriptors = function (li) {
var uniqueDescriptors = {};
var duplicateDescriptors = {};
li.forEach(function (node) {
elementDescriptors(node).forEach(function (descriptor) {
var inUnique = descriptor in uniqueDescriptors;
var inDupes = descriptor in duplicateDescriptors;
if (!inUnique && !inDupes) {
uniqueDescriptors[descriptor] = true;
}
else if (inUnique) {
delete uniqueDescriptors[descriptor];
duplicateDescriptors[descriptor] = true;
}
});
});
return uniqueDescriptors;
};
var uniqueInBoth = function (l1, l2) {
var l1Unique = findUniqueDescriptors(l1);
var l2Unique = findUniqueDescriptors(l2);
var inBoth = {};
Object.keys(l1Unique).forEach(function (key) {
if (l2Unique[key]) {
inBoth[key] = true;
}
});
return inBoth;
};
var removeDone = function (tree) {
delete tree.outerDone;
delete tree.innerDone;
delete tree.valueDone;
if (tree.childNodes) {
return tree.childNodes.every(removeDone);
}
else {
return true;
}
};
var cleanNode = function (diffNode) {
if (Object.prototype.hasOwnProperty.call(diffNode, "data")) {
var textNode = {
nodeName: diffNode.nodeName === "#text" ? "#text" : "#comment",
data: diffNode.data
};
return textNode;
}
else {
var elementNode = {
nodeName: diffNode.nodeName
};
diffNode = diffNode;
if (Object.prototype.hasOwnProperty.call(diffNode, "attributes")) {
elementNode.attributes = diffNode.attributes;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "checked")) {
elementNode.checked = diffNode.checked;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "value")) {
elementNode.value = diffNode.value;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "selected")) {
elementNode.selected = diffNode.selected;
}
if (Object.prototype.hasOwnProperty.call(diffNode, "childNodes")) {
elementNode.childNodes = diffNode.childNodes.map(function (diffChildNode) {
return cleanNode(diffChildNode);
});
}
return elementNode;
}
};
var isEqual = function (e1, e2) {
if (!["nodeName", "value", "checked", "selected", "data"].every(function (element) {
if (e1[element] !== e2[element]) {
return false;
}
return true;
})) {
return false;
}
if (Object.prototype.hasOwnProperty.call(e1, "data")) {
// Comment or Text
return true;
}
e1 = e1;
e2 = e2;
if (Boolean(e1.attributes) !== Boolean(e2.attributes)) {
return false;
}
if (Boolean(e1.childNodes) !== Boolean(e2.childNodes)) {
return false;
}
if (e1.attributes) {
var e1Attributes = Object.keys(e1.attributes);
var e2Attributes = Object.keys(e2.attributes);
if (e1Attributes.length !== e2Attributes.length) {
return false;
}
if (!e1Attributes.every(function (attribute) {
if (e1.attributes[attribute] !==
e2.attributes[attribute]) {
return false;
}
return true;
})) {
return false;
}
}
if (e1.childNodes) {
if (e1.childNodes.length !== e2.childNodes.length) {
return false;
}
if (!e1.childNodes.every(function (childNode, index) {
return isEqual(childNode, e2.childNodes[index]);
})) {
return false;
}
}
return true;
};
var roughlyEqual = function (e1, e2, uniqueDescriptors, sameSiblings, preventRecursion) {
if (preventRecursion === void 0) { preventRecursion = false; }
if (!e1 || !e2) {
return false;
}
if (e1.nodeName !== e2.nodeName) {
return false;
}
if (["#text", "#comment"].includes(e1.nodeName)) {
// Note that we initially don't care what the text content of a node is,
// the mere fact that it's the same tag and "has text" means it's roughly
// equal, and then we can find out the true text difference later.
return preventRecursion
? true
: e1.data === e2.data;
}
e1 = e1;
e2 = e2;
if (e1.nodeName in uniqueDescriptors) {
return true;
}
if (e1.attributes && e2.attributes) {
if (e1.attributes.id) {
if (e1.attributes.id !== e2.attributes.id) {
return false;
}
else {
var idDescriptor = "".concat(e1.nodeName, "#").concat(e1.attributes.id);
if (idDescriptor in uniqueDescriptors) {
return true;
}
}
}
if (e1.attributes["class"] &&
e1.attributes["class"] === e2.attributes["class"]) {
var classDescriptor = "".concat(e1.nodeName, ".").concat(e1.attributes["class"].replace(/ /g, "."));
if (classDescriptor in uniqueDescriptors) {
return true;
}
}
}
if (sameSiblings) {
return true;
}
var nodeList1 = e1.childNodes ? e1.childNodes.slice().reverse() : [];
var nodeList2 = e2.childNodes ? e2.childNodes.slice().reverse() : [];
if (nodeList1.length !== nodeList2.length) {
return false;
}
if (preventRecursion) {
return nodeList1.every(function (element, index) {
return element.nodeName === nodeList2[index].nodeName;
});
}
else {
// note: we only allow one level of recursion at any depth. If 'preventRecursion'
// was not set, we must explicitly force it to true for child iterations.
var childUniqueDescriptors_1 = uniqueInBoth(nodeList1, nodeList2);
return nodeList1.every(function (element, index) {
return roughlyEqual(element, nodeList2[index], childUniqueDescriptors_1, true, true);
});
}
};
/**
* based on https://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Longest_common_substring#JavaScript
*/
var findCommonSubsets = function (c1, c2, marked1, marked2) {
var lcsSize = 0;
var index = [];
var c1Length = c1.length;
var c2Length = c2.length;
var // set up the matching table
matches = Array.apply(void 0, new Array(c1Length + 1)).map(function () { return []; });
var uniqueDescriptors = uniqueInBoth(c1, c2);
var // If all of the elements are the same tag, id and class, then we can
// consider them roughly the same even if they have a different number of
// children. This will reduce removing and re-adding similar elements.
subsetsSame = c1Length === c2Length;
if (subsetsSame) {
c1.some(function (element, i) {
var c1Desc = elementDescriptors(element);
var c2Desc = elementDescriptors(c2[i]);
if (c1Desc.length !== c2Desc.length) {
subsetsSame = false;
return true;
}
c1Desc.some(function (description, i) {
if (description !== c2Desc[i]) {
subsetsSame = false;
return true;
}
});
if (!subsetsSame) {
return true;
}
});
}
// fill the matches with distance values
for (var c1Index = 0; c1Index < c1Length; c1Index++) {
var c1Element = c1[c1Index];
for (var c2Index = 0; c2Index < c2Length; c2Index++) {
var c2Element = c2[c2Index];
if (!marked1[c1Index] &&
!marked2[c2Index] &&
roughlyEqual(c1Element, c2Element, uniqueDescriptors, subsetsSame)) {
matches[c1Index + 1][c2Index + 1] = matches[c1Index][c2Index]
? matches[c1Index][c2Index] + 1
: 1;
if (matches[c1Index + 1][c2Index + 1] >= lcsSize) {
lcsSize = matches[c1Index + 1][c2Index + 1];
index = [c1Index + 1, c2Index + 1];
}
}
else {
matches[c1Index + 1][c2Index + 1] = 0;
}
}
}
if (lcsSize === 0) {
return false;
}
return {
oldValue: index[0] - lcsSize,
newValue: index[1] - lcsSize,
length: lcsSize
};
};
var makeBooleanArray = function (n, v) {
return Array.apply(void 0, new Array(n)).map(function () { return v; });
};
/**
* Generate arrays that indicate which node belongs to which subset,
* or whether it's actually an orphan node, existing in only one
* of the two trees, rather than somewhere in both.
*
* So if t1 = <img><canvas><br>, t2 = <canvas><br><img>.
* The longest subset is "<canvas><br>" (length 2), so it will group 0.
* The second longest is "<img>" (length 1), so it will be group 1.
* gaps1 will therefore be [1,0,0] and gaps2 [0,0,1].
*
* If an element is not part of any group, it will stay being 'true', which
* is the initial value. For example:
* t1 = <img><p></p><br><canvas>, t2 = <b></b><br><canvas><img>
*
* The "<p></p>" and "<b></b>" do only show up in one of the two and will
* therefore be marked by "true". The remaining parts are parts of the
* groups 0 and 1:
* gaps1 = [1, true, 0, 0], gaps2 = [true, 0, 0, 1]
*
*/
var getGapInformation = function (t1, t2, stable) {
var gaps1 = t1.childNodes
? makeBooleanArray(t1.childNodes.length, true)
: [];
var gaps2 = t2.childNodes
? makeBooleanArray(t2.childNodes.length, true)
: [];
var group = 0;
// give elements from the same subset the same group number
stable.forEach(function (subset) {
var endOld = subset.oldValue + subset.length;
var endNew = subset.newValue + subset.length;
for (var j = subset.oldValue; j < endOld; j += 1) {
gaps1[j] = group;
}
for (var j = subset.newValue; j < endNew; j += 1) {
gaps2[j] = group;
}
group += 1;
});
return {
gaps1: gaps1,
gaps2: gaps2
};
};
/**
* Find all matching subsets, based on immediate child differences only.
*/
var markBoth = function (marked1, marked2, subset, i) {
marked1[subset.oldValue + i] = true;
marked2[subset.newValue + i] = true;
};
var markSubTrees = function (oldTree, newTree) {
// note: the child lists are views, and so update as we update old/newTree
var oldChildren = oldTree.childNodes ? oldTree.childNodes : [];
var newChildren = newTree.childNodes ? newTree.childNodes : [];
var marked1 = makeBooleanArray(oldChildren.length, false);
var marked2 = makeBooleanArray(newChildren.length, false);
var subsets = [];
var returnIndex = function () {
return arguments[1];
};
var foundAllSubsets = false;
var _loop_1 = function () {
var subset = findCommonSubsets(oldChildren, newChildren, marked1, marked2);
if (subset) {
subsets.push(subset);
var subsetArray = Array.apply(void 0, new Array(subset.length)).map(returnIndex);
subsetArray.forEach(function (item) {
return markBoth(marked1, marked2, subset, item);
});
}
else {
foundAllSubsets = true;
}
};
while (!foundAllSubsets) {
_loop_1();
}
oldTree.subsets = subsets;
oldTree.subsetsAge = 100;
return subsets;
};
var DiffTracker = /** @class */ (function () {
function DiffTracker() {
this.list = [];
}
DiffTracker.prototype.add = function (diffs) {
var _a;
(_a = this.list).push.apply(_a, diffs);
};
DiffTracker.prototype.forEach = function (fn) {
this.list.forEach(function (li) { return fn(li); });
};
return DiffTracker;
}());
//export const elementHasValue = (element: Element) : boolean => element instanceof HTMLButtonElement || element instanceof HTMLDataElement || element instanceof HTMLInputElement || element instanceof HTMLLIElement || element instanceof HTMLMeterElement || element instanceof HTMLOptionElement || element instanceof HTMLProgressElement || element instanceof HTMLParamElement
var Diff = /** @class */ (function () {
function Diff(options) {
if (options === void 0) { options = {}; }
var _this = this;
Object.entries(options).forEach(function (_a) {
var key = _a[0], value = _a[1];
return (_this[key] = value);
});
}
Diff.prototype.toString = function () {
return JSON.stringify(this);
};
Diff.prototype.setValue = function (aKey, aValue) {
this[aKey] = aValue;
return this;
};
return Diff;
}());
// ===== Apply a virtual diff =====
function getFromVirtualRoute(tree, route) {
var node = tree;
var parentNode;
var nodeIndex;
route = route.slice();
while (route.length > 0) {
nodeIndex = route.splice(0, 1)[0];
parentNode = node;
node = node.childNodes ? node.childNodes[nodeIndex] : undefined;
}
return {
node: node,
parentNode: parentNode,
nodeIndex: nodeIndex
};
}
function applyVirtualDiff(tree, diff, options // {preVirtualDiffApply, postVirtualDiffApply, _const}
) {
var _a;
var node, parentNode, nodeIndex;
if (![options._const.addElement, options._const.addTextElement].includes(diff[options._const.action])) {
// For adding nodes, we calculate the route later on. It's different because it includes the position of the newly added item.
var routeInfo = getFromVirtualRoute(tree, diff[options._const.route]);
node = routeInfo.node;
parentNode = routeInfo.parentNode;
nodeIndex = routeInfo.nodeIndex;
}
var newSubsets = [];
// pre-diff hook
var info = {
diff: diff,
node: node
};
if (options.preVirtualDiffApply(info)) {
return true;
}
var newNode;
var nodeArray;
var route;
switch (diff[options._const.action]) {
case options._const.addAttribute:
if (!node.attributes) {
node.attributes = {};
}
node.attributes[diff[options._const.name]] =
diff[options._const.value];
if (diff[options._const.name] === "checked") {
node.checked = true;
}
else if (diff[options._const.name] === "selected") {
node.selected = true;
}
else if (node.nodeName === "INPUT" &&
diff[options._const.name] === "value") {
node.value = diff[options._const.value];
}
break;
case options._const.modifyAttribute:
node.attributes[diff[options._const.name]] =
diff[options._const.newValue];
break;
case options._const.removeAttribute:
delete node.attributes[diff[options._const.name]];
if (Object.keys(node.attributes).length === 0) {
delete node.attributes;
}
if (diff[options._const.name] === "checked") {
node.checked = false;
}
else if (diff[options._const.name] === "selected") {
delete node.selected;
}
else if (node.nodeName === "INPUT" &&
diff[options._const.name] === "value") {
delete node.value;
}
break;
case options._const.modifyTextElement:
node.data = diff[options._const.newValue];
if (parentNode.nodeName === "TEXTAREA") {
parentNode.value = diff[options._const.newValue];
}
break;
case options._const.modifyValue:
node.value = diff[options._const.newValue];
break;
case options._const.modifyComment:
node.data = diff[options._const.newValue];
break;
case options._const.modifyChecked:
node.checked = diff[options._const.newValue];
break;
case options._const.modifySelected:
node.selected = diff[options._const.newValue];
break;
case options._const.replaceElement:
newNode = diff[options._const.newValue];
parentNode.childNodes[nodeIndex] = newNode;
break;
case options._const.relocateGroup:
nodeArray = node.childNodes
.splice(diff[options._const.from], diff[options._const.groupLength])
.reverse();
nodeArray.forEach(function (movedNode) {
return node.childNodes.splice(diff[options._const.to], 0, movedNode);
});
if (node.subsets) {
node.subsets.forEach(function (map) {
if (diff[options._const.from] < diff[options._const.to] &&
map.oldValue <= diff[options._const.to] &&
map.oldValue > diff[options._const.from]) {
map.oldValue -= diff[options._const.groupLength];
var splitLength = map.oldValue + map.length - diff[options._const.to];
if (splitLength > 0) {
// new insertion splits map.
newSubsets.push({
oldValue: diff[options._const.to] +
diff[options._const.groupLength],
newValue: map.newValue + map.length - splitLength,
length: splitLength
});
map.length -= splitLength;
}
}
else if (diff[options._const.from] > diff[options._const.to] &&
map.oldValue > diff[options._const.to] &&
map.oldValue < diff[options._const.from]) {
map.oldValue += diff[options._const.groupLength];
var splitLength = map.oldValue + map.length - diff[options._const.to];
if (splitLength > 0) {
// new insertion splits map.
newSubsets.push({
oldValue: diff[options._const.to] +
diff[options._const.groupLength],
newValue: map.newValue + map.length - splitLength,
length: splitLength
});
map.length -= splitLength;
}
}
else if (map.oldValue === diff[options._const.from]) {
map.oldValue = diff[options._const.to];
}
});
}
break;
case options._const.removeElement:
parentNode.childNodes.splice(nodeIndex, 1);
if (parentNode.subsets) {
parentNode.subsets.forEach(function (map) {
if (map.oldValue > nodeIndex) {
map.oldValue -= 1;
}
else if (map.oldValue === nodeIndex) {
map["delete"] = true;
}
else if (map.oldValue < nodeIndex &&
map.oldValue + map.length > nodeIndex) {
if (map.oldValue + map.length - 1 === nodeIndex) {
map.length--;
}
else {
newSubsets.push({
newValue: map.newValue + nodeIndex - map.oldValue,
oldValue: nodeIndex,
length: map.length - nodeIndex + map.oldValue - 1
});
map.length = nodeIndex - map.oldValue;
}
}
});
}
node = parentNode;
break;
case options._const.addElement: {
route = diff[options._const.route].slice();
var c_1 = route.splice(route.length - 1, 1)[0];
node = (_a = getFromVirtualRoute(tree, route)) === null || _a === void 0 ? void 0 : _a.node;
newNode = diff[options._const.element];
if (!node.childNodes) {
node.childNodes = [];
}
if (c_1 >= node.childNodes.length) {
node.childNodes.push(newNode);
}
else {
node.childNodes.splice(c_1, 0, newNode);
}
if (node.subsets) {
node.subsets.forEach(function (map) {
if (map.oldValue >= c_1) {
map.oldValue += 1;
}
else if (map.oldValue < c_1 &&
map.oldValue + map.length > c_1) {
var splitLength = map.oldValue + map.length - c_1;
newSubsets.push({
newValue: map.newValue + map.length - splitLength,
oldValue: c_1 + 1,
length: splitLength
});
map.length -= splitLength;
}
});
}
break;
}
case options._const.removeTextElement:
parentNode.childNodes.splice(nodeIndex, 1);
if (parentNode.nodeName === "TEXTAREA") {
delete parentNode.value;
}
if (parentNode.subsets) {
parentNode.subsets.forEach(function (map) {
if (map.oldValue > nodeIndex) {
map.oldValue -= 1;
}
else if (map.oldValue === nodeIndex) {
map["delete"] = true;
}
else if (map.oldValue < nodeIndex &&
map.oldValue + map.length > nodeIndex) {
if (map.oldValue + map.length - 1 === nodeIndex) {
map.length--;
}
else {
newSubsets.push({
newValue: map.newValue + nodeIndex - map.oldValue,
oldValue: nodeIndex,
length: map.length - nodeIndex + map.oldValue - 1
});
map.length = nodeIndex - map.oldValue;
}
}
});
}
node = parentNode;
break;
case options._const.addTextElement: {
route = diff[options._const.route].slice();
var c_2 = route.splice(route.length - 1, 1)[0];
newNode = {};
newNode.nodeName = "#text";
newNode.data = diff[options._const.value];
node = getFromVirtualRoute(tree, route).node;
if (!node.childNodes) {
node.childNodes = [];
}
if (c_2 >= node.childNodes.length) {
node.childNodes.push(newNode);
}
else {
node.childNodes.splice(c_2, 0, newNode);
}
if (node.nodeName === "TEXTAREA") {
node.value = diff[options._const.newValue];
}
if (node.subsets) {
node.subsets.forEach(function (map) {
if (map.oldValue >= c_2) {
map.oldValue += 1;
}
if (map.oldValue < c_2 && map.oldValue + map.length > c_2) {
var splitLength = map.oldValue + map.length - c_2;
newSubsets.push({
newValue: map.newValue + map.length - splitLength,
oldValue: c_2 + 1,
length: splitLength
});
map.length -= splitLength;
}
});
}
break;
}
default:
console.log("unknown action");
}
if (node.subsets) {
node.subsets = node.subsets.filter(function (map) { return !map["delete"] && map.oldValue !== map.newValue; });
if (newSubsets.length) {
node.subsets = node.subsets.concat(newSubsets);
}
}
options.postVirtualDiffApply({
node: info.node,
diff: info.diff,
newNode: newNode
});
return;
}
function applyVirtual(tree, diffs, options) {
diffs.forEach(function (diff) {
applyVirtualDiff(tree, diff, options);
});
return true;
}
function nodeToObj(aNode, options) {
if (options === void 0) { options = { valueDiffing: true }; }
var objNode = {
nodeName: aNode.nodeName
};
if (aNode instanceof Text || aNode instanceof Comment) {
objNode.data = aNode.data;
}
else {
if (aNode.attributes && aNode.attributes.length > 0) {
objNode.attributes = {};
var nodeArray = Array.prototype.slice.call(aNode.attributes);
nodeArray.forEach(function (attribute) {
return (objNode.attributes[attribute.name] = attribute.value);
});
}
if (aNode.childNodes && aNode.childNodes.length > 0) {
objNode.childNodes = [];
var nodeArray = Array.prototype.slice.call(aNode.childNodes);
nodeArray.forEach(function (childNode) {
return objNode.childNodes.push(nodeToObj(childNode, options));
});
}
if (options.valueDiffing) {
if (aNode instanceof HTMLTextAreaElement) {
objNode.value = aNode.value;
}
if (aNode instanceof HTMLInputElement &&
["radio", "checkbox"].includes(aNode.type.toLowerCase()) &&
aNode.checked !== undefined) {
objNode.checked = aNode.checked;
}
else if (aNode instanceof HTMLButtonElement ||
aNode instanceof HTMLDataElement ||
aNode instanceof HTMLInputElement ||
aNode instanceof HTMLLIElement ||
aNode instanceof HTMLMeterElement ||
aNode instanceof HTMLOptionElement ||
aNode instanceof HTMLProgressElement ||
aNode instanceof HTMLParamElement) {
objNode.value = aNode.value;
}
if (aNode instanceof HTMLOptionElement) {
objNode.selected = aNode.selected;
}
}
}
return objNode;
}
// from html-parse-stringify (MIT)
var tagRE = /<\s*\/*[a-zA-Z:_][a-zA-Z0-9:_\-.]*\s*(?:"[^"]*"['"]*|'[^']*'['"]*|[^'"/>])*\/*\s*>|<!--(?:.|\n|\r)*?-->/g;
var attrRE = /\s([^'"/\s><]+?)[\s/>]|([^\s=]+)=\s?(".*?"|'.*?')/g;
function unescape(string) {
return string
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&");
}
// create optimized lookup object for
// void elements as listed here:
// https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements
var lookup = {
area: true,
base: true,
br: true,
col: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
menuItem: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true
};
var parseTag = function (tag) {
var res = {
nodeName: "",
attributes: {}
};
var voidElement = false;
var type = "tag";
var tagMatch = tag.match(/<\/?([^\s]+?)[/\s>]/);
if (tagMatch) {
res.nodeName = tagMatch[1];
if (lookup[tagMatch[1]] || tag.charAt(tag.length - 2) === "/") {
voidElement = true;
}
// handle comment tag
if (res.nodeName.startsWith("!--")) {
var endIndex = tag.indexOf("-->");
return {
type: "comment",
node: {
nodeName: "#comment",
data: endIndex !== -1 ? tag.slice(4, endIndex) : ""
},
voidElement: voidElement
};
}
}
var reg = new RegExp(attrRE);
var result = null;
var done = false;
while (!done) {
result = reg.exec(tag);
if (result === null) {
done = true;
}
else if (result[0].trim()) {
if (result[1]) {
var attr = result[1].trim();
var arr = [attr, ""];
if (attr.indexOf("=") > -1)
{ arr = attr.split("="); }
res.attributes[arr[0]] = arr[1];
reg.lastIndex--;
}
else if (result[2])
{ res.attributes[result[2]] = result[3]
.trim()
.substring(1, result[3].length - 1); }
}
}
return {
type: type,
node: res,
voidElement: voidElement
};
};
var stringToObj = function (html, options) {
if (options === void 0) { options = { valueDiffing: true }; }
var result = [];
var current;
var level = -1;
var arr = [];
var inComponent = false;
// handle text at top level
if (html.indexOf("<") !== 0) {
var end = html.indexOf("<");
result.push({
nodeName: "#text",
data: end === -1 ? html : html.substring(0, end)
});
}
html.replace(tagRE, function (tag, index) {
var isOpen = tag.charAt(1) !== "/";
var isComment = tag.startsWith("<!--");
var start = index + tag.length;
var nextChar = html.charAt(start);
if (isComment) {
var comment = parseTag(tag).node;
// if we're at root, push new base node
if (level < 0) {
result.push(comment);
return "";
}
var parent_1 = arr[level];
if (parent_1 && comment.nodeName) {
if (!parent_1.node.childNodes) {
parent_1.node.childNodes = [];
}
parent_1.node.childNodes.push(comment);
}
return "";
}
if (isOpen) {
current = parseTag(tag);
level++;
if (!current.voidElement &&
!inComponent &&
nextChar &&
nextChar !== "<") {
if (!current.node.childNodes) {
current.node.childNodes = [];
}
var data = unescape(html.slice(start, html.indexOf("<", start)));
current.node.childNodes.push({
nodeName: "#text",
data: data
});
if (options.valueDiffing &&
current.node.nodeName === "TEXTAREA") {
current.node.value = data;
}
}
// if we're at root, push new base node
if (level === 0 && current.node.nodeName) {
result.push(current.node);
}
var parent_2 = arr[level - 1];
if (parent_2 && current.node.nodeName) {
if (!parent_2.node.childNodes) {
parent_2.node.childNodes = [];
}
parent_2.node.childNodes.push(current.node);
}
arr[level] = current;
}
if (!isOpen || current.voidElement) {
if (level > -1 &&
(current.voidElement ||
current.node.nodeName.toUpperCase() ===
tag.slice(2, -1).toUpperCase())) {
level--;
// move current up a level to match the end tag
if (level > -1) {
current = arr[level];
}
}
if (nextChar !== "<" && nextChar) {
// trailing text node
// if we're at the root, push a base text node. otherwise add as
// a child to the current node.
var childNodes = level === -1 ? result : arr[level].node.childNodes || [];
// calculate correct end of the data slice in case there's
// no tag after the text node.
var end = html.indexOf("<", start);
var data = unescape(html.slice(start, end === -1 ? undefined : end));
childNodes.push({
nodeName: "#text",
data: data
});
}
}
return "";
});
return result[0];
};
// ===== Create a diff =====
var DiffFinder = /** @class */ (function () {
function DiffFinder(t1Node, t2Node, options) {
this.options = options;
this.t1 = (typeof Element !== "undefined" && t1Node instanceof Element
? nodeToObj(t1Node, this.options)
: typeof t1Node === "string"
? stringToObj(t1Node, this.options)
: JSON.parse(JSON.stringify(t1Node)));
this.t2 = (typeof Element !== "undefined" && t2Node instanceof Element
? nodeToObj(t2Node, this.options)
: typeof t2Node === "string"
? stringToObj(t2Node, this.options)
: JSON.parse(JSON.stringify(t2Node)));
this.diffcount = 0;
this.foundAll = false;
if (this.debug) {
this.t1Orig =
typeof Element !== "undefined" && t1Node instanceof Element
? nodeToObj(t1Node, this.options)
: typeof t1Node === "string"
? stringToObj(t1Node, this.options)
: JSON.parse(JSON.stringify(t1Node));
this.t2Orig =
typeof Element !== "undefined" && t2Node instanceof Element
? nodeToObj(t2Node, this.options)
: typeof t2Node === "string"
? stringToObj(t2Node, this.options)
: JSON.parse(JSON.stringify(t2Node));
}
this.tracker = new DiffTracker();
}
DiffFinder.prototype.init = function () {
return this.findDiffs(this.t1, this.t2);
};
DiffFinder.prototype.findDiffs = function (t1, t2) {
var diffs;
do {
if (this.options.debug) {
this.diffcount += 1;
if (this.diffcount > this.options.diffcap) {
throw new Error("surpassed diffcap:".concat(JSON.stringify(this.t1Orig), " -> ").concat(JSON.stringify(this.t2Orig)));
}
}
diffs = this.findNextDiff(t1, t2, []);
if (diffs.length === 0) {
// Last check if the elements really are the same now.
// If not, remove all info about being done and start over.
// Sometimes a node can be marked as done, but the creation of subsequent diffs means that it has to be changed again.
if (!isEqual(t1, t2)) {
if (this.foundAll) {
console.error("Could not find remaining diffs!");
}
else {
this.foundAll = true;
removeDone(t1);
diffs = this.findNextDiff(t1, t2, []);
}
}
}
if (diffs.length > 0) {
this.foundAll = false;
this.tracker.add(diffs);
applyVirtual(t1, diffs, this.options);
}
} while (diffs.length > 0);
return this.tracker.list;
};
DiffFinder.prototype.findNextDiff = function (t1, t2, route) {
var diffs;
var fdiffs;
if (this.options.maxDepth && route.length > this.options.maxDepth) {
return [];
}
// outer differences?
if (!t1.outerDone) {
diffs = this.findOuterDiff(t1, t2, route);
if (this.options.filterOuterDiff) {
fdiffs = this.options.filterOuterDiff(t1, t2, diffs);
if (fdiffs)
{ diffs = fdiffs; }
}
if (diffs.length > 0) {
t1.outerDone = true;
return diffs;
}
else {
t1.outerDone = true;
}
}
if (Object.prototype.hasOwnProperty.call(t1, "data")) {
// Comment or Text
return [];
}
t1 = t1;
t2 = t2;
// inner differences?
if (!t1.innerDone) {
diffs = this.findInnerDiff(t1, t2, route);
if (diffs.length > 0) {
return diffs;
}
else {
t1.innerDone = true;
}
}
if (this.options.valueDiffing && !t1.valueDone) {
// value differences?
diffs = this.findValueDiff(t1, t2, route);
if (diffs.length > 0) {
t1.valueDone = true;
return diffs;
}
else {
t1.valueDone = true;
}
}
// no differences
return [];
};
DiffFinder.prototype.findOuterDiff = function (t1, t2, route) {
var diffs = [];
var attr;
var attr1;
var attr2;
var attrLength;
var pos;
var i;
if (t1.nodeName !== t2.nodeName) {
if (!route.length) {
throw new Error("Top level nodes have to be of the same kind.");
}
return [
new Diff()
.setValue(this.options._const.action, this.options._const.replaceElement)
.setValue(this.options._const.oldValue, cleanNode(t1))
.setValue(this.options._const.newValue, cleanNode(t2))
.setValue(this.options._const.route, route) ];
}
if (route.length &&
this.options.diffcap <
Math.abs((t1.childNodes || []).length - (t2.childNodes || []).length)) {
return [
new Diff()
.setValue(this.options._const.action, this.options._const.replaceElement)
.setValue(this.options._const.oldValue, cleanNode(t1))
.setValue(this.options._const.newValue, cleanNode(t2))
.setValue(this.options._const.route, route) ];
}
if (Object.prototype.hasOwnProperty.call(t1, "data") &&
t1.data !== t2.data) {
// Comment or text node.
if (t1.nodeName === "#text") {
return [
new Diff()
.setValue(this.options._const.action, this.options._const.modifyTextElement)
.setValue(this.options._const.route, route)
.setValue(this.options._const.oldValue, t1.data)
.setValue(this.options._const.newValue, t2.data) ];
}
else {
return [
new Diff()
.setValue(this.options._const.action, this.options._const.modifyComment)
.setValue(this.options._const.route, route)
.setValue(this.options._const.oldValue, t1.data)
.setValue(this.options._const.newValue, t2.data) ];
}
}
t1 = t1;
t2 = t2;
attr1 = t1.attributes ? Object.keys(t1.attributes).sort() : [];
attr2 = t2.attributes ? Object.keys(t2.attributes).sort() : [];
attrLength = attr1.length;
for (i = 0; i < attrLength; i++) {
attr = attr1[i];
pos = attr2.indexOf(attr);
if (pos === -1) {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeAttribute)
.setValue(this.options._const.route, route)
.setValue(this.options._const.name, attr)
.setValue(this.options._const.value, t1.attributes[attr]));
}
else {
attr2.splice(pos, 1);
if (t1.attributes[attr] !== t2.attributes[attr]) {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.modifyAttribute)
.setValue(this.options._const.route, route)
.setValue(this.options._const.name, attr)
.setValue(this.options._const.oldValue, t1.attributes[attr])
.setValue(this.options._const.newValue, t2.attributes[attr]));
}
}
}
attrLength = attr2.length;
for (i = 0; i < attrLength; i++) {
attr = attr2[i];
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.addAttribute)
.setValue(this.options._const.route, route)
.setValue(this.options._const.name, attr)
.setValue(this.options._const.value, t2.attributes[attr]));
}
return diffs;
};
DiffFinder.prototype.findInnerDiff = function (t1, t2, route) {
var t1ChildNodes = t1.childNodes ? t1.childNodes.slice() : [];
var t2ChildNodes = t2.childNodes ? t2.childNodes.slice() : [];
var last = Math.max(t1ChildNodes.length, t2ChildNodes.length);
var childNodesLengthDifference = Math.abs(t1ChildNodes.length - t2ChildNodes.length);
var diffs = [];
var index = 0;
if (!this.options.maxChildCount || last < this.options.maxChildCount) {
var cachedSubtrees = Boolean(t1.subsets && t1.subsetsAge--);
var subtrees = cachedSubtrees
? t1.subsets
: t1.childNodes && t2.childNodes
? markSubTrees(t1, t2)
: [];
if (subtrees.length > 0) {
/* One or more groups have been identified among the childnodes of t1
* and t2.
*/
diffs = this.attemptGroupRelocation(t1, t2, subtrees, route, cachedSubtrees);
if (diffs.length > 0) {
return diffs;
}
}
}
/* 0 or 1 groups of similar child nodes have been found
* for t1 and t2. 1 If there is 1, it could be a sign that the
* contents are the same. When the number of groups is below 2,
* t1 and t2 are made to have the same length and each of the
* pairs of child nodes are diffed.
*/
for (var i = 0; i < last; i += 1) {
var e1 = t1ChildNodes[i];
var e2 = t2ChildNodes[i];
if (childNodesLengthDifference) {
/* t1 and t2 have different amounts of childNodes. Add
* and remove as necessary to obtain the same length */
if (e1 && !e2) {
if (e1.nodeName === "#text") {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeTextElement)
.setValue(this.options._const.route, route.concat(index))
.setValue(this.options._const.value, e1.data));
index -= 1;
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeElement)
.setValue(this.options._const.route, route.concat(index))
.setValue(this.options._const.element, cleanNode(e1)));
index -= 1;
}
}
else if (e2 && !e1) {
if (e2.nodeName === "#text") {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.addTextElement)
.setValue(this.options._const.route, route.concat(index))
.setValue(this.options._const.value, e2.data));
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.addElement)
.setValue(this.options._const.route, route.concat(index))
.setValue(this.options._const.element, cleanNode(e2)));
}
}
}
/* We are now guaranteed that childNodes e1 and e2 exist,
* and that they can be diffed.
*/
/* Diffs in child nodes should not affect the parent node,
* so we let these diffs be submitted together with other
* diffs.
*/
if (e1 && e2) {
if (!this.options.maxChildCount ||
last < this.options.maxChildCount) {
diffs = diffs.concat(this.findNextDiff(e1, e2, route.concat(index)));
}
else if (!isEqual(e1, e2)) {
if (t1ChildNodes.length > t2ChildNodes.length) {
if (e1.nodeName === "#text") {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeTextElement)
.setValue(this.options._const.route, route.concat(index))
.setValue(this.options._const.value, e1.data));
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeElement)
.setValue(this.options._const.element, cleanNode(e1))
.setValue(this.options._const.route, route.concat(index)));
}
t1ChildNodes.splice(i, 1);
i -= 1;
index -= 1;
childNodesLengthDifference -= 1;
}
else if (t1ChildNodes.length < t2ChildNodes.length) {
var cloneChild = cleanNode(e2);
diffs = diffs.concat([
new Diff()
.setValue(this.options._const.action, this.options._const.addElement)
.setValue(this.options._const.element, cloneChild)
.setValue(this.options._const.route, route.concat(index)) ]);
t1ChildNodes.splice(i, 0, cloneChild);
childNodesLengthDifference -= 1;
}
else {
diffs = diffs.concat([
new Diff()
.setValue(this.options._const.action, this.options._const.replaceElement)
.setValue(this.options._const.oldValue, cleanNode(e1))
.setValue(this.options._const.newValue, cleanNode(e2))
.setValue(this.options._const.route, route.concat(index)) ]);
}
}
}
index += 1;
}
t1.innerDone = true;
return diffs;
};
DiffFinder.prototype.attemptGroupRelocation = function (t1, t2, subtrees, route, cachedSubtrees) {
/* Either t1.childNodes and t2.childNodes have the same length, or
* there are at least two groups of similar elements can be found.
* attempts are made at equalizing t1 with t2. First all initial
* elements with no group affiliation (gaps=true) are removed (if
* only in t1) or added (if only in t2). Then the creation of a group
* relocation diff is attempted.
*/
var gapInformation = getGapInformation(t1, t2, subtrees);
var gaps1 = gapInformation.gaps1;
var gaps2 = gapInformation.gaps2;
var shortest = Math.min(gaps1.length, gaps2.length);
var destinationDifferent;
var toGroup;
var group;
var node;
var similarNode;
var testI;
var diffs = [];
for (var index2 = 0, index1 = 0; index2 < shortest; index1 += 1, index2 += 1) {
if (cachedSubtrees &&
(gaps1[index2] === true || gaps2[index2] === true)) ;
else if (gaps1[index2] === true) {
node = t1.childNodes[index1];
if (node.nodeName === "#text") {
if (t2.childNodes[index2].nodeName === "#text") {
if (node.data !==
t2.childNodes[index2].data) {
testI = index1;
while (t1.childNodes.length > testI + 1 &&
t1.childNodes[testI + 1].nodeName === "#text") {
testI += 1;
if (t2.childNodes[index2]
.data ===
t1.childNodes[testI]
.data) {
similarNode = true;
break;
}
}
if (!similarNode) {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const
.modifyTextElement)
.setValue(this.options._const.route, route.concat(index2))
.setValue(this.options._const.oldValue, node.data)
.setValue(this.options._const.newValue, t2.childNodes[index2].data));
return diffs;
}
}
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeTextElement)
.setValue(this.options._const.route, route.concat(index2))
.setValue(this.options._const.value, node.data));
gaps1.splice(index2, 1);
shortest = Math.min(gaps1.length, gaps2.length);
index2 -= 1;
}
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.removeElement)
.setValue(this.options._const.route, route.concat(index2))
.setValue(this.options._const.element, cleanNode(node)));
gaps1.splice(index2, 1);
shortest = Math.min(gaps1.length, gaps2.length);
index2 -= 1;
}
}
else if (gaps2[index2] === true) {
node = t2.childNodes[index2];
if (node.nodeName === "#text") {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.addTextElement)
.setValue(this.options._const.route, route.concat(index2))
.setValue(this.options._const.value, node.data));
gaps1.splice(index2, 0, true);
shortest = Math.min(gaps1.length, gaps2.length);
index1 -= 1;
}
else {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.addElement)
.setValue(this.options._const.route, route.concat(index2))
.setValue(this.options._const.element, cleanNode(node)));
gaps1.splice(index2, 0, true);
shortest = Math.min(gaps1.length, gaps2.length);
index1 -= 1;
}
}
else if (gaps1[index2] !== gaps2[index2]) {
if (diffs.length > 0) {
return diffs;
}
// group relocation
group = subtrees[gaps1[index2]];
toGroup = Math.min(group.newValue, t1.childNodes.length - group.length);
if (toGroup !== group.oldValue) {
// Check whether destination nodes are different than originating ones.
destinationDifferent = false;
for (var j = 0; j < group.length; j += 1) {
if (!roughlyEqual(t1.childNodes[toGroup + j], t1.childNodes[group.oldValue + j], {}, false, true)) {
destinationDifferent = true;
}
}
if (destinationDifferent) {
return [
new Diff()
.setValue(this.options._const.action, this.options._const.relocateGroup)
.setValue(this.options._const.groupLength, group.length)
.setValue(this.options._const.from, group.oldValue)
.setValue(this.options._const.to, toGroup)
.setValue(this.options._const.route, route) ];
}
}
}
}
return diffs;
};
DiffFinder.prototype.findValueDiff = function (t1, t2, route) {
// Differences of value. Only useful if the value/selection/checked value
// differs from what is represented in the DOM. For example in the case
// of filled out forms, etc.
var diffs = [];
if (t1.selected !== t2.selected) {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.modifySelected)
.setValue(this.options._const.oldValue, t1.selected)
.setValue(this.options._const.newValue, t2.selected)
.setValue(this.options._const.route, route));
}
if ((t1.value || t2.value) &&
t1.value !== t2.value &&
t1.nodeName !== "OPTION") {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.modifyValue)
.setValue(this.options._const.oldValue, t1.value || "")
.setValue(this.options._const.newValue, t2.value || "")
.setValue(this.options._const.route, route));
}
if (t1.checked !== t2.checked) {
diffs.push(new Diff()
.setValue(this.options._const.action, this.options._const.modifyChecked)
.setValue(this.options._const.oldValue, t1.checked)
.setValue(this.options._const.newValue, t2.checked)
.setValue(this.options._const.route, route));
}
return diffs;
};
return DiffFinder;
}());
var DEFAULT_OPTIONS = {
debug: false,
diffcap: 10,
maxDepth: false,
maxChildCount: 50,
valueDiffing: true,
// syntax: textDiff: function (node, currentValue, expectedValue, newValue)
textDiff: function (node, currentValue, expectedValue, newValue) {
node.data = newValue;
return;
},
// empty functions were benchmarked as running faster than both
// `f && f()` and `if (f) { f(); }`
preVirtualDiffApply: function () { },
postVirtualDiffApply: function () { },
preDiffApply: function () { },
postDiffApply: function () { },
filterOuterDiff: null,
compress: false,
_const: false,
document: typeof window !== "undefined" && window.document
? window.document
: false,
components: []
};
var DiffDOM = /** @class */ (function () {
function DiffDOM(options) {
if (options === void 0) { options = {}; }
// IE11 doesn't have Object.assign and buble doesn't translate object spreaders
// by default, so this is the safest way of doing it currently.
Object.entries(DEFAULT_OPTIONS).forEach(function (_a) {
var key = _a[0], value = _a[1];
if (!Object.prototype.hasOwnProperty.call(options, key)) {
options[key] = value;
}
});
if (!options._const) {
var varNames = [
"addAttribute",
"modifyAttribute",
"removeAttribute",
"modifyTextElement",
"relocateGroup",
"removeElement",
"addElement",
"removeTextElement",
"addTextElement",
"replaceElement",
"modifyValue",
"modifyChecked",
"modifySelected",
"modifyComment",
"action",
"route",
"oldValue",
"newValue",
"element",
"group",
"groupLength",
"from",
"to",
"name",
"value",
"data",
"attributes",
"nodeName",
"childNodes",
"checked",
"selected" ];
var constNames_1 = {};
if (options.compress) {
varNames.forEach(function (varName, index) { return (constNames_1[varName] = index); });
}
else {
varNames.forEach(function (varName) { return (constNames_1[varName] = varName); });
}
options._const = constNames_1;
}
this.options = options;
}
DiffDOM.prototype.apply = function (tree, diffs) {
return applyDOM(tree, diffs, this.options);
};
DiffDOM.prototype.undo = function (tree, diffs) {
return undoDOM(tree, diffs, this.options);
};
DiffDOM.prototype.diff = function (t1Node, t2Node) {
var finder = new DiffFinder(t1Node, t2Node, this.options);
return finder.init();
};
return DiffDOM;
}());
const headingsToVirtualHeaderRowDOM = (headings, columnSettings, columnsState, { classes, format, hiddenHeader, sortable, scrollY, type }, { noColumnWidths, unhideHeader }) => ({
nodeName: "TR",
childNodes: headings.map((heading, index) => {
const column = columnSettings[index] || {
type,
format,
sortable: true,
searchable: true
};
if (column.hidden) {
return;
}
const attributes = {};
if (column.sortable && sortable && (!scrollY.length || unhideHeader)) {
if (column.filter) {
attributes["data-filterable"] = "true";
}
else {
attributes["data-sortable"] = "true";
}
}
if (column.headerClass) {
attributes.class = column.headerClass;
}
if (columnsState.sort && columnsState.sort.column === index) {
const directionClass = columnsState.sort.dir === "asc" ? classes.ascending : classes.descending;
attributes.class = attributes.class ? `${attributes.class} ${directionClass}` : directionClass;
attributes["aria-sort"] = columnsState.sort.dir === "asc" ? "ascending" : "descending";
}
else if (columnsState.filters[index]) {
attributes.class = attributes.class ? `${attributes.class} ${classes.filterActive}` : classes.filterActive;
}
let style = "";
if (columnsState.widths[index] && !noColumnWidths) {
style += `width: ${columnsState.widths[index]}%;`;
}
if (scrollY.length && !unhideHeader) {
style += "padding-bottom: 0;padding-top: 0;border: 0;";
}
if (style.length) {
attributes.style = style;
}
const headerNodes = heading.type === "html" ?
heading.data :
[
{
nodeName: "#text",
data: heading.text ?? String(heading.data)
}
];
return {
nodeName: "TH",
attributes,
childNodes: ((hiddenHeader || scrollY.length) && !unhideHeader) ?
[
{ nodeName: "#text",
data: "" }
] :
!column.sortable || !sortable ?
headerNodes :
[
{
nodeName: "a",
attributes: {
href: "#",
class: column.filter ? classes.filter : classes.sorter
},
childNodes: headerNodes
}
]
};
}).filter((column) => column)
});
const dataToVirtualDOM = (tableAttributes, headings, rows, columnSettings, columnsState, rowCursor, { classes, hiddenHeader, header, footer, format, sortable, scrollY, type, rowRender, tabIndex }, { noColumnWidths, unhideHeader, renderHeader }) => {
const table = {
nodeName: "TABLE",
attributes: { ...tableAttributes },
childNodes: [
{
nodeName: "TBODY",
childNodes: rows.map(({ row, index }) => {
const tr = {
nodeName: "TR",
attributes: {
"data-index": String(index)
},
childNodes: row.map((cell, cIndex) => {
const column = columnSettings[cIndex] || {
type,
format,
sortable: true,
searchable: true
};
if (column.hidden) {
return;
}
const td = column.type === "html" ?
{
nodeName: "TD",
childNodes: cell.data
} :
{
nodeName: "TD",
childNodes: [
{
nodeName: "#text",
data: cell.text ?? String(cell.data)
}
]
};
if (!header && !footer && columnsState.widths[cIndex] && !noColumnWidths) {
td.attributes = {
style: `width: ${columnsState.widths[cIndex]}%;`
};
}
if (column.cellClass) {
if (!td.attributes) {
td.attributes = {};
}
td.attributes.class = column.cellClass;
}
if (column.render) {
const renderedCell = column.render(cell.data, td, index, cIndex);
if (renderedCell) {
if (typeof renderedCell === "string") {
// Convenience method to make it work similarly to what it did up to version 5.
const node = stringToObj(`<td>${renderedCell}</td>`);
if (node.childNodes.length !== 1 || !["#text", "#comment"].includes(node.childNodes[0].nodeName)) {
td.childNodes = node.childNodes;
}
else {
td.childNodes[0].data = renderedCell;
}
}
else {
return renderedCell;
}
}
}
return td;
}).filter((column) => column)
};
if (index === rowCursor) {
tr.attributes.class = classes.cursor;
}
if (rowRender) {
const renderedRow = rowRender(row, tr, index);
if (renderedRow) {
if (typeof renderedRow === "string") {
// Convenience method to make it work similarly to what it did up to version 5.
const node = stringToObj(`<tr>${renderedRow}</tr>`);
if (node.childNodes && (node.childNodes.length !== 1 || !["#text", "#comment"].includes(node.childNodes[0].nodeName))) {
tr.childNodes = node.childNodes;
}
else {
tr.childNodes[0].data = renderedRow;
}
}
else {
return renderedRow;
}
}
}
return tr;
})
}
]
};
table.attributes.class = table.attributes.class ? `${table.attributes.class} ${classes.table}` : classes.table;
if (header || footer || renderHeader) {
const headerRow = headingsToVirtualHeaderRowDOM(headings, columnSettings, columnsState, { classes,
hiddenHeader,
sortable,
scrollY }, { noColumnWidths,
unhideHeader });
if (header || renderHeader) {
const thead = {
nodeName: "THEAD",
childNodes: [headerRow]
};
if ((scrollY.length || hiddenHeader) && !unhideHeader) {
thead.attributes = { style: "height: 0px;" };
}
table.childNodes.unshift(thead);
}
if (footer) {
const footerRow = header ? structuredClone(headerRow) : headerRow;
const tfoot = {
nodeName: "TFOOT",
childNodes: [footerRow]
};
if ((scrollY.length || hiddenHeader) && !unhideHeader) {
tfoot.attributes = { style: "height: 0px;" };
}
table.childNodes.push(tfoot);
}
}
if (tabIndex !== false) {
table.attributes.tabindex = String(tabIndex);
}
return table;
};
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
var dayjs_minExports = {};
var dayjs_min = {
get exports(){ return dayjs_minExports; },
set exports(v){ dayjs_minExports = v; },
};
(function (module, exports) {
!function(t,e){module.exports=e();}(commonjsGlobal,(function(){var t=1e3,e=6e4,n=36e5,r="millisecond",i="second",s="minute",u="hour",a="day",o="week",f="month",h="quarter",c="year",d="date",l="Invalid Date",$=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,M={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(t){var e=["th","st","nd","rd"],n=t%100;return "["+t+(e[(n-20)%10]||e[n]||e[0])+"]"}},m=function(t,e,n){var r=String(t);return !r||r.length>=e?t:""+Array(e+1-r.length).join(n)+t},v={s:m,z:function(t){var e=-t.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),i=n%60;return (e<=0?"+":"-")+m(r,2,"0")+":"+m(i,2,"0")},m:function t(e,n){if(e.date()<n.date())return -t(n,e);var r=12*(n.year()-e.year())+(n.month()-e.month()),i=e.clone().add(r,f),s=n-i<0,u=e.clone().add(r+(s?-1:1),f);return +(-(r+(n-i)/(s?i-u:u-i))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(t){return {M:f,y:c,w:o,d:a,D:d,h:u,m:s,s:i,ms:r,Q:h}[t]||String(t||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},g="en",D={};D[g]=M;var p=function(t){return t instanceof _},S=function t(e,n,r){var i;if(!e)return g;if("string"==typeof e){var s=e.toLowerCase();D[s]&&(i=s),n&&(D[s]=n,i=s);var u=e.split("-");if(!i&&u.length>1)return t(u[0])}else {var a=e.name;D[a]=e,i=a;}return !r&&i&&(g=i),i||!r&&g},w=function(t,e){if(p(t))return t.clone();var n="object"==typeof e?e:{};return n.date=t,n.args=arguments,new _(n)},O=v;O.l=S,O.i=p,O.w=function(t,e){return w(t,{locale:e.$L,utc:e.$u,x:e.$x,$offset:e.$offset})};var _=function(){function M(t){this.$L=S(t.locale,null,!0),this.parse(t);}var m=M.prototype;return m.parse=function(t){this.$d=function(t){var e=t.date,n=t.utc;if(null===e)return new Date(NaN);if(O.u(e))return new Date;if(e instanceof Date)return new Date(e);if("string"==typeof e&&!/Z$/i.test(e)){var r=e.match($);if(r){var i=r[2]-1||0,s=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)):new Date(r[1],i,r[3]||1,r[4]||0,r[5]||0,r[6]||0,s)}}return new Date(e)}(t),this.$x=t.x||{},this.init();},m.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds();},m.$utils=function(){return O},m.isValid=function(){return !(this.$d.toString()===l)},m.isSame=function(t,e){var n=w(t);return this.startOf(e)<=n&&n<=this.endOf(e)},m.isAfter=function(t,e){return w(t)<this.startOf(e)},m.isBefore=function(t,e){return this.endOf(e)<w(t)},m.$g=function(t,e,n){return O.u(t)?this[e]:this.set(n,t)},m.unix=function(){return Math.floor(this.valueOf()/1e3)},m.valueOf=function(){return this.$d.getTime()},m.startOf=function(t,e){var n=this,r=!!O.u(e)||e,h=O.p(t),l=function(t,e){var i=O.w(n.$u?Date.UTC(n.$y,e,t):new Date(n.$y,e,t),n);return r?i:i.endOf(a)},$=function(t,e){return O.w(n.toDate()[t].apply(n.toDate("s"),(r?[0,0,0,0]:[23,59,59,999]).slice(e)),n)},y=this.$W,M=this.$M,m=this.$D,v="set"+(this.$u?"UTC":"");switch(h){case c:return r?l(1,0):l(31,11);case f:return r?l(1,M):l(0,M+1);case o:var g=this.$locale().weekStart||0,D=(y<g?y+7:y)-g;return l(r?m-D:m+(6-D),M);case a:case d:return $(v+"Hours",0);case u:return $(v+"Minutes",1);case s:return $(v+"Seconds",2);case i:return $(v+"Milliseconds",3);default:return this.clone()}},m.endOf=function(t){return this.startOf(t,!1)},m.$set=function(t,e){var n,o=O.p(t),h="set"+(this.$u?"UTC":""),l=(n={},n[a]=h+"Date",n[d]=h+"Date",n[f]=h+"Month",n[c]=h+"FullYear",n[u]=h+"Hours",n[s]=h+"Minutes",n[i]=h+"Seconds",n[r]=h+"Milliseconds",n)[o],$=o===a?this.$D+(e-this.$W):e;if(o===f||o===c){var y=this.clone().set(d,1);y.$d[l]($),y.init(),this.$d=y.set(d,Math.min(this.$D,y.daysInMonth())).$d;}else l&&this.$d[l]($);return this.init(),this},m.set=function(t,e){return this.clone().$set(t,e)},m.get=function(t){return this[O.p(t)]()},m.add=function(r,h){var d,l=this;r=Number(r);var $=O.p(h),y=function(t){var e=w(l);return O.w(e.date(e.date()+Math.round(t*r)),l)};if($===f)return this.set(f,this.$M+r);if($===c)return this.set(c,this.$y+r);if($===a)return y(1);if($===o)return y(7);var M=(d={},d[s]=e,d[u]=n,d[i]=t,d)[$]||1,m=this.$d.getTime()+r*M;return O.w(m,this)},m.subtract=function(t,e){return this.add(-1*t,e)},m.format=function(t){var e=this,n=this.$locale();if(!this.isValid())return n.invalidDate||l;var r=t||"YYYY-MM-DDTHH:mm:ssZ",i=O.z(this),s=this.$H,u=this.$m,a=this.$M,o=n.weekdays,f=n.months,h=function(t,n,i,s){return t&&(t[n]||t(e,r))||i[n].slice(0,s)},c=function(t){return O.s(s%12||12,t,"0")},d=n.meridiem||function(t,e,n){var r=t<12?"AM":"PM";return n?r.toLowerCase():r},$={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:O.s(a+1,2,"0"),MMM:h(n.monthsShort,a,f,3),MMMM:h(f,a),D:this.$D,DD:O.s(this.$D,2,"0"),d:String(this.$W),dd:h(n.weekdaysMin,this.$W,o,2),ddd:h(n.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(s),HH:O.s(s,2,"0"),h:c(1),hh:c(2),a:d(s,u,!0),A:d(s,u,!1),m:String(u),mm:O.s(u,2,"0"),s:String(this.$s),ss:O.s(this.$s,2,"0"),SSS:O.s(this.$ms,3,"0"),Z:i};return r.replace(y,(function(t,e){return e||$[t]||i.replace(":","")}))},m.utcOffset=function(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)},m.diff=function(r,d,l){var $,y=O.p(d),M=w(r),m=(M.utcOffset()-this.utcOffset())*e,v=this-M,g=O.m(this,M);return g=($={},$[c]=g/12,$[f]=g,$[h]=g/3,$[o]=(v-m)/6048e5,$[a]=(v-m)/864e5,$[u]=v/n,$[s]=v/e,$[i]=v/t,$)[y]||v,l?g:O.a(g)},m.daysInMonth=function(){return this.endOf(f).$D},m.$locale=function(){return D[this.$L]},m.locale=function(t,e){if(!t)return this.$L;var n=this.clone(),r=S(t,e,!0);return r&&(n.$L=r),n},m.clone=function(){return O.w(this.$d,this)},m.toDate=function(){return new Date(this.valueOf())},m.toJSON=function(){return this.isValid()?this.toISOString():null},m.toISOString=function(){return this.$d.toISOString()},m.toString=function(){return this.$d.toUTCString()},M}(),T=_.prototype;return w.prototype=T,[["$ms",r],["$s",i],["$m",s],["$H",u],["$W",a],["$M",f],["$y",c],["$D",d]].forEach((function(t){T[t[1]]=function(e){return this.$g(e,t[0],t[1])};})),w.extend=function(t,e){return t.$i||(t(e,_,w),t.$i=!0),w},w.locale=S,w.isDayjs=p,w.unix=function(t){return w(1e3*t)},w.en=D[g],w.Ls=D,w.p={},w}));
} (dayjs_min));
var dayjs = dayjs_minExports;
var customParseFormatExports = {};
var customParseFormat$1 = {
get exports(){ return customParseFormatExports; },
set exports(v){ customParseFormatExports = v; },
};
(function (module, exports) {
!function(e,t){module.exports=t();}(commonjsGlobal,(function(){var e={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},t=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,n=/\d\d/,r=/\d\d?/,i=/\d*[^-_:/,()\s\d]+/,o={},s=function(e){return (e=+e)+(e>68?1900:2e3)};var a=function(e){return function(t){this[e]=+t;}},f=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e);}],h=function(e){var t=o[e];return t&&(t.indexOf?t:t.s.concat(t.f))},u=function(e,t){var n,r=o.meridiem;if(r){for(var i=1;i<=24;i+=1)if(e.indexOf(r(i,0,t))>-1){n=i>12;break}}else n=e===(t?"pm":"PM");return n},d={A:[i,function(e){this.afternoon=u(e,!1);}],a:[i,function(e){this.afternoon=u(e,!0);}],S:[/\d/,function(e){this.milliseconds=100*+e;}],SS:[n,function(e){this.milliseconds=10*+e;}],SSS:[/\d{3}/,function(e){this.milliseconds=+e;}],s:[r,a("seconds")],ss:[r,a("seconds")],m:[r,a("minutes")],mm:[r,a("minutes")],H:[r,a("hours")],h:[r,a("hours")],HH:[r,a("hours")],hh:[r,a("hours")],D:[r,a("day")],DD:[n,a("day")],Do:[i,function(e){var t=o.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r);}],M:[r,a("month")],MM:[n,a("month")],MMM:[i,function(e){var t=h("months"),n=(h("monthsShort")||t.map((function(e){return e.slice(0,3)}))).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n;}],MMMM:[i,function(e){var t=h("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t;}],Y:[/[+-]?\d+/,a("year")],YY:[n,function(e){this.year=s(e);}],YYYY:[/\d{4}/,a("year")],Z:f,ZZ:f};function c(n){var r,i;r=n,i=o&&o.formats;for(var s=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,n,r){var o=r&&r.toUpperCase();return n||i[r]||e[r]||i[o].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}))).match(t),a=s.length,f=0;f<a;f+=1){var h=s[f],u=d[h],c=u&&u[0],l=u&&u[1];s[f]=l?{regex:c,parser:l}:h.replace(/^\[|\]$/g,"");}return function(e){for(var t={},n=0,r=0;n<a;n+=1){var i=s[n];if("string"==typeof i)r+=i.length;else {var o=i.regex,f=i.parser,h=e.slice(r),u=o.exec(h)[0];f.call(t,u),e=e.replace(u,"");}}return function(e){var t=e.afternoon;if(void 0!==t){var n=e.hours;t?n<12&&(e.hours+=12):12===n&&(e.hours=0),delete e.afternoon;}}(t),t}}return function(e,t,n){n.p.customParseFormat=!0,e&&e.parseTwoDigitYear&&(s=e.parseTwoDigitYear);var r=t.prototype,i=r.parse;r.parse=function(e){var t=e.date,r=e.utc,s=e.args;this.$u=r;var a=s[1];if("string"==typeof a){var f=!0===s[2],h=!0===s[3],u=f||h,d=s[2];h&&(d=s[2]),o=this.$locale(),!f&&d&&(o=n.Ls[d]),this.$d=function(e,t,n){try{if(["x","X"].indexOf(t)>-1)return new Date(("X"===t?1e3:1)*e);var r=c(t)(e),i=r.year,o=r.month,s=r.day,a=r.hours,f=r.minutes,h=r.seconds,u=r.milliseconds,d=r.zone,l=new Date,m=s||(i||o?1:l.getDate()),M=i||l.getFullYear(),Y=0;i&&!o||(Y=o>0?o-1:l.getMonth());var p=a||0,v=f||0,D=h||0,g=u||0;return d?new Date(Date.UTC(M,Y,m,p,v,D,g+60*d.offset*1e3)):n?new Date(Date.UTC(M,Y,m,p,v,D,g)):new Date(M,Y,m,p,v,D,g)}catch(e){return new Date("")}}(t,a,r),this.init(),d&&!0!==d&&(this.$L=this.locale(d).$L),u&&t!=this.format(a)&&(this.$d=new Date("")),o={};}else if(a instanceof Array)for(var l=a.length,m=1;m<=l;m+=1){s[1]=a[m-1];var M=n.apply(this,s);if(M.isValid()){this.$d=M.$d,this.$L=M.$L,this.init();break}m===l&&(this.$d=new Date(""));}else i.call(this,e);};}}));
} (customParseFormat$1));
var customParseFormat = customParseFormatExports;
dayjs.extend(customParseFormat);
/**
* Use dayjs to parse cell contents for sorting
*/
const parseDate = (content, format) => {
let date;
// Converting to YYYYMMDD ensures we can accurately sort the column numerically
if (format) {
switch (format) {
case "ISO_8601":
// ISO8601 is already lexiographically sorted, so we can just sort it as a string.
date = content;
break;
case "RFC_2822":
date = dayjs(content.slice(5), "DD MMM YYYY HH:mm:ss ZZ").unix();
break;
case "MYSQL":
date = dayjs(content, "YYYY-MM-DD hh:mm:ss").unix();
break;
case "UNIX":
date = dayjs(content).unix();
break;
// User defined format using the data-format attribute or columns[n].format option
default:
date = dayjs(content, format, true).valueOf();
break;
}
}
return date;
};
const readDataCell = (cell, columnSettings) => {
if (cell?.constructor === Object && Object.prototype.hasOwnProperty.call(cell, "data") && !Object.keys(cell).find(key => !(["text", "order", "data"].includes(key)))) {
return cell;
}
const cellData = {
data: cell
};
switch (columnSettings.type) {
case "string":
if (!(typeof cell === "string")) {
cellData.text = String(cellData.data);
cellData.order = cellData.text;
}
break;
case "date":
if (columnSettings.format) {
cellData.order = parseDate(String(cellData.data), columnSettings.format);
}
break;
case "number":
cellData.text = String(cellData.data);
cellData.data = parseInt(cellData.data, 10);
break;
case "html": {
const node = Array.isArray(cellData.data) ?
{ nodeName: "TD",
childNodes: cellData.data } : // If it is an array, we assume it is an array of nodeType
stringToObj(`<td>${String(cellData.data)}</td>`);
cellData.data = node.childNodes || [];
const text = objToText(node);
cellData.text = text;
cellData.order = text;
break;
}
case "boolean":
if (typeof cellData.data === "string") {
cellData.data = cellData.data.toLowerCase().trim();
}
cellData.data = !["false", false, null, undefined, 0].includes(cellData.data);
cellData.order = cellData.data ? 1 : 0;
cellData.text = String(cellData.data);
break;
case "other":
cellData.text = "";
cellData.order = 0;
break;
default:
cellData.text = JSON.stringify(cellData.data);
break;
}
return cellData;
};
const readDOMDataCell = (cell, columnSettings) => {
let cellData;
switch (columnSettings.type) {
case "string":
cellData = {
data: cell.innerText
};
break;
case "date": {
const data = cell.innerText;
cellData = {
data,
order: parseDate(data, columnSettings.format)
};
break;
}
case "number":
cellData = {
data: parseInt(cell.innerText, 10),
text: cell.innerText
};
break;
case "boolean": {
const data = !["false", "0", "null", "undefined"].includes(cell.innerText.toLowerCase().trim());
cellData = {
data,
order: data ? 1 : 0,
text: data ? "1" : "0"
};
break;
}
default: { // "html", "other"
const node = nodeToObj(cell, { valueDiffing: false });
cellData = {
data: node.childNodes || [],
text: cell.innerText,
order: cell.innerText
};
break;
}
}
return cellData;
};
const readHeaderCell = (cell) => {
if (cell instanceof Object &&
cell.constructor === Object &&
cell.hasOwnProperty("data") &&
(typeof cell.text === "string" || typeof cell.data === "string")) {
return cell;
}
const cellData = {
data: cell
};
if (typeof cell === "string") {
if (cell.length) {
const node = stringToObj(`<th>${cell}</th>`);
if (node.childNodes && (node.childNodes.length !== 1 || node.childNodes[0].nodeName !== "#text")) {
cellData.data = node.childNodes;
cellData.type = "html";
const text = objToText(node);
cellData.text = text;
}
}
}
else if ([null, undefined].includes(cell)) {
cellData.text = "";
}
else {
cellData.text = JSON.stringify(cell);
}
return cellData;
};
const readDOMHeaderCell = (cell) => {
const node = nodeToObj(cell, { valueDiffing: false });
let cellData;
if (node.childNodes && (node.childNodes.length !== 1 || node.childNodes[0].nodeName !== "#text")) {
cellData = {
data: node.childNodes,
type: "html",
text: objToText(node)
};
}
else {
cellData = {
data: cell.innerText,
type: "string"
};
}
return cellData;
};
const readTableData = (dataOption, dom = undefined, columnSettings, defaultType, defaultFormat) => {
const data = {
data: [],
headings: []
};
if (dataOption.headings) {
data.headings = dataOption.headings.map((heading) => readHeaderCell(heading));
}
else if (dom?.tHead) {
data.headings = Array.from(dom.tHead.querySelectorAll("th")).map((th, index) => {
const heading = readDOMHeaderCell(th);
if (!columnSettings[index]) {
columnSettings[index] = {
type: defaultType,
format: defaultFormat,
searchable: true,
sortable: true,
isSplitQueryWord: true
};
}
const settings = columnSettings[index];
if (th.dataset.sortable?.trim().toLowerCase() === "false" || th.dataset.sort?.trim().toLowerCase() === "false") {
settings.sortable = false;
}
if (th.dataset.searchable?.trim().toLowerCase() === "false") {
settings.searchable = false;
}
if (th.dataset.hidden?.trim().toLowerCase() === "true" || th.getAttribute("hidden")?.trim().toLowerCase() === "true") {
settings.hidden = true;
}
if (["number", "string", "html", "date", "boolean", "other"].includes(th.dataset.type)) {
settings.type = th.dataset.type;
if (settings.type === "date" && th.dataset.format) {
settings.format = th.dataset.format;
}
}
return heading;
});
}
else if (dataOption.data?.length) {
data.headings = dataOption.data[0].map((_cell) => readHeaderCell(""));
}
else if (dom?.tBodies.length) {
data.headings = Array.from(dom.tBodies[0].rows[0].cells).map((_cell) => readHeaderCell(""));
}
for (let i = 0; i < data.headings.length; i++) {
// Make sure that there are settings for all columns
if (!columnSettings[i]) {
columnSettings[i] = {
type: defaultType,
format: defaultFormat,
sortable: true,
searchable: true
};
}
}
if (dataOption.data) {
data.data = dataOption.data.map((row) => row.map((cell, index) => readDataCell(cell, columnSettings[index])));
}
else if (dom?.tBodies?.length) {
data.data = Array.from(dom.tBodies[0].rows).map(row => Array.from(row.cells).map((cell, index) => {
const cellData = cell.dataset.content ?
readDataCell(cell.dataset.content, columnSettings[index]) :
readDOMDataCell(cell, columnSettings[index]);
if (cell.dataset.order) {
cellData.order = isNaN(parseFloat(cell.dataset.order)) ? cell.dataset.order : parseFloat(cell.dataset.order);
}
return cellData;
}));
}
if (data.data.length && data.data[0].length !== data.headings.length) {
throw new Error("Data heading length mismatch.");
}
return data;
};
/**
* Rows API
*/
class Rows {
constructor(dt) {
this.dt = dt;
this.cursor = false;
}
setCursor(index = false) {
if (index === this.cursor) {
return;
}
const oldCursor = this.cursor;
this.cursor = index;
this.dt._renderTable();
if (index !== false && this.dt.options.scrollY) {
const cursorDOM = this.dt.dom.querySelector(`tr.${this.dt.options.classes.cursor}`);
if (cursorDOM) {
cursorDOM.scrollIntoView({ block: "nearest" });
}
}
this.dt.emit("datatable.cursormove", this.cursor, oldCursor);
}
/**
* Add new row
*/
add(data) {
const row = data.map((cell, index) => {
const columnSettings = this.dt.columns.settings[index];
return readDataCell(cell, columnSettings);
});
this.dt.data.data.push(row);
// We may have added data to an empty table
if (this.dt.data.data.length) {
this.dt.hasRows = true;
}
this.dt.update(true);
}
/**
* Remove row(s)
*/
remove(select) {
if (Array.isArray(select)) {
this.dt.data.data = this.dt.data.data.filter((_row, index) => !select.includes(index));
// We may have emptied the table
if (!this.dt.data.data.length) {
this.dt.hasRows = false;
}
this.dt.update(true);
}
else {
return this.remove([select]);
}
}
/**
* Find index of row by searching for a value in a column
*/
findRowIndex(columnIndex, value) {
// returns row index of first case-insensitive string match
// inside the td innerText at specific column index
return this.dt.data.data.findIndex((row) => (row[columnIndex].text ?? String(row[columnIndex].data)).toLowerCase().includes(String(value).toLowerCase()));
}
/**
* Find index, row, and column data by searching for a value in a column
*/
findRow(columnIndex, value) {
// get the row index
const index = this.findRowIndex(columnIndex, value);
// exit if not found
if (index < 0) {
return {
index: -1,
row: null,
cols: []
};
}
// get the row from data
const row = this.dt.data.data[index];
// return innerHTML of each td
const cols = row.map((cell) => cell.data);
// return everything
return {
index,
row,
cols
};
}
/**
* Update a row with new data
*/
updateRow(select, data) {
const row = data.map((cell, index) => {
const columnSettings = this.dt.columns.settings[index];
return readDataCell(cell, columnSettings);
});
this.dt.data.data.splice(select, 1, row);
this.dt.update(true);
}
}
const readColumnSettings = (columnOptions = [], defaultType, defaultFormat) => {
let columns = [];
let sort = false;
const filters = [];
// Check for the columns option
columnOptions.forEach(data => {
// convert single column selection to array
const columnSelectors = Array.isArray(data.select) ? data.select : [data.select];
columnSelectors.forEach((selector) => {
if (!columns[selector]) {
columns[selector] = {
type: data.type || defaultType,
sortable: true,
searchable: true,
isSplitQueryWord: true,
searchQuerySeparator: " "
};
}
const column = columns[selector];
if (data.render) {
column.render = data.render;
}
if (data.format) {
column.format = data.format;
}
else if (data.type === "date") {
column.format = defaultFormat;
}
if (data.cellClass) {
column.cellClass = data.cellClass;
}
if (data.headerClass) {
column.headerClass = data.headerClass;
}
if (data.locale) {
column.locale = data.locale;
}
if (data.sortable === false) {
column.sortable = false;
}
else {
if (data.numeric) {
column.numeric = data.numeric;
}
if (data.caseFirst) {
column.caseFirst = data.caseFirst;
}
}
if (data.searchable === false) {
column.searchable = false;
}
else {
if (data.sensitivity) {
column.sensitivity = data.sensitivity;
}
}
if (column.searchable || column.sortable) {
if (data.ignorePunctuation) {
column.ignorePunctuation = data.ignorePunctuation;
}
}
if (data.hidden) {
column.hidden = true;
}
if (data.filter) {
column.filter = data.filter;
}
if (data.sortSequence) {
column.sortSequence = data.sortSequence;
}
if (data.sort) {
if (data.filter) {
filters[selector] = data.sort;
}
else {
// We only allow one. The last one will overwrite all other options
sort = { column: selector,
dir: data.sort };
}
}
if (typeof data.isSplitQueryWord !== "undefined") {
column.isSplitQueryWord = data.isSplitQueryWord;
}
if (typeof data.searchQuerySeparator !== "undefined") {
column.searchQuerySeparator = data.searchQuerySeparator;
}
});
});
columns = columns.map(column => column ?
column :
{ type: defaultType,
format: defaultType === "date" ? defaultFormat : undefined,
sortable: true,
searchable: true,
isSplitQueryWord: true,
searchQuerySeparator: " " });
const widths = []; // Width are determined later on by measuring on screen.
return [
columns, { filters,
sort,
widths }
];
};
class Columns {
constructor(dt) {
this.dt = dt;
this.init();
}
init() {
[this.settings, this._state] = readColumnSettings(this.dt.options.columns, this.dt.options.type, this.dt.options.format);
}
/**
* Swap two columns
*/
swap(columns) {
if (columns.length === 2) {
// Get the current column indexes
const cols = this.dt.data.headings.map((_node, index) => index);
const x = columns[0];
const y = columns[1];
const b = cols[y];
cols[y] = cols[x];
cols[x] = b;
return this.order(cols);
}
}
/**
* Reorder the columns
*/
order(columns) {
this.dt.data.headings = columns.map((index) => this.dt.data.headings[index]);
this.dt.data.data = this.dt.data.data.map((row) => columns.map((index) => row[index]));
this.settings = columns.map((index) => this.settings[index]);
// Update
this.dt.update();
}
/**
* Hide columns
*/
hide(columns) {
if (!columns.length) {
return;
}
columns.forEach((index) => {
if (!this.settings[index]) {
this.settings[index] = {
type: "string"
};
}
const column = this.settings[index];
column.hidden = true;
});
this.dt.update();
}
/**
* Show columns
*/
show(columns) {
if (!columns.length) {
return;
}
columns.forEach((index) => {
if (!this.settings[index]) {
this.settings[index] = {
type: "string",
sortable: true
};
}
const column = this.settings[index];
delete column.hidden;
});
this.dt.update();
}
/**
* Check column(s) visibility
*/
visible(columns) {
if (columns === undefined) {
columns = [...Array(this.dt.data.headings.length).keys()];
}
if (Array.isArray(columns)) {
return columns.map(index => !this.settings[index]?.hidden);
}
return !this.settings[columns]?.hidden;
}
/**
* Add a new column
*/
add(data) {
const newColumnSelector = this.dt.data.headings.length;
this.dt.data.headings = this.dt.data.headings.concat([readHeaderCell(data.heading)]);
this.dt.data.data = this.dt.data.data.map((row, index) => row.concat([readDataCell(data.data[index], data)]));
this.settings[newColumnSelector] = {
type: data.type || "string",
sortable: true,
searchable: true
};
if (data.type || data.format || data.sortable || data.render || data.filter) {
const column = this.settings[newColumnSelector];
if (data.render) {
column.render = data.render;
}
if (data.format) {
column.format = data.format;
}
if (data.cellClass) {
column.cellClass = data.cellClass;
}
if (data.headerClass) {
column.headerClass = data.headerClass;
}
if (data.locale) {
column.locale = data.locale;
}
if (data.sortable === false) {
column.sortable = false;
}
else {
if (data.numeric) {
column.numeric = data.numeric;
}
if (data.caseFirst) {
column.caseFirst = data.caseFirst;
}
}
if (data.searchable === false) {
column.searchable = false;
}
else {
if (data.sensitivity) {
column.sensitivity = data.sensitivity;
}
}
if (column.searchable || column.sortable) {
if (data.ignorePunctuation) {
column.ignorePunctuation = data.ignorePunctuation;
}
}
if (data.hidden) {
column.hidden = true;
}
if (data.filter) {
column.filter = data.filter;
}
if (data.sortSequence) {
column.sortSequence = data.sortSequence;
}
}
this.dt.update(true);
}
/**
* Remove column(s)
*/
remove(columns) {
if (Array.isArray(columns)) {
this.dt.data.headings = this.dt.data.headings.filter((_heading, index) => !columns.includes(index));
this.dt.data.data = this.dt.data.data.map((row) => row.filter((_cell, index) => !columns.includes(index)));
this.dt.update(true);
}
else {
return this.remove([columns]);
}
}
/**
* Filter by column
*/
filter(column, init = false) {
if (!this.settings[column]?.filter?.length) {
// There is no filter to apply.
return;
}
const currentFilter = this._state.filters[column];
let newFilterState;
if (currentFilter) {
let returnNext = false;
newFilterState = this.settings[column].filter.find((filter) => {
if (returnNext) {
return true;
}
if (filter === currentFilter) {
returnNext = true;
}
return false;
});
}
else {
const filter = this.settings[column].filter;
newFilterState = filter ? filter[0] : undefined;
}
if (newFilterState) {
this._state.filters[column] = newFilterState;
}
else if (currentFilter) {
this._state.filters[column] = undefined;
}
this.dt._currentPage = 1;
this.dt.update();
if (!init) {
this.dt.emit("datatable.filter", column, newFilterState);
}
}
/**
* Sort by column
*/
sort(index, dir = undefined, init = false) {
const column = this.settings[index];
if (!init) {
this.dt.emit("datatable.sorting", index, dir);
}
if (!dir) {
const currentDir = this._state.sort && this._state.sort.column === index ? this._state.sort?.dir : false;
const sortSequence = column?.sortSequence || ["asc", "desc"];
if (!currentDir) {
dir = sortSequence.length ? sortSequence[0] : "asc";
}
else {
const currentDirIndex = sortSequence.indexOf(currentDir);
if (currentDirIndex === -1) {
dir = sortSequence[0] || "asc";
}
else if (currentDirIndex === sortSequence.length - 1) {
dir = sortSequence[0];
}
else {
dir = sortSequence[currentDirIndex + 1];
}
}
}
const collator = ["string", "html"].includes(column.type) ?
new Intl.Collator(column.locale || this.dt.options.locale, {
usage: "sort",
numeric: column.numeric || this.dt.options.numeric,
caseFirst: column.caseFirst || this.dt.options.caseFirst,
ignorePunctuation: column.ignorePunctuation || this.dt.options.ignorePunctuation
}) :
false;
this.dt.data.data.sort((row1, row2) => {
let order1 = row1[index].order || row1[index].data, order2 = row2[index].order || row2[index].data;
if (dir === "desc") {
const temp = order1;
order1 = order2;
order2 = temp;
}
if (collator) {
return collator.compare(String(order1), String(order2));
}
if (order1 < order2) {
return -1;
}
else if (order1 > order2) {
return 1;
}
return 0;
});
this._state.sort = { column: index,
dir };
if (this.dt._searchQueries.length) {
this.dt.multiSearch(this.dt._searchQueries);
this.dt.emit("datatable.sort", index, dir);
}
else if (!init) {
this.dt._currentPage = 1;
this.dt.update();
this.dt.emit("datatable.sort", index, dir);
}
}
/**
* Measure the actual width of cell content by rendering the entire table with all contents.
* Note: Destroys current DOM and therefore requires subsequent dt.update()
*/
_measureWidths() {
const activeHeadings = this.dt.data.headings.filter((heading, index) => !this.settings[index]?.hidden);
if ((this.dt.options.scrollY.length || this.dt.options.fixedColumns) && activeHeadings?.length) {
this._state.widths = [];
const renderOptions = {
noPaging: true
};
// If we have headings we need only set the widths on them
// otherwise we need a temp header and the widths need applying to all cells
if (this.dt.options.header || this.dt.options.footer) {
if (this.dt.options.scrollY.length) {
renderOptions.unhideHeader = true;
}
if (this.dt.headerDOM) {
// Remove headerDOM for accurate measurements
this.dt.headerDOM.parentElement.removeChild(this.dt.headerDOM);
}
// Reset widths
renderOptions.noColumnWidths = true;
this.dt._renderTable(renderOptions);
const activeDOMHeadings = Array.from(this.dt.dom.querySelector("thead, tfoot")?.firstElementChild?.querySelectorAll("th") || []);
let domCounter = 0;
const absoluteColumnWidths = this.dt.data.headings.map((_heading, index) => {
if (this.settings[index]?.hidden) {
return 0;
}
const width = activeDOMHeadings[domCounter].offsetWidth;
domCounter += 1;
return width;
});
const totalOffsetWidth = absoluteColumnWidths.reduce((total, cellWidth) => total + cellWidth, 0);
this._state.widths = absoluteColumnWidths.map(cellWidth => cellWidth / totalOffsetWidth * 100);
}
else {
renderOptions.renderHeader = true;
this.dt._renderTable(renderOptions);
const activeDOMHeadings = Array.from(this.dt.dom.querySelector("thead, tfoot")?.firstElementChild?.querySelectorAll("th") || []);
let domCounter = 0;
const absoluteColumnWidths = this.dt.data.headings.map((_heading, index) => {
if (this.settings[index]?.hidden) {
return 0;
}
const width = activeDOMHeadings[domCounter].offsetWidth;
domCounter += 1;
return width;
});
const totalOffsetWidth = absoluteColumnWidths.reduce((total, cellWidth) => total + cellWidth, 0);
this._state.widths = absoluteColumnWidths.map(cellWidth => cellWidth / totalOffsetWidth * 100);
}
// render table without options for measurements
this.dt._renderTable();
}
}
}
// Template for custom layouts
const layoutTemplate = (options, dom) => `<div class='${options.classes.top}'>
${options.paging && options.perPageSelect ?
`<div class='${options.classes.dropdown}'>
<label>
<select class='${options.classes.selector}'></select> ${options.labels.perPage}
</label>
</div>` :
""}
${options.searchable ?
`<div class='${options.classes.search}'>
<input class='${options.classes.input}' placeholder='${options.labels.placeholder}' type='search' title='${options.labels.searchTitle}'${dom.id ? ` aria-controls="${dom.id}"` : ""}>
</div>` :
""}
</div>
<div class='${options.classes.container}'${options.scrollY.length ? ` style='height: ${options.scrollY}; overflow-Y: auto;'` : ""}></div>
<div class='${options.classes.bottom}'>
${options.paging ?
`<div class='${options.classes.info}'></div>` :
""}
<nav class='${options.classes.pagination}'></nav>
</div>`;
/**
* Default configuration
*/
const defaultConfig$1 = {
// for sorting
sortable: true,
locale: "en",
numeric: true,
caseFirst: "false",
// for searching
searchable: true,
sensitivity: "base",
ignorePunctuation: true,
destroyable: true,
isSplitQueryWord: true,
searchQuerySeparator: " ",
// data
data: {},
type: "html",
format: "YYYY-MM-DD",
columns: [],
// Pagination
paging: true,
perPage: 10,
perPageSelect: [5, 10, 15, 20, 25],
nextPrev: true,
firstLast: false,
prevText: "‹",
nextText: "›",
firstText: "«",
lastText: "»",
ellipsisText: "…",
truncatePager: true,
pagerDelta: 2,
scrollY: "",
fixedColumns: true,
fixedHeight: false,
footer: false,
header: true,
hiddenHeader: false,
rowNavigation: false,
tabIndex: false,
// for overriding rendering
pagerRender: false,
rowRender: false,
tableRender: false,
diffDomOptions: {
valueDiffing: false
},
// Customise the display text
labels: {
placeholder: "Search...",
searchTitle: "Search within table",
perPage: "entries per page",
noRows: "No entries found",
noResults: "No results match your search query",
info: "Showing {start} to {end} of {rows} entries" //
},
// Customise the layout
template: layoutTemplate,
// Customize the class names used by datatable for different parts
classes: {
active: "datatable-active",
ascending: "datatable-ascending",
bottom: "datatable-bottom",
container: "datatable-container",
cursor: "datatable-cursor",
descending: "datatable-descending",
disabled: "datatable-disabled",
dropdown: "datatable-dropdown",
ellipsis: "datatable-ellipsis",
filter: "datatable-filter",
filterActive: "datatable-filter-active",
empty: "datatable-empty",
headercontainer: "datatable-headercontainer",
hidden: "datatable-hidden",
info: "datatable-info",
input: "datatable-input",
loading: "datatable-loading",
pagination: "datatable-pagination",
paginationList: "datatable-pagination-list",
paginationListItem: "datatable-pagination-list-item",
paginationListItemLink: "datatable-pagination-list-item-link",
search: "datatable-search",
selector: "datatable-selector",
sorter: "datatable-sorter",
table: "datatable-table",
top: "datatable-top",
wrapper: "datatable-wrapper"
}
};
/**
* Pager truncation algorithm
*/
const truncate = (paginationListItems, currentPage, pagesLength, options) => {
const pagerDelta = options.pagerDelta;
const classes = options.classes;
const ellipsisText = options.ellipsisText;
const doublePagerDelta = 2 * pagerDelta;
let previousPage = currentPage - pagerDelta;
let nextPage = currentPage + pagerDelta;
if (currentPage < 4 - pagerDelta + doublePagerDelta) {
nextPage = 3 + doublePagerDelta;
}
else if (currentPage > pagesLength - (3 - pagerDelta + doublePagerDelta)) {
previousPage = pagesLength - (2 + doublePagerDelta);
}
const paginationListItemsToModify = [];
for (let k = 1; k <= pagesLength; k++) {
if (1 == k || k == pagesLength || (k >= previousPage && k <= nextPage)) {
const li = paginationListItems[k - 1];
paginationListItemsToModify.push(li);
}
}
let previousLi;
const modifiedLis = [];
paginationListItemsToModify.forEach(li => {
const pageNumber = parseInt(li.childNodes[0].attributes["data-page"], 10);
if (previousLi) {
const previousPageNumber = parseInt(previousLi.childNodes[0].attributes["data-page"], 10);
if (pageNumber - previousPageNumber == 2) {
modifiedLis.push(paginationListItems[previousPageNumber]);
}
else if (pageNumber - previousPageNumber != 1) {
const newLi = {
nodeName: "LI",
attributes: {
class: `${classes.paginationListItem} ${classes.ellipsis} ${classes.disabled}`
},
childNodes: [
{
nodeName: "A",
attributes: {
class: classes.paginationListItemLink
},
childNodes: [
{
nodeName: "#text",
data: ellipsisText
}
]
}
]
};
modifiedLis.push(newLi);
}
}
modifiedLis.push(li);
previousLi = li;
});
return modifiedLis;
};
const paginationListItem = (page, label, options, state = {}) => ({
nodeName: "LI",
attributes: {
class: (state.active && !state.hidden) ?
`${options.classes.paginationListItem} ${options.classes.active}` :
state.hidden ?
`${options.classes.paginationListItem} ${options.classes.hidden} ${options.classes.disabled}` :
options.classes.paginationListItem
},
childNodes: [
{
nodeName: "A",
attributes: {
"data-page": String(page),
class: options.classes.paginationListItemLink
},
childNodes: [
{
nodeName: "#text",
data: label
}
]
}
]
});
const createVirtualPagerDOM = (onFirstPage, onLastPage, currentPage, totalPages, options) => {
let pagerListItems = [];
// first button
if (options.firstLast) {
pagerListItems.push(paginationListItem(1, options.firstText, options));
}
// prev button
if (options.nextPrev) {
const prev = onFirstPage ? 1 : currentPage - 1;
pagerListItems.push(paginationListItem(prev, options.prevText, options, { hidden: onFirstPage }));
}
let pages = [...Array(totalPages).keys()].map(index => paginationListItem(index + 1, String(index + 1), options, { active: (index === (currentPage - 1)) }));
if (options.truncatePager) {
// truncate the paginationListItems
pages = truncate(pages, currentPage, totalPages, options);
}
// append the paginationListItems
pagerListItems = pagerListItems.concat(pages);
// next button
if (options.nextPrev) {
const next = onLastPage ? totalPages : currentPage + 1;
pagerListItems.push(paginationListItem(next, options.nextText, options, { hidden: onLastPage }));
}
// last button
if (options.firstLast) {
pagerListItems.push(paginationListItem(totalPages, options.lastText, options));
}
const pager = {
nodeName: "UL",
attributes: {
class: options.classes.paginationList
},
childNodes: pages.length > 1 ? pagerListItems : [] // Don't show single page
};
return pager;
};
class DataTable {
constructor(table, options = {}) {
const dom = typeof table === "string" ?
document.querySelector(table) :
table;
if (dom instanceof HTMLTableElement) {
this.dom = dom;
}
else {
this.dom = document.createElement("table");
dom.appendChild(this.dom);
}
const diffDomOptions = {
...defaultConfig$1.diffDomOptions,
...options.diffDomOptions
};
const labels = {
...defaultConfig$1.labels,
...options.labels
};
const classes = {
...defaultConfig$1.classes,
...options.classes
};
// user options
this.options = {
...defaultConfig$1,
...options,
diffDomOptions,
labels,
classes
};
this._initialInnerHTML = this.options.destroyable ? this.dom.innerHTML : ""; // preserve in case of later destruction
if (this.options.tabIndex) {
this.dom.tabIndex = this.options.tabIndex;
}
else if (this.options.rowNavigation && this.dom.tabIndex === -1) {
this.dom.tabIndex = 0;
}
this._listeners = {
onResize: () => this._onResize()
};
this._dd = new DiffDOM(this.options.diffDomOptions || {});
this.initialized = false;
this._events = {};
this._currentPage = 0;
this.onFirstPage = true;
this.hasHeadings = false;
this.hasRows = false;
this._searchQueries = [];
this.init();
}
/**
* Initialize the instance
*/
init() {
if (this.initialized || this.dom.classList.contains(this.options.classes.table)) {
return false;
}
this._virtualDOM = nodeToObj(this.dom, this.options.diffDomOptions || {});
this._tableAttributes = { ...this._virtualDOM.attributes };
this.rows = new Rows(this);
this.columns = new Columns(this);
this.data = readTableData(this.options.data, this.dom, this.columns.settings, this.options.type, this.options.format);
this._render();
setTimeout(() => {
this.emit("datatable.init");
this.initialized = true;
}, 10);
}
/**
* Render the instance
*/
_render() {
// Build
this.wrapperDOM = createElement("div", {
class: `${this.options.classes.wrapper} ${this.options.classes.loading}`
});
this.wrapperDOM.innerHTML = this.options.template(this.options, this.dom);
const selector = this.wrapperDOM.querySelector(`select.${this.options.classes.selector}`);
// Per Page Select
if (selector && this.options.paging && this.options.perPageSelect) {
// Create the options
this.options.perPageSelect.forEach((choice) => {
const [lab, val] = Array.isArray(choice) ? [choice[0], choice[1]] : [String(choice), choice];
const selected = val === this.options.perPage;
const option = new Option(lab, String(val), selected, selected);
selector.appendChild(option);
});
}
else if (selector) {
selector.parentElement.removeChild(selector);
}
this.containerDOM = this.wrapperDOM.querySelector(`.${this.options.classes.container}`);
this._pagerDOMs = [];
Array.from(this.wrapperDOM.querySelectorAll(`.${this.options.classes.pagination}`)).forEach(el => {
if (!(el instanceof HTMLElement)) {
return;
}
// We remove the inner part of the pager containers to ensure they are all the same.
el.innerHTML = `<ul class="${this.options.classes.paginationList}"></ul>`;
this._pagerDOMs.push(el.firstElementChild);
});
this._virtualPagerDOM = {
nodeName: "UL",
attributes: {
class: this.options.classes.paginationList
}
};
this._label = this.wrapperDOM.querySelector(`.${this.options.classes.info}`);
// Insert in to DOM tree
this.dom.parentElement.replaceChild(this.wrapperDOM, this.dom);
this.containerDOM.appendChild(this.dom);
// Store the table dimensions
this._rect = this.dom.getBoundingClientRect();
// // Fix height
this._fixHeight();
//
// Class names
if (!this.options.header) {
this.wrapperDOM.classList.add("no-header");
}
if (!this.options.footer) {
this.wrapperDOM.classList.add("no-footer");
}
if (this.options.sortable) {
this.wrapperDOM.classList.add("sortable");
}
if (this.options.searchable) {
this.wrapperDOM.classList.add("searchable");
}
if (this.options.fixedHeight) {
this.wrapperDOM.classList.add("fixed-height");
}
if (this.options.fixedColumns) {
this.wrapperDOM.classList.add("fixed-columns");
}
this._bindEvents();
if (this.columns._state.sort) {
this.columns.sort(this.columns._state.sort.column, this.columns._state.sort.dir, true);
}
this.update(true);
}
_renderTable(renderOptions = {}) {
let newVirtualDOM = dataToVirtualDOM(this._tableAttributes, this.data.headings, (this.options.paging || this._searchQueries.length || this.columns._state.filters.length) && this._currentPage && this.pages.length && !renderOptions.noPaging ?
this.pages[this._currentPage - 1] :
this.data.data.map((row, index) => ({
row,
index
})), this.columns.settings, this.columns._state, this.rows.cursor, this.options, renderOptions);
if (this.options.tableRender) {
const renderedTableVirtualDOM = this.options.tableRender(this.data, newVirtualDOM, "main");
if (renderedTableVirtualDOM) {
newVirtualDOM = renderedTableVirtualDOM;
}
}
const diff = this._dd.diff(this._virtualDOM, newVirtualDOM);
this._dd.apply(this.dom, diff);
this._virtualDOM = newVirtualDOM;
}
/**
* Render the page
* @return {Void}
*/
_renderPage(lastRowCursor = false) {
if (this.hasRows && this.totalPages) {
if (this._currentPage > this.totalPages) {
this._currentPage = 1;
}
// Use a fragment to limit touching the DOM
this._renderTable();
this.onFirstPage = this._currentPage === 1;
this.onLastPage = this._currentPage === this.lastPage;
}
else {
this.setMessage(this.options.labels.noRows);
}
// Update the info
let current = 0;
let f = 0;
let t = 0;
let items;
if (this.totalPages) {
current = this._currentPage - 1;
f = current * this.options.perPage;
t = f + this.pages[current].length;
f = f + 1;
items = this._searchQueries.length ? this._searchData.length : this.data.data.length;
}
if (this._label && this.options.labels.info.length) {
// CUSTOM LABELS
const string = this.options.labels.info
.replace("{start}", String(f))
.replace("{end}", String(t))
.replace("{page}", String(this._currentPage))
.replace("{pages}", String(this.totalPages))
.replace("{rows}", String(items));
this._label.innerHTML = items ? string : "";
}
if (this._currentPage == 1) {
this._fixHeight();
}
if (this.options.rowNavigation && this._currentPage) {
if (!this.rows.cursor || !this.pages[this._currentPage - 1].find(row => row.index === this.rows.cursor)) {
const rows = this.pages[this._currentPage - 1];
if (rows.length) {
if (lastRowCursor) {
this.rows.setCursor(rows[rows.length - 1].index);
}
else {
this.rows.setCursor(rows[0].index);
}
}
}
}
}
/** Render the pager(s)
*
*/
_renderPagers() {
if (!this.options.paging) {
return;
}
let newPagerVirtualDOM = createVirtualPagerDOM(this.onFirstPage, this.onLastPage, this._currentPage, this.totalPages, this.options);
if (this.options.pagerRender) {
const renderedPagerVirtualDOM = this.options.pagerRender([this.onFirstPage, this.onLastPage, this._currentPage, this.totalPages], newPagerVirtualDOM);
if (renderedPagerVirtualDOM) {
newPagerVirtualDOM = renderedPagerVirtualDOM;
}
}
const diffs = this._dd.diff(this._virtualPagerDOM, newPagerVirtualDOM);
// We may have more than one pager
this._pagerDOMs.forEach((pagerDOM) => {
this._dd.apply(pagerDOM, diffs);
});
this._virtualPagerDOM = newPagerVirtualDOM;
}
// Render header that is not in the same table element as the remainder
// of the table. Used for tables with scrollY.
_renderSeparateHeader() {
const container = this.dom.parentElement;
if (!this.headerDOM) {
this.headerDOM = document.createElement("div");
this._virtualHeaderDOM = {
nodeName: "DIV"
};
}
container.parentElement.insertBefore(this.headerDOM, container);
let tableVirtualDOM = {
nodeName: "TABLE",
attributes: this._tableAttributes,
childNodes: [
{
nodeName: "THEAD",
childNodes: [
headingsToVirtualHeaderRowDOM(this.data.headings, this.columns.settings, this.columns._state, this.options, { unhideHeader: true })
]
}
]
};
tableVirtualDOM.attributes.class = tableVirtualDOM.attributes.class ? `${tableVirtualDOM.attributes.class} ${this.options.classes.table}` : this.options.classes.table;
if (this.options.tableRender) {
const renderedTableVirtualDOM = this.options.tableRender(this.data, tableVirtualDOM, "header");
if (renderedTableVirtualDOM) {
tableVirtualDOM = renderedTableVirtualDOM;
}
}
const newVirtualHeaderDOM = {
nodeName: "DIV",
attributes: {
class: this.options.classes.headercontainer
},
childNodes: [tableVirtualDOM]
};
const diff = this._dd.diff(this._virtualHeaderDOM, newVirtualHeaderDOM);
this._dd.apply(this.headerDOM, diff);
this._virtualHeaderDOM = newVirtualHeaderDOM;
// Compensate for scrollbars
const paddingRight = this.headerDOM.firstElementChild.clientWidth - this.dom.clientWidth;
if (paddingRight) {
const paddedVirtualHeaderDOM = structuredClone(this._virtualHeaderDOM);
paddedVirtualHeaderDOM.attributes.style = `padding-right: ${paddingRight}px;`;
const diff = this._dd.diff(this._virtualHeaderDOM, paddedVirtualHeaderDOM);
this._dd.apply(this.headerDOM, diff);
this._virtualHeaderDOM = paddedVirtualHeaderDOM;
}
if (container.scrollHeight > container.clientHeight) {
// scrollbars on one page means scrollbars on all pages.
container.style.overflowY = "scroll";
}
}
/**
* Bind event listeners
* @return {[type]} [description]
*/
_bindEvents() {
// Per page selector
if (this.options.perPageSelect) {
const selector = this.wrapperDOM.querySelector(`select.${this.options.classes.selector}`);
if (selector && selector instanceof HTMLSelectElement) {
// Change per page
selector.addEventListener("change", () => {
this.options.perPage = parseInt(selector.value, 10);
this.update();
this._fixHeight();
this.emit("datatable.perpage", this.options.perPage);
}, false);
}
}
// Search input
if (this.options.searchable) {
this.wrapperDOM.addEventListener("input", (event) => {
const target = event.target;
if (!(target instanceof HTMLInputElement) || !target.matches(`.${this.options.classes.input}`)) {
return;
}
event.preventDefault();
const searches = Array.from(this.wrapperDOM.querySelectorAll(`.${this.options.classes.input}`)).filter(el => el.value.length).map(el => el.dataset.columns ?
{ term: el.value,
columns: JSON.parse(el.dataset.columns) } :
{ term: el.value,
columns: undefined });
if (searches.length === 1) {
const search = searches[0];
this.search(search.term, search.columns);
}
else {
this.multiSearch(searches);
}
});
}
// Pager(s) / sorting
this.wrapperDOM.addEventListener("click", (event) => {
const target = event.target;
const hyperlink = target.closest("a");
if (!hyperlink) {
return;
}
if (hyperlink.hasAttribute("data-page")) {
this.page(parseInt(hyperlink.getAttribute("data-page"), 10));
event.preventDefault();
}
else if (hyperlink.classList.contains(this.options.classes.sorter)) {
const visibleIndex = Array.from(hyperlink.parentElement.parentElement.children).indexOf(hyperlink.parentElement);
const columnIndex = visibleToColumnIndex(visibleIndex, this.columns.settings);
this.columns.sort(columnIndex);
event.preventDefault();
}
else if (hyperlink.classList.contains(this.options.classes.filter)) {
const visibleIndex = Array.from(hyperlink.parentElement.parentElement.children).indexOf(hyperlink.parentElement);
const columnIndex = visibleToColumnIndex(visibleIndex, this.columns.settings);
this.columns.filter(columnIndex);
event.preventDefault();
}
}, false);
if (this.options.rowNavigation) {
this.dom.addEventListener("keydown", (event) => {
if (event.key === "ArrowUp") {
event.preventDefault();
event.stopPropagation();
let lastRow;
this.pages[this._currentPage - 1].find((row) => {
if (row.index === this.rows.cursor) {
return true;
}
lastRow = row;
return false;
});
if (lastRow) {
this.rows.setCursor(lastRow.index);
}
else if (!this.onFirstPage) {
this.page(this._currentPage - 1, true);
}
}
else if (event.key === "ArrowDown") {
event.preventDefault();
event.stopPropagation();
let foundRow;
const nextRow = this.pages[this._currentPage - 1].find((row) => {
if (foundRow) {
return true;
}
if (row.index === this.rows.cursor) {
foundRow = true;
}
return false;
});
if (nextRow) {
this.rows.setCursor(nextRow.index);
}
else if (!this.onLastPage) {
this.page(this._currentPage + 1);
}
}
else if (["Enter", " "].includes(event.key)) {
this.emit("datatable.selectrow", this.rows.cursor, event);
}
});
this.dom.addEventListener("mousedown", (event) => {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
if (this.dom.matches(":focus")) {
const row = Array.from(this.dom.querySelectorAll("body tr")).find(row => row.contains(target));
if (row && row instanceof HTMLElement) {
this.emit("datatable.selectrow", parseInt(row.dataset.index, 10), event);
}
}
});
}
else {
this.dom.addEventListener("mousedown", (event) => {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
const row = Array.from(this.dom.querySelectorAll("body tr")).find(row => row.contains(target));
if (row && row instanceof HTMLElement) {
this.emit("datatable.selectrow", parseInt(row.dataset.index, 10), event);
}
});
}
window.addEventListener("resize", this._listeners.onResize);
}
/**
* execute on resize
*/
_onResize() {
this._rect = this.containerDOM.getBoundingClientRect();
if (!this._rect.width) {
// No longer shown, likely no longer part of DOM. Give up.
return;
}
this.update(true);
}
/**
* Destroy the instance
* @return {void}
*/
destroy() {
if (!this.options.destroyable) {
return;
}
this.dom.innerHTML = this._initialInnerHTML;
// Remove the className
this.dom.classList.remove(this.options.classes.table);
// Remove the containers
if (this.wrapperDOM.parentElement) {
this.wrapperDOM.parentElement.replaceChild(this.dom, this.wrapperDOM);
}
this.initialized = false;
window.removeEventListener("resize", this._listeners.onResize);
}
/**
* Update the instance
* @return {Void}
*/
update(measureWidths = false) {
if (measureWidths) {
this.columns._measureWidths();
this.hasRows = Boolean(this.data.data.length);
this.hasHeadings = Boolean(this.data.headings.length);
}
this.wrapperDOM.classList.remove(this.options.classes.empty);
this._paginate();
this._renderPage();
this._renderPagers();
if (this.options.scrollY.length) {
this._renderSeparateHeader();
}
this.emit("datatable.update");
}
_paginate() {
let rows = this.data.data.map((row, index) => ({
row,
index
}));
if (this._searchQueries.length) {
rows = [];
this._searchData.forEach((index) => rows.push({ index,
row: this.data.data[index] }));
}
if (this.columns._state.filters.length) {
this.columns._state.filters.forEach((filterState, column) => {
if (!filterState) {
return;
}
rows = rows.filter((row) => typeof filterState === "function" ? filterState(row.row[column].data) : (row.row[column].text ?? row.row[column].data) === filterState);
});
}
if (this.options.paging && this.options.perPage > 0) {
// Check for hidden columns
this.pages = rows
.map((row, i) => i % this.options.perPage === 0 ? rows.slice(i, i + this.options.perPage) : null)
.filter((page) => page);
}
else {
this.pages = [rows];
}
this.totalPages = this.lastPage = this.pages.length;
if (!this._currentPage) {
this._currentPage = 1;
}
return this.totalPages;
}
/**
* Fix the container height
*/
_fixHeight() {
if (this.options.fixedHeight) {
this.containerDOM.style.height = null;
this._rect = this.containerDOM.getBoundingClientRect();
this.containerDOM.style.height = `${this._rect.height}px`;
}
}
/**
* Perform a simple search of the data set
*/
search(term, columns = undefined) {
if (!term.length) {
this._currentPage = 1;
this._searchQueries = [];
this._searchData = [];
this.update();
this.emit("datatable.search", "", []);
this.wrapperDOM.classList.remove("search-results");
return false;
}
this.multiSearch([
{ term,
columns: columns ? columns : undefined }
]);
this.emit("datatable.search", term, this._searchData);
}
/**
* Perform a search of the data set seraching for up to multiple strings in various columns
*/
multiSearch(queries) {
if (!this.hasRows)
return false;
this._currentPage = 1;
this._searchQueries = queries;
this._searchData = [];
queries = queries.filter(query => query.term.length);
if (!queries.length) {
this.update();
this.emit("datatable.multisearch", queries, this._searchData);
this.wrapperDOM.classList.remove("search-results");
return false;
}
const queryWords = queries.map(query => this.columns.settings.map((column, index) => {
if (column.hidden || !column.searchable || (query.columns && !query.columns.includes(index))) {
return false;
}
let columnQuery = query.term;
const sensitivity = column.sensitivity || this.options.sensitivity;
if (["base", "accent"].includes(sensitivity)) {
columnQuery = columnQuery.toLowerCase();
}
if (["base", "case"].includes(sensitivity)) {
columnQuery = columnQuery.normalize("NFD").replace(/\p{Diacritic}/gu, "");
}
const ignorePunctuation = column.ignorePunctuation || this.options.ignorePunctuation;
if (ignorePunctuation) {
columnQuery = columnQuery.replace(/[.,/#!$%^&*;:{}=-_`~()]/g, "");
}
return columnQuery;
}));
this.data.data.forEach((row, idx) => {
const searchRow = row.map((cell, i) => {
let content = (cell.text || String(cell.data)).trim();
if (content.length) {
const column = this.columns.settings[i];
const sensitivity = column.sensitivity || this.options.sensitivity;
if (["base", "accent"].includes(sensitivity)) {
content = content.toLowerCase();
}
if (["base", "case"].includes(sensitivity)) {
content = content.normalize("NFD").replace(/\p{Diacritic}/gu, "");
}
const ignorePunctuation = column.ignorePunctuation || this.options.ignorePunctuation;
if (ignorePunctuation) {
content = content.replace(/[.,/#!$%^&*;:{}=-_`~()]/g, "");
}
}
return content;
});
if (queryWords.every(queries => queries.find((query, index) => query ?
(this.columns.settings[index].isSplitQueryWord ? query.split(this.columns.settings[index].searchQuerySeparator) : [query]).find(queryWord => searchRow[index].includes(queryWord.trim())) :
false))) {
this._searchData.push(idx);
}
});
this.wrapperDOM.classList.add("search-results");
if (this._searchData.length) {
this.update();
}
else {
this.wrapperDOM.classList.remove("search-results");
this.setMessage(this.options.labels.noResults);
}
this.emit("datatable.multisearch", queries, this._searchData);
}
/**
* Change page
*/
page(page, lastRowCursor = false) {
// We don't want to load the current page again.
if (page === this._currentPage) {
return false;
}
if (!isNaN(page)) {
this._currentPage = page;
}
if (page > this.pages.length || page < 0) {
return false;
}
this._renderPage(lastRowCursor);
this._renderPagers();
this.emit("datatable.page", page);
}
/**
* Add new row data
*/
insert(data) {
let rows = [];
if (Array.isArray(data)) {
const headings = this.data.headings.map((heading) => heading.text ?? String(heading.data));
data.forEach((row, rIndex) => {
const r = [];
Object.entries(row).forEach(([heading, cell]) => {
const index = headings.indexOf(heading);
if (index > -1) {
r[index] = readDataCell(cell, this.columns.settings[index]);
}
else if (!this.hasHeadings && !this.hasRows && rIndex === 0) {
r[headings.length] = readDataCell(cell, this.columns.settings[headings.length]);
headings.push(heading);
this.data.headings.push(readHeaderCell(heading));
}
});
rows.push(r);
});
}
else if (isObject(data)) {
if (data.headings && !this.hasHeadings && !this.hasRows) {
this.data = readTableData(data, undefined, this.columns.settings, this.options.type, this.options.format);
}
else if (data.data && Array.isArray(data.data)) {
rows = data.data.map(row => row.map((cell, index) => readDataCell(cell, this.columns.settings[index])));
}
}
if (rows.length) {
rows.forEach((row) => this.data.data.push(row));
}
this.hasHeadings = Boolean(this.data.headings.length);
if (this.columns._state.sort) {
this.columns.sort(this.columns._state.sort.column, this.columns._state.sort.dir, true);
}
this.update(true);
}
/**
* Refresh the instance
*/
refresh() {
if (this.options.searchable) {
Array.from(this.wrapperDOM.querySelectorAll(`.${this.options.classes.input}`)).forEach(el => {
el.value = "";
});
this._searchQueries = [];
}
this._currentPage = 1;
this.onFirstPage = true;
this.update(true);
this.emit("datatable.refresh");
}
/**
* Print the table
*/
print() {
const tableDOM = createElement("table");
const tableVirtualDOM = { nodeName: "TABLE" };
let newTableVirtualDOM = dataToVirtualDOM(this._tableAttributes, this.data.headings, this.data.data.map((row, index) => ({
row,
index
})), this.columns.settings, this.columns._state, false, // No row cursor
this.options, {
noColumnWidths: true,
unhideHeader: true
});
if (this.options.tableRender) {
const renderedTableVirtualDOM = this.options.tableRender(this.data, newTableVirtualDOM, "print");
if (renderedTableVirtualDOM) {
newTableVirtualDOM = renderedTableVirtualDOM;
}
}
const diff = this._dd.diff(tableVirtualDOM, newTableVirtualDOM);
this._dd.apply(tableDOM, diff);
// Open new window
const w = window.open();
// Append the table to the body
w.document.body.appendChild(tableDOM);
// Print
w.print();
}
/**
* Show a message in the table
*/
setMessage(message) {
const activeHeadings = this.data.headings.filter((heading, index) => !this.columns.settings[index]?.hidden);
const colspan = activeHeadings.length || 1;
this.wrapperDOM.classList.add(this.options.classes.empty);
if (this._label) {
this._label.innerHTML = "";
}
this.totalPages = 0;
this._renderPagers();
let newVirtualDOM = {
nodeName: "TABLE",
attributes: this._tableAttributes,
childNodes: [
{
nodeName: "THEAD",
childNodes: [
headingsToVirtualHeaderRowDOM(this.data.headings, this.columns.settings, this.columns._state, this.options, {})
]
},
{
nodeName: "TBODY",
childNodes: [
{
nodeName: "TR",
childNodes: [
{
nodeName: "TD",
attributes: {
class: this.options.classes.empty,
colspan: String(colspan)
},
childNodes: [
{
nodeName: "#text",
data: message
}
]
}
]
}
]
}
]
};
newVirtualDOM.attributes.class = newVirtualDOM.attributes.class ? `${newVirtualDOM.attributes.class} ${this.options.classes.table}` : this.options.classes.table;
if (this.options.tableRender) {
const renderedTableVirtualDOM = this.options.tableRender(this.data, newVirtualDOM, "message");
if (renderedTableVirtualDOM) {
newVirtualDOM = renderedTableVirtualDOM;
}
}
const diff = this._dd.diff(this._virtualDOM, newVirtualDOM);
this._dd.apply(this.dom, diff);
this._virtualDOM = newVirtualDOM;
}
/**
* Add custom event listener
*/
on(event, callback) {
this._events[event] = this._events[event] || [];
this._events[event].push(callback);
}
/**
* Remove custom event listener
*/
off(event, callback) {
if (event in this._events === false)
return;
this._events[event].splice(this._events[event].indexOf(callback), 1);
}
/**
* Fire custom event
*/
emit(event, ...args) {
if (event in this._events === false)
return;
for (let i = 0; i < this._events[event].length; i++) {
this._events[event][i](...args);
}
}
}
/**
* Convert CSV data to fit the format used in the table.
*/
const convertCSV = function (userOptions) {
let obj;
const defaults = {
lineDelimiter: "\n",
columnDelimiter: ",",
removeDoubleQuotes: false
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
if (options.data.length) {
// Import CSV
obj = {
data: []
};
// Split the string into rows
const rows = options.data.split(options.lineDelimiter);
if (rows.length) {
if (options.headings) {
obj.headings = rows[0].split(options.columnDelimiter);
if (options.removeDoubleQuotes) {
obj.headings = obj.headings.map((e) => e.trim().replace(/(^"|"$)/g, ""));
}
rows.shift();
}
rows.forEach((row, i) => {
obj.data[i] = [];
// Split the rows into values
const values = row.split(options.columnDelimiter);
if (values.length) {
values.forEach((value) => {
if (options.removeDoubleQuotes) {
value = value.trim().replace(/(^"|"$)/g, "");
}
obj.data[i].push(value);
});
}
});
}
if (obj) {
return obj;
}
}
return false;
};
/**
* Convert JSON data to fit the format used in the table.
*/
const convertJSON = function (userOptions) {
let obj;
const defaults = {
data: ""
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
if (options.data.length || isObject(options.data)) {
// Import JSON
const json = isJson(options.data) ? JSON.parse(options.data) : false;
// Valid JSON string
if (json) {
obj = {
headings: [],
data: []
};
json.forEach((data, i) => {
obj.data[i] = [];
Object.entries(data).forEach(([column, value]) => {
if (!obj.headings.includes(column)) {
obj.headings.push(column);
}
obj.data[i].push(value);
});
});
}
else {
console.warn("That's not valid JSON!");
}
if (obj) {
return obj;
}
}
return false;
};
const exportCSV = function (dt, userOptions = {}) {
if (!dt.hasHeadings && !dt.hasRows)
return false;
const defaults = {
download: true,
skipColumn: [],
lineDelimiter: "\n",
columnDelimiter: ","
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
const columnShown = (index) => !options.skipColumn.includes(index) && !dt.columns.settings[index]?.hidden;
let rows = [];
const headers = dt.data.headings.filter((_heading, index) => columnShown(index)).map((header) => header.text ?? header.data);
// Include headings
rows[0] = headers;
// Selection or whole table
if (options.selection) {
// Page number
if (Array.isArray(options.selection)) {
// Array of page numbers
for (let i = 0; i < options.selection.length; i++) {
rows = rows.concat(dt.pages[options.selection[i] - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
}
else {
rows = rows.concat(dt.pages[options.selection - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
}
else {
rows = rows.concat(dt.data.data.map((row) => row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
// Only proceed if we have data
if (rows.length) {
let str = "";
rows.forEach(row => {
row.forEach((cell) => {
if (typeof cell === "string") {
cell = cell.trim();
cell = cell.replace(/\s{2,}/g, " ");
cell = cell.replace(/\n/g, " ");
cell = cell.replace(/"/g, "\"\"");
//have to manually encode "#" as encodeURI leaves it as is.
cell = cell.replace(/#/g, "%23");
if (cell.includes(",")) {
cell = `"${cell}"`;
}
}
str += cell + options.columnDelimiter;
});
// Remove trailing column delimiter
str = str.trim().substring(0, str.length - 1);
// Apply line delimiter
str += options.lineDelimiter;
});
// Remove trailing line delimiter
str = str.trim().substring(0, str.length - 1);
// Download
if (options.download) {
// Create a link to trigger the download
const link = document.createElement("a");
link.href = encodeURI(`data:text/csv;charset=utf-8,${str}`);
link.download = `${options.filename || "datatable_export"}.csv`;
// Append the link
document.body.appendChild(link);
// Trigger the download
link.click();
// Remove the link
document.body.removeChild(link);
}
return str;
}
return false;
};
const exportJSON = function (dt, userOptions = {}) {
if (!dt.hasHeadings && !dt.hasRows)
return false;
const defaults = {
download: true,
skipColumn: [],
replacer: null,
space: 4
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
const columnShown = (index) => !options.skipColumn.includes(index) && !dt.columns.settings[index]?.hidden;
let rows = [];
// Selection or whole table
if (options.selection) {
// Page number
if (Array.isArray(options.selection)) {
// Array of page numbers
for (let i = 0; i < options.selection.length; i++) {
rows = rows.concat(dt.pages[options.selection[i] - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
}
else {
rows = rows.concat(dt.pages[options.selection - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
}
else {
rows = rows.concat(dt.data.data.map((row) => row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
const headers = dt.data.headings.filter((_heading, index) => columnShown(index)).map((header) => header.text ?? String(header.data));
// Only proceed if we have data
if (rows.length) {
const arr = [];
rows.forEach((row, x) => {
arr[x] = arr[x] || {};
row.forEach((cell, i) => {
arr[x][headers[i]] = cell;
});
});
// Convert the array of objects to JSON string
const str = JSON.stringify(arr, options.replacer, options.space);
// Download
if (options.download) {
// Create a link to trigger the download
const blob = new Blob([str], {
type: "data:application/json;charset=utf-8"
});
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${options.filename || "datatable_export"}.json`;
// Append the link
document.body.appendChild(link);
// Trigger the download
link.click();
// Remove the link
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
return str;
}
return false;
};
const exportSQL = function (dt, userOptions = {}) {
if (!dt.hasHeadings && !dt.hasRows)
return false;
const defaults = {
download: true,
skipColumn: [],
tableName: "myTable"
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
const columnShown = (index) => !options.skipColumn.includes(index) && !dt.columns.settings[index]?.hidden;
let rows = [];
// Selection or whole table
if (options.selection) {
// Page number
if (Array.isArray(options.selection)) {
// Array of page numbers
for (let i = 0; i < options.selection.length; i++) {
rows = rows.concat(dt.pages[options.selection[i] - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
}
else {
rows = rows.concat(dt.pages[options.selection - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
}
else {
rows = rows.concat(dt.data.data.map((row) => row.filter((_cell, index) => columnShown(index)).map((cell) => cell.text ?? cell.data)));
}
const headers = dt.data.headings.filter((_heading, index) => columnShown(index)).map((header) => header.text ?? String(header.data));
// Only proceed if we have data
if (rows.length) {
// Begin INSERT statement
let str = `INSERT INTO \`${options.tableName}\` (`;
// Convert table headings to column names
headers.forEach((header) => {
str += `\`${header}\`,`;
});
// Remove trailing comma
str = str.trim().substring(0, str.length - 1);
// Begin VALUES
str += ") VALUES ";
// Iterate rows and convert cell data to column values
rows.forEach((row) => {
str += "(";
row.forEach((cell) => {
if (typeof cell === "string") {
str += `"${cell}",`;
}
else {
str += `${cell},`;
}
});
// Remove trailing comma
str = str.trim().substring(0, str.length - 1);
// end VALUES
str += "),";
});
// Remove trailing comma
str = str.trim().substring(0, str.length - 1);
// Add trailing colon
str += ";";
if (options.download) {
str = `data:application/sql;charset=utf-8,${str}`;
}
// Download
if (options.download) {
// Create a link to trigger the download
const link = document.createElement("a");
link.href = encodeURI(str);
link.download = `${options.filename || "datatable_export"}.sql`;
// Append the link
document.body.appendChild(link);
// Trigger the download
link.click();
// Remove the link
document.body.removeChild(link);
}
return str;
}
return false;
};
const exportTXT = function (dt, userOptions = {}) {
if (!dt.hasHeadings && !dt.hasRows)
return false;
const defaults = {
download: true,
skipColumn: [],
lineDelimiter: "\n",
columnDelimiter: ","
};
// Check for the options object
if (!isObject(userOptions)) {
return false;
}
const options = {
...defaults,
...userOptions
};
const columnShown = (index) => !options.skipColumn.includes(index) && !dt.columns.settings[index]?.hidden;
let rows = [];
const headers = dt.data.headings.filter((_heading, index) => columnShown(index)).map((header) => header.text ?? header.data);
// Include headings
rows[0] = headers;
// Selection or whole table
if (options.selection) {
// Page number
if (Array.isArray(options.selection)) {
// Array of page numbers
for (let i = 0; i < options.selection.length; i++) {
rows = rows.concat(dt.pages[options.selection[i] - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
}
else {
rows = rows.concat(dt.pages[options.selection - 1].map((row) => row.row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
}
else {
rows = rows.concat(dt.data.data.map((row) => row.filter((_cell, index) => columnShown(index)).map((cell) => cell.data)));
}
// Only proceed if we have data
if (rows.length) {
let str = "";
rows.forEach(row => {
row.forEach((cell) => {
if (typeof cell === "string") {
cell = cell.trim();
cell = cell.replace(/\s{2,}/g, " ");
cell = cell.replace(/\n/g, " ");
cell = cell.replace(/"/g, "\"\"");
//have to manually encode "#" as encodeURI leaves it as is.
cell = cell.replace(/#/g, "%23");
if (cell.includes(",")) {
cell = `"${cell}"`;
}
}
str += cell + options.columnDelimiter;
});
// Remove trailing column delimiter
str = str.trim().substring(0, str.length - 1);
// Apply line delimiter
str += options.lineDelimiter;
});
// Remove trailing line delimiter
str = str.trim().substring(0, str.length - 1);
if (options.download) {
str = `data:text/csv;charset=utf-8,${str}`;
}
// Download
if (options.download) {
// Create a link to trigger the download
const link = document.createElement("a");
link.href = encodeURI(str);
link.download = `${options.filename || "datatable_export"}.txt`;
// Append the link
document.body.appendChild(link);
// Trigger the download
link.click();
// Remove the link
document.body.removeChild(link);
}
return str;
}
return false;
};
const defaultConfig = {
classes: {
row: "datatable-editor-row",
form: "datatable-editor-form",
item: "datatable-editor-item",
menu: "datatable-editor-menu",
save: "datatable-editor-save",
block: "datatable-editor-block",
cancel: "datatable-editor-cancel",
close: "datatable-editor-close",
inner: "datatable-editor-inner",
input: "datatable-editor-input",
label: "datatable-editor-label",
modal: "datatable-editor-modal",
action: "datatable-editor-action",
header: "datatable-editor-header",
wrapper: "datatable-editor-wrapper",
editable: "datatable-editor-editable",
container: "datatable-editor-container",
separator: "datatable-editor-separator"
},
labels: {
closeX: "x",
editCell: "Edit Cell",
editRow: "Edit Row",
removeRow: "Remove Row",
reallyRemove: "Are you sure?",
reallyCancel: "Do you really want to cancel?",
save: "Save",
cancel: "Cancel"
},
cancelModal: editor => confirm(editor.options.labels.reallyCancel),
// edit inline instead of using a modal lay-over for editing content
inline: true,
// include hidden columns in the editor
hiddenColumns: false,
// enable the context menu
contextMenu: true,
// event to start editing
clickEvent: "dblclick",
// indexes of columns not to be edited
excludeColumns: [],
// set the context menu items
menuItems: [
{
text: (editor) => editor.options.labels.editCell,
action: (editor, _event) => {
if (!(editor.event.target instanceof Element)) {
return;
}
const cell = editor.event.target.closest("td");
return editor.editCell(cell);
}
},
{
text: (editor) => editor.options.labels.editRow,
action: (editor, _event) => {
if (!(editor.event.target instanceof Element)) {
return;
}
const row = editor.event.target.closest("tr");
return editor.editRow(row);
}
},
{
separator: true
},
{
text: (editor) => editor.options.labels.removeRow,
action: (editor, _event) => {
if (!(editor.event.target instanceof Element)) {
return;
}
if (confirm(editor.options.labels.reallyRemove)) {
const row = editor.event.target.closest("tr");
editor.removeRow(row);
}
}
}
]
};
// Source: https://www.freecodecamp.org/news/javascript-debounce-example/
const debounce = function (func, timeout = 300) {
let timer;
return (..._args) => {
clearTimeout(timer);
timer = window.setTimeout(() => func(), timeout);
};
};
/**
* Main lib
* @param {Object} dataTable Target dataTable
* @param {Object} options User config
*/
class Editor {
constructor(dataTable, options = {}) {
this.dt = dataTable;
this.options = {
...defaultConfig,
...options
};
}
/**
* Init instance
* @return {Void}
*/
init() {
if (this.initialized) {
return;
}
this.dt.wrapperDOM.classList.add(this.options.classes.editable);
if (this.options.inline) {
this.originalRowRender = this.dt.options.rowRender;
this.dt.options.rowRender = (row, tr, index) => {
let newTr = this.rowRender(row, tr, index);
if (this.originalRowRender) {
newTr = this.originalRowRender(row, newTr, index);
}
return newTr;
};
}
if (this.options.contextMenu) {
this.containerDOM = createElement("div", {
id: this.options.classes.container
});
this.wrapperDOM = createElement("div", {
class: this.options.classes.wrapper
});
this.menuDOM = createElement("ul", {
class: this.options.classes.menu
});
if (this.options.menuItems && this.options.menuItems.length) {
this.options.menuItems.forEach((item) => {
const li = createElement("li", {
class: item.separator ? this.options.classes.separator : this.options.classes.item
});
if (!item.separator) {
const a = createElement("a", {
class: this.options.classes.action,
href: item.url || "#",
html: typeof item.text === "function" ? item.text(this) : item.text
});
li.appendChild(a);
if (item.action && typeof item.action === "function") {
a.addEventListener("click", (event) => {
event.preventDefault();
item.action(this, event);
});
}
}
this.menuDOM.appendChild(li);
});
}
this.wrapperDOM.appendChild(this.menuDOM);
this.containerDOM.appendChild(this.wrapperDOM);
this.updateMenu();
}
this.data = {};
this.menuOpen = false;
this.editing = false;
this.editingRow = false;
this.editingCell = false;
this.bindEvents();
setTimeout(() => {
this.initialized = true;
this.dt.emit("editable.init");
}, 10);
}
/**
* Bind events to DOM
* @return {Void}
*/
bindEvents() {
this.events = {
keydown: this.keydown.bind(this),
click: this.click.bind(this)
};
// listen for click / double-click
this.dt.dom.addEventListener(this.options.clickEvent, this.events.click);
// listen for right-click
document.addEventListener("keydown", this.events.keydown);
if (this.options.contextMenu) {
this.events.context = this.context.bind(this);
this.events.updateMenu = this.updateMenu.bind(this);
this.events.dismissMenu = this.dismissMenu.bind(this);
this.events.reset = debounce(() => this.events.updateMenu(), 50);
// listen for right-click
this.dt.dom.addEventListener("contextmenu", this.events.context);
// listen for click everywhere except the menu
document.addEventListener("click", this.events.dismissMenu);
// Reset contextmenu on browser window changes
window.addEventListener("resize", this.events.reset);
window.addEventListener("scroll", this.events.reset);
}
}
/**
* contextmenu listener
* @param {Object} event Event
* @return {Void}
*/
context(event) {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
this.event = event;
const cell = target.closest("tbody td");
if (!this.disabled && cell) {
event.preventDefault();
// get the mouse position
let x = event.pageX;
let y = event.pageY;
// check if we're near the right edge of window
if (x > this.limits.x) {
x -= this.rect.width;
}
// check if we're near the bottom edge of window
if (y > this.limits.y) {
y -= this.rect.height;
}
this.wrapperDOM.style.top = `${y}px`;
this.wrapperDOM.style.left = `${x}px`;
this.openMenu();
this.updateMenu();
}
}
/**
* dblclick listener
* @param {Object} event Event
* @return {Void}
*/
click(event) {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
if (this.editing && this.data && this.editingCell) {
const input = this.modalDOM ?
this.modalDOM.querySelector(`input.${this.options.classes.input}[type=text]`) :
this.dt.wrapperDOM.querySelector(`input.${this.options.classes.input}[type=text]`);
this.saveCell(input.value);
}
else if (!this.editing) {
const cell = target.closest("tbody td");
if (cell) {
this.editCell(cell);
event.preventDefault();
}
}
}
/**
* keydown listener
* @param {Object} event Event
* @return {Void}
*/
keydown(event) {
if (this.modalDOM) {
if (event.key === "Escape") { // close button
if (this.options.cancelModal(this)) {
this.closeModal();
}
}
else if (event.key === "Enter") { // save button
// Save
if (this.editingCell) {
const input = this.modalDOM.querySelector(`input.${this.options.classes.input}[type=text]`);
this.saveCell(input.value);
}
else {
const inputs = Array.from(this.modalDOM.querySelectorAll(`input.${this.options.classes.input}[type=text]`));
this.saveRow(inputs.map(input => input.value.trim()), this.data.row);
}
}
}
else if (this.editing && this.data) {
if (event.key === "Enter") {
// Enter key saves
if (this.editingCell) {
const input = this.dt.wrapperDOM.querySelector(`input.${this.options.classes.input}[type=text]`);
this.saveCell(input.value);
}
else if (this.editingRow) {
const inputs = Array.from(this.dt.wrapperDOM.querySelectorAll(`input.${this.options.classes.input}[type=text]`));
this.saveRow(inputs.map(input => input.value.trim()), this.data.row);
}
}
else if (event.key === "Escape") {
// Escape key reverts
if (this.editingCell) {
this.saveCell(this.data.content);
}
else if (this.editingRow) {
this.saveRow(null, this.data.row);
}
}
}
}
/**
* Edit cell
* @param {Object} td The HTMLTableCellElement
* @return {Void}
*/
editCell(td) {
const columnIndex = visibleToColumnIndex(td.cellIndex, this.dt.columns.settings);
if (this.options.excludeColumns.includes(columnIndex)) {
this.closeMenu();
return;
}
const rowIndex = parseInt(td.parentElement.dataset.index, 10);
const row = this.dt.data.data[rowIndex];
const cell = row[columnIndex];
this.data = {
cell,
rowIndex,
columnIndex,
content: cell.text || String(cell.data)
};
this.editing = true;
this.editingCell = true;
if (this.options.inline) {
this.dt.update();
}
else {
this.editCellModal();
}
this.closeMenu();
}
editCellModal() {
const cell = this.data.cell;
const columnIndex = this.data.columnIndex;
const label = this.dt.data.headings[columnIndex].text || String(this.dt.data.headings[columnIndex].data);
const template = [
`<div class='${this.options.classes.inner}'>`,
`<div class='${this.options.classes.header}'>`,
`<h4>${this.options.labels.editCell}</h4>`,
`<button class='${this.options.classes.close}' type='button' data-editor-cancel>${this.options.labels.closeX}</button>`,
" </div>",
`<div class='${this.options.classes.block}'>`,
`<form class='${this.options.classes.form}'>`,
`<div class='${this.options.classes.row}'>`,
`<label class='${this.options.classes.label}'>${escapeText(label)}</label>`,
`<input class='${this.options.classes.input}' value='${escapeText(cell.text || String(cell.data) || "")}' type='text'>`,
"</div>",
`<div class='${this.options.classes.row}'>`,
`<button class='${this.options.classes.cancel}' type='button' data-editor-cancel>${this.options.labels.cancel}</button>`,
`<button class='${this.options.classes.save}' type='button' data-editor-save>${this.options.labels.save}</button>`,
"</div>",
"</form>",
"</div>",
"</div>"
].join("");
const modalDOM = createElement("div", {
class: this.options.classes.modal,
html: template
});
this.modalDOM = modalDOM;
this.openModal();
const input = modalDOM.querySelector(`input.${this.options.classes.input}[type=text]`);
input.focus();
input.selectionStart = input.selectionEnd = input.value.length;
// Close / save
modalDOM.addEventListener("click", (event) => {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
if (target.hasAttribute("data-editor-cancel")) { // cancel button
event.preventDefault();
if (this.options.cancelModal(this)) {
this.closeModal();
}
}
else if (target.hasAttribute("data-editor-save")) { // save button
event.preventDefault();
// Save
this.saveCell(input.value);
}
});
}
/**
* Save edited cell
* @param {Object} row The HTMLTableCellElement
* @param {String} value Cell content
* @return {Void}
*/
saveCell(value) {
const oldData = this.data.content;
// Get the type of that column
const type = this.dt.columns.settings[this.data.columnIndex].type || this.dt.options.type;
const stringValue = value.trim();
let cell;
if (type === "number") {
cell = { data: parseFloat(stringValue) };
}
else if (type === "boolean") {
if (["", "false", "0"].includes(stringValue)) {
cell = { data: false,
text: "false",
order: 0 };
}
else {
cell = { data: true,
text: "true",
order: 1 };
}
}
else if (type === "html") {
cell = { data: [
{ nodeName: "#text",
data: value }
],
text: value,
order: value };
}
else if (type === "string") {
cell = { data: value };
}
else if (type === "date") {
const format = this.dt.columns.settings[this.data.columnIndex].format || this.dt.options.format;
cell = { data: value,
order: parseDate(String(value), format) };
}
else {
cell = { data: value };
}
// Set the cell content
this.dt.data.data[this.data.rowIndex][this.data.columnIndex] = cell;
this.closeModal();
const rowIndex = this.data.rowIndex;
const columnIndex = this.data.columnIndex;
this.data = {};
this.dt.update(true);
this.editing = false;
this.editingCell = false;
this.dt.emit("editable.save.cell", value, oldData, rowIndex, columnIndex);
}
/**
* Edit row
* @param {Object} row The HTMLTableRowElement
* @return {Void}
*/
editRow(tr) {
if (!tr || tr.nodeName !== "TR" || this.editing)
return;
const rowIndex = parseInt(tr.dataset.index, 10);
const row = this.dt.data.data[rowIndex];
this.data = {
row,
rowIndex
};
this.editing = true;
this.editingRow = true;
if (this.options.inline) {
this.dt.update();
}
else {
this.editRowModal();
}
this.closeMenu();
}
editRowModal() {
const row = this.data.row;
const template = [
`<div class='${this.options.classes.inner}'>`,
`<div class='${this.options.classes.header}'>`,
`<h4>${this.options.labels.editRow}</h4>`,
`<button class='${this.options.classes.close}' type='button' data-editor-cancel>${this.options.labels.closeX}</button>`,
" </div>",
`<div class='${this.options.classes.block}'>`,
`<form class='${this.options.classes.form}'>`,
`<div class='${this.options.classes.row}'>`,
`<button class='${this.options.classes.cancel}' type='button' data-editor-cancel>${this.options.labels.cancel}</button>`,
`<button class='${this.options.classes.save}' type='button' data-editor-save>${this.options.labels.save}</button>`,
"</div>",
"</form>",
"</div>",
"</div>"
].join("");
const modalDOM = createElement("div", {
class: this.options.classes.modal,
html: template
});
const inner = modalDOM.firstElementChild;
if (!inner) {
return;
}
const form = inner.lastElementChild?.firstElementChild;
if (!form) {
return;
}
// Add the inputs for each cell
row.forEach((cell, i) => {
const columnSettings = this.dt.columns.settings[i];
if ((!columnSettings.hidden || (columnSettings.hidden && this.options.hiddenColumns)) && !this.options.excludeColumns.includes(i)) {
const label = this.dt.data.headings[i].text || String(this.dt.data.headings[i].data);
form.insertBefore(createElement("div", {
class: this.options.classes.row,
html: [
`<div class='${this.options.classes.row}'>`,
`<label class='${this.options.classes.label}'>${escapeText(label)}</label>`,
`<input class='${this.options.classes.input}' value='${escapeText(cell.text || String(cell.data) || "")}' type='text'>`,
"</div>"
].join("")
}), form.lastElementChild);
}
});
this.modalDOM = modalDOM;
this.openModal();
// Grab the inputs
const inputs = Array.from(form.querySelectorAll(`input.${this.options.classes.input}[type=text]`));
// Remove save button
inputs.pop();
// Close / save
modalDOM.addEventListener("click", (event) => {
const target = event.target;
if (!(target instanceof Element)) {
return;
}
if (target.hasAttribute("data-editor-cancel")) { // cancel button
if (this.options.cancelModal(this)) {
this.closeModal();
}
}
else if (target.hasAttribute("data-editor-save")) { // save button
// Save
this.saveRow(inputs.map((input) => input.value.trim()), this.data.row);
}
});
}
/**
* Save edited row
* @param {Object} row The HTMLTableRowElement
* @param {Array} data Cell data
* @return {Void}
*/
saveRow(data, row) {
// Store the old data for the emitter
const oldData = row.map((cell) => cell.text ?? String(cell.data));
if (data) {
this.dt.data.data[this.data.rowIndex] = this.dt.data.data[this.data.rowIndex].map((oldCell, colIndex) => {
const columnSetting = this.dt.columns.settings[colIndex];
if (columnSetting.hidden || this.options.excludeColumns.includes(colIndex)) {
return oldCell;
}
const type = this.dt.columns.settings[colIndex].type || this.dt.options.type;
const value = data[columnToVisibleIndex(colIndex, this.dt.columns.settings)];
const stringValue = value.trim();
let cell;
if (type === "number") {
cell = { data: parseFloat(stringValue) };
}
else if (type === "boolean") {
if (["", "false", "0"].includes(stringValue)) {
cell = { data: false,
text: "false",
order: 0 };
}
else {
cell = { data: true,
text: "true",
order: 1 };
}
}
else if (type === "html") {
cell = { data: [
{ nodeName: "#text",
data: value }
],
text: value,
order: value };
}
else if (type === "string") {
cell = { data: value };
}
else if (type === "date") {
const format = this.dt.columns.settings[colIndex].format || this.dt.options.format;
cell = { data: value,
order: parseDate(String(value), format) };
}
else {
cell = { data: value };
}
return cell;
});
}
const updatedRow = this.dt.data.data[this.data.rowIndex];
const newData = updatedRow.map(cell => cell.text ?? String(cell.data));
this.data = {};
this.dt.update(true);
this.closeModal();
this.editing = false;
this.dt.emit("editable.save.row", newData, oldData, row);
}
/**
* Open the row editor modal
* @return {Void}
*/
openModal() {
if (this.modalDOM) {
document.body.appendChild(this.modalDOM);
}
}
/**
* Close the row editor modal
* @return {Void}
*/
closeModal() {
if (this.editing && this.modalDOM) {
document.body.removeChild(this.modalDOM);
this.modalDOM = this.editing = this.editingRow = this.editingCell = false;
}
}
/**
* Remove a row
* @param {Object} tr The HTMLTableRowElement
* @return {Void}
*/
removeRow(tr) {
if (!tr || tr.nodeName !== "TR" || this.editing)
return;
const index = parseInt(tr.dataset.index, 10);
this.dt.rows.remove(index);
this.closeMenu();
}
/**
* Update context menu position
* @return {Void}
*/
updateMenu() {
const scrollX = window.scrollX || window.pageXOffset;
const scrollY = window.scrollY || window.pageYOffset;
this.rect = this.wrapperDOM.getBoundingClientRect();
this.limits = {
x: window.innerWidth + scrollX - this.rect.width,
y: window.innerHeight + scrollY - this.rect.height
};
}
/**
* Dismiss the context menu
* @param {Object} event Event
* @return {Void}
*/
dismissMenu(event) {
const target = event.target;
if (!(target instanceof Element) || this.wrapperDOM.contains(target)) {
return;
}
let valid = true;
if (this.editing) {
valid = !(target.matches(`input.${this.options.classes.input}[type=text]`));
}
if (valid) {
this.closeMenu();
}
}
/**
* Open the context menu
* @return {Void}
*/
openMenu() {
if (this.editing && this.data && this.editingCell) {
const input = this.modalDOM ?
this.modalDOM.querySelector(`input.${this.options.classes.input}[type=text]`) :
this.dt.wrapperDOM.querySelector(`input.${this.options.classes.input}[type=text]`);
this.saveCell(input.value);
}
document.body.appendChild(this.containerDOM);
this.menuOpen = true;
this.dt.emit("editable.context.open");
}
/**
* Close the context menu
* @return {Void}
*/
closeMenu() {
if (this.menuOpen) {
this.menuOpen = false;
document.body.removeChild(this.containerDOM);
this.dt.emit("editable.context.close");
}
}
/**
* Destroy the instance
* @return {Void}
*/
destroy() {
this.dt.dom.removeEventListener(this.options.clickEvent, this.events.click);
this.dt.dom.removeEventListener("contextmenu", this.events.context);
document.removeEventListener("click", this.events.dismissMenu);
document.removeEventListener("keydown", this.events.keydown);
window.removeEventListener("resize", this.events.reset);
window.removeEventListener("scroll", this.events.reset);
if (document.body.contains(this.containerDOM)) {
document.body.removeChild(this.containerDOM);
}
if (this.options.inline) {
this.dt.options.rowRender = this.originalRowRender;
}
this.initialized = false;
}
rowRender(row, tr, index) {
if (!this.data || this.data.rowIndex !== index) {
return tr;
}
if (this.editingCell) {
// cell editing
const cell = tr.childNodes[columnToVisibleIndex(this.data.columnIndex, this.dt.columns.settings)];
cell.childNodes = [
{
nodeName: "INPUT",
attributes: {
type: "text",
value: this.data.content,
class: this.options.classes.input
}
}
];
}
else {
// row editing
// Add the inputs for each cell
tr.childNodes.forEach((cell, i) => {
const index = visibleToColumnIndex(i, this.dt.columns.settings);
const dataCell = row[index];
if (!this.options.excludeColumns.includes(index)) {
const cell = tr.childNodes[i];
cell.childNodes = [
{
nodeName: "INPUT",
attributes: {
type: "text",
value: escapeText(dataCell.text || String(dataCell.data) || ""),
class: this.options.classes.input
}
}
];
}
});
}
return tr;
}
}
const makeEditable = function (dataTable, options = {}) {
const editor = new Editor(dataTable, options);
if (dataTable.initialized) {
editor.init();
}
else {
dataTable.on("datatable.init", () => editor.init());
}
return editor;
};
export { DataTable, convertCSV, convertJSON, createElement, exportCSV, exportJSON, exportSQL, exportTXT, isJson, isObject, makeEditable };
//# sourceMappingURL=module.js.map