tobago-tree: custom elements
* custom elements: tobago-tree, tobago-tree-node and tobago-tree-select
* adjust test (currently test for tree-select single fail)
* add Selectable enum with test
issue: TOBAGO-1633: TS refactoring
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIndentRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIndentRenderer.java
index ce63cae..5e64307 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIndentRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIndentRenderer.java
@@ -54,9 +54,7 @@
}
final boolean folder = node.isFolder();
- final int level = node.getLevel();
final boolean showJunctions = treeIndent.isShowJunctions();
- final boolean showRootJunction = data.isShowRootJunction();
final boolean expanded = folder && data.getExpandedState().isExpanded(node.getPath());
final TobagoResponseWriter writer = getResponseWriter(facesContext);
@@ -70,7 +68,7 @@
treeIndent.getCustomClass());
// encode tree junction
- if (!showJunctions || !showRootJunction && level == 0) {
+ if (!showJunctions) {
return;
}
writer.startElement(HtmlElements.I);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
index 8d65b5b..2569227 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeNodeRenderer.java
@@ -33,6 +33,7 @@
import org.apache.myfaces.tobago.renderkit.RendererBase;
import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
+import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
@@ -84,7 +85,7 @@
// select
if (data.getSelectable() != Selectable.none) { // selection
- String selected = requestParameterMap.get(
+ String selected = requestParameterMap.get(
clientId + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
// todo JsonUtils.decodeIntegerArray()StringArray()
selected = selected.replaceAll("\\[", ";");
@@ -128,7 +129,9 @@
Markup markup = Markup.NULL;
final TreePath path = node.getPath();
final SelectedState selectedState = data.getSelectedState();
- if (data instanceof AbstractUITree && selectedState.isSelected(path)) {
+ final boolean selected = data instanceof AbstractUITree && selectedState.isSelected(path);
+
+ if (selected) {
markup = markup.add(Markup.SELECTED);
}
if (folder) {
@@ -149,7 +152,7 @@
writer.writeAttribute(HtmlAttributes.SELECTED, selectedState.isAncestorOfSelected(path));
writer.writeAttribute(DataAttributes.ROW_INDEX, data.getRowIndex());
} else {
- writer.startElement(HtmlElements.DIV);
+ writer.startElement(HtmlElements.TOBAGO_TREE_NODE);
// div id
writer.writeIdAttribute(clientId);
@@ -158,16 +161,20 @@
final boolean hidden = !dataRendersRowContainer && !visible;
writer.writeClassAttribute(
- TobagoClass.TREE_NODE,
+ null,
TobagoClass.TREE_NODE.createMarkup(markup),
hidden ? BootstrapClass.D_NONE : null,
node.getCustomClass());
+ writer.writeAttribute(CustomAttributes.SELECTED, selected);
+ writer.writeAttribute(CustomAttributes.EXPANDABLE, folder);
+ writer.writeAttribute(CustomAttributes.INDEX, data.getRowIndex());
HtmlRendererUtils.writeDataAttributes(facesContext, writer, node);
if (parentId != null) {
// TODO: replace with
// todo writer.writeIdAttribute(parentId + SUB_SEPARATOR + AbstractUITree.SUFFIX_PARENT);
// todo like in TreeListboxRenderer
writer.writeAttribute(DataAttributes.TREE_PARENT, parentId, false);
+ writer.writeAttribute(CustomAttributes.PARENT, parentId, false);
}
writer.writeAttribute(DataAttributes.LEVEL, data.isShowRoot() ? node.getLevel() : node.getLevel() - 1);
}
@@ -189,7 +196,7 @@
}
writer.endElement(HtmlElements.OPTION);
} else {
- writer.endElement(HtmlElements.DIV);
+ writer.endElement(HtmlElements.TOBAGO_TREE_NODE);
}
}
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeRenderer.java
index 382cfa5..81daec0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeRenderer.java
@@ -33,6 +33,7 @@
import org.apache.myfaces.tobago.model.TreePath;
import org.apache.myfaces.tobago.renderkit.RendererBase;
import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
+import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
@@ -95,18 +96,18 @@
return;
}
- writer.startElement(HtmlElements.DIV);
+ writer.startElement(HtmlElements.TOBAGO_TREE);
writer.writeIdAttribute(clientId);
writer.writeClassAttribute(
- TobagoClass.TREE,
- TobagoClass.TREE.createMarkup(markup),
- tree.getCustomClass());
+ tree.getCustomClass(),
+ TobagoClass.TREE.createMarkup(markup));
HtmlRendererUtils.writeDataAttributes(facesContext, writer, tree);
writer.writeAttribute(DataAttributes.SCROLL_PANEL, Boolean.TRUE.toString(), false);
final Selectable selectable = tree.getSelectable();
if (selectable.isSupportedByTree()) {
writer.writeAttribute(DataAttributes.SELECTABLE, selectable.name(), false);
+ writer.writeAttribute(CustomAttributes.SELECTABLE, selectable.name(), false);
}
final SelectedState selectedState = tree.getSelectedState();
@@ -168,6 +169,6 @@
writer.writeAttribute(DataAttributes.SCROLL_POSITION, Boolean.TRUE.toString(), false);
writer.endElement(HtmlElements.INPUT);
- writer.endElement(HtmlElements.DIV);
+ writer.endElement(HtmlElements.TOBAGO_TREE);
}
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeSelectRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeSelectRenderer.java
index 54b7dfd..6c6321b 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeSelectRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeSelectRenderer.java
@@ -51,6 +51,8 @@
@Override
public void decode(final FacesContext facesContext, final UIComponent component) {
+ // TODO do we need this?
+
final AbstractUITreeSelect select = (AbstractUITreeSelect) component;
final AbstractUITreeNodeBase node = ComponentUtils.findAncestor(select, AbstractUITreeNodeBase.class);
final AbstractUIData data = ComponentUtils.findAncestor(node, AbstractUIData.class);
@@ -105,12 +107,11 @@
final boolean folder = data.isFolder();
final Selectable selectable = data.getSelectable();
- writer.startElement(HtmlElements.SPAN);
+ writer.startElement(HtmlElements.TOBAGO_TREE_SELECT);
final Markup markup = treeSelect.getMarkup();
writer.writeClassAttribute(
- TobagoClass.TREE_SELECT,
- TobagoClass.TREE_SELECT.createMarkup(markup),
- treeSelect.getCustomClass());
+ treeSelect.getCustomClass(),
+ TobagoClass.TREE_SELECT.createMarkup(markup));
HtmlRendererUtils.writeDataAttributes(facesContext, writer, treeSelect);
if (treeSelect.isShowCheckbox()
@@ -149,7 +150,7 @@
writer.endElement(HtmlElements.LABEL);
}
- writer.endElement(HtmlElements.SPAN);
+ writer.endElement(HtmlElements.TOBAGO_TREE_SELECT);
}
private String getClientIdWithoutRowIndex(final AbstractUIData data, final String id) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
index af6aade..4a975ce 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
@@ -31,6 +31,7 @@
* <f:ajax> attribute
*/
EXECUTE("execute"),
+ EXPANDABLE("expandable"),
FOCUS_ID("focus-id"),
/**
* The index of the tab inside the tab group.
@@ -41,6 +42,9 @@
MIN_CHARS("min-chars"),
OMIT("omit"),
ORIENTATION("orientation"),
+ PARENT("parent"),
+ SELECTABLE("selectable"),
+ SELECTED("selected"),
/**
* <f:ajax> attribute
*/
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
index bb8cdb6..c3b60e0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/DataAttributes.java
@@ -139,7 +139,9 @@
/**
* The selectable attribute e. g. for trees.
+ * @deprecated since 5.0.0, please use {@link CustomAttributes#SELECTABLE}
*/
+ @Deprecated
SELECTABLE("data-tobago-selectable"),
/**
@@ -174,6 +176,7 @@
/**
* Id of the parent node in a tree node.
+ * @deprecated since 5.0.0, please use {@link CustomAttributes#PARENT}
*/
TREE_PARENT("data-tobago-tree-parent"),
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
index 5394f3c..1294afa 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/HtmlElements.java
@@ -143,7 +143,10 @@
TOBAGO_SUGGEST("tobago-suggest"),
TOBAGO_TAB("tobago-tab"),
TOBAGO_TAB_CONTENT("tobago-tab-content"),
- TOBAGO_TAB_GROUP("tobago-tab-group");
+ TOBAGO_TAB_GROUP("tobago-tab-group"),
+ TOBAGO_TREE("tobago-tree"),
+ TOBAGO_TREE_NODE("tobago-tree-node"),
+ TOBAGO_TREE_SELECT("tobago-tree-select");
private final String value;
private final boolean voidElement;
diff --git a/tobago-core/src/main/resources/scss/_tobago.scss b/tobago-core/src/main/resources/scss/_tobago.scss
index 046b925..7d7ff77 100644
--- a/tobago-core/src/main/resources/scss/_tobago.scss
+++ b/tobago-core/src/main/resources/scss/_tobago.scss
@@ -1526,9 +1526,19 @@
margin-left: 7rem;
}
-@for $i from 0 through 20 {
- .tobago-treeNode[data-tobago-level='#{$i}'] {
- margin-left: #{$i}rem;
+tobago-tree {
+ tobago-tree-node {
+ display: block;
+
+ @for $i from 0 through 20 {
+ &[data-tobago-level='#{$i}'] {
+ margin-left: #{$i}rem;
+ }
+ }
+
+ tobago-tree-select {
+ display: inline;
+ }
}
}
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectableUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectableUnitTest.java
index e26ce79..dbed202 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectableUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectableUnitTest.java
@@ -20,12 +20,56 @@
package org.apache.myfaces.tobago.model;
import org.apache.myfaces.tobago.util.EnumUnitTest;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
public class SelectableUnitTest extends EnumUnitTest {
@Test
public void testNames() throws IllegalAccessException, NoSuchFieldException {
testNames(Selectable.class);
}
+
+ @Test
+ public void testTypeScript() throws IOException {
+ final Path path = Paths.get("").toAbsolutePath().getParent().resolve(
+ Paths.get("tobago-theme", "tobago-theme-standard", "src", "main", "npm", "ts", "tobago-selectable.ts"));
+
+ final List<String> words = getWords(path);
+
+ for (Selectable selectable : Selectable.values()) {
+ Assertions.assertTrue(words.contains(selectable.name()),
+ selectable.name() + " should be found in tobago-selectable.ts");
+ }
+ }
+
+ private List<String> getWords(final Path path) throws IOException {
+ List<String> words = new ArrayList<>();
+
+ final String fileContent = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+
+
+ StringBuilder stringBuilder = new StringBuilder();
+
+ for (char c : fileContent.toCharArray()) {
+ if (('0' <= c && c <= '9')
+ || ('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z')) {
+ stringBuilder.append(c);
+ } else {
+ words.add(stringBuilder.toString());
+ stringBuilder = new StringBuilder();
+ }
+ }
+
+ return words;
+ }
}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/01-select/Tree_Select.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/01-select/Tree_Select.test.js
index 7b638ed..6592f4c 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/01-select/Tree_Select.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/01-select/Tree_Select.test.js
@@ -24,7 +24,7 @@
let outputFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectedNodesOutput span");
let selectableNoneFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:0");
let selectableSingleFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:1");
- let inputFn = testFrameQuerySelectorFn(".tobago-treeSelect input");
+ let inputFn = testFrameQuerySelectorFn("tobago-tree-select input");
let TTT = new TobagoTestTool(assert);
TTT.action(function () {
@@ -68,7 +68,7 @@
let outputFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectedNodesOutput span");
let selectableNoneFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:0");
let selectableSingleLeafOnlyFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:2");
- let inputFn = testFrameQuerySelectorFn(".tobago-treeSelect input");
+ let inputFn = testFrameQuerySelectorFn("tobago-tree-select input");
let TTT = new TobagoTestTool(assert);
TTT.action(function () {
@@ -112,7 +112,7 @@
let outputFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectedNodesOutput span");
let selectableNoneFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:0");
let selectableMultiFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:3");
- let inputFn = testFrameQuerySelectorFn(".tobago-treeSelect input");
+ let inputFn = testFrameQuerySelectorFn("tobago-tree-select input");
let TTT = new TobagoTestTool(assert);
TTT.action(function () {
@@ -164,7 +164,7 @@
let outputFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectedNodesOutput span");
let selectableNoneFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:0");
let selectableMultiLeafOnlyFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:4");
- let inputFn = testFrameQuerySelectorFn(".tobago-treeSelect input");
+ let inputFn = testFrameQuerySelectorFn("tobago-tree-select input");
let TTT = new TobagoTestTool(assert);
TTT.action(function () {
@@ -217,7 +217,7 @@
let outputFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectedNodesOutput span");
let selectableNoneFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:0");
let selectableMultiCascadeFn = testFrameQuerySelectorFn("#page\\:mainForm\\:selectable\\:\\:5");
- let inputFn = testFrameQuerySelectorFn(".tobago-treeSelect input");
+ let inputFn = testFrameQuerySelectorFn("tobago-tree-select input");
let TTT = new TobagoTestTool(assert);
TTT.action(function () {
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-selectable.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-selectable.ts
new file mode 100644
index 0000000..16db2e2
--- /dev/null
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-selectable.ts
@@ -0,0 +1,11 @@
+export enum Selectable {
+ none, // Not selectable.
+ multi, // Multi selection possible. No other limitations.
+ single, // Only one item is selectable.
+ singleOrNone, // Only one of no item is selectable.
+ multiLeafOnly, // Only leafs are selectable.
+ singleLeafOnly, // Only one item is selectable and it must be a leaf.
+ sibling, // Only siblings are selectable.
+ siblingLeafOnly, // Only siblings are selectable and they have to be leafs.
+ multiCascade // Multi selection possible. When (de)selecting an item, the subtree will also be (un)selected.
+}
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree.ts
index 0988f47..016f7ac 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree.ts
@@ -15,223 +15,269 @@
* limitations under the License.
*/
-import {Listener, Phase} from "./tobago-listener";
-import {DomUtils} from "./tobago-utils";
+import {Selectable} from "./tobago-selectable";
-class Tree {
+export class Tree extends HTMLElement {
- static toggleNode = function (event: MouseEvent) {
- const element = event.currentTarget as HTMLElement;
- const node: HTMLDivElement = element.closest(".tobago-treeNode") as HTMLDivElement;
- const data = node.closest(".tobago-tree, .tobago-sheet") as HTMLElement;
- const expanded = data.querySelector(
- ":scope > .tobago-tree-expanded, :scope > .tobago-sheet-expanded") as HTMLInputElement;
- const togglesIcon = node.querySelectorAll(".tobago-treeNode-toggle i") as NodeListOf<HTMLElement>;
- const togglesImage = node.querySelectorAll(".tobago-treeNode-toggle img") as NodeListOf<HTMLImageElement>;
- const rowIndex = Tree.rowIndex(node);
- if (Tree.isExpanded(node, expanded)) {
- Tree.hideChildren(node);
- for (const icon of togglesIcon) {
- icon.classList.remove(icon.dataset["tobagoOpen"]);
- icon.classList.add(icon.dataset["tobagoClosed"]);
- }
- for (const image of togglesImage) {
- let src = image.dataset["tobagoClosed"];
- if (!src) { // use the open icon if there is no closed icon
- src = image.dataset["tobagoOpen"];
- }
- image.setAttribute("src", src);
- }
- const set = Tree.getSet(expanded);
- set.delete(rowIndex);
- Tree.setSet(expanded, set);
- node.classList.remove("tobago-treeNode-markup-expanded");
+ constructor() {
+ super();
+ }
+
+ connectedCallback() {
+ };
+
+ get isSheet(): boolean {
+ // TODO if sheet is implemented as custom element use:
+ // return this.tagName === "TOBAGO-SHEET";
+ return this.classList.contains("tobago-sheet");
+ }
+
+ clearSelectedNodes(): void {
+ this.hiddenInputSelected.value = "[]"; //empty set
+ }
+
+ addSelectedNode(selectedNode: number): void {
+ const selectedNodes = new Set(JSON.parse(this.hiddenInputSelected.value));
+ selectedNodes.add(selectedNode);
+ this.hiddenInputSelected.value = JSON.stringify(Array.from(selectedNodes));
+ }
+
+ deleteSelectedNode(selectedNode: number): void {
+ const selectedNodes = new Set(JSON.parse(this.hiddenInputSelected.value));
+ selectedNodes.delete(selectedNode);
+ this.hiddenInputSelected.value = JSON.stringify(Array.from(selectedNodes));
+ }
+
+ private get hiddenInputSelected(): HTMLInputElement {
+ if (this.isSheet) {
+ return this.querySelector(":scope > .tobago-sheet-selected");
} else {
- const reload = Tree.showChildren(node, expanded);
- Tree.setSet(expanded, Tree.getSet(expanded).add(rowIndex));
- if (reload) {
- jsf.ajax.request(
- node.id,
- event,
- {
- //"javax.faces.behavior.event": "click",
- execute: data.id,
- render: data.id
- });
- } else {
- for (const icon of togglesIcon) {
- icon.classList.remove(icon.dataset["tobagoClosed"]);
- icon.classList.add(icon.dataset["tobagoOpen"]);
- }
- for (const image of togglesImage) {
- let src = image.dataset["tobagoOpen"];
- if (!src) { // use the open icon if there is no closed icon
- src = image.dataset["tobagoClosed"];
- }
- image.setAttribute("src", src);
- }
- node.classList.add("tobago-treeNode-markup-expanded");
- }
+ return this.querySelector(":scope > .tobago-tree-selected");
}
- };
+ }
- /**
- * Hide all children of the node recursively.
- * @param node A HTMLElement as a node of the tree.
- */
- static hideChildren = function (node: HTMLElement): void {
- for (const child of Tree.findTreeChildren(node)) {
- if (Tree.isInSheet(node)) {
- child.parentElement.parentElement.classList.add("d-none");
- } else {
- child.classList.add("d-none");
- }
- Tree.hideChildren(child);
- }
- };
+ clearExpandedNodes(): void {
+ this.hiddenInputExpanded.value = "[]"; //empty set
+ }
- /**
- * Show the children of the node recursively, there parents are expanded.
- * @param node A HTMLElement as a node of the tree.
- * @param expanded The hidden field which contains the expanded state.
- * @return is reload needed (to get all nodes from the server)
- */
- static showChildren = function (node: HTMLElement, expanded: HTMLInputElement): boolean {
- const children = Tree.findTreeChildren(node);
- if (children.length === 0) {
- return true;
+ addExpandedNode(expandedNode: number): void {
+ const expandedNodes = new Set(JSON.parse(this.hiddenInputExpanded.value));
+ expandedNodes.add(expandedNode);
+ this.hiddenInputExpanded.value = JSON.stringify(Array.from(expandedNodes));
+ }
+
+ deleteExpandedNode(expandedNode: number): void {
+ const expandedNodes = new Set(JSON.parse(this.hiddenInputExpanded.value));
+ expandedNodes.delete(expandedNode);
+ this.hiddenInputExpanded.value = JSON.stringify(Array.from(expandedNodes));
+ }
+
+ get expandedNodes(): Set<number> {
+ return new Set(JSON.parse(this.hiddenInputExpanded.value));
+ }
+
+ private get hiddenInputExpanded(): HTMLInputElement {
+ if (this.isSheet) {
+ return this.querySelector(":scope > .tobago-sheet-expanded");
+ } else {
+ return this.querySelector(":scope > .tobago-tree-expanded");
}
- for (const child of children) {
- if (Tree.isInSheet(node)) {
- child.parentElement.parentElement.classList.remove("d-none");
- } else {
- child.classList.remove("d-none");
- }
- if (Tree.isExpanded(child, expanded)) {
- const reload = Tree.showChildren(child, expanded);
- if (reload) {
- return true;
- }
+ }
+
+ get selectable(): Selectable {
+ return Selectable[this.getAttribute("selectable")];
+ }
+}
+
+export class TreeNode extends HTMLElement {
+
+ constructor() {
+ super();
+ }
+
+ connectedCallback() {
+ if (this.isExpandable() && this.toggles !== null) {
+ this.toggles.forEach(element => element.addEventListener("click", this.toggleNode.bind(this)));
+ }
+ }
+
+ get tree(): Tree {
+ return this.closest("tobago-tree") as Tree; //TODO how to detect tobago-sheet?
+ }
+
+ isExpandable(): boolean {
+ return this.getAttribute("expandable") === "expandable";
+ }
+
+ isExpanded(): boolean {
+ for (const expandedNodeIndex of this.tree.expandedNodes) {
+ if (expandedNodeIndex === this.index) {
+ return true;
}
}
return false;
- };
-
- static commandFocus = function (event: FocusEvent) {
- const command = event.currentTarget as HTMLElement;
- const node = command.parentElement;
- const tree = node.closest(".tobago-tree");
- const selected = tree.querySelector(".tobago-tree-selected") as HTMLInputElement;
- selected.value = String(Tree.rowIndex(node));
- for (const otherNode of tree.querySelectorAll(".tobago-treeNode-markup-selected")) {
- if (otherNode !== node) {
- otherNode.classList.remove("tobago-treeNode-markup-selected");
- }
- }
- node.classList.add("tobago-treeNode-markup-selected");
- };
-
- static init = function (element: HTMLElement) {
-
- for (const toggle of DomUtils.selfOrQuerySelectorAll(element, ".tobago-treeNode-markup-folder .tobago-treeNode-toggle")) {
- toggle.addEventListener("click", Tree.toggleNode);
- }
-
- // selected for treeNode
- for (const command of DomUtils.selfOrQuerySelectorAll(element, ".tobago-treeCommand")) {
- command.addEventListener("focus", Tree.commandFocus);
- }
-
- for (const e of DomUtils.selfOrQuerySelectorAll(element, ".tobago-sheet, .tobago-tree")) {
- const sheetOrTree: HTMLDivElement = e as HTMLDivElement;
-
- // init selected field
- const hiddenInputSelected: HTMLInputElement = sheetOrTree.querySelector(":scope > .tobago-sheet-selected, :scope > .tobago-tree-selected");
- if (hiddenInputSelected) {
- const value = new Set<number>();
- for (const selected of sheetOrTree.querySelectorAll(".tobago-treeNode-markup-selected")) {
- value.add(Tree.rowIndex(selected));
- }
- Tree.setSet(hiddenInputSelected, value);
- }
-
- // selected for treeSelect
- for (const select of sheetOrTree.querySelectorAll(".tobago-treeSelect > input") as NodeListOf<HTMLInputElement>) {
- let value: Set<number>;
- // todo may use an class attribute for this value
- if (select.type === "radio") {
- value = new Set();
- value.add(Tree.rowIndex(select));
- } else if (select.checked) {
- value = Tree.getSet(hiddenInputSelected);
- value.add(Tree.rowIndex(select));
- } else {
- value = Tree.getSet(hiddenInputSelected);
- value.delete(Tree.rowIndex(select));
- }
- Tree.setSet(hiddenInputSelected, value);
- }
-
- // init expanded field
- const hiddenInputExpanded: HTMLInputElement = sheetOrTree.querySelector(":scope > .tobago-sheet-expanded, :scope > .tobago-tree-expanded");
- if (hiddenInputExpanded) {
- const value = new Set<number>();
- for (const expanded of sheetOrTree.querySelectorAll(".tobago-treeNode-markup-expanded")) {
- value.add(Tree.rowIndex(expanded));
- }
- Tree.setSet(hiddenInputExpanded, value);
- }
-
- // init tree selection for multiCascade
- if (sheetOrTree.dataset.tobagoSelectable === "multiCascade") {
- for (const treeNode of sheetOrTree.querySelectorAll(".tobago-treeNode") as NodeListOf<HTMLDivElement>) {
-
- const checkbox: HTMLInputElement = treeNode.querySelector(".tobago-treeSelect input[type=checkbox]");
- checkbox.addEventListener("change", function (event: Event) {
- for (const childTreeNode of Tree.findTreeChildren(treeNode)) {
- const childCheckbox: HTMLInputElement = childTreeNode.querySelector(".tobago-treeSelect input[type=checkbox]");
- childCheckbox.checked = checkbox.checked;
-
- const event = document.createEvent('HTMLEvents');
- event.initEvent('change', true, false);
- childCheckbox.dispatchEvent(event);
- }
- });
- }
- }
- }
- };
-
- static getSet(element: HTMLInputElement): Set<number> {
- return new Set(JSON.parse(element.value));
}
- static setSet(element, set: Set<number>) {
- return element.value = JSON.stringify(Array.from(set));
- }
-
- static isExpanded = function (node: Element, expanded: HTMLInputElement) {
- const rowIndex = Tree.rowIndex(node);
- return Tree.getSet(expanded).has(rowIndex);
- };
-
- static rowIndex = function (node: Element): number { // todo: use attribute data-tobago-row-index
- return parseInt(node.id.replace(/.+\:(\d+)(\:\w+)+/, '$1'));
- };
-
- static findTreeChildren = function (treeNode: HTMLElement): NodeListOf<HTMLDivElement> {
- if (Tree.isInSheet(treeNode)) {
- return treeNode.closest("tbody")
- .querySelectorAll(".tobago-sheet-row[data-tobago-tree-parent='" + treeNode.id + "'] .tobago-treeNode");
+ get treeChildNodes(): NodeListOf<TreeNode> {
+ if (this.tree.isSheet) {
+ return this.closest("tbody").querySelectorAll("tobago-tree-node[parent='" + this.id + "']");
} else {
- return treeNode.parentElement.querySelectorAll(".tobago-treeNode[data-tobago-tree-parent='" + treeNode.id + "']");
+ return this.parentElement.querySelectorAll("tobago-tree-node[parent='" + this.id + "']");
}
- };
+ }
- static isInSheet = function (node: Element) {
- return node.parentElement.tagName === "TD";
- };
+ get toggles(): NodeListOf<HTMLSpanElement> {
+ return this.querySelectorAll(".tobago-treeNode-toggle");
+ }
+
+ get icons(): NodeListOf<HTMLElement> {
+ return this.querySelectorAll(".tobago-treeNode-toggle i");
+ }
+
+ get images(): NodeListOf<HTMLImageElement> {
+ return this.querySelectorAll(".tobago-treeNode-toggle img");
+ }
+
+ get index(): number {
+ return Number(this.getAttribute("index"));
+ }
+
+ toggleNode(event: MouseEvent): void {
+ if (this.isExpanded()) {
+ for (const icon of this.icons) {
+ icon.classList.remove(icon.dataset.tobagoOpen);
+ icon.classList.add(icon.dataset.tobagoClosed);
+ }
+ for (const image of this.images) {
+ if (image.dataset.tobagoClosed) {
+ image.src = image.dataset.tobagoClosed;
+ } else {
+ image.src = image.dataset.tobagoOpen;
+ }
+ }
+
+ this.tree.deleteExpandedNode(this.index);
+ this.classList.remove("tobago-treeNode-markup-expanded");
+
+ this.hideNodes(this.treeChildNodes);
+ } else {
+ for (const icon of this.icons) {
+ icon.classList.remove(icon.dataset.tobagoClosed);
+ icon.classList.add(icon.dataset.tobagoOpen);
+ }
+ for (const image of this.images) {
+ if (image.dataset.tobagoOpen) {
+ image.src = image.dataset.tobagoOpen;
+ } else {
+ image.src = image.dataset.tobagoClosed;
+ }
+ }
+
+ this.tree.addExpandedNode(this.index);
+ this.classList.add("tobago-treeNode-markup-expanded");
+
+ if (this.treeChildNodes.length === 0) {
+ jsf.ajax.request(
+ this.id,
+ event,
+ {
+ //"javax.faces.behavior.event": "click",
+ execute: this.tree.id,
+ render: this.tree.id
+ });
+ } else {
+ this.showNodes(this.treeChildNodes);
+ }
+ }
+ }
+
+ hideNodes(treeChildNodes: NodeListOf<TreeNode>): void {
+ for (const treeChildNode of treeChildNodes) {
+
+ if (treeChildNode.tree.isSheet) {
+ treeChildNode.closest("tobago-sheet-row").classList.add("d-none");
+ } else {
+ treeChildNode.classList.add("d-none");
+ }
+
+ this.hideNodes(treeChildNode.treeChildNodes);
+ }
+ }
+
+ showNodes(treeChildNodes: NodeListOf<TreeNode>) {
+ for (const treeChildNode of treeChildNodes) {
+
+ if (treeChildNode.tree.isSheet) {
+ treeChildNode.closest("tobago-sheet-row").classList.remove("d-none");
+ } else {
+ treeChildNode.classList.remove("d-none");
+ }
+
+ this.showNodes(treeChildNode.treeChildNodes);
+ }
+ }
}
-Listener.register(Tree.init, Phase.DOCUMENT_READY);
-Listener.register(Tree.init, Phase.AFTER_UPDATE);
+export class TreeSelect extends HTMLElement {
+
+ constructor() {
+ super();
+ }
+
+ connectedCallback() {
+ this.input.addEventListener("change", this.select.bind(this));
+
+ if (this.tree.selectable === Selectable.multiCascade) {
+ this.input.addEventListener("change", this.selectChildren.bind(this));
+ }
+ }
+
+ get tree(): Tree {
+ return this.closest("tobago-tree") as Tree;
+ }
+
+ get treeNode(): TreeNode {
+ return this.closest("tobago-tree-node") as TreeNode;
+ }
+
+ get input(): HTMLInputElement {
+ return this.querySelector("input");
+ }
+
+ select(event: Event): void {
+ switch (this.input.type) {
+ case "radio":
+ this.tree.clearSelectedNodes();
+ this.tree.addSelectedNode(this.treeNode.index);
+ break;
+ case "checkbox":
+ if (this.input.checked) {
+ this.tree.addSelectedNode(this.treeNode.index);
+ } else {
+ this.tree.deleteSelectedNode(this.treeNode.index);
+ }
+ break;
+ }
+ }
+
+ selectChildren(event: Event): void {
+ for (const treeChildNode of this.treeNode.treeChildNodes) {
+ const child: TreeSelect = treeChildNode.querySelector(":scope > tobago-tree-select");
+ child.input.checked = this.input.checked;
+
+ if (this.input.checked) {
+ this.tree.addSelectedNode(child.treeNode.index);
+ } else {
+ this.tree.deleteSelectedNode(child.treeNode.index);
+ }
+
+ child.input.dispatchEvent(new Event("change", {bubbles: true}));
+ }
+ }
+}
+
+document.addEventListener("DOMContentLoaded", function (event) {
+ window.customElements.define("tobago-tree-select", TreeSelect);
+ window.customElements.define("tobago-tree-node", TreeNode);
+ window.customElements.define("tobago-tree", Tree);
+});