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

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


The following commit(s) were added to refs/heads/main by this push:
     new 91c250a98 WW-5778 feat(tags): support datetextfield in the html5 theme
91c250a98 is described below

commit 91c250a98dfef9b9db95d0ee5f3176f90abd92dd
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Oct 24 15:09:15 2026 +0200

    WW-5778 feat(tags): support datetextfield in the html5 theme
    
    The html5 theme renders a native input type="date" instead of the
    "works only with the JavaTemplates Plugin" placeholder. A java.util.Date
    value is rendered as ISO yyyy-MM-dd in the server's default time zone;
    any other value is rendered as-is so a conversion error re-renders the
    submitted string. The format attribute is now optional: it only lays out
    the JavaTemplates split fields and the html5 theme ignores it.
    
    DateTextField reports the DATE control type, so derived constraints
    apply. A required validator yields only data-msg-required: an empty
    date input submits "", and emitting required for temporal controls
    needs the bound type (WW-5811).
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/struts2/components/DateTextField.java   |  21 +++-
 .../resources/template/html5/datetextfield.ftl     |  31 +++++-
 .../resources/tags/datetextfield-attributes.html   |   4 +-
 .../resources/tags/datetextfield-description.html  |   2 +-
 .../struts2/components/ConstraintAction.java       |  12 +++
 .../struts2/views/jsp/ui/DateTextFieldTest.java    | 118 +++++++++++++++++++++
 .../views/jsp/ui/Html5ConstraintRenderingTest.java |  15 +++
 .../components/ConstraintAction-validation.xml     |   5 +
 .../struts2/views/jsp/ui/DateTextField-1-html5.txt |   1 +
 .../struts2/views/jsp/ui/DateTextField-2-html5.txt |   1 +
 .../struts2/views/jsp/ui/DateTextField-3-html5.txt |   1 +
 .../struts2/views/jsp/ui/DateTextField-4-html5.txt |   1 +
 12 files changed, 206 insertions(+), 6 deletions(-)

diff --git 
a/core/src/main/java/org/apache/struts2/components/DateTextField.java 
b/core/src/main/java/org/apache/struts2/components/DateTextField.java
index 314e37638..aaa04410d 100644
--- a/core/src/main/java/org/apache/struts2/components/DateTextField.java
+++ b/core/src/main/java/org/apache/struts2/components/DateTextField.java
@@ -24,10 +24,17 @@ import jakarta.servlet.http.HttpServletResponse;
 import org.apache.struts2.views.annotations.StrutsTag;
 import org.apache.struts2.views.annotations.StrutsTagAttribute;
 
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.Date;
+
+import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
+
 @StrutsTag(
     name="datetextfield",
     tldTagClass="org.apache.struts2.views.jsp.ui.DateTextFieldTag",
-    description="Render an HTML input fields with the date time",
+    description="Render an HTML input for a date: a native input type=\"date\" 
in the html5 theme,"
+        + " split input fields with the JavaTemplates plugin",
     allowDynamicAttributes=true)
 public class DateTextField extends UIBean {
     /**
@@ -51,9 +58,19 @@ public class DateTextField extends UIBean {
         if (format != null) {
             addParameter("format", findValue(format, String.class));
         }
+
+        if (getAttributes().get(ATTR_NAME_VALUE) instanceof Date date) {
+            addParameter("isoDate", 
ISO_LOCAL_DATE.format(Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault())));
+        }
+    }
+
+    @Override
+    protected HtmlControlType getControlType() {
+        return HtmlControlType.DATE;
     }
 
-    @StrutsTagAttribute(description="Date format attribute", required=true)
+    @StrutsTagAttribute(description="Date format used to lay out the split 
input fields of the JavaTemplates plugin;"
+        + " ignored by the html5 theme, which always uses yyyy-MM-dd")
     public void setFormat(String format) {
         this.format = format;
     }
diff --git a/core/src/main/resources/template/html5/datetextfield.ftl 
b/core/src/main/resources/template/html5/datetextfield.ftl
index 25f198f44..35845fc48 100644
--- a/core/src/main/resources/template/html5/datetextfield.ftl
+++ b/core/src/main/resources/template/html5/datetextfield.ftl
@@ -18,4 +18,33 @@
  * under the License.
  */
 -->
-<div>Tag <pre style="display: inline-block">&lt;s:datetextfield/&gt;</pre> 
works only with the JavaTemplates Plugin!</div>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/controlheader.ftl" 
/><#rt/>
+<input<#rt/>
+ type="date"<#rt/>
+ name="${(attributes.name!"")}"<#rt/>
+<#if attributes.isoDate??>
+ value="${attributes.isoDate}"<#rt/>
+<#elseif attributes.nameValue??>
+ value="${attributes.nameValue}"<#rt/>
+</#if>
+<#if attributes.disabled!false>
+ disabled="disabled"<#rt/>
+</#if>
+<#if attributes.readonly!false>
+ readonly="readonly"<#rt/>
+</#if>
+<#if attributes.tabindex?has_content>
+ tabindex="${attributes.tabindex}"<#rt/>
+</#if>
+<#if attributes.id?has_content>
+ id="${attributes.id}"<#rt/>
+</#if>
+<#include "/${attributes.templateDir}/${attributes.expandTheme}/css.ftl" />
+<#if attributes.title?has_content>
+ title="${attributes.title}"<#rt/>
+</#if>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/scripting-events.ftl" 
/><#rt/>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/common-attributes.ftl" 
/><#rt/>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/dynamic-attributes.ftl" 
/><#rt/>
+/><#rt/>
+<#include 
"/${attributes.templateDir}/${attributes.expandTheme}/controlfooter.ftl" 
/><#rt/>
diff --git a/core/src/site/resources/tags/datetextfield-attributes.html 
b/core/src/site/resources/tags/datetextfield-attributes.html
index e69e04ad0..496271099 100644
--- a/core/src/site/resources/tags/datetextfield-attributes.html
+++ b/core/src/site/resources/tags/datetextfield-attributes.html
@@ -71,11 +71,11 @@
     </tr>
     <tr>
         <td class="tag-attribute">format</td>
-        <td class="tag-attribute"><strong>true</strong></td>
+        <td class="tag-attribute">false</td>
         <td class="tag-attribute"></td>
         <td class="tag-attribute">false</td>
         <td class="tag-attribute">String</td>
-        <td class="tag-attribute">Date format attribute</td>
+        <td class="tag-attribute">Date format used to lay out the split input 
fields of the JavaTemplates plugin; ignored by the html5 theme, which always 
uses yyyy-MM-dd</td>
     </tr>
     <tr>
         <td class="tag-attribute">id</td>
diff --git a/core/src/site/resources/tags/datetextfield-description.html 
b/core/src/site/resources/tags/datetextfield-description.html
index a26aa1a62..34506b8e0 100644
--- a/core/src/site/resources/tags/datetextfield-description.html
+++ b/core/src/site/resources/tags/datetextfield-description.html
@@ -1 +1 @@
-Render an HTML input fields with the date time
+Render an HTML input for a date: a native input type="date" in the html5 
theme, split input fields with the JavaTemplates plugin
diff --git 
a/core/src/test/java/org/apache/struts2/components/ConstraintAction.java 
b/core/src/test/java/org/apache/struts2/components/ConstraintAction.java
index 1f26af95f..8f2a5dc37 100644
--- a/core/src/test/java/org/apache/struts2/components/ConstraintAction.java
+++ b/core/src/test/java/org/apache/struts2/components/ConstraintAction.java
@@ -21,6 +21,8 @@ package org.apache.struts2.components;
 import org.apache.struts2.ActionSupport;
 import org.apache.struts2.interceptor.parameter.StrutsParameter;
 
+import java.util.Date;
+
 public class ConstraintAction extends ActionSupport {
 
     private String username;
@@ -34,6 +36,7 @@ public class ConstraintAction extends ActionSupport {
     private String choice;
     private int priority;
     private Object attachment;
+    private Date birthday;
 
     public String getUsername() {
         return username;
@@ -106,6 +109,15 @@ public class ConstraintAction extends ActionSupport {
         this.attachment = attachment;
     }
 
+    public Date getBirthday() {
+        return birthday;
+    }
+
+    @StrutsParameter
+    public void setBirthday(Date birthday) {
+        this.birthday = birthday;
+    }
+
     public ConstraintUser getOwner() {
         return owner;
     }
diff --git 
a/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTextFieldTest.java 
b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTextFieldTest.java
new file mode 100644
index 000000000..0c177f643
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DateTextFieldTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.struts2.views.jsp.ui;
+
+import org.apache.struts2.action.Action;
+import org.apache.struts2.components.ConstraintAction;
+import org.apache.struts2.views.jsp.AbstractUITagTest;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+
+public class DateTextFieldTest extends AbstractUITagTest {
+
+    private static final Date OCTOBER_24 = Date.from(LocalDate.of(2026, 10, 
24).atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+    @Override
+    public Action getAction() {
+        return new ConstraintAction();
+    }
+
+    public void testGenericHtml5() throws Exception {
+        DateTextFieldTag tag = new DateTextFieldTag();
+        verifyGenericProperties(tag, "html5", new String[]{"value"});
+    }
+
+    public void testHtml5RendersTheDateAsIsoIgnoringFormat() throws Exception {
+        ((ConstraintAction) action).setBirthday(OCTOBER_24);
+
+        DateTextFieldTag tag = html5Tag();
+        tag.setFormat("MM/dd/yyyy");
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-1-html5.txt"));
+    }
+
+    public void testHtml5RendersSqlDateAsIso() throws Exception {
+        ((ConstraintAction) 
action).setBirthday(java.sql.Date.valueOf(LocalDate.of(2026, 10, 24)));
+
+        DateTextFieldTag tag = html5Tag();
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-1-html5.txt"));
+    }
+
+    public void testHtml5RendersNoValueWithoutADate() throws Exception {
+        DateTextFieldTag tag = html5Tag();
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-2-html5.txt"));
+    }
+
+    /**
+     * After a conversion error the field re-renders the raw submitted string, 
which must come back unchanged.
+     */
+    public void testHtml5RendersAStringValueAsIs() throws Exception {
+        DateTextFieldTag tag = html5Tag();
+        tag.setValue("%{'2026-10-24'}");
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-1-html5.txt"));
+    }
+
+    public void testHtml5RendersDynamicMinAndMax() throws Exception {
+        DateTextFieldTag tag = html5Tag();
+        tag.setDynamicAttribute(null, "min", "2026-01-01");
+        tag.setDynamicAttribute(null, "max", "2026-12-31");
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-3-html5.txt"));
+    }
+
+    public void testHtml5RendersCommonAttributes() throws Exception {
+        ((ConstraintAction) action).setBirthday(OCTOBER_24);
+
+        DateTextFieldTag tag = html5Tag();
+        tag.setId("dob");
+        tag.setCssClass("date");
+        tag.setCssStyle("width: 10em");
+        tag.setTitle("Date of birth");
+        tag.setTabindex("3");
+        tag.setDisabled("true");
+        tag.setOnchange("check()");
+        tag.doStartTag();
+        tag.doEndTag();
+
+        
verify(DateTextFieldTag.class.getResource("DateTextField-4-html5.txt"));
+    }
+
+    private DateTextFieldTag html5Tag() {
+        DateTextFieldTag tag = new DateTextFieldTag();
+        tag.setPageContext(pageContext);
+        tag.setTheme("html5");
+        tag.setName("birthday");
+        return tag;
+    }
+}
diff --git 
a/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
 
b/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
index 84fb7e6f7..a0fa997c4 100644
--- 
a/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
+++ 
b/core/src/test/java/org/apache/struts2/views/jsp/ui/Html5ConstraintRenderingTest.java
@@ -143,6 +143,21 @@ public class Html5ConstraintRenderingTest extends 
AbstractUITagTest {
         assertFalse("expected no required in: " + output, 
output.contains("required=\"required\""));
     }
 
+    /**
+     * An empty date input submits "", which only fails {@code required} when 
the bound type converts it to null.
+     */
+    public void testRendersNoRequiredOnADateTextField() throws Exception {
+        String output = renderTag("true", () -> {
+            DateTextFieldTag date = new DateTextFieldTag();
+            date.setName("birthday");
+            return date;
+        });
+
+        assertTrue("expected the required message in: " + output,
+            output.contains("type=\"date\" name=\"birthday\"") && 
output.contains("data-msg-required=\"pick a date\""));
+        assertFalse("expected no required in: " + output, 
output.contains("required=\"required\""));
+    }
+
     public void testRendersRequiredOnAFileInput() throws Exception {
         String output = renderTag("true", () -> {
             FileTag file = new FileTag();
diff --git 
a/core/src/test/resources/org/apache/struts2/components/ConstraintAction-validation.xml
 
b/core/src/test/resources/org/apache/struts2/components/ConstraintAction-validation.xml
index 1eb3dd111..260822407 100644
--- 
a/core/src/test/resources/org/apache/struts2/components/ConstraintAction-validation.xml
+++ 
b/core/src/test/resources/org/apache/struts2/components/ConstraintAction-validation.xml
@@ -63,6 +63,11 @@
             <message>attach a file</message>
         </field-validator>
     </field>
+    <field name="birthday">
+        <field-validator type="required">
+            <message>pick a date</message>
+        </field-validator>
+    </field>
     <field name="user">
         <field-validator type="visitor">
             <message/>
diff --git 
a/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-1-html5.txt
 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-1-html5.txt
new file mode 100644
index 000000000..cef5b8202
--- /dev/null
+++ 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-1-html5.txt
@@ -0,0 +1 @@
+<input type="date" name="birthday" value="2026-10-24" id="birthday"/>
diff --git 
a/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-2-html5.txt
 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-2-html5.txt
new file mode 100644
index 000000000..a7d2a3b34
--- /dev/null
+++ 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-2-html5.txt
@@ -0,0 +1 @@
+<input type="date" name="birthday" id="birthday"/>
diff --git 
a/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-3-html5.txt
 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-3-html5.txt
new file mode 100644
index 000000000..b51cc2a28
--- /dev/null
+++ 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-3-html5.txt
@@ -0,0 +1 @@
+<input type="date" name="birthday" id="birthday" min="2026-01-01" 
max="2026-12-31"/>
diff --git 
a/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-4-html5.txt
 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-4-html5.txt
new file mode 100644
index 000000000..4ae3f69b5
--- /dev/null
+++ 
b/core/src/test/resources/org/apache/struts2/views/jsp/ui/DateTextField-4-html5.txt
@@ -0,0 +1 @@
+<input type="date" name="birthday" value="2026-10-24" disabled="disabled" 
tabindex="3" id="dob" class="date" style="width: 10em" title="Date of birth" 
onchange="check()"/>

Reply via email to