You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by we...@apache.org on 2023/11/14 12:14:16 UTC

(myfaces) branch main updated: https://issues.apache.org/jira/browse/MYFACES-4640:

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

werpu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/myfaces.git


The following commit(s) were added to refs/heads/main by this push:
     new 0f901c07c https://issues.apache.org/jira/browse/MYFACES-4640:
     new e029f1744 Merge pull request #645 from werpu/main
0f901c07c is described below

commit 0f901c07ccab710dcecb4b0741cd7fd91b5f8864
Author: Werner Punz <we...@gmail.com>
AuthorDate: Tue Nov 14 11:49:52 2023 +0100

    https://issues.apache.org/jira/browse/MYFACES-4640:
    
    Fixes fro 4640 and new tests!
---
 api/src/client/package-lock.json                   |  30 +--
 api/src/client/package.json                        |   2 +-
 .../typescript/faces/impl/xhrCore/XhrRequest.ts    |   4 +-
 .../faces/test/xhrCore/RequestParamsTest.spec.ts   | 300 ++++++++++++++++++++-
 api/src/client/typescript/mona_dish/AssocArray.ts  | 114 ++++++--
 5 files changed, 414 insertions(+), 36 deletions(-)

diff --git a/api/src/client/package-lock.json b/api/src/client/package-lock.json
index 6b61c2fa6..df1d17680 100644
--- a/api/src/client/package-lock.json
+++ b/api/src/client/package-lock.json
@@ -22,7 +22,7 @@
         "html-webpack-plugin": "^5.5.1",
         "jsdom": "^21.1.1",
         "jsdom-global": "^3.0.2",
-        "jsf.js_next_gen": "4.0.2-beta.5",
+        "jsf.js_next_gen": "4.0.2-beta.8",
         "mocha": "^10.2.0",
         "npm-check-updates": "^16.10.8",
         "nyc": "^15.1.0",
@@ -5063,12 +5063,12 @@
       }
     },
     "node_modules/jsf.js_next_gen": {
-      "version": "4.0.2-beta.5",
-      "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.0.2-beta.5.tgz",
-      "integrity": "sha512-O6whE+an6vm4sFyMXOVMp7HXk+W/ID0dtn5Vo7yI7NU6lL+17QMqggW7DLTcJhLAxwavr3AVoSfnaSuYUnu+kw==",
+      "version": "4.0.2-beta.8",
+      "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.0.2-beta.8.tgz",
+      "integrity": "sha512-Jvcq52qJlV0M2+l94SRMW2HZ5ICax6fzppVR6HViqIx5u3PwA9YAS8OPsW5IahmPlMIGY600oFXBCrNiHChudw==",
       "dev": true,
       "dependencies": {
-        "mona-dish": "0.28.10"
+        "mona-dish": "0.28.11"
       }
     },
     "node_modules/json-buffer": {
@@ -5961,9 +5961,9 @@
       }
     },
     "node_modules/mona-dish": {
-      "version": "0.28.10",
-      "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.28.10.tgz",
-      "integrity": "sha512-m7SZ8HsFZCCuadDWLaea6jekGeDZ8f2NdhsBkjVgChQoud4gIHCf7zf0lYrz5HDzLNL0zmI9wdzAoRavzidwuA==",
+      "version": "0.28.11",
+      "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.28.11.tgz",
+      "integrity": "sha512-1sfIvsdgt+SdTgnvGS6VHYEtFZKaNUNZo13Oq+XaJjsxEk0gYPGO2XnHpIgog8ZSlNX/KcgF+w7fX3P3MixYsw==",
       "dev": true
     },
     "node_modules/ms": {
@@ -13964,12 +13964,12 @@
       "dev": true
     },
     "jsf.js_next_gen": {
-      "version": "4.0.2-beta.5",
-      "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.0.2-beta.5.tgz",
-      "integrity": "sha512-O6whE+an6vm4sFyMXOVMp7HXk+W/ID0dtn5Vo7yI7NU6lL+17QMqggW7DLTcJhLAxwavr3AVoSfnaSuYUnu+kw==",
+      "version": "4.0.2-beta.8",
+      "resolved": "https://registry.npmjs.org/jsf.js_next_gen/-/jsf.js_next_gen-4.0.2-beta.8.tgz",
+      "integrity": "sha512-Jvcq52qJlV0M2+l94SRMW2HZ5ICax6fzppVR6HViqIx5u3PwA9YAS8OPsW5IahmPlMIGY600oFXBCrNiHChudw==",
       "dev": true,
       "requires": {
-        "mona-dish": "0.28.10"
+        "mona-dish": "0.28.11"
       }
     },
     "json-buffer": {
@@ -14668,9 +14668,9 @@
       }
     },
     "mona-dish": {
-      "version": "0.28.10",
-      "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.28.10.tgz",
-      "integrity": "sha512-m7SZ8HsFZCCuadDWLaea6jekGeDZ8f2NdhsBkjVgChQoud4gIHCf7zf0lYrz5HDzLNL0zmI9wdzAoRavzidwuA==",
+      "version": "0.28.11",
+      "resolved": "https://registry.npmjs.org/mona-dish/-/mona-dish-0.28.11.tgz",
+      "integrity": "sha512-1sfIvsdgt+SdTgnvGS6VHYEtFZKaNUNZo13Oq+XaJjsxEk0gYPGO2XnHpIgog8ZSlNX/KcgF+w7fX3P3MixYsw==",
       "dev": true
     },
     "ms": {
diff --git a/api/src/client/package.json b/api/src/client/package.json
index 93ca0b198..2b10d3569 100644
--- a/api/src/client/package.json
+++ b/api/src/client/package.json
@@ -24,7 +24,7 @@
     "html-webpack-plugin": "^5.5.1",
     "jsdom": "^21.1.1",
     "jsdom-global": "^3.0.2",
-    "jsf.js_next_gen": "4.0.2-beta.5",
+    "jsf.js_next_gen": "4.0.2-beta.8",
     "mocha": "^10.2.0",
     "npm-check-updates": "^16.10.8",
     "nyc": "^15.1.0",
diff --git a/api/src/client/typescript/faces/impl/xhrCore/XhrRequest.ts b/api/src/client/typescript/faces/impl/xhrCore/XhrRequest.ts
index ac5c3097a..abfb4aea5 100644
--- a/api/src/client/typescript/faces/impl/xhrCore/XhrRequest.ts
+++ b/api/src/client/typescript/faces/impl/xhrCore/XhrRequest.ts
@@ -412,11 +412,11 @@ export class XhrRequest extends AsyncRunnable<XMLHttpRequest> {
 
             //Checkbox and radio only value pass if checked is set, otherwise they should not show
             //up at all, and if checked is set, they either can have a value or simply being boolean
-            if((type == "checkbox" || type == "radio") && issuingItem.attr("checked").isAbsent()) {
+            if((type == "checkbox" || type == "radio") && !issuingItem.checked) {
                 return;
             } else if((type == "checkbox" || type == "radio")) {
                 arr.assign(issuingItemId).value = itemValue.orElse(true).value;
-            } else {
+            } else if (itemValue.isPresent()) {
                 arr.assign(issuingItemId).value = itemValue.value;
             }
 
diff --git a/api/src/client/typescript/faces/test/xhrCore/RequestParamsTest.spec.ts b/api/src/client/typescript/faces/test/xhrCore/RequestParamsTest.spec.ts
index 9f8e53889..e6ce77ed0 100644
--- a/api/src/client/typescript/faces/test/xhrCore/RequestParamsTest.spec.ts
+++ b/api/src/client/typescript/faces/test/xhrCore/RequestParamsTest.spec.ts
@@ -265,4 +265,302 @@ describe("test for proper request param patterns identical to the old implementa
         done();
     });
 
-});
\ No newline at end of file
+    /**
+     * This test is based on Tobago 6.0.0 (Jakarte EE 10).
+     */
+    it("tobago tree select", function (done) {
+        window.document.body.innerHTML = `
+<tobago-page locale='de' class='container-fluid' id='page' focus-on-error='true' wait-overlay-delay-full='1000' wait-overlay-delay-ajax='1000'>
+   <form action='/content/090-tree/01-select/Tree_Select.xhtml' id='page::form' method='post' accept-charset='UTF-8' data-tobago-context-path=''>
+    <input type='hidden' name='jakarta.faces.source' id='jakarta.faces.source' disabled='disabled'>
+    <tobago-focus id='page::lastFocusId'>
+     <input type='hidden' name='page::lastFocusId' id='page::lastFocusId::field'>
+    </tobago-focus>
+    <input type='hidden' name='org.apache.myfaces.tobago.webapp.Secret' id='org.apache.myfaces.tobago.webapp.Secret' value='secretValue'>
+    <div class='tobago-page-menuStore'>
+    </div>
+    <div class='tobago-page-toastStore'>
+    </div>
+    <span id='page::faces-state-container'><input type='hidden' name='jakarta.faces.ViewState' id='j_id__v_0:jakarta.faces.ViewState:1' value='viewStateValue' autocomplete='off'><input type='hidden' name='jakarta.faces.RenderKitId' value='tobago'><input type='hidden' id='j_id__v_0:jakarta.faces.ClientWindow:1' name='jakarta.faces.ClientWindow' value='clientWindowValue'></span>
+    <tobago-tree id='page:categoriesTree' data-tobago-selectable='multi' selectable='multi'>
+<tobago-tree-node id='page:categoriesTree:0:j_id_3' class='tobago-folder tobago-expanded' expandable='expandable' index='0' data-tobago-level='0'>
+<span id='page:categoriesTree:0:j_id_4' class='tobago-toggle'><i class='bi-dash-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:0:select' value='page:categoriesTree:0:select' id='page:categoriesTree:0:select'>
+<label class='form-check-label' for='page:categoriesTree:0:select'>Category</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:0:select' execute='page:categoriesTree:0:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:1:j_id_3' index='1' data-tobago-tree-parent='page:categoriesTree:0:j_id_3' parent='page:categoriesTree:0:j_id_3' data-tobago-level='1'>
+<span id='page:categoriesTree:1:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:1:select' value='page:categoriesTree:1:select' id='page:categoriesTree:1:select'>
+<label class='form-check-label' for='page:categoriesTree:1:select'>Sports</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:1:select' execute='page:categoriesTree:1:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:2:j_id_3' index='2' data-tobago-tree-parent='page:categoriesTree:0:j_id_3' parent='page:categoriesTree:0:j_id_3' data-tobago-level='1'>
+<span id='page:categoriesTree:2:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:2:select' value='page:categoriesTree:2:select' id='page:categoriesTree:2:select'>
+<label class='form-check-label' for='page:categoriesTree:2:select'>Movies</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:2:select' execute='page:categoriesTree:2:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:3:j_id_3' class='tobago-selected tobago-folder tobago-expanded' selected='selected' expandable='expandable' index='3' data-tobago-tree-parent='page:categoriesTree:0:j_id_3' parent='page:categoriesTree:0:j_id_3' data-tobago-level='1'>
+<span id='page:categoriesTree:3:j_id_4' class='tobago-toggle'><i class='bi-dash-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:3:select' value='page:categoriesTree:3:select' id='page:categoriesTree:3:select' checked='checked'>
+<label class='form-check-label' for='page:categoriesTree:3:select'>Music</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:3:select' execute='page:categoriesTree:3:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:4:j_id_3' index='4' data-tobago-tree-parent='page:categoriesTree:3:j_id_3' parent='page:categoriesTree:3:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:4:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:4:select' value='page:categoriesTree:4:select' id='page:categoriesTree:4:select'>
+<label class='form-check-label' for='page:categoriesTree:4:select'>Classic</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:4:select' execute='page:categoriesTree:4:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:5:j_id_3' index='5' data-tobago-tree-parent='page:categoriesTree:3:j_id_3' parent='page:categoriesTree:3:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:5:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:5:select' value='page:categoriesTree:5:select' id='page:categoriesTree:5:select'>
+<label class='form-check-label' for='page:categoriesTree:5:select'>Pop</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:5:select' execute='page:categoriesTree:5:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:6:j_id_3' class='tobago-folder' expandable='expandable' index='6' data-tobago-tree-parent='page:categoriesTree:3:j_id_3' parent='page:categoriesTree:3:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:6:j_id_4' class='tobago-toggle'><i class='bi-plus-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:6:select' value='page:categoriesTree:6:select' id='page:categoriesTree:6:select'>
+<label class='form-check-label' for='page:categoriesTree:6:select'>World</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:6:select' execute='page:categoriesTree:6:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:7:j_id_3' index='7' data-tobago-tree-parent='page:categoriesTree:0:j_id_3' parent='page:categoriesTree:0:j_id_3' data-tobago-level='1'>
+<span id='page:categoriesTree:7:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:7:select' value='page:categoriesTree:7:select' id='page:categoriesTree:7:select'>
+<label class='form-check-label' for='page:categoriesTree:7:select'>Games</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:7:select' execute='page:categoriesTree:7:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:8:j_id_3' class='tobago-folder tobago-expanded' expandable='expandable' index='8' data-tobago-tree-parent='page:categoriesTree:0:j_id_3' parent='page:categoriesTree:0:j_id_3' data-tobago-level='1'>
+<span id='page:categoriesTree:8:j_id_4' class='tobago-toggle'><i class='bi-dash-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:8:select' value='page:categoriesTree:8:select' id='page:categoriesTree:8:select'>
+<label class='form-check-label' for='page:categoriesTree:8:select'>Science</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:8:select' execute='page:categoriesTree:8:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:9:j_id_3' class='tobago-folder' expandable='expandable' index='9' data-tobago-tree-parent='page:categoriesTree:8:j_id_3' parent='page:categoriesTree:8:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:9:j_id_4' class='tobago-toggle'><i class='bi-plus-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:9:select' value='page:categoriesTree:9:select' id='page:categoriesTree:9:select'>
+<label class='form-check-label' for='page:categoriesTree:9:select'>Mathematics</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:9:select' execute='page:categoriesTree:9:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:10:j_id_3' index='10' data-tobago-tree-parent='page:categoriesTree:8:j_id_3' parent='page:categoriesTree:8:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:10:j_id_4' class='tobago-toggle invisible'><i class='bi-square invisible'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:10:select' value='page:categoriesTree:10:select' id='page:categoriesTree:10:select'>
+<label class='form-check-label' for='page:categoriesTree:10:select'>Geography</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:10:select' execute='page:categoriesTree:10:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<tobago-tree-node id='page:categoriesTree:11:j_id_3' class='tobago-folder' expandable='expandable' index='11' data-tobago-tree-parent='page:categoriesTree:8:j_id_3' parent='page:categoriesTree:8:j_id_3' data-tobago-level='2'>
+<span id='page:categoriesTree:11:j_id_4' class='tobago-toggle'><i class='bi-plus-square' data-tobago-open='bi-dash-square' data-tobago-closed='bi-plus-square'></i></span>
+<tobago-tree-select class='form-check-inline form-check'>
+<input class='form-check-input' type='checkbox' name='page:categoriesTree:11:select' value='page:categoriesTree:11:select' id='page:categoriesTree:11:select'>
+<label class='form-check-label' for='page:categoriesTree:11:select'>Astronomy</label>
+<tobago-behavior event='change' client-id='page:categoriesTree:11:select' execute='page:categoriesTree:11:select page:categoriesTree' render='page:selectedNodesOutput page:categoriesTree'></tobago-behavior>
+</tobago-tree-select>
+</tobago-tree-node>
+<input type='hidden' name='page:categoriesTree::selected' id='page:categoriesTree::selected' class='tobago-selected' value='[3]'>
+<input type='hidden' name='page:categoriesTree::expanded' id='page:categoriesTree::expanded' class='tobago-expanded' value='[0,3,8]'>
+<tobago-scroll>
+<input id='page:categoriesTree::scrollPosition' name='page:categoriesTree::scrollPosition' type='hidden' value='[0,0]' data-tobago-scroll-position='true'>
+</tobago-scroll>
+</tobago-tree>
+    <tobago-out id='page:selectedNodesOutput' class='tobago-label-container tobago-auto-spacing'><label for='page:selectedNodesOutput' class='col-form-label'>Selected Nodes</label><span class='form-control-plaintext'>Music</span></tobago-out>
+   </form>
+   <noscript>
+    <div class='tobago-page-noscript'>Diese Seite benötigt JavaScript, allerdings ist JavaScript in Ihrem Browser derzeit deaktiviert. Um JavaScript zu aktivieren, lesen Sie ggf. die Anleitung Ihres Browsers.
+    </div>
+   </noscript>
+  </tobago-page>
+`;
+
+        //we now run the tests here
+        try {
+            document.querySelector<HTMLInputElement>("input[name='page:categoriesTree:3:select']").checked = false;
+
+            let event = {
+                isTrusted: true,
+                type: 'change',
+                target: document.getElementById("page:categoriesTree:3:select"),
+                currentTarget: document.getElementById("page:categoriesTree:3:select")
+            };
+            global.debug2 = true;
+            faces.ajax.request(
+                document.getElementById("page:categoriesTree:3:select"),
+                event as any,
+                {
+                    "jakarta.faces.behavior.event": "change",
+                    execute: 'page:categoriesTree:3:select page:categoriesTree',
+                    render: 'page:selectedNodesOutput page:categoriesTree'
+                });
+        } catch (err) {
+            console.error(err);
+            expect(false).to.eq(true);
+        }
+        const requestBody = this.requests[0].requestBody;
+        let arsArr = requestBody.split("&");
+        let resultsMap = {};
+        for (let val of arsArr) {
+            let keyVal = val.split("=");
+
+            if (resultsMap[keyVal[0]]) {
+                console.log("duplicated key '" + keyVal[0] + "'");
+                expect(resultsMap[keyVal[0]]).not.to.exist;
+            }
+            resultsMap[keyVal[0]] = keyVal[1];
+        }
+
+        expect(resultsMap[encodeURIComponent("page::lastFocusId")]).to.exist;
+        expect(resultsMap["org.apache.myfaces.tobago.webapp.Secret"]).to.eq("secretValue");
+        expect(resultsMap["jakarta.faces.ViewState"]).to.eq("viewStateValue");
+        expect(resultsMap["jakarta.faces.RenderKitId"]).to.eq("tobago");
+        expect(resultsMap["jakarta.faces.ClientWindow"]).to.eq("clientWindowValue");
+        expect(resultsMap[encodeURIComponent("page:categoriesTree::selected")]).to.eq(encodeURIComponent("[3]"));
+        expect(resultsMap[encodeURIComponent("page:categoriesTree::expanded")]).to.eq(encodeURIComponent("[0,3,8]"));
+        expect(resultsMap[encodeURIComponent("page:categoriesTree::scrollPosition")]).to.eq(encodeURIComponent("[0,0]"));
+        expect(resultsMap["jakarta.faces.behavior.event"]).to.eq("change");
+        expect(resultsMap["jakarta.faces.partial.event"]).to.eq("change");
+        expect(resultsMap["jakarta.faces.source"]).to.eq(encodeURIComponent("page:categoriesTree:3:select"));
+        expect(resultsMap["jakarta.faces.partial.ajax"]).to.eq("true");
+        expect(resultsMap[encodeURIComponent("page::form")]).to.eq(encodeURIComponent("page::form"));
+        expect(resultsMap["jakarta.faces.partial.execute"]).to.eq(encodeURIComponent("page:categoriesTree:3:select page:categoriesTree"));
+        expect(resultsMap["jakarta.faces.partial.render"]).to.eq(encodeURIComponent("page:selectedNodesOutput page:categoriesTree"));
+        expect(resultsMap[encodeURIComponent("page:categoriesTree:3:select")]).not.to.exist;
+
+        done();
+    });
+
+  /**
+   * This test is based on Tobago 6.0.0 (Jakarte EE 10).
+   */
+  it("tobago selectManyShuttle", function (done) {
+    window.document.body.innerHTML = `
+<tobago-page locale="de" class="container-fluid" id="page" focus-on-error="true" wait-overlay-delay-full="1000" wait-overlay-delay-ajax="1000">
+   <form action="/content/030-select/70-selectManyShuttle/Shuttle.xhtml" id="page::form" method="post" accept-charset="UTF-8" data-tobago-context-path="">
+    <input type="hidden" name="jakarta.faces.source" id="jakarta.faces.source" disabled="disabled">
+    <tobago-focus id="page::lastFocusId">
+     <input type="hidden" name="page::lastFocusId" id="page::lastFocusId::field">
+    </tobago-focus>
+    <input type="hidden" name="org.apache.myfaces.tobago.webapp.Secret" id="org.apache.myfaces.tobago.webapp.Secret" value="secretValue">
+    <div class="tobago-page-menuStore">
+    </div>
+    <div class="tobago-page-toastStore">
+    </div>
+    <span id="page::faces-state-container"><input type="hidden" name="jakarta.faces.ViewState" id="j_id__v_0:jakarta.faces.ViewState:1" value="viewStateValue" autocomplete="off"><input type="hidden" name="jakarta.faces.RenderKitId" value="tobago"><input type="hidden" id="j_id__v_0:jakarta.faces.ClientWindow:1" name="jakarta.faces.ClientWindow" value="clientWindowValue"></span>
+    <tobago-select-many-shuttle id="page:ajaxExample" class="tobago-auto-spacing">
+     <div class="tobago-body">
+      <div class="tobago-unselected-container">
+       <select id="page:ajaxExample::unselected" class="tobago-unselected form-select" multiple="multiple" size="4">
+        <option value="Proxima Centauri">Proxima Centauri
+        </option>
+        <option value="Alpha Centauri">Alpha Centauri
+        </option>
+        <option value="Wolf 359">Wolf 359
+        </option></select>
+      </div>
+      <div class="tobago-controls">
+       <div class="btn-group-vertical">
+        <button type="button" class="btn btn-secondary" id="page:ajaxExample::addAll"><i class="bi-chevron-double-right"></i></button>
+        <button type="button" class="btn btn-secondary" id="page:ajaxExample::add"><i class="bi-chevron-right"></i></button>
+        <button type="button" class="btn btn-secondary" id="page:ajaxExample::remove"><i class="bi-chevron-left"></i></button>
+        <button type="button" class="btn btn-secondary" id="page:ajaxExample::removeAll"><i class="bi-chevron-double-left"></i></button>
+       </div>
+      </div>
+      <div class="tobago-selected-container">
+       <select id="page:ajaxExample::selected" class="tobago-selected form-select" multiple="multiple" size="4">
+        <option value="Sirius">Sirius
+        </option></select>
+      </div>
+      <select class="d-none" id="page:ajaxExample::hidden" name="page:ajaxExample" multiple="multiple">
+       <option value="Proxima Centauri">Proxima Centauri
+       </option>
+       <option value="Alpha Centauri">Alpha Centauri
+       </option>
+       <option value="Wolf 359">Wolf 359
+       </option>
+       <option value="Sirius" selected="selected">Sirius
+       </option></select>
+     </div>
+     <tobago-behavior event="change" client-id="page:ajaxExample" execute="page:ajaxExample" render="page:outputStars"></tobago-behavior>
+    </tobago-select-many-shuttle>
+    <tobago-out id="page:outputStars" class="tobago-label-container tobago-auto-spacing"><label for="page:outputStars" class="col-form-label">Selected Stars</label><span class="form-control-plaintext">[Sirius]</span></tobago-out>
+   </form>
+  </tobago-page>
+`;
+
+    //we now run the tests here
+    try {
+      let siriusOption = document.querySelector<HTMLOptionElement>(".tobago-selected option");
+      document.querySelector<HTMLSelectElement>(".tobago-unselected").add(siriusOption);
+      document.getElementById("page:ajaxExample::hidden")
+          .querySelector<HTMLOptionElement>("option[value='Sirius']").selected = false;
+
+      let event = {
+        isTrusted: true,
+        type: 'change',
+        target: document.getElementById("page:ajaxExample"),
+        currentTarget: document.getElementById("page:ajaxExample")
+      };
+      global.debug2 = true;
+      faces.ajax.request(
+          document.getElementById("page:ajaxExample"),
+          event as any,
+          {
+            "jakarta.faces.behavior.event": "change",
+            execute: 'page:ajaxExample',
+            render: 'page:outputStars'
+          });
+    } catch (err) {
+      console.error(err);
+      expect(false).to.eq(true);
+    }
+    const requestBody = this.requests[0].requestBody;
+    let arsArr = requestBody.split("&");
+    let resultsMap = {};
+    for (let val of arsArr) {
+      let keyVal = val.split("=");
+
+      if (resultsMap[keyVal[0]]) {
+        console.log("duplicated key '" + keyVal[0] + "'");
+        expect(resultsMap[keyVal[0]]).not.to.exist;
+      }
+      resultsMap[keyVal[0]] = keyVal[1];
+    }
+
+    expect(resultsMap[encodeURIComponent("page::lastFocusId")]).to.exist;
+    expect(resultsMap["org.apache.myfaces.tobago.webapp.Secret"]).to.eq("secretValue");
+    expect(resultsMap["jakarta.faces.ViewState"]).to.eq("viewStateValue");
+    expect(resultsMap["jakarta.faces.RenderKitId"]).to.eq("tobago");
+    expect(resultsMap["jakarta.faces.ClientWindow"]).to.eq("clientWindowValue");
+    expect(resultsMap["jakarta.faces.behavior.event"]).to.eq("change");
+    expect(resultsMap["jakarta.faces.partial.event"]).to.eq("change");
+    expect(resultsMap["jakarta.faces.source"]).to.eq(encodeURIComponent("page:ajaxExample"));
+    expect(resultsMap["jakarta.faces.partial.ajax"]).to.eq("true");
+    expect(resultsMap[encodeURIComponent("page::form")]).to.eq(encodeURIComponent("page::form"));
+    expect(resultsMap["jakarta.faces.partial.execute"]).to.eq(encodeURIComponent("page:ajaxExample"));
+    expect(resultsMap["jakarta.faces.partial.render"]).to.eq(encodeURIComponent("page:outputStars"));
+    expect(resultsMap[encodeURIComponent("page:ajaxExample")]).not.to.exist;
+
+    done();
+  });
+});
diff --git a/api/src/client/typescript/mona_dish/AssocArray.ts b/api/src/client/typescript/mona_dish/AssocArray.ts
index 852903fab..f9c03fd2a 100644
--- a/api/src/client/typescript/mona_dish/AssocArray.ts
+++ b/api/src/client/typescript/mona_dish/AssocArray.ts
@@ -251,12 +251,69 @@ export function simpleShallowMerge(...assocArrays) {
    return shallowMerge(true, false, ...assocArrays);
 }
 
+function _appendWithOverwrite(withAppend: boolean, target: { [p: string]: any }, key, arr, toAssign) {
+    if (!withAppend) {
+        target[key] = arr[key];
+    } else {
+        //overwrite means in this case, no double entries!
+        //we do not a deep compare for now a single value compare suffices
+        if ('undefined' == typeof target?.[key]) {
+            target[key] = toAssign
+        } else if (!Array.isArray(target[key])) {
+
+            let oldVal = target[key];
+            let newVals = [];
+            //TODO maybe deep deep compare here, but on the other hand it is
+            //shallow
+            toAssign.forEach(item => {
+                if (oldVal != item) {
+                    newVals.push(item);
+                }
+            });
+            target[key] = new Es2019Array(...[]);
+            target[key].push(oldVal);
+            target[key].push(...newVals);
+        } else {
+            let oldVal = target[key];
+            let newVals = [];
+            //TODO deep compare here
+            toAssign.forEach(item => {
+                if (oldVal.indexOf(item) == -1) {
+                    newVals.push(item);
+                }
+            });
+
+            target[key].push(...newVals);
+        }
+    }
+}
+
+function _appendWithoutOverwrite(withAppend: boolean, target: { [p: string]: any }, key, arr, toAssign) {
+    if (!withAppend) {
+        return;
+    } else {
+        //overwrite means in this case, no double entries!
+        //we do not a deep compare for now a single value compare suffices
+        if ('undefined' == typeof target?.[key]) {
+            target[key] = toAssign
+        } else if (!Array.isArray(target[key])) {
+            let oldVal = target[key];
+            target[key] = new Es2019Array(...[]);
+            target[key].push(oldVal);
+            target[key].push(...toAssign);
+        } else {
+            target[key].push(...toAssign);
+        }
+    }
+}
+
 /**
  * Shallow merge as in config, but on raw associative arrays
  *
- * @param overwrite
- * @param withAppend
- * @param assocArrays
+ * @param overwrite overwrite existing keys, if they exist with their subtrees
+ * @param withAppend if a key exist append the values or drop them
+ * Combination overwrite withappend filters doubles out of merged arrays
+ * @param assocArrays array of assoc arres reduced right to left
  */
 export function shallowMerge(overwrite = true, withAppend = false, ...assocArrays) {
     let target: {[key: string]: any} = {};
@@ -269,23 +326,46 @@ export function shallowMerge(overwrite = true, withAppend = false, ...assocArray
                 toAssign = new Es2019Array(...[toAssign]);
             }
             if(overwrite || !target?.[key]) {
-                if(!withAppend) {
-                    target[key] = arr[key];
-                } else {
-                        if('undefined' == typeof target?.[key]) {
-                            target[key] = toAssign
-                        } else if(!Array.isArray(target[key])) {
-                            let oldVal = target[key];
-                            target[key] = new Es2019Array(...[]);
-                            target[key].push(oldVal);
-                            target[key].push(...toAssign);
-                        } else {
-                            target[key].push(...toAssign);
-                        }
-                }
+                _appendWithOverwrite(withAppend, target, key, arr, toAssign);
+            } else if(!overwrite && target?.[key]) {
+                _appendWithoutOverwrite(withAppend, target, key, arr, toAssign);
             }
+
+
+
         })
     });
     return target;
 }
 
+//TODO test this, slightly altered from https://medium.com/@pancemarko/deep-equality-in-javascript-determining-if-two-objects-are-equal-bf98cf47e934
+//he overlooked some optimizations and a shortcut at typeof!
+export function deepEqual(obj1, obj2) {
+    if(obj1 == obj2) {
+        return false;
+    }
+    if(typeof obj1 != typeof obj2) {
+        return false;
+    }
+    if(Array.isArray(obj1) && Array.isArray(obj2)) {
+        if(obj1.length != obj2.length) {
+            return;
+        }
+        //arrays must be equal, order as well, there is no way around it
+        //this is the major limitation we have
+        return obj1.every((item, cnt) => deepEqual(item, obj2[cnt]));
+    }
+    //string number and other primitives are filtered out here
+    if("object" == typeof obj1 && "object" == typeof obj2) {
+        let keys1 = Object.keys(obj1);
+        let keys2 = Object.keys(obj2);
+        if(keys1.length != keys2.length) {
+            return false;
+        }
+        return keys1.every(key => keys2.indexOf(key) != -1) &&
+        keys1.every(key => deepEqual(obj1[key], obj2[key]));
+    }
+    return false;
+    //done here no match found
+}
+