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
commit cd6f542d2144f93ce45db7d60c9ea416e49de815 Author: Emil Huseynov <[email protected]> AuthorDate: Thu Oct 29 16:44:25 2026 +0400 WW-5774 Move combobox change handling into a static script with a delegated listener The combobox templates emitted two inline script blocks per instance: an autoPopulate_<id> function and a block that bound every select marked with data-struts-combobox. A CSP built on script hashes or static scripts only still blocked the combobox, the block was duplicated in the simple and html5 themes, and N comboboxes in an iterator emitted N blocks. combobox.js registers one delegated change listener per page, in the capture phase, matching the transfer selects after WW-5772. The select now carries the target field name, the header key and the empty-option flag in data-* attributes, and the templates emit no per-instance inline script. --- .../org/apache/struts2/static/combobox.js | 56 ++++++++++++++++++++++ .../src/main/resources/template/html5/combobox.ftl | 33 ++----------- .../main/resources/template/simple/combobox.ftl | 33 ++----------- .../org/apache/struts2/views/jsp/ui/ComboBox-1.txt | 21 +------- .../org/apache/struts2/views/jsp/ui/ComboBox-2.txt | 27 +---------- .../org/apache/struts2/views/jsp/ui/ComboBox-3.txt | 27 +---------- .../org/apache/struts2/views/jsp/ui/ComboBox-4.txt | 21 +------- 7 files changed, 72 insertions(+), 146 deletions(-) diff --git a/core/src/main/resources/org/apache/struts2/static/combobox.js b/core/src/main/resources/org/apache/struts2/static/combobox.js new file mode 100644 index 000000000..94a570fda --- /dev/null +++ b/core/src/main/resources/org/apache/struts2/static/combobox.js @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/* + * Copies the selected option of a combobox select into its text field. The select carries its settings in data-* + * attributes: + * data-struts-combobox-target name of the text field in the same form + * data-struts-header-key value of the header option, which is not copied + * data-struts-empty-option "true" when the empty option is rendered, which is not copied + * The listener is registered once per page, in the capture phase, so it also covers comboboxes rendered later and + * is not stopped by an ancestor's stopPropagation(). + */ +(function () { + if (window.strutsComboboxListener) { + return; + } + window.strutsComboboxListener = true; + + document.addEventListener('change', function (event) { + var select = event.target; + if (!select || !select.getAttribute) { + return; + } + var target = select.getAttribute('data-struts-combobox-target'); + if (target === null || !select.form || select.selectedIndex < 0) { + return; + } + var value = select.options[select.selectedIndex].value; + if (select.hasAttribute('data-struts-header-key') && value === select.getAttribute('data-struts-header-key')) { + return; + } + if (select.getAttribute('data-struts-empty-option') === 'true' && value === '') { + return; + } + var field = select.form.elements[target]; + if (field) { + field.value = value; + } + }, true); +})(); diff --git a/core/src/main/resources/template/html5/combobox.ftl b/core/src/main/resources/template/html5/combobox.ftl index dedda39d6..9f5c1b617 100644 --- a/core/src/main/resources/template/html5/combobox.ftl +++ b/core/src/main/resources/template/html5/combobox.ftl @@ -18,26 +18,14 @@ * under the License. */ --> -<@s.script> -function autoPopulate_${attributes.escapedId}(targetElement) { -<#if attributes.headerKey?? && attributes.headerValue??> - if (targetElement.options[targetElement.selectedIndex].value == '${attributes.headerKey?js_string}') { - return; - } -</#if> -<#if attributes.emptyOption!false> - if (targetElement.options[targetElement.selectedIndex].value == '') { - return; - } -</#if> - targetElement.form.elements['${attributes.name?js_string}'].value=targetElement.options[targetElement.selectedIndex].value; -} -</@s.script> +<@s.script src="${base}${attributes.staticContentPath}/combobox.js" /> <#include "/${attributes.templateDir}/html5/text.ftl" /><#rt/> <#include "/${attributes.templateDir}/${attributes.expandTheme}/controlheader.ftl" /> <#if attributes.list??> -<select data-struts-combobox="${attributes.escapedId}"<#rt/> +<select data-struts-combobox="${attributes.escapedId}" data-struts-combobox-target="${attributes.name}"<#rt/> +<#if attributes.headerKey?? && attributes.headerValue??> data-struts-header-key="${attributes.headerKey}"</#if><#t/> +<#if attributes.emptyOption!false> data-struts-empty-option="true"</#if><#t/> <#include "/${attributes.templateDir}/${attributes.expandTheme}/css.ftl" /><#rt/> <#if attributes.disabled!false> disabled="disabled"<#rt/> @@ -97,18 +85,5 @@ function autoPopulate_${attributes.escapedId}(targetElement) { >${tmpListValue}</option><#rt/> </@s.iterator> </select><#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); - }); - } - } - })(); -</@s.script> </#if> <#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 ca17e4a05..13dd95851 100644 --- a/core/src/main/resources/template/simple/combobox.ftl +++ b/core/src/main/resources/template/simple/combobox.ftl @@ -18,25 +18,13 @@ * under the License. */ --> -<@s.script> - function autoPopulate_${attributes.escapedId}(targetElement) { - <#if attributes.headerKey?? && attributes.headerValue??> - if (targetElement.options[targetElement.selectedIndex].value == '${attributes.headerKey?js_string}') { - return; - } - </#if> - <#if attributes.emptyOption!false> - if (targetElement.options[targetElement.selectedIndex].value == '') { - return; - } - </#if> - targetElement.form.elements['${attributes.name?js_string}'].value=targetElement.options[targetElement.selectedIndex].value; - } -</@s.script> +<@s.script src="${base}${attributes.staticContentPath}/combobox.js" /> <#include "/${attributes.templateDir}/simple/text.ftl" /> <br /> <#if attributes.list??> -<select data-struts-combobox="${attributes.escapedId}"<#rt/> +<select data-struts-combobox="${attributes.escapedId}" data-struts-combobox-target="${attributes.name}"<#rt/> +<#if attributes.headerKey?? && attributes.headerValue??> data-struts-header-key="${attributes.headerKey}"</#if><#t/> +<#if attributes.emptyOption!false> data-struts-empty-option="true"</#if><#t/> <#include "/${attributes.templateDir}/${attributes.expandTheme}/css.ftl" /> <#if attributes.disabled!false> disabled="disabled"<#rt/> @@ -98,17 +86,4 @@ </option><#lt/> </@s.iterator> </select> -<@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); - }); - } - } - })(); -</@s.script> </#if> 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 b3c6f098a..8c91cbd7f 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 @@ -1,29 +1,12 @@ <tr> <td class="tdLabel"><label for="cb" class="label">mylabel:</label></td> <td class="tdInput"> -<script nonce="r4nd0m"> - function autoPopulate_cb(targetElement) { - targetElement.form.elements['foo'].value=targetElement.options[targetElement.selectedIndex].value; - } -</script> +<script src="/static/combobox.js" nonce="r4nd0m"></script> <input type="text" name="foo" value="hello" id="cb"/><br/> -<select data-struts-combobox="cb"> +<select data-struts-combobox="cb" data-struts-combobox-target="foo"> <option value="foo">foo</option> <option value="bar">bar</option> <option value="baz">baz</option> </select> -<script nonce="r4nd0m"> - (function () { - var selects = document.querySelectorAll('select[data-struts-combobox="cb"]'); - for (var i = 0; i < selects.length; i++) { - if (!selects[i].strutsComboboxBound) { - selects[i].strutsComboboxBound = true; - selects[i].addEventListener('change', function () { - autoPopulate_cb(this); - }); - } - } - })(); -</script> </td> </tr> 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 7bea2770f..2f39ed24d 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 @@ -1,19 +1,9 @@ <tr> <td class="tdLabel"><label for="myFavouriteFruit" class="label">My Favourite Fruit:</label></td> <td class="tdInput"> -<script> - function autoPopulate_myFavouriteFruit(targetElement) { - if (targetElement.options[targetElement.selectedIndex].value == '-1') { - return; - } - if (targetElement.options[targetElement.selectedIndex].value == '') { - return; - } - targetElement.form.elements['myFavouriteFruit'].value=targetElement.options[targetElement.selectedIndex].value; - } -</script> +<script src="/static/combobox.js"></script> <input type="text" name="myFavouriteFruit" value="banana" id="myFavouriteFruit"/><br/> -<select data-struts-combobox="myFavouriteFruit"> +<select data-struts-combobox="myFavouriteFruit" data-struts-combobox-target="myFavouriteFruit" data-struts-header-key="-1" data-struts-empty-option="true"> <option value="-1">--- Please Select ---</option> <option value=""></option> <option value="apple">apple</option> @@ -21,18 +11,5 @@ <option value="pineaple">pineaple</option> <option value="grapes">grapes</option> </select> -<script> - (function () { - var selects = document.querySelectorAll('select[data-struts-combobox="myFavouriteFruit"]'); - for (var i = 0; i < selects.length; i++) { - if (!selects[i].strutsComboboxBound) { - selects[i].strutsComboboxBound = true; - selects[i].addEventListener('change', function () { - autoPopulate_myFavouriteFruit(this); - }); - } - } - })(); -</script> </td> </tr> \ 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 7bea2770f..2f39ed24d 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 @@ -1,19 +1,9 @@ <tr> <td class="tdLabel"><label for="myFavouriteFruit" class="label">My Favourite Fruit:</label></td> <td class="tdInput"> -<script> - function autoPopulate_myFavouriteFruit(targetElement) { - if (targetElement.options[targetElement.selectedIndex].value == '-1') { - return; - } - if (targetElement.options[targetElement.selectedIndex].value == '') { - return; - } - targetElement.form.elements['myFavouriteFruit'].value=targetElement.options[targetElement.selectedIndex].value; - } -</script> +<script src="/static/combobox.js"></script> <input type="text" name="myFavouriteFruit" value="banana" id="myFavouriteFruit"/><br/> -<select data-struts-combobox="myFavouriteFruit"> +<select data-struts-combobox="myFavouriteFruit" data-struts-combobox-target="myFavouriteFruit" data-struts-header-key="-1" data-struts-empty-option="true"> <option value="-1">--- Please Select ---</option> <option value=""></option> <option value="apple">apple</option> @@ -21,18 +11,5 @@ <option value="pineaple">pineaple</option> <option value="grapes">grapes</option> </select> -<script> - (function () { - var selects = document.querySelectorAll('select[data-struts-combobox="myFavouriteFruit"]'); - for (var i = 0; i < selects.length; i++) { - if (!selects[i].strutsComboboxBound) { - selects[i].strutsComboboxBound = true; - selects[i].addEventListener('change', function () { - autoPopulate_myFavouriteFruit(this); - }); - } - } - })(); -</script> </td> </tr> \ 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 71063a4ab..1e6830100 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 @@ -1,27 +1,10 @@ <tr> <td class="tdLabel"><label for="cb['"."']=bc(){};//" class="label">mylabel:</label></td> <td class="tdInput"> - <script> - function autoPopulate_cb__________bc_______(targetElement) { - targetElement.form.elements['foo'].value=targetElement.options[targetElement.selectedIndex].value; - } - </script> + <script src="/static/combobox.js"></script> <input type="text" name="foo" value="hello" id="cb['"."']=bc(){};//"/><br/> - <select data-struts-combobox="cb__________bc_______"> + <select data-struts-combobox="cb__________bc_______" data-struts-combobox-target="foo"> <option value="foo">foo</option> </select> -<script> - (function () { - var selects = document.querySelectorAll('select[data-struts-combobox="cb__________bc_______"]'); - for (var i = 0; i < selects.length; i++) { - if (!selects[i].strutsComboboxBound) { - selects[i].strutsComboboxBound = true; - selects[i].addEventListener('change', function () { - autoPopulate_cb__________bc_______(this); - }); - } - } - })(); -</script> </td> </tr>
