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 2025/05/09 10:12:13 UTC

(myfaces-tobago) branch tobago-5.x updated: fix: no client validation while immediate (#6421)

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

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


The following commit(s) were added to refs/heads/tobago-5.x by this push:
     new 7f6856271e fix: no client validation while immediate (#6421)
7f6856271e is described below

commit 7f6856271e46d480dda7e5b6912779f68ccbc5bb
Author: Udo Schnurpfeil <lo...@apache.org>
AuthorDate: Fri May 9 12:12:07 2025 +0200

    fix: no client validation while immediate (#6421)
    
    issue: TOBAGO-2418
---
 .../myfaces/tobago/internal/renderkit/Command.java | 28 +++++++++++++++++
 .../renderkit/renderer/TabGroupRenderer.java       |  3 ++
 .../renderer/TobagoClientBehaviorRenderer.java     |  6 +++-
 .../myfaces/tobago/internal/util/RenderUtils.java  |  3 +-
 .../myfaces/tobago/renderkit/RendererBase.java     |  4 ++-
 .../tobago/renderkit/html/CustomAttributes.java    |  1 +
 .../tobago/internal/renderkit/CommandUnitTest.java |  6 ++--
 .../tobago/internal/util/JsonUtilsUnitTest.java    | 15 ++++-----
 .../2510-tab-file/TabGroup_Immediate_File.xhtml    | 36 ++++++++++++++++++++++
 .../tobago-theme-standard/src/main/js/tobago.js    |  2 +-
 .../src/main/js/tobago.js.map                      |  2 +-
 .../src/main/ts/tobago-behavior.ts                 | 21 +++++++++++--
 12 files changed, 109 insertions(+), 18 deletions(-)

diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Command.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Command.java
index b65d330fb4..81ddf1af81 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Command.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/Command.java
@@ -39,7 +39,24 @@ public class Command {
   private Boolean resetValues;
   private Boolean stopPropagation;
   private String customEventName;
+  private Boolean immediate;
 
+  public Command(
+      final String clientId, final String fieldId, final Boolean transition, final String target, final String execute,
+      final String render, final String confirmation, final Integer delay,
+      final Collapse collapse, final Boolean omit, final Boolean stopPropagation, final String customEventName,
+      final Boolean immediate) {
+    this(clientId, fieldId, transition, target, execute, render, confirmation, delay, collapse, omit, stopPropagation,
+        customEventName);
+    this.immediate = immediate;
+  }
+
+  /**
+   * @deprecated Please use
+   * {@link Command#Command(String, String, Boolean, String, String, String, String, Integer, Collapse, Boolean,
+   * Boolean, String, Boolean)}
+   */
+  @Deprecated(since = "6.7.2")
   public Command(
       final String clientId, final String fieldId, final Boolean transition, final String target, final String execute,
       final String render, final String confirmation, final Integer delay,
@@ -158,6 +175,14 @@ public String getCustomEventName() {
     return customEventName;
   }
 
+  public Boolean getImmediate() {
+    return immediate;
+  }
+
+  public void setImmediate(Boolean immediate) {
+    this.immediate = immediate;
+  }
+
   public void merge(final Command c) {
 
     //XXX TBD: check if this is okay.
@@ -210,5 +235,8 @@ public void merge(final Command c) {
     if (customEventName == null) {
       customEventName = c.customEventName;
     }
+    if (immediate == null) {
+      immediate = c.immediate;
+    }
   }
 }
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
index 36f58b594f..7ac5fa89ce 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TabGroupRenderer.java
@@ -79,6 +79,7 @@ public void processEvent(final ComponentSystemEvent event) {
     final AbstractUITabGroup tabGroup = (AbstractUITabGroup) event.getComponent();
     final FacesContext facesContext = FacesContext.getCurrentInstance();
     final ClientBehaviors tabChange = ClientBehaviors.tabChange;
+    final boolean immediate = tabGroup.isImmediate();
     switch (tabGroup.getSwitchType()) {
       case none:
         break;
@@ -91,6 +92,7 @@ public void processEvent(final ComponentSystemEvent event) {
             UINamingContainer.getSeparatorChar(facesContext) + tabGroup.getClientId(facesContext));
         ajaxBehavior.setExecute(ids);
         ajaxBehavior.setRender(ids);
+        ajaxBehavior.setImmediate(immediate);
         tabGroup.addClientBehavior(tabChange.getJsEvent(), ajaxBehavior);
         break;
       case reloadPage:
@@ -100,6 +102,7 @@ public void processEvent(final ComponentSystemEvent event) {
         tabGroup.getChildren().add(component);
         final EventBehavior eventBehavior = new EventBehavior();
         eventBehavior.setFor(component.getId());
+        eventBehavior.setImmediate(immediate);
         tabGroup.addClientBehavior(tabChange.getJsEvent(), eventBehavior);
         break;
       default:
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
index 416cf0ac6f..427004027a 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
@@ -81,6 +81,7 @@ public String getScript(final ClientBehaviorContext behaviorContext, final Clien
     Integer delay = null;
     Boolean stopPropagation = null;
     String customEventName = null;
+    boolean immediate = false;
 
     final String confirmation = ComponentUtils.getConfirmation(uiComponent);
     if (behavior instanceof AjaxBehavior) {
@@ -120,6 +121,7 @@ public String getScript(final ClientBehaviorContext behaviorContext, final Clien
         omit = command.isOmit() || StringUtils.isNotBlank(RenderUtils.generateUrl(facesContext, command));
       }
       renderIds = ComponentUtils.evaluateClientIds(facesContext, uiComponent, render.toArray(new String[0]));
+      immediate = ajaxBehavior.isImmediate();
     } else if (behavior instanceof EventBehavior) { // <tc:event>
       final EventBehavior eventBehavior = (EventBehavior) behavior;
       final AbstractUIEvent event = RenderUtils.getAbstractUIEvent(uiComponent, eventBehavior);
@@ -137,6 +139,7 @@ public String getScript(final ClientBehaviorContext behaviorContext, final Clien
           omit = event.isOmit() || StringUtils.isNotBlank(RenderUtils.generateUrl(facesContext, event));
           stopPropagation = event.getStopPropagation();
           customEventName = event.getCustomEventName();
+          immediate = event.isImmediate();
         }
       }
     } else {
@@ -155,7 +158,8 @@ public String getScript(final ClientBehaviorContext behaviorContext, final Clien
         collapse,
         omit,
         stopPropagation,
-        customEventName);
+        customEventName,
+        immediate);
 
     if (resetValues) {
       command.setResetValues(true);
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 07ec1b058c..0027dd9528 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
@@ -340,7 +340,8 @@ public static CommandMap getBehaviorCommands(
           TobagoClientBehaviorRenderer.createCollapsible(facesContext, holder),
           holder.isOmit(),
           null,
-          null));
+          null,
+          holder.isImmediate()));
     }
 
     return commandMap;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
index 55b5c57f6d..162c886d9d 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/RendererBase.java
@@ -308,6 +308,7 @@ private void encodeBehavior(
     }
     writer.writeAttribute(CustomAttributes.CUSTOM_EVENT_NAME, command.getCustomEventName(), true);
     // todo: all the other attributes
+    writer.writeAttribute(CustomAttributes.IMMEDIATE, command.getImmediate());
     writer.endElement(HtmlElements.TOBAGO_BEHAVIOR);
   }
 
@@ -359,7 +360,8 @@ protected CommandMap getBehaviorCommands(
           TobagoClientBehaviorRenderer.createCollapsible(facesContext, holder),
           holder.isOmit(),
           null,
-          null));
+          null,
+          holder.isImmediate()));
     }
 
     return commandMap;
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
index 0fb69ef669..d3e3c02403 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/renderkit/html/CustomAttributes.java
@@ -44,6 +44,7 @@ public enum CustomAttributes implements MarkupLanguageAttributes {
    * Internationalization strings for e.g. date specific labels, e.g. January or Monday.
    */
   I18N("i18n"),
+  IMMEDIATE("immediate"),
   /**
    * The index of the tab inside the tab group.
    */
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
index f48784cee4..a6f34bb83c 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/renderkit/CommandUnitTest.java
@@ -30,11 +30,11 @@ public void test() {
 
     final Command a = new Command(
         "a client id", "a field id", null, null, "a execute", null, "a conf", null,
-        new Collapse(Collapse.Operation.show, "a collapse"), false, false, "dummy");
+        new Collapse(Collapse.Operation.show, "a collapse"), false, false, "dummy", false);
 
     final Command b = new Command(
         "b client id", "b field id", null, null, "b execute", null, "b conf", null,
-        new Collapse(Collapse.Operation.show, "b collapse"), false, false, "dummy");
+        new Collapse(Collapse.Operation.show, "b collapse"), false, false, "dummy", true);
 
     a.merge(b);
 
@@ -50,7 +50,7 @@ public void test() {
 
     final Command c = new Command(
         null, null, null, null, null, null, null, null,
-        null, false, false, "dummy");
+        null, false, false, "dummy", false);
 
     c.merge(b);
 
diff --git a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
index 5fcb3a0367..20ebd59332 100644
--- a/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
+++ b/tobago-core/src/test/java/org/apache/myfaces/tobago/internal/util/JsonUtilsUnitTest.java
@@ -56,7 +56,7 @@ public void empty() {
   @Test
   public void click() {
     final CommandMap map = new CommandMap();
-    map.setClick(new Command(null, null, null, null, "", null, null, null, null, null, false, "dummy"));
+    map.setClick(new Command(null, null, null, null, "", null, null, null, null, null, false, "dummy", false));
     final String expected = "{'click':{}}".replaceAll("'", "\"");
     Assertions.assertEquals(expected, JsonUtils.encode(map));
   }
@@ -65,7 +65,7 @@ public void click() {
   public void change() {
     final CommandMap map = new CommandMap();
     map.addCommand(ClientBehaviors.change,
-        new Command(null, null, null, null, null, null, null, null, null, null, false, "dummy"));
+        new Command(null, null, null, null, null, null, null, null, null, null, false, "dummy", false));
     final String expected = "{'change':{}}".replaceAll("'", "\"");
     Assertions.assertEquals(expected, JsonUtils.encode(map));
   }
@@ -74,9 +74,9 @@ public void change() {
   public void two() {
     final CommandMap map = new CommandMap();
     map.addCommand(ClientBehaviors.click,
-        new Command(null, null, null, "target", null, null, null, null, null, null, false, "dummy"));
+        new Command(null, null, null, "target", null, null, null, null, null, null, false, "dummy", true));
     map.addCommand(ClientBehaviors.change,
-        new Command(null, null, null, null, null, null, null, null, null, null, false, "dummy"));
+        new Command(null, null, null, null, null, null, null, null, null, null, false, "dummy", false));
     final String expected = "{'click':{'target':'target'},'change':{}}".replaceAll("'", "\"");
     Assertions.assertEquals(expected, JsonUtils.encode(map));
   }
@@ -84,7 +84,8 @@ public void two() {
   @Test
   public void transition() {
     final CommandMap commandMap = new CommandMap();
-    commandMap.setClick(new Command(null, null, false, null, null, null, null, null, null, null, false, "dummy"));
+    commandMap.setClick(new Command(null, null, false, null, null, null, null, null, null, null, false, "dummy",
+        false));
     final String expected = "{'click':{'transition':false}}".replaceAll("'", "\"");
     Assertions.assertEquals(expected, JsonUtils.encode(commandMap));
   }
@@ -103,7 +104,7 @@ public void more() {
         "_blank",
         StringUtils.join(Arrays.asList("id1", "id2"), ' '),
         StringUtils.join(Arrays.asList("id1", "id2"), ' '),
-        "Really?", 1000, new Collapse(Collapse.Operation.show, "myId"), true, false, "dummy");
+        "Really?", 1000, new Collapse(Collapse.Operation.show, "myId"), true, false, "dummy", false);
     click.setResetValues(true);
     map.setClick(click);
     final String expected = (
@@ -179,7 +180,7 @@ public void testCommandMap() {
         ClientBehaviors.blur,
         new Command(
             "doit", null, false, "field", "execute", "render", "Do \"you\" want?", 100,
-            new Collapse(Collapse.Operation.hide, "box"), false, false, "dummy"));
+            new Collapse(Collapse.Operation.hide, "box"), false, false, "dummy", false));
 
     final String expected
         = ("{'blur':"
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2510-tab-file/TabGroup_Immediate_File.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2510-tab-file/TabGroup_Immediate_File.xhtml
new file mode 100644
index 0000000000..33c875d203
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2510-tab-file/TabGroup_Immediate_File.xhtml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<ui:composition template="/main.xhtml"
+                xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:tc="http://myfaces.apache.org/tobago/component"
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+
+  <tc:tabGroup switchType="reloadTab" immediate="true">
+    <tc:tab id="nt1" label="One">
+      <tc:file required="true"/>
+    </tc:tab>
+    <tc:tab id="nt2" label="Two">
+      Two
+    </tc:tab>
+  </tc:tabGroup>
+
+  <tc:button action="ok" label="ok"/>
+
+</ui:composition>
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
index 9491cfb99f..d742d5ff37 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js
@@ -15,5 +15,5 @@ const st=new Map,nt={set(e,t,s){st.has(e)||st.set(e,new Map);const n=st.get(e);n
       ${s>0?" tabindex='"+String(s)+"'":""}
       @click="${this.removeBadge.bind(this)}"
       @focus="${this.focusEvent.bind(this)}"
-      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,n=this.hiddenSelect.querySelector(`[value="${s}"]`);n.selected=!1;const i=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=i.previousElementSibling,r="SPAN"===(null===(t=i.nextElementSibling)||void 0===t?void 0:t.tagName)?i.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
+      @blur="${this.blurEvent.bind(this)}"><i class='bi-x-lg'></i></button>`}removeBadge(e){var t;const s=e.target.closest(".btn-group").dataset.tobagoValue,n=this.hiddenSelect.querySelector(`[value="${s}"]`);n.selected=!1;const i=this.badges.querySelector(`[data-tobago-value="${s}"]`),o=i.previousElementSibling,r="SPAN"===(null===(t=i.nextElementSibling)||void 0===t?void 0:t.tagName)?i.nextElementSibling:null;o?o.querySelector("button.btn.badge").focus():r?r.querySelector("button.btn.ba [...]
 //# sourceMappingURL=tobago.js.map
diff --git a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
index f479b37eca..e9199d6793 100644
--- a/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
+++ b/tobago-theme/tobago-theme-standard/src/main/js/tobago.js.map
@@ -1 +1 @@
-{"version":3,"file":"tobago.js","sources":["../ts/tobago-css.ts","../ts/tobago-key.ts","../ts/tobago-client-behaviors.ts","../ts/tobago-dropdown-menu.ts","../ts/tobago-bar.ts","../ts/tobago-date.ts","../ts/tobago-menu-store.ts","../ts/tobago-dropdown-item-factory.ts","../ts/tobago-dropdown-item.ts","../ts/tobago-dropdown.ts","../ts/tobago-overlay-type.ts","../ts/tobago-suggest-filter.ts","../ts/tobago-page-static.ts","../ts/tobago-page.ts","../ts/tobago-overlay.ts","../ts/tobago-file.ts" [...]
\ No newline at end of file
+{"version":3,"file":"tobago.js","sources":["../ts/tobago-css.ts","../ts/tobago-key.ts","../ts/tobago-client-behaviors.ts","../ts/tobago-dropdown-menu.ts","../ts/tobago-bar.ts","../ts/tobago-date.ts","../ts/tobago-menu-store.ts","../ts/tobago-dropdown-item-factory.ts","../ts/tobago-dropdown-item.ts","../ts/tobago-dropdown.ts","../ts/tobago-overlay-type.ts","../ts/tobago-suggest-filter.ts","../ts/tobago-page-static.ts","../ts/tobago-page.ts","../ts/tobago-overlay.ts","../ts/tobago-file.ts" [...]
\ No newline at end of file
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
index 3f2e8b3a67..1d217fbeba 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-behavior.ts
@@ -77,9 +77,12 @@ class Behavior extends HTMLElement {
 
     switch (this.mode) {
       case BehaviorMode.ajax:
-        if (!this.validate(this.execute)) {
-          console.warn("Validation failed! No request will be sent.");
-          return;
+        if (!this.immediate) {
+          const valid = this.validate(this.execute);
+          if (!valid) {
+            console.warn("Validation failed! No request will be sent.");
+            return;
+          }
         }
         break;
       case BehaviorMode.full:
@@ -385,6 +388,18 @@ class Behavior extends HTMLElement {
     }
   }
 
+  get immediate(): boolean {
+    return this.hasAttribute("immediate");
+  }
+
+  set immediate(immediate: boolean) {
+    if (immediate) {
+      this.setAttribute("immediate", "");
+    } else {
+      this.removeAttribute("immediate");
+    }
+  }
+
   get stopPropagation(): boolean {
     return this.hasAttribute("stop-propagation");
   }