From 243984f6ffb78231d93e78971041e482d3010da7 Mon Sep 17 00:00:00 2001 From: Aaron Elijah Mars <272311952+aeonframework@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:18:20 -0400 Subject: [PATCH 1/2] WW-5772 Bind transfer-select buttons from data attributes instead of inline onclick The optiontransferselect, inputtransferselect and updownselect tags rendered every button with a framework-generated onclick handler that built a JavaScript call from the select ids and the headerKey / doubleHeaderKey tag attributes. Pages using these tags therefore needed 'unsafe-inline' in their Content-Security-Policy. Each button now carries its action, target select ids and header keys in data-* attributes, and optiontransferselect.js / inputtransferselect.js register one delegated click listener per page (capture phase) that calls the same functions with the same arguments as before. A developer-supplied *Onclick attribute on optiontransferselect is still rendered as onclick and still runs after the framework action. The rendered markup now depends on the matching static JS: an application that serves its own copy of optiontransferselect.js or inputtransferselect.js through struts.ui.staticContentPath must update it together with the templates. Generated-by: an AI coding assistant; reviewed and tested by the author. --- .../struts2/static/inputtransferselect.js | 43 +++++ .../struts2/static/optiontransferselect.js | 93 ++++++++++ .../template/simple/inputtransferselect.ftl | 16 +- .../template/simple/optiontransferselect.ftl | 165 ++++++------------ .../template/simple/updownselect.ftl | 12 +- .../jsp/ui/OptionTransferSelectTagTest.java | 28 +++ .../views/jsp/ui/UpDownSelectTagTest.java | 18 ++ .../struts2/views/jsp/ui/Formtag-11.txt | 6 +- .../apache/struts2/views/jsp/ui/Formtag-2.txt | 6 +- .../struts2/views/jsp/ui/Formtag-22.txt | 6 +- .../struts2/views/jsp/ui/Formtag-24.txt | 6 +- .../apache/struts2/views/jsp/ui/Formtag-6.txt | 6 +- .../views/jsp/ui/inputtransferselect-1.txt | 10 +- .../views/jsp/ui/inputtransferselect-2.txt | 10 +- .../views/jsp/ui/optiontransferselect-1.txt | 18 +- .../views/jsp/ui/optiontransferselect-2.txt | 18 +- .../views/jsp/ui/optiontransferselect-3.txt | 18 +- .../views/jsp/ui/optiontransferselect-4.txt | 18 +- .../views/jsp/ui/optiontransferselect-5.txt | 18 +- .../views/jsp/ui/optiontransferselect-6.txt | 18 +- .../views/jsp/ui/optiontransferselect-7.txt | 12 +- .../views/jsp/ui/optiontransferselect-8.txt | 18 +- .../views/jsp/ui/updownselecttag-1.txt | 6 +- .../views/jsp/ui/updownselecttag-10.txt | 6 +- .../views/jsp/ui/updownselecttag-11.txt | 6 +- .../views/jsp/ui/updownselecttag-2.txt | 6 +- .../views/jsp/ui/updownselecttag-3.txt | 6 +- .../views/jsp/ui/updownselecttag-4.txt | 6 +- .../views/jsp/ui/updownselecttag-5.txt | 6 +- .../views/jsp/ui/updownselecttag-6.txt | 4 +- .../views/jsp/ui/updownselecttag-7.txt | 4 +- .../views/jsp/ui/updownselecttag-8.txt | 4 +- .../views/jsp/ui/updownselecttag-9.txt | 6 +- 33 files changed, 371 insertions(+), 252 deletions(-) diff --git a/core/src/main/resources/org/apache/struts2/static/inputtransferselect.js b/core/src/main/resources/org/apache/struts2/static/inputtransferselect.js index 80e5a270ff..36f70b71f4 100644 --- a/core/src/main/resources/org/apache/struts2/static/inputtransferselect.js +++ b/core/src/main/resources/org/apache/struts2/static/inputtransferselect.js @@ -133,3 +133,46 @@ function swapOptions(objTargetElement, first, second) { opt[first] = temp2; opt[second] = temp; } + +/* + * Buttons rendered by the inputtransferselect tag carry their action and target select id in data-* attributes + * instead of inline onclick handlers, so the tag works under a Content-Security-Policy without 'unsafe-inline': + * data-struts-inputtransferselect="add|remove|removeAll|moveUp|moveDown", data-struts-target, optional + * data-struts-header-key + * The listener is registered once per page, in the capture phase, so it is not stopped by an ancestor's + * stopPropagation(). + */ +(function () { + if (window.strutsInputTransferSelectListener) { + return; + } + window.strutsInputTransferSelectListener = true; + + document.addEventListener('click', function (event) { + var button = event.target; + if (!button || !button.getAttribute) { + return; + } + var action = button.getAttribute('data-struts-inputtransferselect'); + if (!action) { + return; + } + var targetId = button.getAttribute('data-struts-target'); + var target = document.getElementById(targetId); + if (!target) { + return; + } + var headerKey = button.getAttribute('data-struts-header-key') || ''; + if (action === 'add') { + addOption(document.getElementById(targetId + '_input'), target); + } else if (action === 'remove') { + removeOptions(target); + } else if (action === 'removeAll') { + removeAllOptions(target); + } else if (action === 'moveUp') { + moveOptionUp(target, 'key', headerKey); + } else if (action === 'moveDown') { + moveOptionDown(target, 'key', headerKey); + } + }, true); +})(); diff --git a/core/src/main/resources/org/apache/struts2/static/optiontransferselect.js b/core/src/main/resources/org/apache/struts2/static/optiontransferselect.js index 681450b7b9..a9d91312f8 100644 --- a/core/src/main/resources/org/apache/struts2/static/optiontransferselect.js +++ b/core/src/main/resources/org/apache/struts2/static/optiontransferselect.js @@ -189,3 +189,96 @@ function swapOptions(objTargetElement, first, second) { opt[first] = temp2; opt[second] = temp; } + +/* + * Buttons rendered by the optiontransferselect and updownselect tags carry their action, target select ids and header + * keys in data-* attributes instead of inline onclick handlers, so the tags work under a Content-Security-Policy + * without 'unsafe-inline': + * data-struts-updownselect="moveUp|moveDown|selectAll", data-struts-target, optional data-struts-header-key + * data-struts-optiontransferselect="leftUp|leftDown|rightUp|rightDown|addToLeft|addToRight|addAllToLeft| + * addAllToRight|selectAll", data-struts-left, data-struts-right, optional data-struts-header-key and + * data-struts-double-header-key + * The listener is registered once per page, in the capture phase, so the framework action still runs before a + * developer-supplied onclick attribute on the same button and is not stopped by an ancestor's stopPropagation(). + */ +(function () { + if (window.strutsOptionTransferSelectListener) { + return; + } + window.strutsOptionTransferSelectListener = true; + + function headerKey(button, name) { + return button.hasAttribute(name) ? button.getAttribute(name) : undefined; + } + + function selectAll(target, key) { + if (key === undefined) { + selectAllOptions(target); + } else { + selectAllOptionsExceptSome(target, 'key', key); + } + } + + function transfer(move, source, target, key) { + if (key === undefined) { + move(source, target, false, ''); + } else { + move(source, target, false, key, ''); + } + } + + function updownselect(button, action) { + var target = document.getElementById(button.getAttribute('data-struts-target')); + var key = headerKey(button, 'data-struts-header-key'); + if (action === 'moveUp') { + moveOptionUp(target, 'key', key === undefined ? '' : key); + } else if (action === 'moveDown') { + moveOptionDown(target, 'key', key === undefined ? '' : key); + } else if (action === 'selectAll') { + selectAll(target, key); + } + } + + function optiontransferselect(button, action) { + var left = document.getElementById(button.getAttribute('data-struts-left')); + var right = document.getElementById(button.getAttribute('data-struts-right')); + var leftKey = headerKey(button, 'data-struts-header-key'); + var rightKey = headerKey(button, 'data-struts-double-header-key'); + if (action === 'leftDown') { + moveOptionDown(left, 'key', leftKey === undefined ? '' : leftKey); + } else if (action === 'leftUp') { + moveOptionUp(left, 'key', leftKey === undefined ? '' : leftKey); + } else if (action === 'rightDown') { + moveOptionDown(right, 'key', rightKey === undefined ? '' : rightKey); + } else if (action === 'rightUp') { + moveOptionUp(right, 'key', rightKey === undefined ? '' : rightKey); + } else if (action === 'addToLeft') { + transfer(moveSelectedOptions, right, left, rightKey); + } else if (action === 'addToRight') { + transfer(moveSelectedOptions, left, right, leftKey); + } else if (action === 'addAllToLeft') { + transfer(moveAllOptions, right, left, rightKey); + } else if (action === 'addAllToRight') { + transfer(moveAllOptions, left, right, leftKey); + } else if (action === 'selectAll') { + selectAll(left, leftKey); + selectAll(right, rightKey); + } + } + + document.addEventListener('click', function (event) { + var button = event.target; + if (!button || !button.getAttribute) { + return; + } + var action = button.getAttribute('data-struts-updownselect'); + if (action) { + updownselect(button, action); + return; + } + action = button.getAttribute('data-struts-optiontransferselect'); + if (action) { + optiontransferselect(button, action); + } + }, true); +})(); diff --git a/core/src/main/resources/template/simple/inputtransferselect.ftl b/core/src/main/resources/template/simple/inputtransferselect.ftl index c0ff17a146..90f124d2eb 100644 --- a/core/src/main/resources/template/simple/inputtransferselect.ftl +++ b/core/src/main/resources/template/simple/inputtransferselect.ftl @@ -69,7 +69,7 @@ <#if attributes.buttonCssStyle?has_content> style="${attributes.buttonCssStyle}" <#t/> - value="${addLabel}" onclick="addOption(document.getElementById('${attributes.id}_input'), document.getElementById('${attributes.id}'))" />

+ value="${addLabel}" data-struts-inputtransferselect="add" data-struts-target="${attributes.id}" />

<#t/> <#assign removeLabel=(attributes.removeLabel!"<-") /><#t/> style="${attributes.buttonCssStyle}" <#t/> - value="${removeLabel}" onclick="removeOptions(document.getElementById('${attributes.id}'))" />

+ value="${removeLabel}" data-struts-inputtransferselect="remove" data-struts-target="${attributes.id}" />

<#t/> <#assign removeAllLabel=(attributes.removeAllLabel!"<<--") /><#t/> style="${attributes.buttonCssStyle}" <#t/> - value="${removeAllLabel}" onclick="removeAllOptions(document.getElementById('${attributes.id}'))" />

+ value="${removeAllLabel}" data-struts-inputtransferselect="removeAll" data-struts-target="${attributes.id}" />

<#if attributes.rightTitle?has_content><#t/> @@ -99,20 +99,18 @@ <#include "/${attributes.templateDir}/simple/select.ftl" /> <#if attributes.allowUpDown!true> - onclick="moveOptionDown(document.getElementById('${attributes.id}'), 'key', '${attributes.headerKey}');" -<#else> - onclick="moveOptionDown(document.getElementById('${attributes.id}'), 'key', '');" + data-struts-header-key="${attributes.headerKey}" <#if attributes.downLabel?has_content> value="${attributes.downLabel}" /> - onclick="moveOptionUp(document.getElementById('${attributes.id}'), 'key', '${attributes.headerKey}');" -<#else> - onclick="moveOptionUp(document.getElementById('${attributes.id}'), 'key', '');" + data-struts-header-key="${attributes.headerKey}" <#if attributes.upLabel?has_content> value="${attributes.upLabel}" diff --git a/core/src/main/resources/template/simple/optiontransferselect.ftl b/core/src/main/resources/template/simple/optiontransferselect.ftl index abf920cd8a..28d1fdec04 100644 --- a/core/src/main/resources/template/simple/optiontransferselect.ftl +++ b/core/src/main/resources/template/simple/optiontransferselect.ftl @@ -22,6 +22,18 @@ <@s.script src="${base}${attributes.staticContentPath}/optiontransferselect.js" /> <#assign temporaryVariable = stack.setValue("#optiontransferselect_js_included", "true") /><#t/> <#t/> +<#-- + Buttons carry their action, both select ids and the header keys in data-* attributes; optiontransferselect.js binds + them, so the tag needs no framework-generated inline JavaScript under a Content-Security-Policy. A developer-supplied + *Onclick attribute is still rendered as onclick and runs after the framework action. The attribute line uses <#rt/>, + not <#t/>, to keep the space that separates it from the preceding attribute. +--> +<#macro transferButton action onclick><#t/> + data-struts-optiontransferselect="${action}" data-struts-left="${attributes.id}" data-struts-right="${attributes.doubleId!}"<#rt/> +<#if attributes.headerKey??> data-struts-header-key="${attributes.headerKey}"<#t/> +<#if attributes.doubleHeaderKey??> data-struts-double-header-key="${attributes.doubleHeaderKey}"<#t/> +<#if onclick?has_content> onclick="<#outputformat 'JavaScript'>${onclick};"<#t/> + diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/OptionTransferSelectTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/OptionTransferSelectTagTest.java index 97338d5d52..389d6db713 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/OptionTransferSelectTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/OptionTransferSelectTagTest.java @@ -1142,6 +1142,34 @@ public void testDisableSomeButtons_clearTagStateSet() throws Exception { strutsBodyTagsAreReflectionEqual(tag, freshTag)); } + public void testButtonsCarryHeaderKeysAsDataAttributesWithoutInlineHandlers() throws Exception { + TestAction testaction = (TestAction) action; + testaction.setCollection(new ArrayList<>(List.of("Left1"))); + testaction.setList2(new ArrayList<>(List.of("Right1"))); + + OptionTransferSelectTag tag = new OptionTransferSelectTag(); + tag.setPageContext(pageContext); + tag.setId("id"); + tag.setName("collection"); + tag.setList("collection"); + tag.setHeaderKey("it's-left"); + tag.setHeaderValue("Left"); + tag.setDoubleId("doubleId"); + tag.setDoubleName("list2"); + tag.setDoubleList("list2"); + tag.setDoubleHeaderKey("it's-right"); + tag.setDoubleHeaderValue("Right"); + + tag.doStartTag(); + tag.doEndTag(); + + String output = writer.toString(); + assertFalse("framework buttons must not need inline event handlers", output.contains("onclick=")); + assertFalse("data-* attributes must be separated from the preceding attribute", output.contains("\"data-struts-")); + assertTrue(output.contains("value=\"<-\" data-struts-optiontransferselect=\"addToLeft\" data-struts-left=\"id\" data-struts-right=\"doubleId\"" + + " data-struts-header-key=\"it's-left\" data-struts-double-header-key=\"it's-right\"")); + } + public void testDynamicAttributes() throws Exception { List left = new ArrayList(); left.add("Left1"); diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/UpDownSelectTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/UpDownSelectTagTest.java index dc05b54bfc..1cba9bd97d 100644 --- a/core/src/test/java/org/apache/struts2/views/jsp/ui/UpDownSelectTagTest.java +++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/UpDownSelectTagTest.java @@ -161,6 +161,24 @@ public void testWithHeaderAndEmptyOption() throws Exception { strutsBodyTagsAreReflectionEqual(tag, freshTag)); } + public void testButtonsCarryHeaderKeyAsDataAttributeWithoutInlineHandlers() throws Exception { + UpDownSelectTag tag = new UpDownSelectTag(); + tag.setPageContext(pageContext); + tag.setId("myId"); + tag.setName("myName"); + tag.setList("myMap"); + tag.setValue("mySelectedMapIds"); + tag.setHeaderKey("it's-a-key"); + tag.setHeaderValue("--- Please Order ---"); + + tag.doStartTag(); + tag.doEndTag(); + + String output = writer.toString(); + assertFalse("framework buttons must not need inline event handlers", output.contains("onclick=")); + assertTrue(output.contains("data-struts-updownselect=\"selectAll\" data-struts-target=\"myId\" data-struts-header-key=\"it's-a-key\"")); + } + public void testWithHeaderAndEmptyOption_clearTagStateSet() throws Exception { UpDownSelectTag tag = new UpDownSelectTag(); diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-11.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-11.txt index 6098d380ee..40dd8f001b 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-11.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-11.txt @@ -10,9 +10,9 @@
@@ -32,13 +44,13 @@ <#include "/${attributes.templateDir}/simple/select.ftl" /> <#if attributes.allowUpDownOnLeft!true> <#if attributes.leftDownLabel??> value="${attributes.leftDownLabel}" /> <#if attributes.leftUpLabel??> value="${attributes.leftUpLabel}" @@ -48,132 +60,59 @@ <#if attributes.allowAddToLeft!true><#t/> - <#assign addToLeftLabel = attributes.addToLeftLabel!"<-"/><#t/> - <#if attributes.doubleHeaderKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addToLeftLabel}" onclick="moveSelectedOptions(document.getElementById('${attributes.doubleId}'), document.getElementById('${attributes.id}'), false, '${attributes.doubleHeaderKey}', '');<#if attributes.addToLeftOnclick?has_content><#outputformat 'JavaScript'>${attributes.addToLeftOnclick};" />

- <#else><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addToLeftLabel}" onclick="moveSelectedOptions(document.getElementById('${attributes.doubleId}'), document.getElementById('${attributes.id}'), false, '');<#if attributes.addToLeftOnclick?has_content><#outputformat 'JavaScript'>${attributes.addToLeftOnclick};" />

+ <#assign addToLeftLabel=attributes.addToLeftLabel!"<-" /><#t/> + <#t/> + class="${attributes.buttonCssClass}" <#t/> + <#if attributes.buttonCssStyle??><#t/> + style="${attributes.buttonCssStyle}" + <#t/> + value="${addToLeftLabel}"<@transferButton action="addToLeft" onclick=attributes.addToLeftOnclick!""/> />

<#t/> <#if attributes.allowAddToRight!true><#t/> <#assign addToRightLabel=attributes.addToRightLabel!"->" /><#t/> - <#if attributes.headerKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addToRightLabel}" onclick="moveSelectedOptions(document.getElementById('${attributes.id}'), document.getElementById('${attributes.doubleId}'), false, '${attributes.headerKey}', '');<#if attributes.addToRightOnclick?has_content><#outputformat 'JavaScript'>${attributes.addToRightOnclick};" />

- <#else><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addToRightLabel}" onclick="moveSelectedOptions(document.getElementById('${attributes.id}'), document.getElementById('${attributes.doubleId}'), false, '');<#if attributes.addToRightOnclick?has_content><#outputformat 'JavaScript'>${attributes.addToRightOnclick};" />

+ <#t/> + class="${attributes.buttonCssClass}" + <#t/> + <#if attributes.buttonCssStyle??><#t/> + style="${attributes.buttonCssStyle}" <#t/> + value="${addToRightLabel}"<@transferButton action="addToRight" onclick=attributes.addToRightOnclick!""/> />

<#t/> <#if attributes.allowAddAllToLeft!true><#t/> <#assign addAllToLeftLabel=attributes.addAllToLeftLabel!"<<--" /><#t/> - <#if attributes.doubleHeaderKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addAllToLeftLabel}" onclick="moveAllOptions(document.getElementById('${attributes.doubleId}'), document.getElementById('${attributes.id}'), false, '${attributes.doubleHeaderKey}', '');<#if attributes.addAllToLeftOnclick?has_content><#outputformat 'JavaScript'>${attributes.addAllToLeftOnclick};" />

- <#else><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addAllToLeftLabel}" onclick="moveAllOptions(document.getElementById('${attributes.doubleId}'), document.getElementById('${attributes.id}'), false, '');<#if attributes.addAllToLeftOnclick?has_content><#outputformat 'JavaScript'>${attributes.addAllToLeftOnclick};" />

+ <#t/> + class="${attributes.buttonCssClass}" <#t/> + <#if attributes.buttonCssStyle??><#t/> + style="${attributes.buttonCssStyle}" + <#t/> + value="${addAllToLeftLabel}"<@transferButton action="addAllToLeft" onclick=attributes.addAllToLeftOnclick!""/> />

<#t/> <#if attributes.allowAddAllToRight!true><#t/> <#assign addAllToRightLabel=attributes.addAllToRightLabel!"-->>" /><#t/> - <#if attributes.headerKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addAllToRightLabel}" onclick="moveAllOptions(document.getElementById('${attributes.id}'), document.getElementById('${attributes.doubleId}'), false, '${attributes.headerKey}', '');<#if attributes.addAllToRightOnclick?has_content><#outputformat 'JavaScript'>${attributes.addAllToRightOnclick};" />

- <#else><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${addAllToRightLabel}" onclick="moveAllOptions(document.getElementById('${attributes.id}'), document.getElementById('${attributes.doubleId}'), false, '');<#if attributes.addAllToRightOnclick?has_content><#outputformat 'JavaScript'>${attributes.addAllToRightOnclick};" />

+ <#t/> + class="${attributes.buttonCssClass}" + <#t/> + <#if attributes.buttonCssStyle??><#t/> + style="${attributes.buttonCssStyle}" <#t/> + value="${addAllToRightLabel}"<@transferButton action="addAllToRight" onclick=attributes.addAllToRightOnclick!""/> />

<#t/> <#if attributes.allowSelectAll!true><#t/> <#assign selectAllLabel=attributes.selectAllLabel!"<*>" /><#t/> - <#if attributes.headerKey?? && attributes.doubleHeaderKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${selectAllLabel}" onclick="selectAllOptionsExceptSome(document.getElementById('${attributes.id}'), 'key', '${attributes.headerKey}');selectAllOptionsExceptSome(document.getElementById('${attributes.doubleId}'), 'key', '${attributes.doubleHeaderKey}');<#if attributes.selectAllOnclick?has_content><#outputformat 'JavaScript'>${attributes.selectAllOnclick};" />

- <#elseif attributes.headerKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${selectAllLabel}" onclick="selectAllOptionsExceptSome(document.getElementById('${attributes.id}'), 'key', '${attributes.headerKey}');selectAllOptions(document.getElementById('${attributes.doubleId}'));<#if attributes.selectAllOnclick?has_content><#outputformat 'JavaScript'>${attributes.selectAllOnclick};" />

- <#elseif attributes.doubleHeaderKey??><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${selectAllLabel}" onclick="selectAllOptions(document.getElementById('${attributes.id}'));selectAllOptionsExceptSome(document.getElementById('${attributes.doubleId}'), 'key', '${attributes.doubleHeaderKey}');<#if attributes.selectAllOnclick?has_content><#outputformat 'JavaScript'>${attributes.selectAllOnclick};" />

- <#else><#t/> - <#t/> - class="${attributes.buttonCssClass}" - <#t/> - <#if attributes.buttonCssStyle??><#t/> - style="${attributes.buttonCssStyle}" - <#t/> - value="${selectAllLabel}" onclick="selectAllOptions(document.getElementById('${attributes.id}'));selectAllOptions(document.getElementById('${attributes.doubleId}'));<#if attributes.selectAllOnclick?has_content><#outputformat 'JavaScript'>${attributes.selectAllOnclick};" />

+ <#t/> + class="${attributes.buttonCssClass}" + <#t/> + <#if attributes.buttonCssStyle??><#t/> + style="${attributes.buttonCssStyle}" <#t/> + value="${selectAllLabel}"<@transferButton action="selectAll" onclick=attributes.selectAllOnclick!""/> />

<#t/>
@@ -285,13 +224,13 @@ <#if attributes.allowUpDownOnRight!true> <#if attributes.rightDownLabel??> value="${attributes.rightDownLabel}" /> <#if attributes.rightUpLabel??> value="${attributes.rightUpLabel}" diff --git a/core/src/main/resources/template/simple/updownselect.ftl b/core/src/main/resources/template/simple/updownselect.ftl index a2e652702b..16431e6683 100644 --- a/core/src/main/resources/template/simple/updownselect.ftl +++ b/core/src/main/resources/template/simple/updownselect.ftl @@ -30,25 +30,25 @@ <#if attributes.allowMoveUp!true><#t/> <#assign defMoveUpLabel="${attributes.moveUpLabel!'^'}" /><#t/> <#if attributes.headerKey??><#t/> -    +    <#else><#t/> -    +    <#t/> <#t/> <#if attributes.allowMoveDown!true><#t/> <#assign defMoveDownLabel="${attributes.moveDownLabel!'v'}" /><#t/> <#if attributes.headerKey??><#t/> -    +    <#else><#t/> -    +    <#t/> <#t/> <#if attributes.allowSelectAll!true><#t/> <#assign defSelectAllLabel="${attributes.selectAllLabel!'*'}" /><#t/> <#if attributes.headerKey??><#t/> -    +    <#else><#t/> -    +    <#t/> <#t/>
-    -    -    +    +    +   
diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt index 4a698bffbb..4d2404bb08 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-2.txt @@ -10,9 +10,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt index bb5ebea3d6..90c8293b95 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-22.txt @@ -10,9 +10,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt index f63d81d2de..1af874b6b4 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-24.txt @@ -10,9 +10,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-6.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-6.txt index 859c79a6f3..cb74c99deb 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-6.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/Formtag-6.txt @@ -9,9 +9,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-1.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-1.txt index ef160a5c2b..e69306856c 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-1.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-1.txt @@ -4,11 +4,11 @@ - @@ -17,8 +17,8 @@ ItemOne ItemTwo - - + +
+

- +

- +

\ No newline at end of file diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-2.txt index 6260cc68c5..af5acfdb27 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/inputtransferselect-2.txt @@ -7,11 +7,11 @@ + data-struts-inputtransferselect="add" data-struts-target="collection" />

- +

- +

@@ -21,8 +21,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-1.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-1.txt index c94d405654..f9e7edff16 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-1.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-1.txt @@ -13,20 +13,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />


@@ -44,8 +44,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-2.txt index 1939871cb9..5de903cad0 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-2.txt @@ -14,20 +14,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />


- - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-3.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-3.txt index 1b0d645d59..617cbd685a 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-3.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-3.txt @@ -13,20 +13,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" />


@@ -42,8 +42,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-4.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-4.txt index 6f09523278..7150a9c0c1 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-4.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-4.txt @@ -14,20 +14,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" />


@@ -43,8 +43,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-5.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-5.txt index 2cdccaefe8..dd7ccb14f3 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-5.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-5.txt @@ -13,20 +13,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />


@@ -42,8 +42,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-6.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-6.txt index 56fdba726e..6e13c164e9 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-6.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-6.txt @@ -14,20 +14,20 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Left" data-struts-optiontransferselect="addAllToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="All Right" data-struts-optiontransferselect="addAllToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />



+ value="Select All" data-struts-optiontransferselect="selectAll" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" />


@@ -43,8 +43,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-7.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-7.txt index e5a0392301..07f3d26b0e 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-7.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-7.txt @@ -14,14 +14,14 @@ - - + +

+ value="Left" data-struts-optiontransferselect="addToLeft" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" onclick="alert('Moving Left');" />



+ value="Right" data-struts-optiontransferselect="addToRight" data-struts-left="id" data-struts-right="doubleId" data-struts-header-key="Header Key" data-struts-double-header-key="Double Header Key" onclick="alert('Moving Right');" />


@@ -38,8 +38,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-8.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-8.txt index 69358f9328..d8225ee1a7 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-8.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/optiontransferselect-8.txt @@ -10,28 +10,28 @@ - - + + + data-struts-optiontransferselect="addToLeft" data-struts-left="collection" data-struts-right="list2" />

+ data-struts-optiontransferselect="addToRight" data-struts-left="collection" data-struts-right="list2" />

+ data-struts-optiontransferselect="addAllToLeft" data-struts-left="collection" data-struts-right="list2" />

+ data-struts-optiontransferselect="addAllToRight" data-struts-left="collection" data-struts-right="list2" />

+ data-struts-optiontransferselect="selectAll" data-struts-left="collection" data-struts-right="list2" />

@@ -41,8 +41,8 @@ - - + + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-1.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-1.txt index eb86508da8..f757cae418 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-1.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-1.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-10.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-10.txt index 76489dda30..8a26cab2cc 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-10.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-10.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-11.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-11.txt index 943766ef62..ba63777497 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-11.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-11.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-2.txt index fd2d70840d..816e0c1e17 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-2.txt @@ -11,9 +11,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-3.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-3.txt index 3a1cf7e652..9766b0a350 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-3.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-3.txt @@ -13,9 +13,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-4.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-4.txt index c27eeb97d4..bb0f751494 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-4.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-4.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-5.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-5.txt index 168ed9c071..69436b8222 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-5.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-5.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-6.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-6.txt index 47644f52d2..4981f617b1 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-6.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-6.txt @@ -12,8 +12,8 @@ -    -    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-7.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-7.txt index 551e397415..d47a496d6f 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-7.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-7.txt @@ -12,8 +12,8 @@ -    -    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-8.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-8.txt index d089a88be4..55de5f3f6b 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-8.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-8.txt @@ -12,8 +12,8 @@ -    -    +    +    diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-9.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-9.txt index 26d4629bc3..3b99f0ad71 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-9.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/updownselecttag-9.txt @@ -12,9 +12,9 @@ -    -    -    +    +    +    From c01fc861dac33e4d7e76349d2e3a51864559edf6 Mon Sep 17 00:00:00 2001 From: Aaron Elijah Mars <272311952+aeonframework@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:18:21 -0400 Subject: [PATCH 2/2] WW-5772 Bind combobox select change handler from a nonce'd script The combobox tag (simple and html5 themes) rendered its select with an inline onChange="autoPopulate_(this)" handler. The select now carries data-struts-combobox="" and the handler is attached with addEventListener inside an <@s.script> block, which carries the CSP nonce like the autoPopulate function the template already emits. The script binds every select with its marker that is not bound yet, so comboboxes repeated with the same generated id, for example inside an iterator, each get exactly one handler, as they did with the inline attribute. It looks the selects up instead of using document.currentScript, so it also works when the fragment is loaded over AJAX and its scripts are evaluated after insertion (jQuery .html() evaluates them through a temporary script element in ). Generated-by: an AI coding assistant; reviewed and tested by the author. --- .../main/resources/template/html5/combobox.ftl | 15 ++++++++++++++- .../main/resources/template/simple/combobox.ftl | 15 ++++++++++++++- .../apache/struts2/views/jsp/ui/ComboBox-1.txt | 15 ++++++++++++++- .../apache/struts2/views/jsp/ui/ComboBox-2.txt | 15 ++++++++++++++- .../apache/struts2/views/jsp/ui/ComboBox-3.txt | 15 ++++++++++++++- .../apache/struts2/views/jsp/ui/ComboBox-4.txt | 15 ++++++++++++++- 6 files changed, 84 insertions(+), 6 deletions(-) diff --git a/core/src/main/resources/template/html5/combobox.ftl b/core/src/main/resources/template/html5/combobox.ftl index 2ef917f781..dedda39d6e 100644 --- a/core/src/main/resources/template/html5/combobox.ftl +++ b/core/src/main/resources/template/html5/combobox.ftl @@ -37,7 +37,7 @@ function autoPopulate_${attributes.escapedId}(targetElement) { <#include "/${attributes.templateDir}/${attributes.expandTheme}/controlheader.ftl" /> <#if attributes.list??> - <#include "/${attributes.templateDir}/${attributes.expandTheme}/css.ftl" /><#rt/> <#if attributes.disabled!false> disabled="disabled"<#rt/> @@ -97,5 +97,18 @@ function autoPopulate_${attributes.escapedId}(targetElement) { >${tmpListValue}<#rt/> <#rt/> +<@s.script> + (function () { + var selects = document.querySelectorAll('select[data-struts-combobox="${attributes.escapedId}"]'); + for (var i = 0; i < selects.length; i++) { + if (!selects[i].strutsComboboxBound) { + selects[i].strutsComboboxBound = true; + selects[i].addEventListener('change', function () { + autoPopulate_${attributes.escapedId}(this); + }); + } + } + })(); + <#include "/${attributes.templateDir}/${attributes.expandTheme}/controlfooter.ftl" /><#rt/> diff --git a/core/src/main/resources/template/simple/combobox.ftl b/core/src/main/resources/template/simple/combobox.ftl index 400ae450da..ca17e4a050 100644 --- a/core/src/main/resources/template/simple/combobox.ftl +++ b/core/src/main/resources/template/simple/combobox.ftl @@ -36,7 +36,7 @@ <#include "/${attributes.templateDir}/simple/text.ftl" />
<#if attributes.list??> - <#include "/${attributes.templateDir}/${attributes.expandTheme}/css.ftl" /> <#if attributes.disabled!false> disabled="disabled"<#rt/> @@ -98,4 +98,17 @@ <#lt/> +<@s.script> + (function () { + var selects = document.querySelectorAll('select[data-struts-combobox="${attributes.escapedId}"]'); + for (var i = 0; i < selects.length; i++) { + if (!selects[i].strutsComboboxBound) { + selects[i].strutsComboboxBound = true; + selects[i].addEventListener('change', function () { + autoPopulate_${attributes.escapedId}(this); + }); + } + } + })(); + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-1.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-1.txt index 670f704c3d..b3c6f098a3 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-1.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-1.txt @@ -7,10 +7,23 @@ }
- + diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-2.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-2.txt index 4f908b1e7b..7bea2770fc 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-2.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-2.txt @@ -13,7 +13,7 @@ }
- @@ -21,5 +21,18 @@ + \ No newline at end of file diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-3.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-3.txt index 4f908b1e7b..7bea2770fc 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-3.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-3.txt @@ -13,7 +13,7 @@ }
- @@ -21,5 +21,18 @@ + \ No newline at end of file diff --git a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-4.txt b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-4.txt index 2e925eff7a..71063a4aba 100644 --- a/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-4.txt +++ b/core/src/test/resources/org/apache/struts2/views/jsp/ui/ComboBox-4.txt @@ -7,8 +7,21 @@ }
- +