This is an automated email from the ASF dual-hosted git repository.
arunpati pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git
The following commit(s) were added to refs/heads/trunk by this push:
new f2cb89730 Improved: Add a multi parameter dependent drop-down example
(OFBIZ-11724) (#353)
f2cb89730 is described below
commit f2cb897306cc648be583af0f686df175a958fa62
Author: toaditi <[email protected]>
AuthorDate: Wed Aug 5 13:50:00 2026 +0530
Improved: Add a multi parameter dependent drop-down example (OFBIZ-11724)
(#353)
Thanks to Pierre Smits for reporting OFBIZ-11724 and for identifying
that only the first parameter of the list was being used.
### ⚠️ Please merge apache/ofbiz-framework#1547 first
This is not merely inert without the framework fix — it is actively
misleading. Against unfixed framework code the screen still renders and
still looks like it works, but the generated selector is:
```
#DependentDropDownMultiParamExampleForm_exampleId,asOfDate
```
`asOfDate` there parses as a CSS *type* selector, matches nothing, and
the date is silently never sent, so the service quietly falls back to
"now". Merged on its own, this would ship a demo screen that documents
the bug rather than the fix.
### Why an example was needed
The reason OFBIZ-11724 sat open since 2020 is that nothing in OFBiz
passes two parameters to a dependent drop-down, so there was nothing to
click that failed. All 14 existing callers pass a single
`countryGeoId`-style value.
The `example` component even told readers, in
`FormWidgetExampleScreens.xml`, to look at `EditProductPriceRules` if
they needed more than one drop-down — and that screen sidesteps the
template entirely, hand-writing the `getDependentDropdownValues` calls
with fully-qualified ids. The workaround had quietly become the
documented path. This PR updates that comment to point at the new
example instead.
### What it does
Pick an Example and an "as of" date; the feature drop-down lists only
the features applied to that Example on that date. `ExampleFeatureAppl`
is date-ranged, so this exercises `filterByDate` for real rather than
just passing two arbitrary values — which is the case the issue is
actually named after.
- `getAssociatedExampleFeatures` service (`exampleId` + `asOfDate` →
`exampleFeatureList`), with a JSON request-map
- `DependentDropDownMultiParamExampleForm` and its screen section, using
`paramKey` and `mainId` of `exampleId,asOfDate`
- `ExampleFeature` / `ExampleFeatureAppl` demo rows with deliberately
non-overlapping date ranges, so changing the date visibly changes the
result: EX01 shows "Colour: red" in 2011, "Colour: blue" in 2013, and
"Size: large" today
- `testGetAssociatedExampleFeaturesFiltersByDate`, which asserts that
two different dates return two different non-empty lists, plus an empty
one outside both ranges — so a service that ignored the date would fail
it
The screen comment also spells out something the existing example does
not: `paramKey` and `mainId` are paired **by position**, so the two
lists must be given in the same order. Getting that wrong swaps values
silently with no error.
### Testing
`gradlew "ofbiz --test component=example --test
suitename=example-tests"` — 27 tests, 0 failures, 0 errors.
Verified on a local instance that the generated script now prefixes both
field names, that the service returns different features for 2011, 2013
and today over HTTP, and that the pre-existing single-parameter example
on the same page is byte-identical to before the framework change.
### Known limitation
Picking a date from the **calendar** refreshes the drop-down;
**hand-typing** a date does not. This is pre-existing, framework-wide
`date-time` behaviour rather than anything this change introduces: the
datepicker is bound to the hidden canonical input and fires `change` on
it, but the typed-input path in `OfbizUtil.js` has no
`.trigger('change')`. Fixing it would change behaviour for every date
field in OFBiz, so it does not belong in this issue — I will raise it
separately and link it.
To be precise about the evidence: I established the calendar path by
reading the vendored `jquery-ui.js` `_selectDate` and the rendered
markup, not by observing it in a browser.
Happy to adjust any of this if a different approach is preferred.
---
example/config/ExampleUiLabels.xml | 9 ++++++
example/data/ExampleDemoData.xml | 13 ++++++++
example/servicedef/services.xml | 8 +++++
.../ofbiz/example/ExampleServicesScript.groovy | 26 +++++++++++++++
.../apache/ofbiz/example/test/ExampleTests.groovy | 27 ++++++++++++++++
example/testdef/data/ExampleTestData.xml | 7 ++++
example/webapp/example/WEB-INF/controller.xml | 9 ++++++
example/widget/example/FormWidgetExampleForms.xml | 24 +++++++++++++-
.../widget/example/FormWidgetExampleScreens.xml | 37 ++++++++++++++++++++--
9 files changed, 156 insertions(+), 4 deletions(-)
diff --git a/example/config/ExampleUiLabels.xml
b/example/config/ExampleUiLabels.xml
index 9c8b55474..60aa9130c 100644
--- a/example/config/ExampleUiLabels.xml
+++ b/example/config/ExampleUiLabels.xml
@@ -55,6 +55,9 @@
<value xml:lang="zh">样例应用</value>
<value xml:lang="zh-TW">範例應用</value>
</property>
+ <property key="ExampleAsOfDate">
+ <value xml:lang="en">As of date</value>
+ </property>
<property key="ExampleAutocompleteDropdown">
<value xml:lang="en">Autocomplete Dropdown</value>
<value xml:lang="fr">Liste déroulante à auto complétion</value>
@@ -332,6 +335,12 @@
<value xml:lang="zh">相互依赖的下拉框</value>
<value xml:lang="zh-TW">相互依賴的下拉清單</value>
</property>
+ <property key="ExampleMultiParamDependentDropDowns">
+ <value xml:lang="en">Dependent drop-down driven by two parent
fields</value>
+ </property>
+ <property key="ExampleMultiParamDependentDropDownTooltip">
+ <value xml:lang="en">Pick an Example and a date. The feature drop-down
lists only the features applied to that Example on that date, so both parent
fields are sent to the service.</value>
+ </property>
<property key="ExampleDisabledFields">
<value xml:lang="en">Disabled Fields</value>
</property>
diff --git a/example/data/ExampleDemoData.xml b/example/data/ExampleDemoData.xml
index 65de8a9d0..f744b9f4a 100644
--- a/example/data/ExampleDemoData.xml
+++ b/example/data/ExampleDemoData.xml
@@ -90,4 +90,17 @@ under the License.
<ExampleStatus exampleId="EX02" statusDate="2010-01-02 00:00:00"
statusEndDate="2011-01-02 00:00:00" statusId="EXST_IN_DESIGN"/>
<ExampleStatus exampleId="EX02" statusDate="2011-01-02 00:00:01"
statusEndDate="2012-01-02 00:00:00" statusId="EXST_DEFINED"/>
<ExampleStatus exampleId="EX02" statusDate="2012-01-02 00:00:00"
statusEndDate="2013-01-02 00:00:00" statusId="EXST_APPROVED"/>
+
+ <!-- Features for the multi parameter dependent drop-down example
(OFBIZ-11724).
+ The date ranges deliberately do not overlap, so changing the date on
the
+ Form Widget Examples screen visibly changes the drop-down contents.
-->
+ <ExampleFeature exampleFeatureId="EXFT01"
featureSourceEnumId="EXFTSRC_CUSTOMER" description="Colour: red"/>
+ <ExampleFeature exampleFeatureId="EXFT02"
featureSourceEnumId="EXFTSRC_CUSTOMER" description="Colour: blue"/>
+ <ExampleFeature exampleFeatureId="EXFT03"
featureSourceEnumId="EXFTSRC_PARTNER" description="Size: large"/>
+ <ExampleFeature exampleFeatureId="EXFT04"
featureSourceEnumId="EXFTSRC_EMPLOYEE" description="Finish: matte"/>
+
+ <ExampleFeatureAppl exampleId="EX01" exampleFeatureId="EXFT01"
fromDate="2010-01-01 00:00:00" thruDate="2012-01-01 00:00:00"
exampleFeatureApplTypeId="REQUIRED" sequenceNum="1"/>
+ <ExampleFeatureAppl exampleId="EX01" exampleFeatureId="EXFT02"
fromDate="2012-01-01 00:00:00" thruDate="2014-01-01 00:00:00"
exampleFeatureApplTypeId="DESIRED" sequenceNum="2"/>
+ <ExampleFeatureAppl exampleId="EX01" exampleFeatureId="EXFT03"
fromDate="2014-01-01 00:00:00" exampleFeatureApplTypeId="REQUIRED"
sequenceNum="3"/>
+ <ExampleFeatureAppl exampleId="EX02" exampleFeatureId="EXFT04"
fromDate="2010-01-01 00:00:00" exampleFeatureApplTypeId="DESIRED"
sequenceNum="1"/>
</entity-engine-xml>
diff --git a/example/servicedef/services.xml b/example/servicedef/services.xml
index 66227d0c8..bd0d25c77 100644
--- a/example/servicedef/services.xml
+++ b/example/servicedef/services.xml
@@ -53,6 +53,14 @@ under the License.
<auto-attributes include="pk" mode="IN" optional="true"/>
<attribute name="exampleList" mode="OUT" optional="true" type="List"/>
</service>
+ <service name="getAssociatedExampleFeatures" engine="groovy" auth="true"
+
location="component://example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy"
invoke="getAssociatedExampleFeatures">
+ <description>List the ExampleFeatures applied to an Example and active
on a given date.
+ Backs the multi parameter dependent drop-down example
(OFBIZ-11724).</description>
+ <attribute name="exampleId" mode="IN" type="String" optional="false"/>
+ <attribute name="asOfDate" mode="IN" type="Timestamp" optional="true"/>
+ <attribute name="exampleFeatureList" mode="OUT" type="List"/>
+ </service>
<service name="createExampleStatus" default-entity-name="ExampleStatus"
engine="groovy"
location="component://example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy"
invoke="createExampleStatus" auth="true">
<description>Create a ExampleStatus</description>
diff --git
a/example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy
b/example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy
index a34204e80..555fd346d 100644
---
a/example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy
+++
b/example/src/main/groovy/org/apache/ofbiz/example/ExampleServicesScript.groovy
@@ -18,6 +18,8 @@
*/
package org.apache.ofbiz.example
+import java.sql.Timestamp
+
import org.apache.ofbiz.base.util.UtilDateTime
import org.apache.ofbiz.entity.GenericValue
@@ -88,3 +90,27 @@ Map createExampleStatus() {
return result
}
+
+Map getAssociatedExampleFeatures() {
+ Map result = success()
+ List exampleFeatureList = []
+
+ Timestamp asOfDate = parameters.asOfDate ?: UtilDateTime.nowTimestamp()
+
+ List<GenericValue> featureAppls = from('ExampleFeatureAppl')
+ .where('exampleId', parameters.exampleId)
+ .filterByDate(asOfDate)
+ .orderBy('sequenceNum')
+ .queryList()
+
+ for (GenericValue featureAppl : featureAppls) {
+ GenericValue exampleFeature =
featureAppl.getRelatedOne('ExampleFeature', true)
+ if (exampleFeature) {
+ exampleFeatureList << [exampleFeatureId:
exampleFeature.exampleFeatureId,
+ description: exampleFeature.description]
+ }
+ }
+
+ result.exampleFeatureList = exampleFeatureList
+ return result
+}
diff --git
a/example/src/test/groovy/org/apache/ofbiz/example/test/ExampleTests.groovy
b/example/src/test/groovy/org/apache/ofbiz/example/test/ExampleTests.groovy
index 24d42ea59..eb71ad660 100644
--- a/example/src/test/groovy/org/apache/ofbiz/example/test/ExampleTests.groovy
+++ b/example/src/test/groovy/org/apache/ofbiz/example/test/ExampleTests.groovy
@@ -18,6 +18,8 @@
*/
package org.apache.ofbiz.example.test
+import java.sql.Timestamp
+
import org.apache.ofbiz.entity.GenericValue
import org.apache.ofbiz.service.ServiceUtil
import org.apache.ofbiz.service.testtools.OFBizTestCase
@@ -75,4 +77,29 @@ class ExampleTests extends OFBizTestCase {
assert example == null
}
+ void testGetAssociatedExampleFeaturesFiltersByDate() {
+ GenericValue userLogin = from('UserLogin').where('userLoginId',
'system').queryOne()
+ String serviceName = 'getAssociatedExampleFeatures'
+ String exampleId = 'EXTEST01'
+
+ // 2011 falls inside EXFTTEST01's range only
+ Map<String, Object> serviceResult = dispatcher.runSync(serviceName,
+ [exampleId: exampleId, asOfDate: Timestamp.valueOf('2011-06-01
00:00:00'), userLogin: userLogin])
+ assert ServiceUtil.isSuccess(serviceResult)
+ assert serviceResult.exampleFeatureList*.exampleFeatureId ==
['EXFTTEST01']
+ assert serviceResult.exampleFeatureList[0].description == 'Test
feature one'
+
+ // 2013 falls inside EXFTTEST02's range only, proving asOfDate is
honoured
+ serviceResult = dispatcher.runSync(serviceName,
+ [exampleId: exampleId, asOfDate: Timestamp.valueOf('2013-06-01
00:00:00'), userLogin: userLogin])
+ assert ServiceUtil.isSuccess(serviceResult)
+ assert serviceResult.exampleFeatureList*.exampleFeatureId ==
['EXFTTEST02']
+
+ // 2020 falls outside both ranges
+ serviceResult = dispatcher.runSync(serviceName,
+ [exampleId: exampleId, asOfDate: Timestamp.valueOf('2020-06-01
00:00:00'), userLogin: userLogin])
+ assert ServiceUtil.isSuccess(serviceResult)
+ assert serviceResult.exampleFeatureList.isEmpty()
+ }
+
}
diff --git a/example/testdef/data/ExampleTestData.xml
b/example/testdef/data/ExampleTestData.xml
index 25bcefc29..1d10b0c70 100644
--- a/example/testdef/data/ExampleTestData.xml
+++ b/example/testdef/data/ExampleTestData.xml
@@ -30,4 +30,11 @@ under the License.
<ExampleType exampleTypeId="CONTRIVED" description="Contrived Example
Type"/>
<Example exampleId="TestExampleUpdate" exampleTypeId="CONTRIVED"
statusId="EXST_IN_DESIGN" exampleName="Test Example for Update"/>
<Example exampleId="TestExampleDelete" exampleTypeId="CONTRIVED"
statusId="EXST_IN_DESIGN" exampleName="Test Example for Delete"/>
+
+ <!-- Fixtures for testGetAssociatedExampleFeaturesFiltersByDate
(OFBIZ-11724) -->
+ <Example exampleId="EXTEST01" exampleName="Multi param dependent drop-down
test" exampleTypeId="CONTRIVED" statusId="EXST_IN_DESIGN"/>
+ <ExampleFeature exampleFeatureId="EXFTTEST01"
featureSourceEnumId="EXFTSRC_CUSTOMER" description="Test feature one"/>
+ <ExampleFeature exampleFeatureId="EXFTTEST02"
featureSourceEnumId="EXFTSRC_CUSTOMER" description="Test feature two"/>
+ <ExampleFeatureAppl exampleId="EXTEST01" exampleFeatureId="EXFTTEST01"
fromDate="2010-01-01 00:00:00" thruDate="2012-01-01 00:00:00"
exampleFeatureApplTypeId="REQUIRED" sequenceNum="1"/>
+ <ExampleFeatureAppl exampleId="EXTEST01" exampleFeatureId="EXFTTEST02"
fromDate="2012-01-01 00:00:00" thruDate="2014-01-01 00:00:00"
exampleFeatureApplTypeId="DESIRED" sequenceNum="2"/>
</entity-engine-xml>
diff --git a/example/webapp/example/WEB-INF/controller.xml
b/example/webapp/example/WEB-INF/controller.xml
index 464f30a7b..219128852 100644
--- a/example/webapp/example/WEB-INF/controller.xml
+++ b/example/webapp/example/WEB-INF/controller.xml
@@ -248,6 +248,15 @@ under the License.
<!-- end of request mappings -->
+ <!--========================== AJAX events =====================-->
+ <!-- Get ExampleFeatures applied to an Example, active on a given date -->
+ <request-map uri="getAssociatedExampleFeatures">
+ <security https="true" auth="true"/>
+ <event type="service" invoke="getAssociatedExampleFeatures"/>
+ <response name="success" type="request" value="json"/>
+ <response name="error" type="request" value="json"/>
+ </request-map>
+
<!-- View Mappings -->
<view-map name="main" type="screen"
page="component://example/widget/example/CommonScreens.xml#main"/>
diff --git a/example/widget/example/FormWidgetExampleForms.xml
b/example/widget/example/FormWidgetExampleForms.xml
index 8d4c60c58..cb5d58f3d 100644
--- a/example/widget/example/FormWidgetExampleForms.xml
+++ b/example/widget/example/FormWidgetExampleForms.xml
@@ -325,7 +325,29 @@ under the License.
<entity-order-by field-name="geoName"/>
</entity-options>
</drop-down>
- </field>
+ </field>
+ </form>
+
+ <!-- ***************************************************** -->
+ <!-- *** Dependent dropdown with several parameters *** -->
+ <!-- ***************************************************** -->
+ <form name="DependentDropDownMultiParamExampleForm" type="single" title="">
+ <field name="multiParamDependentDropDownFields"
title="${uiLabelMap.ExampleMultiParamDependentDropDowns}">
+ <display
description="${uiLabelMap.ExampleMultiParamDependentDropDownTooltip}"/>
+ </field>
+ <field name="exampleId" title="${uiLabelMap.ExampleExampleId}">
+ <drop-down>
+ <entity-options entity-name="Example"
key-field-name="exampleId" description="${exampleName}">
+ <entity-order-by field-name="exampleId"/>
+ </entity-options>
+ </drop-down>
+ </field>
+ <field name="asOfDate" title="${uiLabelMap.ExampleAsOfDate}">
+ <date-time type="date"
default-value="${groovy:org.apache.ofbiz.base.util.UtilDateTime.nowTimestamp()}"/>
+ </field>
+ <field name="exampleFeatureId" title="${uiLabelMap.ExampleFeature}">
+ <drop-down allow-empty="false"/>
+ </field>
</form>
<!-- Simple form to print a FOP PDF file with the fonts available in
OFBiz Project -->
diff --git a/example/widget/example/FormWidgetExampleScreens.xml
b/example/widget/example/FormWidgetExampleScreens.xml
index 909b7546a..90c6a63a2 100644
--- a/example/widget/example/FormWidgetExampleScreens.xml
+++ b/example/widget/example/FormWidgetExampleScreens.xml
@@ -55,9 +55,9 @@ under the License.
</container>
<section>
<actions>
- <!-- fields for
SetDependentDropdownValuesJs.ftl, it's a try on generalization,
- if you need an example with
more than one dropdown in a form have a look at
- EditProductPriceRules screen
in PriceScreens.xml-->
+ <!-- fields for
SetDependentDropdownValuesJs.ftl.
+ For a dependent drop-down
driven by more than one parent field, see the
+
DependentDropDownMultiParamExampleForm section further down this screen. -->
<set field="dependentForm"
value="DropDownFieldsExampleForm"/>
<set field="paramKey"
value="countryGeoId"/>
<set field="mainId"
value="countryGeoId"/>
@@ -84,6 +84,37 @@ under the License.
</widgets>
</section>
</container>
+ <container style="screenlet-body">
+ <container style="button-bar"><label
style="h2">${uiLabelMap.ExampleMultiParamDependentDropDowns}</label></container>
+ <container style="screenlet-body">
+ <label
style="h3">${uiLabelMap.ExampleSourceCode}</label>
+
<label>${uiLabelMap.ExampleDefinitionName}
"DependentDropDownMultiParamExampleForm"
${uiLabelMap.ExampleDefinitionFile}</label>
+ <link
target="${formWidgetExampleFormsUrl}" url-mode="plain" target-window="svnPage"
text="${uiLabelMap.ExampleDefinitionFileTooltip}" style="buttontext"/>
+ </container>
+ <section>
+ <actions>
+ <!-- A dependent drop-down driven
by TWO parent fields (OFBIZ-11724).
+ paramKey and mainId are comma
separated lists, and the two lists are
+ paired up by position: the
Nth paramKey goes with the Nth mainId, so
+ they must be listed in the
same order. SetDependentDropdownValuesJs.ftl
+ prefixes each field with the
form name, so the service receives both
+ exampleId and asOfDate and
can filter ExampleFeatureAppl by date. -->
+ <set field="dependentForm"
value="DependentDropDownMultiParamExampleForm"/>
+ <set field="paramKey"
value="exampleId,asOfDate"/>
+ <set field="mainId"
value="exampleId,asOfDate"/>
+ <set field="dependentId"
value="exampleFeatureId"/>
+ <set field="requestName"
value="getAssociatedExampleFeatures"/>
+ <set field="responseName"
value="exampleFeatureList"/>
+ <set field="dependentKeyName"
value="exampleFeatureId"/>
+ <set field="descName"
value="description"/>
+ <set
field="selectedDependentOption" value="_none_"/>
+ </actions>
+ <widgets>
+
<platform-specific><html><html-template multi-block="true"
location="component://common-theme/template/includes/SetDependentDropdownValuesJs.ftl"/></html></platform-specific>
+ <include-form
name="DependentDropDownMultiParamExampleForm"
location="component://example/widget/example/FormWidgetExampleForms.xml"/>
+ </widgets>
+ </section>
+ </container>
<container style="screenlet-body">
<container style="button-bar"><label
style="h2">${uiLabelMap.ExampleLookupFieldsTitle}</label></container>
<container style="screenlet-body">