Skip to content

Commit

Permalink
feat: add itemClassNameGenerator to generate CSS class names (#6177)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Jun 17, 2024
1 parent f60daad commit 40db058
Show file tree
Hide file tree
Showing 6 changed files with 353 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.combobox.test;

import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;

@Route("vaadin-combo-box/item-class-name")
public class ComboBoxItemClassNamePage extends Div {

public ComboBoxItemClassNamePage() {
ComboBox<String> comboBox = new ComboBox<>();
comboBox.setItems("foo", "bar", "baz");

NativeButton setClassNameGenerator = new NativeButton(
"Set class name generator", e -> {
comboBox.setClassNameGenerator(item -> "item-" + item);
});
setClassNameGenerator.setId("set-generator");

NativeButton resetClassNameGenerator = new NativeButton(
"Reset class name generator", e -> {
comboBox.setClassNameGenerator(item -> null);
});
resetClassNameGenerator.setId("reset-generator");

add(comboBox, setClassNameGenerator, resetClassNameGenerator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.combobox.test;

import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;

@Route("vaadin-multi-select-combo-box/item-class-name")
public class MultiSelectComboBoxItemClassNamePage extends Div {

public MultiSelectComboBoxItemClassNamePage() {
MultiSelectComboBox<String> comboBox = new MultiSelectComboBox<>();
comboBox.setItems("foo", "bar", "baz");

// Make component wider, so that we can fit multiple chips
comboBox.setWidth("300px");

NativeButton setClassNameGenerator = new NativeButton(
"Set class name generator", e -> {
comboBox.setClassNameGenerator(item -> "item-" + item);
});
setClassNameGenerator.setId("set-generator");

NativeButton resetClassNameGenerator = new NativeButton(
"Reset class name generator", e -> {
comboBox.setClassNameGenerator(item -> null);
});
resetClassNameGenerator.setId("reset-generator");

NativeButton setValue = new NativeButton("Set value", e -> {
comboBox.select("foo", "bar");
});
setValue.setId("set-value");

add(comboBox, setClassNameGenerator, resetClassNameGenerator, setValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.combobox.test;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.flow.component.combobox.testbench.ComboBoxElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.tests.AbstractComponentIT;
import com.vaadin.testbench.ElementQuery;
import com.vaadin.testbench.TestBenchElement;

@TestPath("vaadin-combo-box/item-class-name")
public class ComboBoxItemClassNameIT extends AbstractComponentIT {

private ComboBoxElement comboBox;

@Before
public void init() {
open();
comboBox = $(ComboBoxElement.class).first();
}

@Test
public void noClassesOnItemsSetInitially() {
comboBox.openPopup();

assertItemClassNames("", "", "");
}

@Test
public void setClassNameGenerator_classesGenerated() {
click("set-generator");
comboBox.openPopup();

assertItemClassNames("item-foo", "item-bar", "item-baz");
}

@Test
public void changeClassNameGeneratorToReturnNull_classesRemoved() {
click("set-generator");
click("reset-generator");
comboBox.openPopup();

assertItemClassNames("", "", "");
}

private void assertItemClassNames(String... expectedClassNames) {
TestBenchElement overlay = $("vaadin-combo-box-overlay").first();
ElementQuery<TestBenchElement> items = overlay
.$("vaadin-combo-box-item");

for (int i = 0; i < expectedClassNames.length; i++) {
Assert.assertEquals(items.get(i).getAttribute("class"),
expectedClassNames[i]);
}
}

private void click(String id) {
$(TestBenchElement.class).id(id).click();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* Licensed 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 com.vaadin.flow.component.combobox.test;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.flow.component.combobox.testbench.MultiSelectComboBoxElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.tests.AbstractComponentIT;
import com.vaadin.testbench.ElementQuery;
import com.vaadin.testbench.TestBenchElement;

@TestPath("vaadin-multi-select-combo-box/item-class-name")
public class MultiSelectComboBoxItemClassNameIT extends AbstractComponentIT {

private MultiSelectComboBoxElement comboBox;

@Before
public void init() {
open();
comboBox = $(MultiSelectComboBoxElement.class).first();
}

@Test
public void noClassesOnItemsSetInitially() {
comboBox.openPopup();

assertItemClassNames("", "", "");
}

@Test
public void setClassNameGenerator_itemClassesGenerated() {
click("set-generator");
comboBox.openPopup();

assertItemClassNames("item-foo", "item-bar", "item-baz");
}

@Test
public void changeClassNameGeneratorToReturnNull_itemClassesRemoved() {
click("set-generator");
click("reset-generator");
comboBox.openPopup();

assertItemClassNames("", "", "");
}

@Test
public void setValue_noClassesOnChipsSetInitially() {
click("set-value");

assertChipClassNames("", "");
}

@Test
public void setClassNameGenerator_setValue_chipClassesGenerated() {
click("set-generator");
click("set-value");

assertChipClassNames("item-foo", "item-bar");
}

@Test
public void setValue_setClassNameGenerator_chipClassesGenerated() {
click("set-value");
click("set-generator");

assertChipClassNames("item-foo", "item-bar");
}

@Test
public void changeClassNameGeneratorToReturnNull_chipClassesRemoved() {
click("set-generator");
click("set-value");

click("reset-generator");

assertChipClassNames("", "");
}

private void assertItemClassNames(String... expectedClassNames) {
TestBenchElement overlay = $("vaadin-multi-select-combo-box-overlay")
.first();
ElementQuery<TestBenchElement> items = overlay
.$("vaadin-multi-select-combo-box-item");

for (int i = 0; i < expectedClassNames.length; i++) {
Assert.assertEquals(items.get(i).getAttribute("class"),
expectedClassNames[i]);
}
}

private void assertChipClassNames(String... expectedClassNames) {
ElementQuery<TestBenchElement> chips = comboBox
.$("vaadin-multi-select-combo-box-chip");

for (int i = 0; i < expectedClassNames.length; i++) {
// Skip first chip as it's used for overflow items
Assert.assertEquals(chips.get(i + 1).getAttribute("class"),
expectedClassNames[i]);
}
}

private void click(String id) {
$(TestBenchElement.class).id(id).click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public void remove() {
}

private ItemLabelGenerator<TItem> itemLabelGenerator = String::valueOf;
private SerializableFunction<TItem, String> classNameGenerator = item -> null;
private final ComboBoxRenderManager<TItem> renderManager;
private final ComboBoxDataController<TItem> dataController;
private int customValueListenersCount;
Expand Down Expand Up @@ -165,6 +166,9 @@ public Locale get() {
dataController = new ComboBoxDataController<>(this, localeSupplier);
dataController.getDataGenerator().addDataGenerator((item,
jsonObject) -> jsonObject.put("label", generateLabel(item)));
dataController.getDataGenerator()
.addDataGenerator((item, jsonObject) -> jsonObject
.put("className", generateClassName(item)));

// Configure web component to use key property from the generated
// wrapper items for identification
Expand Down Expand Up @@ -427,6 +431,56 @@ protected String generateLabel(TItem item) {
return label;
}

/**
* Sets the function that is used for generating CSS class names for the
* dropdown items in the ComboBox. Returning {@code null} from the generator
* results in no custom class name being set. Multiple class names can be
* returned from the generator as space-separated.
*
* @since 24.5
* @param classNameGenerator
* the class name generator to set, not {@code null}
* @throws NullPointerException
* if {@code classNameGenerator} is {@code null}
*/
public void setClassNameGenerator(
SerializableFunction<TItem, String> classNameGenerator) {
Objects.requireNonNull(classNameGenerator,
"Class name generator can not be null");
this.classNameGenerator = classNameGenerator;
dataController.reset();
}

/**
* Gets the item class name generator that is used for generating CSS class
* names for the dropdown items in the ComboBox.
*
* @since 24.5
* @return the item class name generator, not null
*/
public SerializableFunction<TItem, String> getItemClassNameGenerator() {
return classNameGenerator;
}

/**
* Generates a string class name for a data item using the current item
* class name generator
*
* @param item
* the data item
* @return string class name for the data item
*/
protected String generateClassName(TItem item) {
if (item == null) {
return "";
}
String label = getItemClassNameGenerator().apply(item);
if (label == null) {
return "";
}
return label;
}

/**
* Sets the Renderer responsible to render the individual items in the list
* of possible choices of the ComboBox. It doesn't affect how the selected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,10 @@ import { ComboBoxPlaceholder } from '@vaadin/combo-box/src/vaadin-combo-box-plac
'custom-value-set',
tryCatchWrapper((e) => e.preventDefault())
);

comboBox.itemClassNameGenerator = tryCatchWrapper(function (item) {
return item.className || '';
});
})(comboBox)
};
})();
Expand Down

0 comments on commit 40db058

Please sign in to comment.