Java tutorial
/* * Copyright (c) 2008-2017 Haulmont. * * 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.haulmont.cuba.web.widgets; import com.haulmont.cuba.web.widgets.client.verticalmenu.CubaSideMenuClientRpc; import com.haulmont.cuba.web.widgets.client.verticalmenu.CubaSideMenuServerRpc; import com.haulmont.cuba.web.widgets.client.verticalmenu.CubaSideMenuState; import com.vaadin.server.KeyMapper; import com.vaadin.server.Resource; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.Component; import elemental.json.Json; import elemental.json.JsonArray; import elemental.json.JsonObject; import org.apache.commons.lang3.StringUtils; import javax.annotation.Nullable; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.*; import java.util.function.Consumer; public class CubaSideMenu extends AbstractComponent implements Component.Focusable { protected static final String MENU_ITEM_ID = "id"; protected static final String MENU_ITEM_CAPTION = "caption"; protected static final String MENU_ITEM_DESCRIPTION = "description"; protected static final String MENU_ITEM_STYLE_NAME = "styleName"; protected static final String MENU_ITEM_VISIBLE = "visible"; protected static final String MENU_ITEM_ENABLED = "enabled"; protected static final String MENU_ITEM_EXPANDED = "expanded"; protected static final String MENU_ITEM_CAPTION_AS_HTML = "captionAsHtml"; protected static final String MENU_ITEM_CUBA_ID = "cubaId"; protected static final String MENU_ITEM_ICON = "icon"; protected static final String MENU_ITEM_BADGE_TEXT = "badgeText"; protected static final String MENU_ITEM_CHILDREN = "children"; protected static final String MENU_ITEM_COMMAND = "command"; protected List<MenuItem> menuItems = new ArrayList<>(); protected MenuItem selectedItem; protected boolean structureChanged = true; protected boolean selectedChanged = true; protected KeyMapper<MenuItem> menuItemIdMapper = new KeyMapper<>(); protected KeyMapper<Resource> menuIconsKeyMapper = new KeyMapper<>(); protected Set<String> menuIconResourceKeys = new HashSet<>(); protected List<MenuItem> badgeUpdates = new ArrayList<>(); protected PropertyChangeListener itemsPropertyChangeListener = this::menuItemPropertyChanged; protected Consumer<MenuItem> beforeMenuItemTriggeredHandler = null; public CubaSideMenu() { CubaSideMenuServerRpc menuRpc = new CubaSideMenuServerRpc() { @Override public void menuItemTriggered(String itemId) { MenuItem menuItem = menuItemIdMapper.get(itemId); if (menuItem != null) { if (isSelectOnClick()) { selectedItem = menuItem; } if (beforeMenuItemTriggeredHandler != null) { beforeMenuItemTriggeredHandler.accept(menuItem); } if (menuItem.getCommand() != null) { menuItem.getCommand().accept(new MenuItemTriggeredEvent(CubaSideMenu.this, menuItem)); } removeStyleName("c-sidemenu-open"); } } @Override public void headerItemExpandChanged(String itemId, boolean expanded) { MenuItem menuItem = menuItemIdMapper.get(itemId); if (menuItem != null) { menuItem.expanded = expanded; } } }; registerRpc(menuRpc); } public Consumer<MenuItem> getBeforeMenuItemTriggeredHandler() { return beforeMenuItemTriggeredHandler; } public void setBeforeMenuItemTriggeredHandler(Consumer<MenuItem> beforeMenuItemTriggeredHandler) { this.beforeMenuItemTriggeredHandler = beforeMenuItemTriggeredHandler; } @Override protected CubaSideMenuState getState() { return (CubaSideMenuState) super.getState(); } @Override protected CubaSideMenuState getState(boolean markAsDirty) { return (CubaSideMenuState) super.getState(markAsDirty); } @Override public void beforeClientResponse(boolean initial) { super.beforeClientResponse(initial); if (initial || this.structureChanged) { JsonArray menuTreeJson = toJson(menuItems); getRpcProxy(CubaSideMenuClientRpc.class).buildMenu(menuTreeJson); this.structureChanged = false; } if (this.selectedChanged) { String key = selectedItem == null ? null : menuItemIdMapper.key(selectedItem); getRpcProxy(CubaSideMenuClientRpc.class).selectItem(key); this.selectedChanged = false; } if (!badgeUpdates.isEmpty()) { Map<String, String> updatedBadgeValues = new HashMap<>(); for (MenuItem updatedItem : badgeUpdates) { // send null as empty string to remove badge String badgeText = updatedItem.getBadgeText(); updatedBadgeValues.put(menuItemIdMapper.key(updatedItem), badgeText != null ? badgeText : ""); } badgeUpdates.clear(); getRpcProxy(CubaSideMenuClientRpc.class).updateBadge(updatedBadgeValues); } } @Override public void focus() { super.focus(); } @Override public int getTabIndex() { return getState(false).tabIndex; } @Override public void setTabIndex(int tabIndex) { if (getTabIndex() != tabIndex) { getState().tabIndex = tabIndex; } } public boolean isSelectOnClick() { return getState(false).selectOnClick; } public void setSelectOnClick(boolean selectOnClick) { if (getState(false).selectOnClick != selectOnClick) { getState().selectOnClick = selectOnClick; } } public void setShowSingleExpandedMenu(boolean singleExpandedMenu) { if (getState(false).singleExpandedMenu != singleExpandedMenu) { getState().singleExpandedMenu = singleExpandedMenu; } } public boolean isShowSingleExpandedMenu() { return getState(false).singleExpandedMenu; } public MenuItem getSelectedItem() { return selectedItem; } public void setSelectedItem(MenuItem selectedItem) { if (this.selectedItem != selectedItem) { this.selectedItem = selectedItem; markAsDirty(); this.selectedChanged = true; } } public void addMenuItem(MenuItem menuItem) { if (menuItems.contains(menuItem)) { menuItems.remove(menuItem); } menuItems.add(menuItem); addPropertyChangeListenerRecursive(menuItem, itemsPropertyChangeListener); markMenuStructureAsDirty(); } public void addMenuItem(MenuItem menuItem, int index) { if (menuItems.contains(menuItem)) { int existingIndex = menuItems.indexOf(menuItem); if (index > existingIndex) { index--; } menuItems.remove(menuItem); } menuItems.add(index, menuItem); addPropertyChangeListenerRecursive(menuItem, itemsPropertyChangeListener); markMenuStructureAsDirty(); } public void removeMenuItem(MenuItem menuItem) { menuItems.remove(menuItem); removePropertyChangeListenerRecursive(menuItem, itemsPropertyChangeListener); markMenuStructureAsDirty(); } public void removeMenuItem(int index) { MenuItem menuItem = menuItems.remove(index); removePropertyChangeListenerRecursive(menuItem, itemsPropertyChangeListener); markMenuStructureAsDirty(); } public List<MenuItem> getMenuItems() { return Collections.unmodifiableList(menuItems); } public boolean hasMenuItems() { return !menuItems.isEmpty(); } protected JsonArray toJson(List<MenuItem> menuItems) { JsonArray array = Json.createArray(); int i = 0; for (MenuItem menuItem : menuItems) { if (menuItem.isVisible()) { JsonObject item = Json.createObject(); item.put(MENU_ITEM_ID, Json.create(menuItemIdMapper.key(menuItem))); if (menuItem.getCaption() != null) { item.put(MENU_ITEM_CAPTION, Json.create(menuItem.getCaption())); } if (menuItem.getDescription() != null) { item.put(MENU_ITEM_DESCRIPTION, Json.create(menuItem.getDescription())); } if (menuItem.getStyleName() != null) { item.put(MENU_ITEM_STYLE_NAME, Json.create(menuItem.getStyleName())); } item.put(MENU_ITEM_EXPANDED, Json.create(menuItem.isExpanded())); item.put(MENU_ITEM_CAPTION_AS_HTML, Json.create(menuItem.isCaptionAsHtml())); if (menuItem.getBadgeText() != null) { item.put(MENU_ITEM_BADGE_TEXT, Json.create(menuItem.getBadgeText())); } if (menuItem.getCubaId() != null) { item.put(MENU_ITEM_CUBA_ID, Json.create(menuItem.getCubaId())); } if (menuItem.getIcon() != null) { String resourceKey = menuIconsKeyMapper.key(menuItem.getIcon()); menuIconResourceKeys.add(resourceKey); setResource(resourceKey, menuItem.getIcon()); item.put(MENU_ITEM_ICON, Json.create(resourceKey)); } if (!menuItem.getChildren().isEmpty()) { JsonArray childrenJsonArray = toJson(menuItem.getChildren()); item.put(MENU_ITEM_CHILDREN, childrenJsonArray); } array.set(i, item); i++; } } return array; } @SuppressWarnings("unchecked") protected void menuItemPropertyChanged(PropertyChangeEvent event) { if (event.getPropertyName() == null) { return; } if (!structureChanged && MENU_ITEM_BADGE_TEXT.equals(event.getPropertyName())) { badgeUpdates.add((MenuItem) event.getSource()); markAsDirty(); } if (MENU_ITEM_CHILDREN.equals(event.getPropertyName())) { List<MenuItem> oldItems = (List<MenuItem>) event.getOldValue(); List<MenuItem> newItems = (List<MenuItem>) event.getNewValue(); if (oldItems != null) { for (MenuItem oldItem : oldItems) { oldItem.removePropertyChangeListener(itemsPropertyChangeListener); } } if (newItems != null) { for (MenuItem newItem : newItems) { newItem.addPropertyChangeListener(itemsPropertyChangeListener); } } } switch (event.getPropertyName()) { case MENU_ITEM_CAPTION: case MENU_ITEM_DESCRIPTION: case MENU_ITEM_ICON: case MENU_ITEM_CHILDREN: case MENU_ITEM_STYLE_NAME: case MENU_ITEM_VISIBLE: case MENU_ITEM_ENABLED: case MENU_ITEM_CAPTION_AS_HTML: case MENU_ITEM_CUBA_ID: case MENU_ITEM_EXPANDED: markMenuStructureAsDirty(); break; } } protected void markMenuStructureAsDirty() { markAsDirty(); this.structureChanged = true; for (String menuIconsKey : menuIconResourceKeys) { Resource resource = getResource(menuIconsKey); menuIconsKeyMapper.remove(resource); setResource(menuIconsKey, null); } menuItemIdMapper.removeAll(); menuIconsKeyMapper.removeAll(); // all badges will be sent to client with items badgeUpdates.clear(); } protected void addPropertyChangeListenerRecursive(MenuItem menuItem, PropertyChangeListener itemsPropertyChangeListener) { menuItem.addPropertyChangeListener(itemsPropertyChangeListener); for (MenuItem item : menuItem.getChildren()) { addPropertyChangeListenerRecursive(item, itemsPropertyChangeListener); } } protected void removePropertyChangeListenerRecursive(MenuItem menuItem, PropertyChangeListener itemsPropertyChangeListener) { menuItem.removePropertyChangeListener(itemsPropertyChangeListener); for (MenuItem item : menuItem.getChildren()) { removePropertyChangeListenerRecursive(item, itemsPropertyChangeListener); } } public static class MenuItemTriggeredEvent extends EventObject { private MenuItem menuItem; public MenuItemTriggeredEvent(CubaSideMenu source, MenuItem menuItem) { super(source); this.menuItem = menuItem; } public MenuItem getMenuItem() { return menuItem; } @Override public CubaSideMenu getSource() { return (CubaSideMenu) super.getSource(); } } public static class MenuItem { protected String caption; protected String description; protected Resource icon; protected boolean captionAsHtml = false; protected boolean visible = true; protected boolean expanded = false; protected String badgeText; protected String cubaId; protected List<String> styles; protected Consumer<MenuItemTriggeredEvent> command; protected List<MenuItem> children = new ArrayList<>(4); protected MenuItem parent; protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public MenuItem() { } public MenuItem(String caption) { this.caption = caption; } public MenuItem(String caption, Resource icon) { this.caption = caption; this.icon = icon; } public MenuItem(String caption, @Nullable Resource icon, @Nullable Consumer<MenuItemTriggeredEvent> command) { this.caption = caption; this.icon = icon; this.command = command; } public String getCaption() { return caption; } public void setCaption(String caption) { if (!Objects.equals(this.caption, caption)) { String oldCaption = this.caption; this.caption = caption; propertyChangeSupport.firePropertyChange(MENU_ITEM_CAPTION, oldCaption, caption); } } public String getDescription() { return description; } public void setDescription(String description) { if (!Objects.equals(this.description, description)) { String oldDescription = this.description; this.description = description; propertyChangeSupport.firePropertyChange(MENU_ITEM_DESCRIPTION, oldDescription, description); } } public Resource getIcon() { return icon; } public void setIcon(Resource icon) { if (this.icon != icon) { Resource oldIcon = this.icon; this.icon = icon; propertyChangeSupport.firePropertyChange(MENU_ITEM_ICON, oldIcon, icon); } } public boolean isCaptionAsHtml() { return captionAsHtml; } public void setCaptionAsHtml(boolean captionAsHtml) { if (this.captionAsHtml != captionAsHtml) { boolean oldCaptionAsHtml = this.captionAsHtml; this.captionAsHtml = captionAsHtml; propertyChangeSupport.firePropertyChange(MENU_ITEM_CAPTION_AS_HTML, oldCaptionAsHtml, captionAsHtml); } } public boolean isVisible() { return visible; } public void setVisible(boolean visible) { if (this.visible != visible) { boolean oldVisible = this.visible; this.visible = visible; propertyChangeSupport.firePropertyChange(MENU_ITEM_VISIBLE, oldVisible, visible); } } public boolean isExpanded() { return expanded; } public void setExpanded(boolean expanded) { if (this.expanded != expanded) { boolean oldExpanded = this.expanded; this.expanded = expanded; propertyChangeSupport.firePropertyChange(MENU_ITEM_EXPANDED, oldExpanded, expanded); } } public String getStyleName() { String s = ""; if (this.styles != null) { s = StringUtils.join(this.styles, " "); } return s; } public void setStyleName(String style) { String oldStyleName = this.getStyleName(); if (style == null || "".equals(style)) { this.styles = null; String newStyleName = getStyleName(); if (!Objects.equals(oldStyleName, newStyleName)) { propertyChangeSupport.firePropertyChange(MENU_ITEM_STYLE_NAME, oldStyleName, newStyleName); } return; } if (this.styles == null) { this.styles = new ArrayList<>(); } List<String> styles = this.styles; styles.clear(); StringTokenizer tokenizer = new StringTokenizer(style, " "); while (tokenizer.hasMoreTokens()) { styles.add(tokenizer.nextToken()); } String newStyleName = getStyleName(); if (!Objects.equals(oldStyleName, newStyleName)) { propertyChangeSupport.firePropertyChange(MENU_ITEM_STYLE_NAME, oldStyleName, newStyleName); } } public String getCubaId() { return cubaId; } public void setCubaId(String cubaId) { if (!Objects.equals(this.cubaId, cubaId)) { String oldCubaId = this.cubaId; this.cubaId = cubaId; propertyChangeSupport.firePropertyChange(MENU_ITEM_CUBA_ID, oldCubaId, cubaId); } } public String getBadgeText() { return badgeText; } public void setBadgeText(String badgeText) { if (!Objects.equals(this.badgeText, badgeText)) { String oldBadgeText = this.badgeText; this.badgeText = badgeText; propertyChangeSupport.firePropertyChange(MENU_ITEM_BADGE_TEXT, oldBadgeText, badgeText); } } public Consumer<MenuItemTriggeredEvent> getCommand() { return command; } public void setCommand(Consumer<MenuItemTriggeredEvent> command) { if (this.command != command) { Consumer<MenuItemTriggeredEvent> oldCommand = this.command; this.command = command; propertyChangeSupport.firePropertyChange(MENU_ITEM_COMMAND, oldCommand, command); } } public void addChildItem(MenuItem menuItem) { if (children.contains(menuItem)) { children.remove(menuItem); } List<MenuItem> childrenOld = new ArrayList<>(children); children.add(menuItem); menuItem.setParent(this); propertyChangeSupport.firePropertyChange(MENU_ITEM_CHILDREN, childrenOld, Collections.unmodifiableList(children)); } public void addChildItem(MenuItem menuItem, int index) { if (children.contains(menuItem)) { int existingIndex = children.indexOf(menuItem); if (index > existingIndex) { index--; } children.remove(menuItem); } List<MenuItem> childrenOld = new ArrayList<>(children); children.add(index, menuItem); menuItem.setParent(this); propertyChangeSupport.firePropertyChange(MENU_ITEM_CHILDREN, childrenOld, Collections.unmodifiableList(children)); } public void removeChildItem(MenuItem menuItem) { List<MenuItem> childrenOld = new ArrayList<>(children); children.remove(menuItem); menuItem.setParent(null); propertyChangeSupport.firePropertyChange(MENU_ITEM_CHILDREN, childrenOld, Collections.unmodifiableList(children)); } public void removeChildItem(int index) { List<MenuItem> childrenOld = new ArrayList<>(children); MenuItem removed = children.remove(index); removed.setParent(null); propertyChangeSupport.firePropertyChange(MENU_ITEM_CHILDREN, childrenOld, Collections.unmodifiableList(children)); } public List<MenuItem> getChildren() { return Collections.unmodifiableList(children); } public boolean hasChildren() { return !children.isEmpty(); } public MenuItem getParent() { return parent; } public void setParent(MenuItem parent) { this.parent = parent; } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void removeStyleName(String style) { String oldStyleName = this.getStyleName(); if (this.styles != null) { StringTokenizer tokenizer = new StringTokenizer(style, " "); while (tokenizer.hasMoreTokens()) { this.styles.remove(tokenizer.nextToken()); } } String newStyleName = getStyleName(); if (!Objects.equals(oldStyleName, newStyleName)) { propertyChangeSupport.firePropertyChange(MENU_ITEM_STYLE_NAME, oldStyleName, newStyleName); } } public void addStyleName(String style) { if (style == null || "".equals(style)) { return; } if (style.contains(" ")) { // Split space separated style names and add them one by one. StringTokenizer tokenizer = new StringTokenizer(style, " "); while (tokenizer.hasMoreTokens()) { addStyleName(tokenizer.nextToken()); } return; } String oldStyleName = this.getStyleName(); if (this.styles == null) { this.styles = new ArrayList<>(); } List<String> styles = this.styles; if (!styles.contains(style)) { styles.add(style); } String newStyleName = getStyleName(); if (!Objects.equals(oldStyleName, newStyleName)) { propertyChangeSupport.firePropertyChange(MENU_ITEM_STYLE_NAME, oldStyleName, newStyleName); } } } }