TOBAGO-1994: TreeListbox is not working correctly
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUITree.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUITree.java
index 7bb1fb4..4a345af 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUITree.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUITree.java
@@ -37,23 +37,7 @@
*/
public abstract class AbstractUITree extends AbstractUIData implements NamingContainer, Visual {
- /**
- * @deprecated since 2.0.0
- */
- @Deprecated
- public static final String SEP = "-";
-
- /**
- * @deprecated since 2.0.0
- */
- @Deprecated
- public static final String SELECT_STATE = SEP + "selectState";
-
- /**
- * @deprecated since 2.0.0
- */
- @Deprecated
- public static final String MARKED = "marked";
+ public static final String SUFFIX_PARENT = "parent";
private TreeState state;
@@ -87,20 +71,6 @@
setRowIndex(-1);
}
- /**
- * @deprecated since 2.0.0
- */
- @Deprecated
- public UIComponent getRoot() {
- // find the UITreeNode in the children.
- for (final UIComponent child : getChildren()) {
- if (child instanceof AbstractUITreeNodeBase) {
- return child;
- }
- }
- return null;
- }
-
@Override
public boolean getRendersChildren() {
return true;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
index eb59af5..4f3ac05 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SheetRenderer.java
@@ -656,6 +656,9 @@
}
final String parentId = sheet.getRowParentClientId();
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);
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeListboxRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeListboxRenderer.java
index ec2ca3d..c0a7991 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeListboxRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeListboxRenderer.java
@@ -20,6 +20,7 @@
package org.apache.myfaces.tobago.internal.renderkit.renderer;
import org.apache.myfaces.tobago.context.Markup;
+import org.apache.myfaces.tobago.internal.component.AbstractUIData;
import org.apache.myfaces.tobago.internal.component.AbstractUITree;
import org.apache.myfaces.tobago.internal.component.AbstractUITreeLabel;
import org.apache.myfaces.tobago.internal.component.AbstractUITreeListbox;
@@ -27,6 +28,7 @@
import org.apache.myfaces.tobago.internal.component.AbstractUITreeSelect;
import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
import org.apache.myfaces.tobago.internal.util.JsonUtils;
+import org.apache.myfaces.tobago.internal.util.RenderUtils;
import org.apache.myfaces.tobago.renderkit.RendererBase;
import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
@@ -42,9 +44,17 @@
import java.util.ArrayList;
import java.util.List;
+import static org.apache.myfaces.tobago.util.ComponentUtils.SUB_SEPARATOR;
+
public class TreeListboxRenderer extends RendererBase {
@Override
+ public void decode(final FacesContext facesContext, final UIComponent component) {
+ final AbstractUITree tree = (AbstractUITree) component;
+ RenderUtils.decodedStateOfTreeData(facesContext, tree);
+ }
+
+ @Override
public void encodeChildren(final FacesContext context, final UIComponent component) throws IOException {
// will be rendered in encodeEnd()
}
@@ -56,47 +66,23 @@
final AbstractUITreeListbox tree = (AbstractUITreeListbox) component;
final String clientId = tree.getClientId(facesContext);
final Markup markup = tree.getMarkup();
- // final Style scrollDivStyle = new Style();
writer.startElement(HtmlElements.DIV);
-// scrollDivStyle.setWidth(Measure.valueOf(6 * 160)); // todo: depth * width of a select
-// scrollDivStyle.setHeight(style.getHeight() // todo: what, when there is no scrollbar?
-// .subtract(15)); // todo: scrollbar height
-// scrollDivStyle.setPosition(Position.ABSOLUTE);
-// writer.writeStyleAttribute(scrollDivStyle);
-
- writer.startElement(HtmlElements.DIV);
- // todo: the id must be in this DIV
+ writer.writeIdAttribute(clientId);
writer.writeAttribute(DataAttributes.MARKUP, JsonUtils.encode(markup), false);
writer.writeClassAttribute(
TobagoClass.TREE_LISTBOX,
TobagoClass.TREE_LISTBOX.createMarkup(markup));
HtmlRendererUtils.writeDataAttributes(facesContext, writer, tree);
+ writer.writeAttribute(DataAttributes.SELECTION_MODE, tree.getSelectable().name(), false);
writer.startElement(HtmlElements.INPUT);
writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
- writer.writeNameAttribute(clientId);
- writer.writeIdAttribute(clientId);
- writer.writeAttribute(HtmlAttributes.VALUE, ";", false);
+ writer.writeNameAttribute(clientId + SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
+ writer.writeIdAttribute(clientId + SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
+ writer.writeAttribute(HtmlAttributes.VALUE, JsonUtils.encodeEmptyArray(), false);
writer.endElement(HtmlElements.INPUT);
- writer.startElement(HtmlElements.INPUT);
- writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
- writer.writeNameAttribute(clientId + ComponentUtils.SUB_SEPARATOR + AbstractUITree.SUFFIX_MARKED);
- writer.writeIdAttribute(clientId + ComponentUtils.SUB_SEPARATOR + AbstractUITree.SUFFIX_MARKED);
- writer.writeAttribute(HtmlAttributes.VALUE, "", false);
- writer.endElement(HtmlElements.INPUT);
-
- if (tree.getSelectable().isSupportedByTreeListbox()) {
- writer.startElement(HtmlElements.INPUT);
- writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.HIDDEN);
- writer.writeNameAttribute(clientId + AbstractUITree.SELECT_STATE);
- writer.writeIdAttribute(clientId + AbstractUITree.SELECT_STATE);
- writer.writeAttribute(HtmlAttributes.VALUE, ";", false);
- writer.writeAttribute(DataAttributes.SELECTION_MODE, tree.getSelectable().name(), false);
- writer.endElement(HtmlElements.INPUT);
- }
-
List<Integer> thisLevel = new ArrayList<>();
thisLevel.add(0);
List<Integer> nextLevel = new ArrayList<>();
@@ -126,7 +112,7 @@
writer.endElement(HtmlElements.SELECT);
}
- for(final Integer rowIndex : thisLevel) {
+ for (final Integer rowIndex : thisLevel) {
encodeSelectBox(facesContext, tree, writer, rowIndex, nextLevel, size);
}
@@ -139,7 +125,6 @@
}
writer.endElement(HtmlElements.DIV);
- writer.endElement(HtmlElements.DIV);
tree.setRowIndex(-1);
}
@@ -156,9 +141,7 @@
writer.startElement(HtmlElements.SELECT);
writer.writeClassAttribute(TobagoClass.TREE_LISTBOX__SELECT);
- if (parentId != null) {
- writer.writeAttribute(DataAttributes.TREE_PARENT, parentId, false);
- }
+ writer.writeIdAttribute(parentId + SUB_SEPARATOR + AbstractUITree.SUFFIX_PARENT);
writer.writeAttribute(HtmlAttributes.SIZE, size);
// writer.writeAttribute(HtmlAttributes.MULTIPLE, siblingMode);
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 80c1f9d..27d7251 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
@@ -30,6 +30,8 @@
import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
import org.apache.myfaces.tobago.internal.util.JsonUtils;
import org.apache.myfaces.tobago.model.Selectable;
+import org.apache.myfaces.tobago.model.SelectedState;
+import org.apache.myfaces.tobago.model.TreePath;
import org.apache.myfaces.tobago.renderkit.RendererBase;
import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
@@ -68,12 +70,13 @@
final String clientId = data.getClientId(facesContext);
final String nodeStateId = node.nodeStateId(facesContext);
final Map<String, String> requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
- final String id = node.getClientId(facesContext);
+ final String nodeId = node.getClientId(facesContext);
final boolean folder = node.isFolder();
// expand state
if (folder) {
- final boolean expanded = Boolean.parseBoolean(requestParameterMap.get(id + "-expanded"));
+ final boolean expanded = Boolean.parseBoolean(requestParameterMap.get(
+ nodeId + ComponentUtils.SUB_SEPARATOR + AbstractUITree.SUFFIX_EXPANDED));
/* XXX check
if (node.isExpanded() != expanded) {
new TreeExpansionEvent(node, node.isExpanded(), expanded).queue();
@@ -83,7 +86,12 @@
// select
if (data.getSelectable() != Selectable.none) { // selection
- final String selected = requestParameterMap.get(clientId + AbstractUITree.SELECT_STATE);
+ String selected = requestParameterMap.get(
+ clientId + ComponentUtils.SUB_SEPARATOR + AbstractUIData.SUFFIX_SELECTED);
+// todo JsonUtils.decodeIntegerArray()StringArray()
+ selected = selected.replaceAll("\\[", ";");
+ selected = selected.replaceAll("]", ";");
+ selected = selected.replaceAll(",", ";");
final String searchString = ";" + node.getClientId(facesContext) + ";";
final AbstractUITreeSelect treeSelect = ComponentUtils.findDescendant(node, AbstractUITreeSelect.class);
if (treeSelect != null) {
@@ -120,12 +128,14 @@
final boolean visible = data.isRowVisible();
final boolean folder = node.isFolder();
Markup markup = Markup.NULL;
- if (data instanceof AbstractUITree && data.getSelectedState().isSelected(node.getPath())) {
+ final TreePath path = node.getPath();
+ final SelectedState selectedState = data.getSelectedState();
+ if (data instanceof AbstractUITree && selectedState.isSelected(path)) {
markup = markup.add(Markup.SELECTED);
}
if (folder) {
markup = markup.add(Markup.FOLDER);
- if (data.getExpandedState().isExpanded(node.getPath())) {
+ if (data.getExpandedState().isExpanded(path)) {
markup = markup.add(Markup.EXPANDED);
}
}
@@ -139,7 +149,8 @@
writer.writeAttribute(HtmlAttributes.VALUE, clientId, true);
writer.writeIdAttribute(clientId);
writer.writeAttribute(DataAttributes.MARKUP, JsonUtils.encode(markup), false);
- writer.writeAttribute(HtmlAttributes.SELECTED, folder);
+ writer.writeAttribute(HtmlAttributes.SELECTED, selectedState.isAncestorOfSelected(path));
+ writer.writeAttribute(DataAttributes.ROW_INDEX, data.getRowIndex());
} else {
writer.startElement(HtmlElements.DIV);
@@ -157,6 +168,9 @@
node.getCustomClass());
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(DataAttributes.LEVEL, data.isShowRoot() ? node.getLevel() : node.getLevel() - 1);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
index 70aa35e..4d9d18f 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/JsonUtils.java
@@ -362,4 +362,8 @@
builder.append(']');
return builder.toString();
}
+
+ public static String encodeEmptyArray() {
+ return "[]";
+ }
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
index b67a8b2..9f25509 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
@@ -226,7 +226,11 @@
try {
string = facesContext.getExternalContext().getRequestParameterMap().get(key);
if (string != null) {
- return StringUtils.parseIntegerList(string);
+ if (string.startsWith("[")) {
+ return JsonUtils.decodeIntegerArray(string);
+ } else {
+ return StringUtils.parseIntegerList(string); // todo remove this case after migrating all to JSON
+ }
}
} catch (final Exception e) {
// should not happen
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/StringUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/StringUtils.java
index a677b1b..30ad0e8 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/StringUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/StringUtils.java
@@ -31,7 +31,7 @@
}
public static List<Integer> parseIntegerList(final String integerList) throws NumberFormatException {
- return parseIntegerList(integerList, ", ");
+ return parseIntegerList(integerList, ", ;");
}
public static List<Integer> parseIntegerList(final String integerList, final String delimiters)
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/model/SelectedState.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/model/SelectedState.java
index f1e4357..d01d97e 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/model/SelectedState.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/model/SelectedState.java
@@ -32,13 +32,30 @@
private Set<TreePath> selectedPaths = new HashSet<>();
/**
- * Checks if the given is selected.
+ * Checks if the given path is selected.
*/
public boolean isSelected(final TreePath path) {
return selectedPaths.contains(path);
}
/**
+ * Checks if the given path is an ancestor of a selected node.
+ */
+ public boolean isAncestorOfSelected(final TreePath ancestorPath) {
+ if (ancestorPath.isRoot()) {
+ return !selectedPaths.isEmpty();
+ }
+ for (TreePath selectedPath : selectedPaths) {
+ for (TreePath p = selectedPath; !p.isRoot(); p = p.getParent()) {
+ if (p.equals(ancestorPath)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
* Select the given path.
*/
public void select(final TreePath path) {
@@ -53,8 +70,7 @@
}
/**
- * Set the selected path and remove all prior selections.
- * This is useful for "single selection" mode.
+ * Set the selected path and remove all prior selections. This is useful for "single selection" mode.
*/
public void clearAndSelect(final TreePath path) {
clear();
@@ -78,4 +94,9 @@
unselect(path);
}
}
+
+ @Override
+ public String toString() {
+ return selectedPaths.toString();
+ }
}
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 b78c536..7825257 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
@@ -147,7 +147,7 @@
ROW_ACTION("data-tobago-row-action"),
/*
- * Holds the index of the row in a sheet, if the sheed has a rowRendered attribute.
+ * Holds the index of the row in a sheet, if the sheet has a rowRendered attribute.
*/
ROW_INDEX("data-tobago-row-index"),
diff --git a/tobago-core/src/main/resources/scss/_tobago.scss b/tobago-core/src/main/resources/scss/_tobago.scss
index 012c974..eeb9999 100644
--- a/tobago-core/src/main/resources/scss/_tobago.scss
+++ b/tobago-core/src/main/resources/scss/_tobago.scss
@@ -1500,9 +1500,6 @@
.tobago-tree-expanded,
.tobago-tree-selected,
.tobago-treeLabel,
-.tobago-treeListbox,
-.tobago-treeListbox-level,
-.tobago-treeListbox-select,
.tobago-treeSelect,
.tobago-treeSelect-label {
}
@@ -1534,6 +1531,19 @@
}
}
+/* treeListbox ---------------------------------------------------------------------- */
+.tobago-treeListbox {
+}
+
+.tobago-treeListbox-level {
+ display: inline-block;
+ min-width: 10rem;
+}
+
+.tobago-treeListbox-select {
+ width: 100%;
+}
+
/* textarea --------------------------------------------------------- */
.tobago-textarea {
&:disabled {
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectedStateUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectedStateUnitTest.java
new file mode 100644
index 0000000..318c3ba
--- /dev/null
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/model/SelectedStateUnitTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SelectedStateUnitTest {
+
+ @Test
+ public void testAncestorOfSelected() {
+ SelectedState state = new SelectedState();
+ state.select(new TreePath(0, 0));
+ state.select(new TreePath(1, 1, 1));
+
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath()));
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath(0)));
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath(0, 0)));
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath(1)));
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath(1, 1)));
+ Assert.assertTrue(state.isAncestorOfSelected(new TreePath(1, 1, 1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(2)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(0, 1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(1, 0)));
+ }
+
+ @Test
+ public void testAncestorOfSelectedEmpty() {
+ SelectedState state = new SelectedState();
+
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath()));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(0)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(0, 0)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(1, 1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(1, 1, 1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(2)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(0, 1)));
+ Assert.assertFalse(state.isAncestorOfSelected(new TreePath(1, 0)));
+ }
+
+ @Test
+ public void testSelected() {
+ SelectedState state = new SelectedState();
+ state.select(new TreePath(0, 0));
+ state.select(new TreePath(1, 1, 1));
+
+ Assert.assertFalse(state.isSelected(new TreePath()));
+ Assert.assertFalse(state.isSelected(new TreePath(0)));
+ Assert.assertTrue(state.isSelected(new TreePath(0, 0)));
+ Assert.assertFalse(state.isSelected(new TreePath(1)));
+ Assert.assertFalse(state.isSelected(new TreePath(1, 1)));
+ Assert.assertTrue(state.isSelected(new TreePath(1, 1, 1)));
+ Assert.assertFalse(state.isSelected(new TreePath(2)));
+ Assert.assertFalse(state.isSelected(new TreePath(0, 1)));
+ Assert.assertFalse(state.isSelected(new TreePath(1, 0)));
+ }
+
+}
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeListboxController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeListboxController.java
new file mode 100644
index 0000000..97ca6a8
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeListboxController.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.myfaces.tobago.example.demo;
+
+import org.apache.myfaces.tobago.model.TreePath;
+import org.apache.myfaces.tobago.model.TreeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.enterprise.context.SessionScoped;
+import javax.faces.context.FacesContext;
+import javax.inject.Named;
+import javax.swing.tree.DefaultMutableTreeNode;
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+
+@SessionScoped
+@Named
+public class TreeListboxController implements Serializable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private DefaultMutableTreeNode sample;
+
+ private TreeState state;
+
+ public TreeListboxController() {
+ sample = CategoryTree.createSample();
+ state = new TreeState();
+ state.getSelectedState().select(new TreePath(2, 2)); // world music
+ }
+
+ public String submit() {
+ LOG.info("Selected: {}", state.getSelectedState());
+ return FacesContext.getCurrentInstance().getViewRoot().getViewId();
+ }
+
+ public DefaultMutableTreeNode getSample() {
+ return sample;
+ }
+
+ public TreeState getState() {
+ return state;
+ }
+
+ public void setState(TreeState state) {
+ this.state = state;
+ }
+
+// public String getSelectedNodes() {
+// return TreeUtils.getSelectedNodes(sample);
+// }
+
+}
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeSelectController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeSelectController.java
index 80fcd9a..7165cde 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeSelectController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeSelectController.java
@@ -60,37 +60,10 @@
public void setSelectable(final String selectable) {
this.selectable = selectable;
- resetSelection(sample);
- }
-
- public void resetSelection(final DefaultMutableTreeNode node) {
- final Node userObject = (Node) node.getUserObject();
- userObject.setSelected(false);
- for (int i = 0; i < node.getChildCount(); i++) {
- final DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
- resetSelection(child);
- }
+ TreeUtils.resetSelection(sample);
}
public String getSelectedNodes() {
- final StringBuilder stringBuilder = new StringBuilder();
- buildSelectedNodesString(stringBuilder, sample);
- if (stringBuilder.length() > 2) {
- return stringBuilder.substring(2); // Remove ', '.
- } else {
- return "";
- }
- }
-
- private void buildSelectedNodesString(final StringBuilder stringBuilder, final DefaultMutableTreeNode node) {
- final Node userObject = (Node) node.getUserObject();
- if (userObject.isSelected()) {
- stringBuilder.append(", ");
- stringBuilder.append(userObject.getName());
- }
- for (int i = 0; i < node.getChildCount(); i++) {
- final DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
- buildSelectedNodesString(stringBuilder, child);
- }
+ return TreeUtils.getSelectedNodes(sample);
}
}
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeUtils.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeUtils.java
new file mode 100644
index 0000000..3401454
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/TreeUtils.java
@@ -0,0 +1,41 @@
+package org.apache.myfaces.tobago.example.demo;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+
+public class TreeUtils {
+
+ private TreeUtils() {
+ }
+
+ public static void resetSelection(final DefaultMutableTreeNode node) {
+ final Node userObject = (Node) node.getUserObject();
+ userObject.setSelected(false);
+ for (int i = 0; i < node.getChildCount(); i++) {
+ final DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
+ resetSelection(child);
+ }
+ }
+
+ public static String getSelectedNodes(final DefaultMutableTreeNode treeNode) {
+ final StringBuilder stringBuilder = new StringBuilder();
+ buildSelectedNodesString(stringBuilder, treeNode);
+ if (stringBuilder.length() > 2) {
+ return stringBuilder.substring(2); // Remove ', '.
+ } else {
+ return "";
+ }
+ }
+
+ private static void buildSelectedNodesString(final StringBuilder stringBuilder, final DefaultMutableTreeNode node) {
+ final Node userObject = (Node) node.getUserObject();
+ if (userObject.isSelected()) {
+ stringBuilder.append(", ");
+ stringBuilder.append(userObject.getName());
+ }
+ for (int i = 0; i < node.getChildCount(); i++) {
+ final DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
+ buildSelectedNodesString(stringBuilder, child);
+ }
+ }
+
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/04-listbox/Tree_Listbox.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/04-listbox/Tree_Listbox.xhtml
index 7324073..3304530 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/04-listbox/Tree_Listbox.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/090-tree/04-listbox/Tree_Listbox.xhtml
@@ -17,7 +17,7 @@
* limitations under the License.
-->
-<ui:composition template="/main.xhtml"
+<ui:composition template="/plain.xhtml"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:tc="http://myfaces.apache.org/tobago/component"
xmlns:ui="http://java.sun.com/jsf/facelets">
@@ -28,12 +28,16 @@
link="#{apiController.base}/doc/#{apiController.currentRelease}/tld/tc/treeListbox.html"/>
<tc:section label="Example">
- <pre><code class="language-markup"><tc:treeListbox value="\#{treeController.sample}" ...></code></pre>
- <tc:treeListbox value="#{treeController.sample}" var="node">
- <tc:treeNode>
- <tc:treeIndent/>
- <tc:treeLabel value="#{node.userObject.name}"/>
+ <pre><code class="language-markup"><tc:treeListbox value="\#{treeListboxController.sample}" ...></code></pre>
+ <tc:treeListbox id="listbox" value="#{treeListboxController.sample}" var="node" state="#{treeListboxController.state}">
+ <tc:treeNode id="node">
+ <tc:treeLabel id="label" value="#{node.userObject.name}"/>
</tc:treeNode>
</tc:treeListbox>
+
+ <tc:button label="Submit" action="#{treeListboxController.submit}"/>
+
+ <tc:in readonly="true" label="Selection" tip="as set of tree pathes" value="#{treeListboxController.state.selectedState}"/>
+<!-- <tc:in readonly="true" label="Selection" value="#{treeListboxController.selectedNodes}"/>-->
</tc:section>
</ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/90000-attic/treeListbox/TreeListbox.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/90000-attic/treeListbox/TreeListbox.xhtml
deleted file mode 100644
index fb4720d..0000000
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/90000-attic/treeListbox/TreeListbox.xhtml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
--->
-
-<!-- XXX This is an old page. Content might not be up to date. Needs to be refactored, or just deleted. -->
-<f:view
- xmlns:tc="http://myfaces.apache.org/tobago/component"
- xmlns:ui="http://java.sun.com/jsf/facelets"
- xmlns:f="http://java.sun.com/jsf/core">
-
- <tc:page>
- <!-- <tc:gridLayoutConstraint width="600px" height="300px"/> -->
-
- <tc:treeListbox id="tree" value="#{treeTestController.tree}" var="node">
- <tc:treeNode id="template">
- <tc:treeLabel value="#{node.userObject.name}"/>
- </tc:treeNode>
- </tc:treeListbox>
-
- </tc:page>
-</f:view>
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
index 128229e..eebe119 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-sheet.ts
@@ -34,14 +34,14 @@
mousemoveData: any;
mousedownOnRowData: any;
- static init = function (element: HTMLElement) {
+ static init(element: HTMLElement) {
console.time("[tobago-sheet] init");
for (const sheetElement of DomUtils.selfOrElementsByClassName(element, "tobago-sheet")) {
const sheet = new Sheet(sheetElement);
Sheet.SHEETS.set(sheet.id, sheet);
}
console.timeEnd("[tobago-sheet] init");
- };
+ }
private static getScrollBarSize() {
const body = document.getElementsByTagName("body").item(0);
diff --git a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree-listbox.ts b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree-listbox.ts
index 94b8821..8026b54 100644
--- a/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree-listbox.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/npm/ts/tobago-tree-listbox.ts
@@ -16,74 +16,88 @@
*/
import {Listener, Phase} from "./tobago-listener";
-import {Tobago4Utils} from "./tobago-utils";
+import {DomUtils} from "./tobago-utils";
class TreeListbox {
- static init = function (elements) {
- elements = elements.jQuery ? elements : jQuery(elements); // fixme jQuery -> ES5
- var treeListbox = Tobago4Utils.selectWithJQuery(elements, ".tobago-treeListbox");
- // hide select tags for level > root
- treeListbox.children().find("select:not(:first)").hide();
- var listboxSelects = treeListbox.find("select");
+ id: string;
- listboxSelects.children("option").each(TreeListbox.initNextLevel);
- listboxSelects.each(TreeListbox.initListeners);
- };
-
-// find all option tags and add the dedicated select tag in its data section.
- static initNextLevel = function () {
- var option = jQuery(this);
- var select = option.closest(".tobago-treeListbox-level").next()
- .find("[data-tobago-tree-parent='" + option.attr("id") + "']");
- if (select.length == 1) {
- option.data("tobago-select", select);
- } else {
- var empty = option.closest(".tobago-treeListbox-level").next().children(":first");
- option.data("tobago-select", empty);
+ static init = function (element) {
+ for (const treeListbox of DomUtils.selfOrElementsByClassName(element, "tobago-treeListbox")) {
+ new TreeListbox(treeListbox);
}
};
-// add on change on all select tag, all options that are not selected hide there dedicated
-// select tag, and the selected option show its dedicated select tag.
- static initListeners = function () {
+ constructor(element: HTMLElement) {
- jQuery(this).change(TreeListbox.onChange);
+ this.id = element.id;
- jQuery(this).focus(function () {
- jQuery(this).change();
- });
- };
+ const selects = element.getElementsByTagName("select");
+ for (let i = 0; i < selects.length; i++) {
+ const listbox = <HTMLSelectElement>selects.item(i);
+ // hide select tags for level > root
+ if (listbox.previousElementSibling) {
+ listbox.classList.add("d-none");
+ }
- static onChange = function () {
- var listbox = jQuery(this);
- listbox.children("option:not(:selected)").each(function () {
- jQuery(this).data("tobago-select").hide();
- });
- listbox.children("option:selected").each(function () {
- jQuery(this).data("tobago-select").show();
- });
- TreeListbox.setSelected(listbox);
+ // add on change on all select tag, all options that are not selected hide there dedicated
+ // select tag, and the selected option show its dedicated select tag.
+ if (!listbox.disabled) {
+ listbox.addEventListener("change", this.onChange.bind(this));
+ }
+ }
+ }
+
+ onChange(event: TextEvent) {
+ let listbox = <HTMLSelectElement>event.currentTarget;
+ for (const child of listbox.children) {
+ const option = <HTMLOptionElement>child;
+ if (option.tagName === "OPTION") {
+ if (option.selected) {
+ this.setSelected(option);
+ let select = <HTMLSelectElement>document.getElementById(option.id + DomUtils.SUB_COMPONENT_SEP + "parent");
+ if (!select) {
+ select = <HTMLSelectElement>listbox.parentElement.nextElementSibling.children[0]; // dummy
+ }
+ select.classList.remove("d-none");
+ for (const sibling of listbox.parentElement.nextElementSibling.children) {
+ if (sibling === select) {
+ (<HTMLElement>sibling).classList.remove("d-none");
+ } else {
+ (<HTMLElement>sibling).classList.add("d-none");
+ }
+ }
+ }
+ }
+ }
// Deeper level (2nd and later) should only show the empty select tag.
// The first child is the empty selection.
- listbox.parent().nextAll(":not(:first)").each(function () {
- jQuery(this).children(":not(:first)").hide();
- jQuery(this).children(":first").show();
- });
- };
-
- static setSelected = function (listbox) {
- var hidden = listbox.closest(".tobago-treeListbox").children("[data-tobago-selection-mode]");
- if (hidden.length == 1) {
- var selectedValue = ";";
- listbox.children("option:selected").each(function () {
- selectedValue += jQuery(this).attr("id") + ";";
- });
- hidden.val(selectedValue);
+ let next = listbox.parentElement.nextElementSibling;
+ if (next) {
+ for (next = next.nextElementSibling; next; next = next.nextElementSibling) {
+ for (const child of next.children) {
+ const select = <HTMLSelectElement>child;
+ if (select.previousElementSibling) { // is not the first
+ select.classList.add("d-none");
+ } else { // is the first
+ select.classList.remove("d-none");
+ }
+ }
+ }
}
- };
+ }
+
+ setSelected(option: HTMLOptionElement) {
+ const hidden = <HTMLInputElement>document.getElementById(this.id + DomUtils.SUB_COMPONENT_SEP + "selected");
+ if (hidden) {
+ let value = <number[]>JSON.parse(hidden.value);
+ value = []; // todo: multi-select
+ value.push(parseInt(option.dataset["tobagoRowIndex"]));
+ hidden.value = JSON.stringify(value);
+ }
+ }
}
Listener.register(TreeListbox.init, Phase.DOCUMENT_READY);