You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lo...@apache.org on 2019/07/25 09:19:50 UTC

[myfaces-tobago] 01/03: TOBAGO-1633: TS refactoring

This is an automated email from the ASF dual-hosted git repository.

lofwyr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git

commit a26ba992dac5d40e89215fc487cbb54434bfe0e2
Author: Udo Schnurpfeil <lo...@apache.org>
AuthorDate: Thu Jul 25 11:05:13 2019 +0200

    TOBAGO-1633: TS refactoring
    
    TOBAGO-1998: Use JSON for data interchange
    
    sheet: fix row selection position (may be 1 too high)
    
    cleanup
---
 .../internal/renderkit/renderer/ImageRenderer.java |   3 +-
 .../internal/renderkit/renderer/SheetRenderer.java |   5 +-
 .../renderkit/renderer/TreeIconRenderer.java       |   3 +-
 .../internal/renderkit/renderer/TreeRenderer.java  |  16 +--
 .../myfaces/tobago/internal/util/JsonUtils.java    |   3 +
 .../myfaces/tobago/internal/util/RenderUtils.java  |   8 +-
 .../myfaces/tobago/internal/util/StringUtils.java  | 120 ++-----------------
 .../webapp/DebugResponseWriterWrapper.java         |   3 +-
 .../src/main/npm/ts/tobago-sheet.ts                |  32 ++---
 .../src/main/npm/ts/tobago-tree.ts                 | 132 ++++++++++++---------
 10 files changed, 120 insertions(+), 205 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/ImageRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/ImageRenderer.java
index 651ca0a..67f7fe0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/ImageRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/ImageRenderer.java
@@ -24,7 +24,6 @@ import org.apache.myfaces.tobago.internal.component.AbstractUICommandBase;
 import org.apache.myfaces.tobago.internal.component.AbstractUIImage;
 import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
 import org.apache.myfaces.tobago.internal.util.JsonUtils;
-import org.apache.myfaces.tobago.internal.util.StringUtils;
 import org.apache.myfaces.tobago.renderkit.RendererBase;
 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
@@ -47,7 +46,7 @@ public class ImageRenderer extends RendererBase {
 
     final AbstractUIImage image = (AbstractUIImage) component;
     final String value = image.getUrl();
-    final boolean fontAwesome = StringUtils.startsWith(value, "fa-");
+    final boolean fontAwesome = value != null && value.startsWith("fa-");
     final boolean disabled = image.isDisabled()
         || (image.getParent() instanceof AbstractUICommandBase
         && ((AbstractUICommandBase) image.getParent()).isDisabled());
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 4f3ac05..22a1ca9 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
@@ -46,7 +46,6 @@ import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
 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.internal.util.StringUtils;
 import org.apache.myfaces.tobago.layout.ShowPosition;
 import org.apache.myfaces.tobago.layout.TextAlign;
 import org.apache.myfaces.tobago.layout.VerticalAlign;
@@ -124,7 +123,7 @@ public class SheetRenderer extends RendererBase {
       }
       List<Integer> selectedRows;
       try {
-        selectedRows = StringUtils.parseIntegerList(selected);
+        selectedRows = JsonUtils.decodeIntegerArray(selected);
       } catch (final NumberFormatException e) {
         LOG.warn(selected, e);
         selectedRows = Collections.emptyList();
@@ -343,7 +342,7 @@ public class SheetRenderer extends RendererBase {
 
     if (selectable != Selectable.none) {
       encodeHiddenInput(writer,
-          StringUtils.joinWithSurroundingSeparator(selectedRows),
+          JsonUtils.encode(selectedRows),
           sheetId + SUFFIX_SELECTED);
     }
 
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIconRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIconRenderer.java
index 2d57b2c..f317b3d 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIconRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TreeIconRenderer.java
@@ -24,7 +24,6 @@ import org.apache.myfaces.tobago.internal.component.AbstractUITreeIcon;
 import org.apache.myfaces.tobago.internal.component.AbstractUITreeNode;
 import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
 import org.apache.myfaces.tobago.internal.util.JsonUtils;
-import org.apache.myfaces.tobago.internal.util.StringUtils;
 import org.apache.myfaces.tobago.renderkit.RendererBase;
 import org.apache.myfaces.tobago.renderkit.css.Icons;
 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
@@ -97,7 +96,7 @@ public class TreeIconRenderer extends RendererBase {
         TobagoClass.TREE_NODE__TOGGLE,
         treeIcon.getCustomClass());
 
-    if (StringUtils.startsWith(source, "fa-")) {
+    if (source != null && source.startsWith( "fa-")) {
       writer.startElement(HtmlElements.I);
       writer.writeClassAttribute(Icons.FA, Icons.custom(source));
       if (folder) {
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 99d1944..e4e01ff 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
@@ -46,6 +46,8 @@ import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
 
 public class TreeRenderer extends RendererBase {
 
@@ -109,10 +111,10 @@ public class TreeRenderer extends RendererBase {
     }
 
     final SelectedState selectedState = tree.getSelectedState();
-    final StringBuilder selectedValue = new StringBuilder(",");
+    final List<Integer> selectedValue = new ArrayList<>();
 
     final ExpandedState expandedState = tree.getExpandedState();
-    final StringBuilder expandedValue = new StringBuilder(",");
+    final List<Integer> expandedValue = new ArrayList<>();
 
     final int last = tree.isRowsUnlimited() ? Integer.MAX_VALUE : tree.getFirst() + tree.getRows();
     for (int rowIndex = tree.getFirst(); rowIndex < last; rowIndex++) {
@@ -124,13 +126,11 @@ public class TreeRenderer extends RendererBase {
       final TreePath path = tree.getPath();
 
       if (selectedState.isSelected(path)) {
-        selectedValue.append(rowIndex);
-        selectedValue.append(",");
+        selectedValue.add(rowIndex);
       }
 
       if (tree.isFolder() && expandedState.isExpanded(path)) {
-        expandedValue.append(rowIndex);
-        expandedValue.append(",");
+        expandedValue.add(rowIndex);
       }
 
       for (final UIComponent child : tree.getChildren()) {
@@ -149,7 +149,7 @@ public class TreeRenderer extends RendererBase {
     writer.writeNameAttribute(selectedId);
     writer.writeIdAttribute(selectedId);
     writer.writeClassAttribute(TobagoClass.TREE__SELECTED);
-    writer.writeAttribute(HtmlAttributes.VALUE, selectedValue.toString(), false);
+    writer.writeAttribute(HtmlAttributes.VALUE, JsonUtils.encode(selectedValue), false);
     writer.endElement(HtmlElements.INPUT);
 
     writer.startElement(HtmlElements.INPUT);
@@ -158,7 +158,7 @@ public class TreeRenderer extends RendererBase {
     writer.writeNameAttribute(expandedId);
     writer.writeIdAttribute(expandedId);
     writer.writeClassAttribute(TobagoClass.TREE__EXPANDED);
-    writer.writeAttribute(HtmlAttributes.VALUE, expandedValue.toString(), false);
+    writer.writeAttribute(HtmlAttributes.VALUE, JsonUtils.encode(expandedValue), false);
     writer.endElement(HtmlElements.INPUT);
 
     writer.startElement(HtmlElements.INPUT);
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 4d9d18f..b0726df 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
@@ -324,6 +324,9 @@ public class JsonUtils {
   }
 
   public static List<Integer> decodeIntegerArray(final String json) {
+    if (json == null) {
+      return null;
+    }
     String string = json.trim();
     final List<Integer> result = new ArrayList<>();
     if (string.length() < 2 || string.charAt(0) != '[' || string.charAt(string.length() - 1) != ']') {
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 9f25509..95d0df2 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
@@ -225,13 +225,7 @@ public final class RenderUtils {
     final String key = data.getClientId(facesContext) + ComponentUtils.SUB_SEPARATOR + suffix;
     try {
       string = facesContext.getExternalContext().getRequestParameterMap().get(key);
-      if (string != null) {
-        if (string.startsWith("[")) {
-          return JsonUtils.decodeIntegerArray(string);
-        } else {
-          return StringUtils.parseIntegerList(string); // todo remove this case after migrating all to JSON
-        }
-      }
+      return JsonUtils.decodeIntegerArray(string);
     } catch (final Exception e) {
       // should not happen
       LOG.warn("Can't parse " + suffix + ": '" + string + "' from parameter '" + key + "'", e);
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 30ad0e8..5e1baf8 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
@@ -30,36 +30,6 @@ public final class StringUtils {
     // to prevent instantiation
   }
 
-  public static List<Integer> parseIntegerList(final String integerList) throws NumberFormatException {
-    return parseIntegerList(integerList, ", ;");
-  }
-
-  public static List<Integer> parseIntegerList(final String integerList, final String delimiters)
-      throws NumberFormatException {
-    final List<Integer> list = new ArrayList<>();
-
-    final StringTokenizer tokenizer = new StringTokenizer(integerList, delimiters);
-    while (tokenizer.hasMoreElements()) {
-      final String token = tokenizer.nextToken().trim();
-      if (token.length() > 0) {
-        list.add(new Integer(token));
-      }
-    }
-
-    return list;
-  }
-
-  public static <T> String joinWithSurroundingSeparator(final List<T> list) {
-    final StringBuilder buffer = new StringBuilder(",");
-    if (list != null) {
-      for (final T t : list) {
-        buffer.append(t);
-        buffer.append(",");
-      }
-    }
-    return buffer.toString();
-  }
-
   public static int[] getIndices(final String list) {
     if (list == null) {
       return new int[0];
@@ -313,22 +283,6 @@ public final class StringUtils {
   /**
    * Basically taken from commons-lang
    */
-  public static boolean isAlpha(final String string) {
-    if (string == null) {
-      return false;
-    }
-    final int sz = string.length();
-    for (int i = 0; i < sz; i++) {
-      if (!Character.isLetter(string.charAt(i))) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Basically taken from commons-lang
-   */
   public static boolean isEmpty(final String value) {
     return value == null || value.length() == 0;
   }
@@ -367,50 +321,19 @@ public final class StringUtils {
   }
 
   /**
-   * Basically taken from commons-lang
-   */
-  public static String replace(final String text, final String searchString, final String replacement) {
-    if (isEmpty(text) || isEmpty(searchString) || replacement == null) {
-      return text;
-    }
-    int start = 0;
-    int end = text.indexOf(searchString, start);
-    if (end == -1) {
-      return text;
-    }
-    final int replLength = searchString.length();
-    int increase = replacement.length() - replLength;
-    increase = increase < 0 ? 0 : increase * 16;
-    final StringBuilder buf = new StringBuilder(text.length() + increase);
-    while (end != -1) {
-      buf.append(text.substring(start, end)).append(replacement);
-      start = end + replLength;
-      end = text.indexOf(searchString, start);
-    }
-    buf.append(text.substring(start));
-    return buf.toString();
-  }
-
-  /**
-   * Basically taken from commons-lang
-   */
-  public static String repeat(final String str, final int repeat) {
-    final int outputLength = str.length() * repeat;
-    final StringBuilder buf = new StringBuilder(outputLength);
-    for (int i = 0; i < repeat; i++) {
-      buf.append(str);
-    }
-    return buf.toString();
-  }
-
-  /**
-   * Returns a string of the same length to hide confidential passwords from log files etc.
+   * Returns a string with asterisks of the same length to hide confidential passwords from log files etc.
    */
   public static String toConfidentialString(final String string, final boolean confidential) {
     if (string == null) {
       return "<null>";
     } else if (confidential) {
-      return repeat("*", string.length()) + " (confidential)";
+      final int repeat = string.length();
+      final StringBuilder builder = new StringBuilder(repeat + 15);
+      for (int i = 0; i < repeat; i++) {
+        builder.append('*');
+      }
+      builder.append(" (confidential)");
+      return builder.toString();
     } else {
       return string;
     }
@@ -419,26 +342,6 @@ public final class StringUtils {
   /**
    * Basically taken from commons-lang
    */
-  public static String uncapitalize(final String str) {
-    return String.valueOf(Character.toLowerCase(str.charAt(0))) + str.substring(1);
-  }
-
-  /**
-   * Basically taken from commons-lang
-   */
-  public static boolean isAlphanumeric(final String str) {
-    final int sz = str.length();
-    for (int i = 0; i < sz; i++) {
-      if (!Character.isLetterOrDigit(str.charAt(i))) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Basically taken from commons-lang
-   */
   public static String join(final List<String> list, final char separator) {
     final int size = list.size();
     if (size <= 0) {
@@ -493,13 +396,6 @@ public final class StringUtils {
     return true;
   }
 
-  public static boolean startsWith(final String string, final String prefix) {
-    if (string == null || prefix == null) {
-      return string == null && prefix == null;
-    }
-    return prefix.length() <= string.length() && string.regionMatches(0, prefix, 0, prefix.length());
-  }
-
   /**
    * <p>
    * Checks if the String contains any character in the given set of characters.
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/webapp/DebugResponseWriterWrapper.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/webapp/DebugResponseWriterWrapper.java
index 56806a6..f7e6df3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/webapp/DebugResponseWriterWrapper.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/webapp/DebugResponseWriterWrapper.java
@@ -19,7 +19,6 @@
 
 package org.apache.myfaces.tobago.internal.webapp;
 
-import org.apache.myfaces.tobago.internal.util.StringUtils;
 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
 import org.apache.myfaces.tobago.renderkit.html.HtmlTypes;
 import org.apache.myfaces.tobago.renderkit.html.MarkupLanguageAttributes;
@@ -62,7 +61,7 @@ public class DebugResponseWriterWrapper extends TobagoResponseWriter {
       LOG.error("Comment must not contain the sequence '--', comment = '" + comment + "'.",
           new IllegalArgumentException());
 
-      commentStr = StringUtils.replace(commentStr, "--", "++");
+      commentStr = commentStr.replaceAll("--", "++");
     }
     responseWriter.writeComment(commentStr);
   }
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 eebe119..a1ec4fe 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
@@ -402,10 +402,10 @@ class Sheet {
 
       const lastClickedRowIndex = parseInt(sheet.dataset["tobagoLastClickedRowIndex"]);
       if (event.shiftKey && selectionMode === "multi" && lastClickedRowIndex > -1) {
-        if (lastClickedRowIndex <= row.rowIndex) {
-          this.selectRange(rows, lastClickedRowIndex, row.rowIndex, true, false);
+        if (lastClickedRowIndex <= row.sectionRowIndex) {
+          this.selectRange(rows, lastClickedRowIndex, row.sectionRowIndex, true, false);
         } else {
-          this.selectRange(rows, row.rowIndex, lastClickedRowIndex, true, false);
+          this.selectRange(rows, row.sectionRowIndex, lastClickedRowIndex, true, false);
         }
       } else if (selectionMode !== "singleOrNone" || !this.isRowSelected(row)) {
         this.toggleSelection(row, selector);
@@ -517,7 +517,7 @@ class Sheet {
 
   doDblClick(event) {
     const row = <HTMLTableRowElement>event.currentTarget;
-    const rowIndex = row.rowIndex + this.getFirst();
+    const rowIndex = row.sectionRowIndex + this.getFirst();
     if (this.dblClickActionId) {
       let action;
       const index = this.dblClickActionId.indexOf(this.id);
@@ -559,21 +559,22 @@ class Sheet {
   isRowSelected(row: HTMLTableRowElement) {
     let rowIndex = +row.dataset["tobagoRowIndex"];
     if (!rowIndex) {
-      rowIndex = row.rowIndex + this.getFirst();
+      rowIndex = row.sectionRowIndex + this.getFirst();
     }
     return this.isSelected(rowIndex);
   }
 
-  isSelected(rowIndex) {
-    return this.getHiddenSelected().getAttribute("value").indexOf("," + rowIndex + ",") >= 0;
+  isSelected(rowIndex: number) {
+    const value = <number[]>JSON.parse(this.getHiddenSelected().value);
+    return value.indexOf(rowIndex) > -1;
   }
 
   resetSelected() {
-    this.getHiddenSelected().setAttribute("value", ",");
+    this.getHiddenSelected().value = JSON.stringify([]);
   }
 
   toggleSelection(row: HTMLTableRowElement, checkbox: HTMLInputElement) {
-    this.getElement().dataset["tobagoLastClickedRowIndex"] = String(row.rowIndex);
+    this.getElement().dataset["tobagoLastClickedRowIndex"] = String(row.sectionRowIndex);
     if (checkbox && !checkbox.disabled) {
       const selected = this.getHiddenSelected();
       const rowIndex = this.getDataIndex(row);
@@ -603,13 +604,13 @@ class Sheet {
   selectRange(
       rows: NodeListOf<HTMLTableRowElement>, first: number, last: number, selectDeselected: boolean, deselectSelected: boolean) {
     const selected = this.getHiddenSelected();
-    const value = selected.value;
+    const value = new Set<number>(JSON.parse(selected.value));
     for (let i = first; i <= last; i++) {
       const row = rows.item(i);
       const checkbox = this.getSelectorCheckbox(row);
       if (checkbox && !checkbox.disabled) {
         const rowIndex = this.getDataIndex(row);
-        const on = value.indexOf("," + rowIndex + ",") >= 0;
+        const on = value.has(rowIndex);
         if (selectDeselected && !on) {
           this.selectRow(selected, rowIndex, row, checkbox);
         } else if (deselectSelected && on) {
@@ -624,7 +625,7 @@ class Sheet {
     if (rowIndex) {
       return rowIndex;
     } else {
-      return row.rowIndex + this.getFirst();
+      return row.sectionRowIndex + this.getFirst();
     }
   }
 
@@ -635,7 +636,8 @@ class Sheet {
    * @param checkbox input-element: selector in the row.
    */
   selectRow(selected: HTMLInputElement, rowIndex: number, row: HTMLTableRowElement, checkbox: HTMLInputElement) {
-    selected.value = selected.value + rowIndex + ",";
+    const selectedSet = new Set<number>(JSON.parse(selected.value));
+    selected.value = JSON.stringify(Array.from(selectedSet.add(rowIndex)));
     row.classList.add("tobago-sheet-row-markup-selected");
     row.classList.add("table-info");
     checkbox.checked = true;
@@ -651,7 +653,9 @@ class Sheet {
    * @param checkbox input-element: selector in the row.
    */
   deselectRow(selected: HTMLInputElement, rowIndex: number, row: HTMLTableRowElement, checkbox: HTMLInputElement) {
-    selected.value = selected.value.replace(new RegExp("," + rowIndex + ","), ",");
+    const selectedSet = new Set<number>(JSON.parse(selected.value));
+    selectedSet.delete(rowIndex);
+    selected.value = JSON.stringify(Array.from(selectedSet));
     row.classList.remove("tobago-sheet-row-markup-selected");
     row.classList.remove("table-info");
     checkbox.checked = false;
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 df1aebc..49cf70d 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
@@ -23,11 +23,12 @@ class Tree {
     var src;
     var $node = $element.closest(".tobago-treeNode, .tobago-treeMenuNode");
     var $data = $node.closest(".tobago-treeMenu, .tobago-tree, .tobago-sheet");
-    var $expanded = $data.children(".tobago-treeMenu-expanded, .tobago-tree-expanded, .tobago-sheet-expanded");
+    const expanded: HTMLInputElement
+        = $data.children(".tobago-treeMenu-expanded, .tobago-tree-expanded, .tobago-sheet-expanded").get(0);
     var $toggles = $node.find(".tobago-treeMenuNode-toggle, .tobago-treeNode-toggle");
-    var rowIndex = Tree.rowIndex($node);
-    if (Tree.isExpanded($node, $expanded)) {
-      Tree.hideChildren($node);
+    var rowIndex = Tree.rowIndex($node.get(0));
+    if (Tree.isExpanded($node.get(0), expanded)) {
+      Tree.hideChildren($node.get(0));
       $toggles.find("i").each(function () {
         var $toggle = jQuery(this);
         $toggle.removeClass($toggle.data("tobago-open")).addClass($toggle.data("tobago-closed"));
@@ -40,12 +41,14 @@ class Tree {
         }
         $toggle.attr("src", src);
       });
-      $expanded.val($expanded.val().replace(new RegExp("," + rowIndex + ","), ","));
+      const set = Tree.getSet(expanded);
+      set.delete(rowIndex);
+      Tree.setSet(expanded, set);
       $node.filter(".tobago-treeNode").removeClass("tobago-treeNode-markup-expanded");
       $node.filter(".tobago-treeMenuNode").removeClass("tobago-treeMenuNode-markup-expanded");
     } else {
-      var reload = Tree.showChildren($node, $expanded);
-      $expanded.val($expanded.val() + rowIndex + ",");
+      const reload = Tree.showChildren($node.get(0), expanded);
+      Tree.setSet(expanded, Tree.getSet(expanded).add(rowIndex));
       if (reload) {
         jsf.ajax.request(
             $toggles.parent().attr("id"),
@@ -78,7 +81,7 @@ class Tree {
    * Hide all children of the node recursively.
    * @param node A jQuery-Object as a node of the tree.
    */
-  static hideChildren = function (node) {
+  static hideChildren = function (node: HTMLElement) {
     var inSheet = Tree.isInSheet(node);
     var children = Tree.findChildren(node);
     children.each(function () {
@@ -87,16 +90,17 @@ class Tree {
       } else {
         jQuery(this).hide();
       }
-      Tree.hideChildren(jQuery(this));
+      Tree.hideChildren(this);
     });
   };
 
   /**
    * Show the children of the node recursively, there parents are expanded.
    * @param node A jQuery-Object 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, expanded) {
+  static showChildren = function (node: HTMLElement, expanded: HTMLInputElement) {
     var inSheet = Tree.isInSheet(node);
     var children = Tree.findChildren(node);
     if (children.length == 0) {
@@ -104,14 +108,14 @@ class Tree {
       return true;
     }
     children.each(function () {
-      var child = jQuery(this);
+      var $child = jQuery(this);
       if (inSheet) {
-        child.parent().parent().show();
+        $child.parent().parent().show();
       } else {
-        child.show();
+        $child.show();
       }
-      if (Tree.isExpanded(child, expanded)) {
-        var reload = Tree.showChildren(child, expanded);
+      if (Tree.isExpanded($child.get(0), expanded)) {
+        var reload = Tree.showChildren($child.get(0), expanded);
         if (reload) {
           return true;
         }
@@ -146,12 +150,12 @@ class Tree {
     // selected for treeNode
     Tobago4Utils.selectWithJQuery(elements, ".tobago-treeCommand").focus(function () {
       var command = jQuery(this);
-      var node = command.parent(".tobago-treeNode");
-      var tree = node.closest(".tobago-tree");
+      var $node = command.parent(".tobago-treeNode");
+      var tree = $node.closest(".tobago-tree");
       var selected = tree.children(".tobago-tree-selected");
-      selected.val(Tree.rowIndex(node));
+      selected.val(Tree.rowIndex($node.get(0)));
       tree.find(".tobago-treeNode").removeClass("tobago-treeNode-markup-selected");
-      node.addClass("tobago-treeNode-markup-selected");
+      $node.addClass("tobago-treeNode-markup-selected");
     });
 
     // selected for treeSelect
@@ -159,48 +163,58 @@ class Tree {
       var select = jQuery(this);
       var selected = select.prop("checked");
       var data = select.closest(".tobago-treeMenu, .tobago-tree, .tobago-sheet");
-      var hidden = data.children(".tobago-treeMenu-selected, .tobago-tree-selected, .tobago-sheet-selected");
-      var newValue;
+      const hidden: HTMLInputElement
+          = data.children(".tobago-treeMenu-selected, .tobago-tree-selected, .tobago-sheet-selected").get(0);
+      let value: Set<number>;
+      // todo may use an class attribute for this value
       if (select.attr("type") === "radio") {
-        newValue = "," + Tree.rowIndex(select) + ",";
+        value = new Set();
+        value.add(Tree.rowIndex(select.get(0)));
       } else if (selected) {
-        newValue = hidden.val() + Tree.rowIndex(select) + ",";
+        value = Tree.getSet(hidden);
+        value.add(Tree.rowIndex(select.get(0)));
       } else {
-        newValue = (hidden.val() as string).replace(new RegExp("," + Tree.rowIndex(select) + ","), ",");
+        value = Tree.getSet(hidden);
+        value.delete(Tree.rowIndex(select.get(0)));
       }
-      // XXX optimize: regexp and Tobago.Tree.rowIndex
-      hidden.val(newValue);
+      Tree.setSet(hidden, value);
     });
 
     // selected for treeMenuNode
     Tobago4Utils.selectWithJQuery(elements, ".tobago-treeMenuCommand").focus(function () {
       var command = jQuery(this);
-      var node = command.parent(".tobago-treeMenuNode");
-      var tree = node.closest(".tobago-treeMenu");
-      var selected = tree.children(".tobago-treeMenu-selected");
-      selected.val(Tree.rowIndex(node));
+      var $node = command.parent(".tobago-treeMenuNode");
+      var tree = $node.closest(".tobago-treeMenu");
+      const selected: HTMLInputElement = tree.children(".tobago-treeMenu-selected").get(0);
+      const set = new Set<number>();
+      set.add(Tree.rowIndex($node.get(0)));
+      Tree.setSet(selected, set);
       tree.find(".tobago-treeMenuNode").removeClass("tobago-treeMenuNode-markup-selected");
-      node.addClass("tobago-treeMenuNode-markup-selected");
+      $node.addClass("tobago-treeMenuNode-markup-selected");
     });
 
     // init selected field
     Tobago4Utils.selectWithJQuery(elements, ".tobago-treeMenu, .tobago-tree, .tobago-sheet").each(function () {
-      var hidden = jQuery(this).children(".tobago-treeMenu-selected, .tobago-tree-selected, .tobago-sheet-selected");
-      var string = ",";
-      jQuery(this).find(".tobago-treeMenuNode-markup-selected, .tobago-treeNode-markup-selected").each(function () {
-        string += Tree.rowIndex(jQuery(this)) + ",";
-      });
-      hidden.val(string);
+      const hidden: HTMLInputElement = jQuery(this).children(".tobago-treeMenu-selected, .tobago-tree-selected, .tobago-sheet-selected").get(0);
+      if (hidden) {
+        const value = new Set<number>();
+        jQuery(this).find(".tobago-treeMenuNode-markup-selected, .tobago-treeNode-markup-selected").each(function () {
+          value.add(Tree.rowIndex(this));
+        });
+        Tree.setSet(hidden, value);
+      }
     });
 
     // init expanded field
     Tobago4Utils.selectWithJQuery(elements, ".tobago-treeMenu, .tobago-tree, .tobago-sheet").each(function () {
-      var hidden = jQuery(this).children(".tobago-treeMenu-expanded, .tobago-tree-expanded, .tobago-sheet-expanded");
-      var string = ",";
-      jQuery(this).find(".tobago-treeMenuNode-markup-expanded, .tobago-treeNode-markup-expanded").each(function () {
-        string += Tree.rowIndex(jQuery(this)) + ",";
-      });
-      hidden.val(string);
+      const hidden: HTMLInputElement = jQuery(this).children(".tobago-treeMenu-expanded, .tobago-tree-expanded, .tobago-sheet-expanded").get(0);
+      if (hidden) {
+        const value = new Set<number>();
+        jQuery(this).find(".tobago-treeMenuNode-markup-expanded, .tobago-treeNode-markup-expanded").each(function () {
+          value.add(Tree.rowIndex(this));
+        });
+        Tree.setSet(hidden, value);
+      }
     });
 
     // init tree selection for multiCascade
@@ -210,7 +224,7 @@ class Tree {
         jQuery(this).change(function (event) {
           var node = jQuery(event.target).parents(".tobago-treeNode");
           var checked = node.find("input[type=checkbox]").prop("checked");
-          var children = Tree.findChildren(node);
+          var children = Tree.findChildren(node.get(0));
           children.each(function () {
             var childsCheckbox = jQuery(this).find("input[type=checkbox]");
             childsCheckbox.prop("checked", checked);
@@ -222,28 +236,36 @@ class Tree {
 
   };
 
-  static isExpanded = function (node, expanded) {
-    var rowIndex = Tree.rowIndex(node);
-    return expanded.val().indexOf("," + rowIndex + ",") > -1;
+  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: HTMLElement, expanded: HTMLInputElement) {
+    const rowIndex = Tree.rowIndex(node);
+    return Tree.getSet(expanded).has(rowIndex);
   };
 
-  static rowIndex = function (node) {
-    return node.attr("id").replace(/.+\:(\d+)(\:\w+)+/, '$1');
+  static rowIndex = function (node: HTMLElement): number { // todo: use attribute data-tobago-row-index
+    return parseInt(node.id.replace(/.+\:(\d+)(\:\w+)+/, '$1'));
   };
 
-  static findChildren = function (node) {
-    var treeParentSelector = "[data-tobago-tree-parent='" + node.attr("id") + "']";
+  static findChildren = function (node: HTMLElement) {
+    var treeParentSelector = "[data-tobago-tree-parent='" + node.id + "']";
     var children;
     if (Tree.isInSheet(node)) {
-      children = node.parent("td").parent("tr").nextAll().children().children(treeParentSelector);
+      children = jQuery(node).parent("td").parent("tr").nextAll().children().children(treeParentSelector);
     } else { // normal tree
-      children = node.nextAll(treeParentSelector);
+      children = jQuery(node).nextAll(treeParentSelector);
     }
     return children;
   };
 
-  static isInSheet = function (node) {
-    return node.parent("td").length === 1;
+  static isInSheet = function (node: HTMLElement) {
+    return jQuery(node).parent("td").length === 1;
   };
 }