You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2025/04/15 08:33:03 UTC
(myfaces-tobago) 02/03: fix(selectOneList): deselection
This is an automated email from the ASF dual-hosted git repository.
hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git
commit 6f1b67ff03a6eca9486949e476872591c48f0a6f
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Mon Apr 14 10:31:13 2025 +0200
fix(selectOneList): deselection
* add test
* deselection is now possible like it is for the selectOneListbox component
* adjust unit test
Issue: TOBAGO-2403
---
.../renderkit/renderer/SelectOneListRenderer.java | 1 +
.../renderer/selectOneList/selectOneListAjax.html | 2 +-
.../demo/test/onelist/DeselectController.java | 90 +++++++++++++++++++
.../2098-selectOneList/deselect/Deselect.test.js | 100 +++++++++++++++++++++
.../2098-selectOneList/deselect/Deselect.xhtml | 42 +++++++++
.../src/main/ts/tobago-select-one-list.ts | 6 +-
6 files changed, 237 insertions(+), 4 deletions(-)
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListRenderer.java
index 114caa8d2e..f2280f6ab5 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/SelectOneListRenderer.java
@@ -84,6 +84,7 @@ private void encodeHiddenSelect(
writer.writeAttribute(HtmlAttributes.DISABLED, disabled);
writer.writeAttribute(HtmlAttributes.REQUIRED, component.isRequired());
writer.writeClassAttribute(BootstrapClass.D_NONE);
+ writer.writeAttribute(HtmlAttributes.SIZE, 2);
final String submittedValue = (String) component.getSubmittedValue();
renderSelectItems(component, null, items, component.getValue(), submittedValue, writer, facesContext);
diff --git a/tobago-core/src/test/resources/renderer/selectOneList/selectOneListAjax.html b/tobago-core/src/test/resources/renderer/selectOneList/selectOneListAjax.html
index d29494144d..2666bdb22f 100644
--- a/tobago-core/src/test/resources/renderer/selectOneList/selectOneListAjax.html
+++ b/tobago-core/src/test/resources/renderer/selectOneList/selectOneListAjax.html
@@ -16,7 +16,7 @@
-->
<tobago-select-one-list id='id' class='tobago-auto-spacing'>
-<select id='id::field' name='id' class='d-none'>
+<select id='id::field' name='id' class='d-none' size='2'>
<option value='Stratocaster'>Stratocaster</option>
<option value='Telecaster'>Telecaster</option></select>
<div class='dropdown'>
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/test/onelist/DeselectController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/test/onelist/DeselectController.java
new file mode 100644
index 0000000000..f493babb60
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/test/onelist/DeselectController.java
@@ -0,0 +1,90 @@
+/*
+ * 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.myfaces.tobago.example.demo.test.onelist;
+
+import org.apache.myfaces.tobago.example.demo.AstroData;
+import org.apache.myfaces.tobago.example.demo.SolarObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.SessionScoped;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.util.Date;
+import java.util.List;
+
+@Named
+@SessionScoped
+public class DeselectController implements Serializable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ @Inject
+ private AstroData astroData;
+
+ private List<SolarObject> planets;
+ private SolarObject selectedPlanet;
+ private boolean required = false;
+ private boolean expanded = true;
+
+ @PostConstruct
+ public void init() {
+ planets = astroData.getSatellites("Sun");
+ }
+
+ public void reset() {
+ selectedPlanet = null;
+ }
+
+ public List<SolarObject> getPlanets() {
+ return planets;
+ }
+
+ public SolarObject getSelectedPlanet() {
+ return selectedPlanet;
+ }
+
+ public void setSelectedPlanet(SolarObject selectedPlanet) {
+ this.selectedPlanet = selectedPlanet;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ public boolean isExpanded() {
+ return expanded;
+ }
+
+ public void setExpanded(boolean expanded) {
+ this.expanded = expanded;
+ }
+
+ public long getCurrentTimestamp() {
+ return new Date().getTime();
+ }
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.test.js
new file mode 100644
index 0000000000..1faf23945a
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.test.js
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
+import {elementByIdFn, querySelectorFn} from "/script/tobago-test.js";
+
+it("deselection if required=false", function (done) {
+ const hiddenSelect = elementByIdFn("page:mainForm:selectOneList::field");
+ const spanSelected = querySelectorFn("#page\\:mainForm\\:selectOneList\\:\\:selectField span");
+ const saturnRow = querySelectorFn(".tobago-options[name='page:mainForm:selectOneList'] tr[data-tobago-value='Saturn']");
+ const output = querySelectorFn("#page\\:mainForm\\:output .form-control-plaintext");
+ const submit = elementByIdFn("page:mainForm:submit");
+ const reset = elementByIdFn("page:mainForm:reset");
+ const requiredCheckbox = elementByIdFn("page:mainForm:requiredCheckbox::field");
+
+ let timestamp;
+
+ const test = new JasmineTestTool(done);
+ test.setup(() => hiddenSelect().required === false,
+ () => requiredCheckbox().checked = false, "change", requiredCheckbox);
+ test.setup(() => spanSelected().textContent === "" && output().textContent === "", null, "click", reset);
+ test.do(() => expect(hiddenSelect().required).toBeFalse());
+ test.do(() => expect(spanSelected().textContent).toEqual(""));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => clickRow(saturnRow));
+ test.waitMs(1000);
+ test.do(() => expect(spanSelected().textContent).toEqual("Saturn"));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => clickRow(saturnRow));
+ test.waitMs(1000);
+ test.do(() => expect(spanSelected().textContent).toEqual(""));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => timestamp = currentTimestamp());
+ test.event("click", submit, () => currentTimestamp() > timestamp);
+ test.do(() => expect(spanSelected().textContent).toEqual(""));
+ test.do(() => expect(output().textContent).toEqual(""));
+ test.start();
+});
+
+it("deselection if required=true", function (done) {
+ const hiddenSelect = elementByIdFn("page:mainForm:selectOneList::field");
+ const spanSelected = querySelectorFn("#page\\:mainForm\\:selectOneList\\:\\:selectField span");
+ const jupiterRow = querySelectorFn(".tobago-options[name='page:mainForm:selectOneList'] tr[data-tobago-value='Jupiter']");
+ const output = querySelectorFn("#page\\:mainForm\\:output .form-control-plaintext");
+ const submit = elementByIdFn("page:mainForm:submit");
+ const reset = elementByIdFn("page:mainForm:reset");
+ const requiredCheckbox = elementByIdFn("page:mainForm:requiredCheckbox::field");
+
+ let timestamp;
+
+ const test = new JasmineTestTool(done);
+ test.setup(() => hiddenSelect().required === true,
+ () => requiredCheckbox().checked = true, "change", requiredCheckbox);
+ test.setup(() => spanSelected().textContent === "" && output().textContent === "", null, "click", reset);
+ test.do(() => expect(hiddenSelect().required).toBeTrue());
+ test.do(() => expect(spanSelected().textContent).toEqual(""));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => clickRow(jupiterRow));
+ test.waitMs(1000);
+ test.do(() => expect(spanSelected().textContent).toEqual("Jupiter"));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => clickRow(jupiterRow));
+ test.waitMs(1000);
+ test.do(() => expect(spanSelected().textContent).toEqual("Jupiter"));
+ test.do(() => expect(output().textContent).toEqual(""));
+
+ test.do(() => timestamp = currentTimestamp());
+ test.event("click", submit, () => currentTimestamp() > timestamp);
+ test.do(() => expect(spanSelected().textContent).toEqual("Jupiter"));
+ test.do(() => expect(output().textContent).toEqual("Jupiter"));
+ test.start();
+});
+
+function clickRow(row) {
+ row().dispatchEvent(new Event("click", {bubbles: true}))
+}
+
+function currentTimestamp() {
+ const timestampOut = querySelectorFn("#page\\:mainForm\\:timestamp .form-control-plaintext");
+ return Number(timestampOut().textContent);
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.xhtml
new file mode 100644
index 0000000000..54ef24b5b8
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/900-test/2098-selectOneList/deselect/Deselect.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<ui:composition template="/main.xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:f="http://xmlns.jcp.org/jsf/core"
+ xmlns:tc="http://myfaces.apache.org/tobago/component"
+ xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+
+ <tc:selectOneList id="selectOneList" label="SelectOneList" value="#{deselectController.selectedPlanet}"
+ required="#{deselectController.required}"
+ expanded="#{deselectController.expanded}">
+ <tc:selectItems value="#{deselectController.planets}"
+ var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+ </tc:selectOneList>
+ <tc:out id="output" label="Planet" value="#{deselectController.selectedPlanet}"/>
+ <tc:button id="submit" label="Submit"/>
+ <tc:button id="reset" label="Reset" action="#{deselectController.reset()}"/>
+ <tc:selectBooleanCheckbox id="requiredCheckbox" value="#{deselectController.required}" itemLabel="required">
+ <f:ajax render="selectOneList timestamp"/>
+ </tc:selectBooleanCheckbox>
+ <tc:selectBooleanCheckbox id="expandedCheckbox" value="#{deselectController.expanded}" itemLabel="expanded">
+ <f:ajax render="selectOneList timestamp"/>
+ </tc:selectBooleanCheckbox>
+ <tc:out id="timestamp" label="Timestamp" value="#{deselectController.currentTimestamp}"/>
+</ui:composition>
diff --git a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
index 337fafbfd2..d589a979a4 100644
--- a/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
+++ b/tobago-theme/tobago-theme-standard/src/main/ts/tobago-select-one-list.ts
@@ -79,16 +79,16 @@ class SelectOneList extends SelectListBase {
protected select(row: HTMLTableRowElement): void {
const itemValue = row.dataset.tobagoValue;
const option: HTMLOptionElement = this.hiddenSelect.querySelector(`[value="${itemValue}"]`);
- option.selected = true;
+ option.selected = this.hiddenSelect.required ? true : !option.selected;
this.filterInput.value = null;
this.sync();
this.hiddenSelect.dispatchEvent(new Event("change", {bubbles: true}));
}
private sync() {
+ this.spanText = this.selectedOption?.textContent;
this.rows.forEach((row) => {
if (row.dataset.tobagoValue === this.hiddenSelect.value) {
- this.spanText = this.selectedOption.textContent;
row.classList.add(Css.TABLE_PRIMARY); // highlight list row
} else {
row.classList.remove(Css.TABLE_PRIMARY); // remove highlight list row
@@ -100,7 +100,7 @@ class SelectOneList extends SelectListBase {
this.focused = false;
this.filterInput.value = null;
this.filterInput.dispatchEvent(new Event("input"));
- this.spanText = this.selectedOption.textContent;
+ this.spanText = this.selectedOption?.textContent;
this.dropdownMenu?.hide();
}