Skip to content

Commit

Permalink
feat: allow theming individual menu items (#2066) (#2323)
Browse files Browse the repository at this point in the history
This change enables theming individual menu bar items, for example to display one item as the primary item. As the menu bar uses the context menu to display submenus, most of the changes are in there.

* feat: allow theming individual menu items

Backported changes for Vaadin 14 from
vaadin/web-components#2066.

Depends on vaadin/vaadin-menu-bar#140.

Part of
#880.

* Bump vaadin-context-menu 4.6.0-alpha1 and vaadin-menu-bar  1.3.0-alpha1
  • Loading branch information
tltv authored Nov 10, 2021
1 parent 8112302 commit cbb662c
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
<artifactId>vaadin-context-menu</artifactId>
<version>4.5.0</version>
<version>4.6.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
* </p>
*/
@Tag("vaadin-context-menu")
@NpmPackage(value = "@vaadin/vaadin-context-menu", version = "4.5.0")
@NpmPackage(value = "@vaadin/vaadin-context-menu", version = "4.6.0-alpha1")
@JsModule("@vaadin/vaadin-context-menu/src/vaadin-context-menu.js")
@HtmlImport("frontend://bower_components/vaadin-context-menu/src/vaadin-context-menu.html")
public abstract class GeneratedVaadinContextMenu<R extends GeneratedVaadinContextMenu<R>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,45 @@
*/
package com.vaadin.flow.component.contextmenu;

import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasComponents;
import com.vaadin.flow.component.HasEnabled;
import com.vaadin.flow.component.HasText;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;

/**
* Base class for item component used inside {@link ContextMenu}s.
*
* @see MenuItem
*
* @param <C>
* the context menu type
* @param <I>
* the menu item type
* @param <S>
* the sub menu type
*
* @author Vaadin Ltd.
* @see MenuItem
*/
@SuppressWarnings("serial")
@Tag("vaadin-context-menu-item")

public abstract class MenuItemBase<C extends ContextMenuBase<C, I, S>, I extends MenuItemBase<C, I, S>, S extends SubMenuBase<C, I, S>>
extends Component implements HasText, HasComponents, HasEnabled {

private static final String PRIVATE_THEME_ATTRIBUTE = "_theme";

private final C contextMenu;
private S subMenu;

private boolean checkable = false;

private Set<String> themeNames = new LinkedHashSet<>();

/**
* Default constructor
*
Expand Down Expand Up @@ -158,12 +163,9 @@ public void setChecked(boolean checked) {

getElement().setProperty("_checked", checked);

getElement().getNode().runWhenAttached(
ui -> ui.beforeClientResponse(this, context -> {
ui.getPage().executeJavaScript(
"window.Vaadin.Flow.contextMenuConnector.setChecked($0, $1)",
getElement(), checked);
}));
executeJsWhenAttached(
"window.Vaadin.Flow.contextMenuConnector.setChecked($0, $1)",
getElement(), checked);
}

/**
Expand All @@ -179,5 +181,61 @@ public boolean isChecked() {
return getElement().getProperty("_checked", false);
}

/**
* Adds one or more theme names to this item. Multiple theme names can be
* specified by using multiple parameters.
*
* @param themeNames
* the theme name or theme names to be added to the item
*/
public void addThemeNames(String... themeNames) {
this.themeNames.addAll(Arrays.asList(themeNames));
setThemeName();
}

/**
* Removes one or more theme names from this item. Multiple theme names can
* be specified by using multiple parameters.
*
* @param themeNames
* the theme name or theme names to be removed from the item
*/
public void removeThemeNames(String... themeNames) {
this.themeNames.removeAll(Arrays.asList(themeNames));
setThemeName();
}

/**
* Checks if the item has the given theme name.
*
* @param themeName
* the theme name to check for
* @return <code>true</code> if the item has the given theme name,
* <code>false</code> otherwise
*/
public boolean hasThemeName(String themeName) {
return themeNames.contains(themeName);
}

private void setThemeName() {
String themeName = themeNames.stream().collect(Collectors.joining(" "));
if (themeName != null) {
getElement().setProperty(PRIVATE_THEME_ATTRIBUTE, themeName);
} else {
getElement().removeProperty(PRIVATE_THEME_ATTRIBUTE);
}

executeJsWhenAttached(
"window.Vaadin.Flow.contextMenuConnector.setTheme($0, $1)",
getElement(), themeName);
}

protected abstract S createSubMenu();

protected void executeJsWhenAttached(String expression,
Serializable... parameters) {
getElement().getNode().runWhenAttached(ui -> ui.beforeClientResponse(
this,
context -> ui.getPage().executeJs(expression, parameters)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@
const getChildItems = function(parent) {
const container = getContainer(parent._containerNodeId);
const items = container && Array.from(container.children).map(child => {
const item = {component: child, checked: child._checked};
const item = {
component: child,
checked: child._checked,
theme: child._theme
};
if (child.tagName == "VAADIN-CONTEXT-MENU-ITEM" && child._containerNodeId) {
item.children = getChildItems(child);
}
Expand All @@ -109,6 +113,13 @@
if (component._item) {
component._item.checked = checked;
}
})(component, checked)
})(component, checked),

setTheme: (component, theme) =>
tryCatchWrapper((component, theme) => {
if (component._item) {
component._item.theme = theme;
}
})(component, theme)
};
})();
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public void initView() {
createTertiaryThemeVariant();
createTertiaryInlineThemeVariant();
createSmallThemeVariant();
createSingleButtonPrimaryThemeVariant();
}

private void createBasicDemo() {
Expand Down Expand Up @@ -386,4 +387,40 @@ private void createSmallThemeVariant() {
addCard("Theme Variants", "Small Buttons", menuBar, message);
}

private void createSingleButtonPrimaryThemeVariant() {
// begin-source-example
// source-example-heading: Individual Button Themes
MenuBar menuBar = new MenuBar();

Text selected = new Text("");
Div message = new Div(new Text("Selected: "), selected);

MenuItem project = menuBar.addItem("Project");
project.addThemeNames(MenuBarVariant.LUMO_PRIMARY.getVariantName());
MenuItem account = menuBar.addItem("Account");
MenuItem signOut = menuBar.addItem(VaadinIcon.SIGN_OUT.create(),
e -> selected.setText("Sign Out"));
signOut.addThemeNames(MenuBarVariant.LUMO_TERTIARY.getVariantName());

SubMenu projectSubMenu = project.getSubMenu();
MenuItem users = projectSubMenu.addItem("Users");
MenuItem billing = projectSubMenu.addItem("Billing");

SubMenu usersSubMenu = users.getSubMenu();
usersSubMenu.addItem("List", e -> selected.setText("List"));
usersSubMenu.addItem("Add", e -> selected.setText("Add"));

SubMenu billingSubMenu = billing.getSubMenu();
billingSubMenu.addItem("Invoices", e -> selected.setText("Invoices"));
billingSubMenu.addItem("Balance Events",
e -> selected.setText("Balance Events"));

account.getSubMenu().addItem("Edit Profile",
e -> selected.setText("Edit Profile"));
account.getSubMenu().addItem("Privacy Settings",
e -> selected.setText("Privacy Settings"));

// end-source-example
addCard("Theme Variants", "Individual Button Themes", menuBar, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
@PreserveOnRefresh
public class MenuBarTestPage extends Div {

public static final String MENU_BAR_THEME = "menu-bar-theme";
public static final String MENU_ITEM_THEME = "menu-item-theme";
public static final String SUB_ITEM_THEME = "sub-item-theme";

public MenuBarTestPage() {
MenuBar menuBar = new MenuBar();
add(menuBar);
Expand Down Expand Up @@ -103,9 +107,40 @@ public MenuBarTestPage() {
});
toggleAttachedButton.setId("toggle-attached");

NativeButton toggleMenuBarThemeButton = new NativeButton("toggle theme",
e -> {
if (menuBar.hasThemeName(MENU_BAR_THEME)) {
menuBar.removeThemeName(MENU_BAR_THEME);
} else {
menuBar.addThemeName(MENU_BAR_THEME);
}
});
toggleMenuBarThemeButton.setId("toggle-theme");

NativeButton toggleMenuItemThemeButton = new NativeButton(
"toggle item theme", e -> {
if (item1.hasThemeName(MENU_ITEM_THEME)) {
item1.removeThemeNames(MENU_ITEM_THEME);
} else {
item1.addThemeNames(MENU_ITEM_THEME);
}
});
toggleMenuItemThemeButton.setId("toggle-item-theme");

NativeButton toggleSubItemThemeButton = new NativeButton(
"toggle sub theme", e -> {
if (subItem2.hasThemeName(SUB_ITEM_THEME)) {
subItem2.removeThemeNames(SUB_ITEM_THEME);
} else {
subItem2.addThemeNames(SUB_ITEM_THEME);
}
});
toggleSubItemThemeButton.setId("toggle-sub-theme");

add(new Hr(), addRootItemButton, addSubItemButton, removeItemButton,
openOnHoverButton, setWidthButton, resetWidthButton,
disableButton, visibleButton, checkedButton,
toggleAttachedButton);
toggleAttachedButton, toggleMenuBarThemeButton,
toggleMenuItemThemeButton, toggleSubItemThemeButton);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,58 @@ public void addSubItem_clickMenuItem_clickButton_subMenuOpenedAndClosed() {
verifyClosed();
}

@Test
public void toggleMenuBarTheme_themeIsToggled() {
Assert.assertFalse(menuBar.hasAttribute("theme"));
click("toggle-theme");
Assert.assertEquals(menuBar.getAttribute("theme"),
MenuBarTestPage.MENU_BAR_THEME);
click("toggle-theme");
Assert.assertFalse(menuBar.hasAttribute("theme"));
}

@Test
public void toggleMenuItemTheme_themeIsToggled() {
TestBenchElement menuButton1 = menuBar.getButtons().get(0);
Assert.assertFalse(menuButton1.hasAttribute("theme"));
click("toggle-item-theme");
menuButton1 = menuBar.getButtons().get(0);
Assert.assertEquals(menuButton1.getAttribute("theme"),
MenuBarTestPage.MENU_ITEM_THEME);
click("toggle-item-theme");
menuButton1 = menuBar.getButtons().get(0);
Assert.assertFalse(menuButton1.hasAttribute("theme"));
}

@Test
public void toggleSubMenuItemTheme_themeIsToggled() {
menuBar.getButtons().get(0).click();
Assert.assertFalse(getOverlayMenuItems().get(1).hasAttribute("theme"));

click("toggle-sub-theme");
verifyClosed();

menuBar.getButtons().get(0).click();
Assert.assertEquals(getOverlayMenuItems().get(1).getAttribute("theme"),
MenuBarTestPage.SUB_ITEM_THEME);

click("toggle-sub-theme");
verifyClosed();

menuBar.getButtons().get(0).click();
Assert.assertFalse(getOverlayMenuItems().get(1).hasAttribute("theme"));
}

@Test
public void toggleMenuBarTheme_toggleMenuItemTheme_themeIsOverridden() {
click("toggle-theme");
click("toggle-item-theme");

TestBenchElement menuButton1 = menuBar.getButtons().get(0);
Assert.assertEquals(MenuBarTestPage.MENU_ITEM_THEME,
menuButton1.getAttribute("theme"));
}

@After
public void afterTest() {
checkLogsForErrors();
Expand Down
4 changes: 2 additions & 2 deletions vaadin-menu-bar-flow-parent/vaadin-menu-bar-flow/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
<artifactId>vaadin-menu-bar</artifactId>
<version>1.2.2</version>
<version>1.3.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
Expand All @@ -23,7 +23,7 @@
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
<artifactId>vaadin-context-menu</artifactId>
<version>4.5.0</version>
<version>4.6.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.webjars.bowergithub.vaadin</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
@JsModule("./menubarConnector.js")
@HtmlImport("frontend://bower_components/vaadin-menu-bar/src/vaadin-menu-bar.html")
@JsModule("@vaadin/vaadin-menu-bar/src/vaadin-menu-bar.js")
@NpmPackage(value = "@vaadin/vaadin-menu-bar", version = "1.2.2")
@NpmPackage(value = "@vaadin/vaadin-menu-bar", version = "1.3.0-alpha1")
public class MenuBar extends Component
implements HasMenuItems, HasSize, HasStyle, HasTheme {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,33 @@ public void setVisible(boolean visible) {
super.setVisible(visible);
menuBar.resetContent();
}

/**
* Adds one or more theme names to this item. Multiple theme names can be
* specified by using multiple parameters.
* <p>
* Note that the themes set via {@link MenuBar#setThemeName(String)} will be
* overridden when using this method.
*
* @param themeNames
* the theme name or theme names to be added to the item
*/
@Override
public void addThemeNames(String... themeNames) {
super.addThemeNames(themeNames);
menuBar.updateButtons();
}

/**
* Removes one or more theme names from this item. Multiple theme names can
* be specified by using multiple parameters.
*
* @param themeNames
* the theme name or theme names to be removed from the item
*/
@Override
public void removeThemeNames(String... themeNames) {
super.removeThemeNames(themeNames);
menuBar.updateButtons();
}
}

0 comments on commit cbb662c

Please sign in to comment.