/*********************************************************************** * This file is part of iDempiere ERP Open Source * * http://www.idempiere.org * * * * Copyright (C) Contributors * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * * Contributors: * * - hengsin * **********************************************************************/ package org.adempiere.webui.adwindow; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import org.adempiere.base.IServiceHolder; import org.adempiere.webui.ClientInfo; import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.action.Actions; import org.adempiere.webui.action.IAction; import org.adempiere.webui.component.ADTabListModel.ADTabLabel; import org.adempiere.webui.component.Label; import org.adempiere.webui.component.Panel; import org.adempiere.webui.component.Tab; import org.adempiere.webui.component.Tabbox; import org.adempiere.webui.component.ToolBar; import org.adempiere.webui.component.ToolBarButton; import org.adempiere.webui.component.Window; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.theme.ThemeManager; import org.adempiere.webui.util.ZKUpdateUtil; import org.adempiere.webui.window.CustomizeGridViewDialog; import org.adempiere.webui.window.WRecordInfo; import org.compiere.model.DataStatusEvent; import org.compiere.model.GridTab; import org.compiere.model.MToolBarButton; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; import org.zkoss.image.AImage; import org.zkoss.zhtml.Text; import org.zkoss.zk.au.out.AuScript; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Execution; import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.HtmlBasedComponent; import org.zkoss.zk.ui.IdSpace; import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.KeyEvent; import org.zkoss.zk.ui.sys.ExecutionCtrl; import org.zkoss.zk.ui.util.Clients; import org.zkoss.zul.A; import org.zkoss.zul.Div; import org.zkoss.zul.Hbox; import org.zkoss.zul.Hlayout; import org.zkoss.zul.LayoutRegion; import org.zkoss.zul.Popup; import org.zkoss.zul.Separator; import org.zkoss.zul.Space; import org.zkoss.zul.Tabpanels; import org.zkoss.zul.Tabs; import org.zkoss.zul.Toolbar; /** * Detail panel that display the child tabs of a parent {@link ADTabpanel} tab.
* Implemented as a panel with {@link Tabbox}. * * @author hengsin */ public class DetailPane extends Panel implements EventListener, IdSpace { /** * generated serial id */ private static final long serialVersionUID = 3764215603459946930L; public static final String BTN_PROCESS_ID = "BtnProcess"; public static final String BTN_DELETE_ID = "BtnDelete"; public static final String BTN_EDIT_ID = "BtnEdit"; public static final String BTN_NEW_ID = "BtnNew"; public static final String BTN_SAVE_ID = "BtnSave"; public static final String BTN_QUICK_FORM_ID = "BtnQuickForm"; public static final String BTN_CUSTOMIZE_ID = "BtnCustomize"; private static final String BTN_TOGGLE_ID = "BtnToggle"; /** Boolean execution attribute to indicate tabbox is handling ON_SELECT event **/ private static final String TABBOX_ONSELECT_ATTRIBUTE = "detailpane.tabbox.onselect"; /** event after handling of ON_SElECT event of a detail tab */ private static final String ON_POST_SELECT_TAB_EVENT = "onPostSelectTab"; /** Attribute use by {@link #messageContainers} to hold status text **/ private static final String STATUS_TEXT_ATTRIBUTE = "status.text"; /** Attribute use by {@link #messageContainers} to hold error text **/ private static final String STATUS_ERROR_ATTRIBUTE = "status.error"; private static final String CUSTOMIZE_IMAGE = "images/Customize16.png"; private static final String DELETE_IMAGE = "images/Delete16.png"; private static final String EDIT_IMAGE = "images/EditRecord16.png"; private static final String NEW_IMAGE = "images/New16.png"; private static final String PROCESS_IMAGE = "images/Process16.png"; private static final String SAVE_IMAGE = "images/Save16.png"; private static final String QUICK_FORM_IMAGE = "images/QuickForm16.png"; private static final String TOGGLE_IMAGE = "images/Multi16.png"; /** tabbox for AD_Tabs **/ private Tabbox tabbox; /** Registered event listener for DetailPane events **/ private EventListener eventListener; /** AD_Tab_ID:Hbox. Message (status, error) container for each tab. **/ private Map messageContainers = new HashMap(); /** content for message popup **/ private Div msgPopupCnt; /** message popup window **/ private Window msgPopup; /** last selected tab index **/ private int prevSelectedIndex = 0; /** * On activate event for detail tab.
* Use to activate detail tab or notify detail tab after header tab change. */ public static final String ON_ACTIVATE_DETAIL_EVENT = "onActivateDetail"; /** on delete event for selected tab **/ public static final String ON_DELETE_EVENT = "onDelete"; /** on new event for selected tab **/ public static final String ON_NEW_EVENT = "onNew"; /** event to edit current row of selected tab **/ public static final String ON_EDIT_EVENT = "onEdit"; /** on save event for selected tab **/ public static final String ON_SAVE_EVENT = "onSave"; /** on quick form event for selected tab **/ public static final String ON_QUICK_FORM_EVENT = "onQuickForm"; /** * Record navigation event for selected tab.
* Event data is the navigation action (previous, next, first and last). */ public static final String ON_RECORD_NAVIGATE_EVENT = "onRecordNavigate"; /** * default constructor */ public DetailPane() { tabbox = new Tabbox(); tabbox.setParent(this); tabbox.addEventListener(Events.ON_SELECT, new EventListener() { @Override public void onEvent(Event event) throws Exception { fireActivateDetailEvent(); Events.postEvent(new Event(ON_POST_SELECT_TAB_EVENT, DetailPane.this)); Executions.getCurrent().setAttribute(TABBOX_ONSELECT_ATTRIBUTE, Boolean.TRUE); } }); tabbox.setSclass("adwindow-detailpane-tabbox"); tabbox.setTabscroll(true); ZKUpdateUtil.setWidth(tabbox, "100%"); createPopup(); this.setSclass("adwindow-detailpane"); addEventListener(LayoutUtils.ON_REDRAW_EVENT, this); setId("detailPane"); } /** * Get selected tab index * @return selected tab index */ public int getSelectedIndex() { return tabbox.getSelectedIndex(); } /** * Set selected tab index * @param curTabIndex */ public void setSelectedIndex(int curTabIndex) { tabbox.setSelectedIndex(curTabIndex); prevSelectedIndex = curTabIndex; } /** * Get number of tabs * @return number of tabs */ public int getTabcount() { int count = 0; Tabs tabs = tabbox.getTabs(); if (tabs != null) count = tabs.getChildren().size(); return count; } /** * Undo last tab selection */ public void undoLastTabSelection() { tabbox.setSelectedIndex(prevSelectedIndex); } /** * Redraw tabbox */ public void refresh() { tabbox.invalidate(); } /** * Replace or add IADTabpanel to tabbox. * @param index * @param tabPanel * @param tabLabel */ public void setADTabpanel(int index, IADTabpanel tabPanel, ADTabLabel tabLabel) { if (index < getTabcount()) { tabbox.getTabpanel(index).appendChild(tabPanel); } else { addADTabpanel(tabPanel, tabLabel); } } /** * Replace or add IADTabpanel to tabbox. * @param index * @param tabPanel * @param tabLabel * @param enabled */ public void setADTabpanel(int index, IADTabpanel tabPanel, ADTabLabel tabLabel, boolean enabled) { if (index < getTabcount()) { tabbox.getTabpanel(index).appendChild(tabPanel); } else { addADTabpanel(tabPanel, tabLabel, enabled); } } /** * Add IADTabpanel to tabbox * @param tabPanel * @param tabLabel */ public void addADTabpanel(IADTabpanel tabPanel, ADTabLabel tabLabel) { addADTabpanel(tabPanel, tabLabel, true); } /** * Add IADTabpanel to tabbox * @param tabPanel * @param tabLabel * @param enabled */ public void addADTabpanel(IADTabpanel tabPanel, ADTabLabel tabLabel, boolean enabled) { Tabs tabs = tabbox.getTabs(); if (tabs == null) { tabs = new Tabs(); tabbox.appendChild(tabs); } Tab tab = new Tab(); tabs.appendChild(tab); tab.setLabel(tabLabel.label); if (!enabled) { tab.setDisabled(true); tab.setSclass("adwindow-detailpane-sub-tab"); } tab.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { //click on tab title trigger edit of current row Tab tab = (Tab) event.getTarget(); if (!tab.isSelected()) return; if (Executions.getCurrent().getAttribute(TABBOX_ONSELECT_ATTRIBUTE) != null) return; org.zkoss.zul.Tabpanel zkTabpanel = tab.getLinkedPanel(); IADTabpanel adtab = null; for(Component c : zkTabpanel.getChildren()) { if (c instanceof IADTabpanel) { adtab = (IADTabpanel) c; break; } } if (adtab != null && adtab.isDetailPaneMode()) { onEdit(adtab.getGridTab().isSingleRow()); } } }); Tabpanels tabpanels = tabbox.getTabpanels(); if (tabpanels == null) { tabpanels = new Tabpanels(); ZKUpdateUtil.setWidth(tabpanels, "100%"); tabbox.appendChild(tabpanels); } Tabpanel tp = new Tabpanel(); tabpanels.appendChild(tp); //setup toolbar ToolBar toolbar = tp.getToolbar(); HashMap buttons = new HashMap(); ToolBarButton button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-New"); else button.setImage(ThemeManager.getThemeResource(NEW_IMAGE)); button.setId(BTN_NEW_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) onNew(); } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "SaveCreate")) + " Shift+Alt+N"); buttons.put(BTN_NEW_ID.substring(3, BTN_NEW_ID.length()), button); button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Edit"); else button.setImage(ThemeManager.getThemeResource(EDIT_IMAGE)); button.setId(BTN_EDIT_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) onEdit(true); } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "EditRecord")) + " Shift+Alt+E"); buttons.put(BTN_EDIT_ID.substring(3, BTN_EDIT_ID.length()), button); button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Delete"); else button.setImage(ThemeManager.getThemeResource(DELETE_IMAGE)); button.setId(BTN_DELETE_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) { Event openEvent = new Event(ON_DELETE_EVENT, DetailPane.this); eventListener.onEvent(openEvent); } } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Delete")) + " Shift+Alt+D"); buttons.put(BTN_DELETE_ID.substring(3, BTN_DELETE_ID.length()), button); button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Save"); else button.setImage(ThemeManager.getThemeResource(SAVE_IMAGE)); button.setId(BTN_SAVE_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) { Event openEvent = new Event(ON_SAVE_EVENT, DetailPane.this); eventListener.onEvent(openEvent); } } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Save")) + " Shift+Alt+S"); buttons.put(BTN_SAVE_ID.substring(3, BTN_SAVE_ID.length()), button); if (!tabPanel.getGridTab().isSortTab()) { button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Process"); else button.setImage(ThemeManager.getThemeResource(PROCESS_IMAGE)); button.setId(BTN_PROCESS_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) onProcess(event.getTarget()); } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Process")) + " Shift+Alt+O"); buttons.put(BTN_PROCESS_ID.substring(3, BTN_PROCESS_ID.length()), button); } // ADD Quick Form Button button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-QuickForm"); else button.setImage(ThemeManager.getThemeResource(QUICK_FORM_IMAGE)); button.setId(BTN_QUICK_FORM_ID); button.addEventListener(Events.ON_CLICK, new EventListener() { @Override public void onEvent(Event event) throws Exception { if (event.getTarget().isVisible()) { Event openEvent = new Event(ON_QUICK_FORM_EVENT, DetailPane.this); eventListener.onEvent(openEvent); } } }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "QuickForm")) + " Shift+Alt+F"); buttons.put(BTN_QUICK_FORM_ID.substring(3, BTN_QUICK_FORM_ID.length()), button); // ADD Customize grid button button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Customize"); else button.setImage(ThemeManager.getThemeResource(CUSTOMIZE_IMAGE)); button.setId(BTN_CUSTOMIZE_ID); button.addEventListener(Events.ON_CLICK, e -> { if(e.getTarget().isVisible()) onCustomize(e); }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Customize"))); buttons.put(BTN_CUSTOMIZE_ID.substring(3, BTN_CUSTOMIZE_ID.length()), button); // ADD toggle grid button button = new ToolBarButton(); if (ThemeManager.isUseFontIconForImage()) button.setIconSclass("z-icon-Multi"); else button.setImage(ThemeManager.getThemeResource(TOGGLE_IMAGE)); button.setId(BTN_TOGGLE_ID); button.addEventListener(Events.ON_CLICK, e -> { if(e.getTarget().isVisible()) onToggle(e); }); button.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Toggle")) + " Shift+Alt+T"); buttons.put(BTN_TOGGLE_ID.substring(3, BTN_TOGGLE_ID.length()), button); //Detail toolbar button configure at AD_ToolBarButton MToolBarButton[] officialButtons = MToolBarButton.getToolbarButtons("D", null); for (MToolBarButton toolbarButton : officialButtons) { if ( !toolbarButton.isActive() ) { buttons.remove(toolbarButton.getComponentName()); } else { if ( toolbarButton.isCustomization() ) { String actionId = toolbarButton.getActionClassName(); IServiceHolder serviceHolder = Actions.getAction(actionId); if ( serviceHolder != null && serviceHolder.getService() != null ) { String labelKey = actionId + ".label"; String tooltipKey = actionId + ".tooltip"; String label = Msg.getMsg(Env.getCtx(), labelKey, true); String tooltiptext = Msg.getMsg(Env.getCtx(), labelKey, false); if (Util.isEmpty(tooltiptext, true)) tooltiptext = Msg.getMsg(Env.getCtx(), tooltipKey, true); if ( labelKey.equals(label) ) { label = toolbarButton.getName(); } if ( tooltipKey.equals(tooltiptext) || labelKey.equals(tooltiptext)) { tooltiptext = label; } ToolBarButton btn = new ToolBarButton(); btn.setName("Btn"+toolbarButton.getComponentName()); btn.setId("Btn"+toolbarButton.getComponentName()); btn.setTooltiptext(tooltiptext); btn.setDisabled(false); btn.setIconSclass(null); if (ThemeManager.isUseFontIconForImage()) { String iconSclass = Actions.getActionIconSclass(actionId); if (!Util.isEmpty(iconSclass, true)) { btn.setIconSclass(iconSclass); LayoutUtils.addSclass("font-icon-toolbar-button", btn); } } //not using font icon, fallback to image or label if (Util.isEmpty(btn.getIconSclass(), true)) { AImage aImage = Actions.getActionImage(actionId); if ( aImage != null ) { btn.setImageContent(aImage); } else { btn.setLabel(label); } } ToolbarCustomButton toolbarCustomBtn = new ToolbarCustomButton(toolbarButton, btn, actionId, tabPanel.getGridTab().getWindowNo(), tabPanel.getGridTab().getTabNo()); tp.toolbarCustomButtons.put(btn, toolbarCustomBtn); toolbar.appendChild(btn); } } else { if (buttons.get(toolbarButton.getComponentName()) != null) { toolbar.appendChild(buttons.get(toolbarButton.getComponentName())); if (toolbarButton.isAddSeparator()) { toolbar.appendChild(new Separator("vertical")); } } } } } //container for status and error text Hbox messageContainer = new Hbox(); messageContainer.setPack("end"); messageContainer.setAlign("center"); messageContainer.setSclass("adwindow-detailpane-message"); messageContainer.setId("messages"); if (ClientInfo.minWidth(ClientInfo.SMALL_WIDTH)) toolbar.appendChild(new Space()); toolbar.appendChild(messageContainer); toolbar.setSclass("adwindow-detailpane-toolbar"); ZKUpdateUtil.setVflex(toolbar, "0"); messageContainers.put(tabLabel.AD_Tab_ID, messageContainer); tabPanel.setAttribute("AD_Tab_ID", tabLabel.AD_Tab_ID); if (ClientInfo.isMobile() && ClientInfo.maxWidth(ClientInfo.SMALL_WIDTH)) { tp.createOverflowButton(); } RecordToolbar recordToolbar = new RecordToolbar(tabPanel.getGridTab()); recordToolbar.addEventListener(ON_RECORD_NAVIGATE_EVENT, eventListener); tp.setRecordToolbar(recordToolbar); tp.setADTabpanel(tabPanel); if (tabPanel instanceof ADTabpanel adTabpanel) { tp.addEventListener(ADTabpanel.ON_SWIPE_RIGHT, e -> { RecordToolbar rtb = tp.getRecordToolbar(); if (rtb != null && rtb.isVisible()) { if (!rtb.btnPrevious.isDisabled()) { LayoutUtils.addSclass(ADTabpanel.SLIDE_RIGHT_OUT_CSS, adTabpanel.form); Events.sendEvent(Events.ON_CLICK, rtb.btnPrevious, null); } } }); tp.addEventListener(ADTabpanel.ON_SWIPE_LEFT, e -> { RecordToolbar rtb = tp.getRecordToolbar(); if (rtb != null && rtb.isVisible()) { if (!rtb.btnNext.isDisabled()) { LayoutUtils.addSclass(ADTabpanel.SLIDE_LEFT_OUT_CSS, adTabpanel.form); Events.sendEvent(Events.ON_CLICK, rtb.btnNext, null); } } }); adTabpanel.setupFormSwipeListener(tp); } if (tabPanel.getGridView() != null) { tabPanel.addEventListener(ADTabpanel.ON_DYNAMIC_DISPLAY_EVENT, this); tabPanel.getGridView().addEventListener(ON_EDIT_EVENT, new EventListener() { @Override public void onEvent(Event event) throws Exception { GridView gridView = (GridView) event.getTarget(); if (gridView.isDetailPaneMode()) onEdit(true); } }); } } /** * Toggle between grid and form view * @param e */ protected void onToggle(Event e) { var adTabPanel = getSelectedADTabpanel(); if(!(adTabPanel instanceof ADSortTab)) { adTabPanel.switchRowPresentation(); ToolBarButton btnCustomize = getSelectedPanel().getToolbarButton(BTN_CUSTOMIZE_ID); if (btnCustomize != null) btnCustomize.setDisabled(!adTabPanel.isGridView()); Tabpanel tabPanel = (Tabpanel) tabbox.getSelectedTabpanel(); tabPanel.setToggleToFormView(!adTabPanel.isGridView()); tabPanel.afterToggle(); if (adTabPanel != null && adTabPanel instanceof HtmlBasedComponent) { ((HtmlBasedComponent)adTabPanel).focus(); } } } /** * Open customize grid view dialog. * @param e */ protected void onCustomize(Event e) { if (getSelectedADTabpanel() instanceof ADTabpanel) { ADTabpanel tabPanel = (ADTabpanel) getSelectedADTabpanel(); CustomizeGridViewDialog.onCustomize(tabPanel, b -> { ADWindow adwindow = ADWindow.findADWindow(DetailPane.this); if (adwindow != null) adwindow.getADWindowContent().focusToLastFocusEditor(); }); } } /** * Open process list popup * @param button */ protected void onProcess(Component button) { ProcessButtonPopup popup = new ProcessButtonPopup(); IADTabpanel adtab = getSelectedADTabpanel(); if (adtab.getToolbarButtons() != null && adtab.getToolbarButtons().size() > 0) popup.render(adtab.getToolbarButtons()); if (popup.getChildren().size() > 0) { popup.setPage(button.getPage()); popup.open(button, "after_start"); } } /** * Set event listener for DetailPane events * @param listener */ public void setEventListener(EventListener listener) { eventListener = listener; } /** * Remove all tabs and tabpanels */ public void reset() { if (tabbox.getTabs() != null) { tabbox.getTabs().getChildren().clear(); } if (tabbox.getTabpanels() != null) { tabbox.getTabpanels().getChildren().clear(); } } /** * Get IADTabpanel at index * @param index * @return IADTabpanel at index */ public IADTabpanel getADTabpanel(int index) { if (index < 0 || index >= tabbox.getTabpanels().getChildren().size()) return null; org.zkoss.zul.Tabpanel tabPanel = tabbox.getTabpanel(index); for(Component c : tabPanel.getChildren()) { if (c instanceof IADTabpanel) return (IADTabpanel)c; } return null; } /** * Get IADTabpanel for selected tab * @return selected IADTabpanel */ public IADTabpanel getSelectedADTabpanel() { org.zkoss.zul.Tabpanel selectedPanel = tabbox.getSelectedPanel(); if (selectedPanel != null) { for(Component c : selectedPanel.getChildren()) { if (c instanceof IADTabpanel) return (IADTabpanel)c; } } return null; } /** * Get tab panel of selected tab * @return selected {@link Tabpanel} */ public Tabpanel getSelectedPanel() { return (Tabpanel) tabbox.getSelectedPanel(); } /** * Set status and error text for selected tab. * @param status * @param error */ public void setStatusMessage(String status, boolean error) { IADTabpanel tabPanel = getSelectedADTabpanel(); if (tabPanel == null) return; Hbox messageContainer = messageContainers.get(tabPanel.getAttribute("AD_Tab_ID")); Execution execution = Executions.getCurrent(); if (execution != null) { String key = this.getClass().getName()+"."+messageContainer.getUuid(); Object o = execution.getAttribute(key); if (o != null) { if (status == null || status.trim().length() == 0) return; } else { execution.setAttribute(key, Boolean.TRUE); } } messageContainer.getChildren().clear(); //store in attribute for retrieval in ON_CLICK event messageContainer.setAttribute(STATUS_ERROR_ATTRIBUTE, error); messageContainer.setAttribute(STATUS_TEXT_ATTRIBUTE, status); messageContainer.setSclass(error ? "docstatus-error" : "docstatus-normal"); if (status == null || status.trim().length() == 0) return; String labelText = buildLabelText(status); if (error) { Component ref = isCollapsed(this) ? findTabpanel(this) : findTabpanel(messageContainer); Clients.showNotification(buildNotificationText(status), "error", ref, "top_left", 3500, true); } Label label = new Label(labelText); messageContainer.appendChild(label); if (labelText.length() != status.length()) { label.addEventListener(Events.ON_CLICK, this); label.setStyle("cursor: pointer"); label = new Label(" ..."); label.setStyle("cursor: pointer"); messageContainer.appendChild(label); label.addEventListener(Events.ON_CLICK, this); } else if (ClientInfo.maxWidth(ClientInfo.SMALL_WIDTH)) { label.addEventListener(Events.ON_CLICK, this); label.setStyle("cursor: pointer"); } messageContainer.appendChild(new Space()); if (!tabPanel.isGridView()) { Tabpanel tp = (Tabpanel) tabbox.getSelectedTabpanel(); if (tp.getRecordToolbar() != null) { tp.getRecordToolbar().dynamicDisplay(); } } } /** * Is parent of detailPane in collapsed state * @param detailPane * @return true if parent of detailPane is in collapsed state */ private boolean isCollapsed(DetailPane detailPane) { Component parent = detailPane.getParent(); while (parent != null) { if (parent instanceof LayoutRegion lr) return !lr.isOpen(); parent = parent.getParent(); } return false; } /** * Shorten status text to a more presentable length. * @param statusText * @return shorten status text */ private String buildLabelText(String statusText) { if (statusText == null) return ""; if (statusText.length() <= 80) return statusText; int index = statusText.indexOf(" - java.lang.Exception"); if (index > 0) return statusText.substring(0, index); return statusText.substring(0, 80); } /** * Shorten notification text to a more presentable length. * @param statusText * @return shorten notification text */ private String buildNotificationText(String statusText) { if (statusText == null) return ""; if (statusText.length() <= 140) return statusText; int index = statusText.indexOf(" - java.lang.Exception"); if (index > 0) return statusText.substring(0, index); return statusText.substring(0, 136) + " ..."; } @Override public void onEvent(Event event) throws Exception { if (event.getName().equals(Events.ON_CLICK)) { Component messageContainer = event.getTarget().getParent(); Boolean error = (Boolean) messageContainer.getAttribute(STATUS_ERROR_ATTRIBUTE); String status = (String) messageContainer.getAttribute(STATUS_TEXT_ATTRIBUTE); showPopup(error, status); } else if (event.getName().equals(ADTabpanel.ON_DYNAMIC_DISPLAY_EVENT)) { if (LayoutUtils.isReallyVisible(this)) updateProcessToolbar(); } else if (event.getName().equals(LayoutUtils.ON_REDRAW_EVENT)) { ExecutionCtrl ctrl = (ExecutionCtrl) Executions.getCurrent(); Event evt = ctrl.getNextEvent(); if (evt != null) { Events.sendEvent(evt); Events.postEvent(new Event(LayoutUtils.ON_REDRAW_EVENT, this)); return; } LayoutUtils.redraw(this); } else if (event.getName().equals(Events.ON_CTRL_KEY)) { KeyEvent keyEvent = (KeyEvent) event; if (LayoutUtils.isReallyVisible(this)) this.onCtrlKeyEvent(keyEvent); } } /** * Create popup content for message popup window * @param status */ protected void createPopupContent(String status) { Text t = new Text(status); msgPopupCnt.getChildren().clear(); msgPopupCnt.appendChild(t); } /** * Show notification popup using Clients.showNotification * @param error * @param msg */ private void showPopup(boolean error, String msg) { Clients.showNotification(buildNotificationText(msg), "error", null, "at_pointer", 3500, true); } /** * Create message popup window */ private void createPopup() { msgPopupCnt = new Div(); ZKUpdateUtil.setVflex(msgPopupCnt, "1"); msgPopup = new Window(); msgPopup.setVisible(false); msgPopup.setBorder(true); msgPopup.setClosable(true); msgPopup.setSizable(true); msgPopup.setContentStyle("overflow: auto"); ZKUpdateUtil.setWidth(msgPopup, "500px"); msgPopup.appendChild(msgPopupCnt); msgPopup.setPage(SessionManager.getAppDesktop().getComponent().getPage()); msgPopup.setShadow(true); } @Override public void onPageDetached(Page page) { super.onPageDetached(page); if (msgPopup != null) msgPopup.detach(); try { SessionManager.getSessionApplication().getKeylistener().removeEventListener(Events.ON_CTRL_KEY, this); } catch (Exception e){} } @Override public void onPageAttached(Page newpage, Page oldpage) { super.onPageAttached(newpage, oldpage); if (newpage != null) { SessionManager.getSessionApplication().getKeylistener().addEventListener(Events.ON_CTRL_KEY, this); } } /* (non-Javadoc) * @see org.zkoss.zk.ui.HtmlBasedComponent#setVflex(java.lang.String) */ @Override public void setVflex(String flex) { if (getHeight() != null) setHeight(null); super.setVflex(flex); ZKUpdateUtil.setVflex(tabbox, flex); } /** * Update toolbar button state * @param changed * @param readOnly */ public void updateToolbar(boolean changed, boolean readOnly) { int index = getSelectedIndex(); if (index < 0 || index >= getTabcount()) return; Tabpanel tabpanel = (Tabpanel) tabbox.getTabpanel(index); Toolbar toolbar = tabpanel.getToolbar(); IADTabpanel adtab = getADTabpanel(index); if (adtab == null) return; if (adtab.getGridTab().isSortTab() || adtab.getGridTab().isReadOnly()) readOnly = true; boolean insertRecord = !readOnly; boolean deleteRecord = !readOnly; if (insertRecord) { insertRecord = adtab.getGridTab().isInsertRecord(); } boolean enableNew = insertRecord && !adtab.getGridTab().isSortTab(); if (deleteRecord) { deleteRecord = adtab.getGridTab().isDeleteRecord(); } boolean enableDelete = !changed && deleteRecord && !adtab.getGridTab().isSortTab() && !adtab.getGridTab().isProcessed(); boolean enableCustomize = !adtab.getGridTab().isSortTab() && adtab.isGridView(); ADWindow adwindow = ADWindow.findADWindow(this); if (adwindow == null) return; List tabRestrictList = adwindow.getTabToolbarRestrictList(adtab.getGridTab().getAD_Tab_ID()); List windowRestrictList = adwindow.getWindowToolbarRestrictList(); for(Component c : toolbar.getChildren()) { if (c instanceof ToolBarButton) { ToolBarButton btn = (ToolBarButton) c; if (BTN_NEW_ID.equals(btn.getId())) { btn.setDisabled(!enableNew); } else if (BTN_DELETE_ID.equals(btn.getId())) { btn.setDisabled(!enableDelete); } else if (BTN_EDIT_ID.equals(btn.getId())) { btn.setDisabled(false); } else if (BTN_SAVE_ID.equals(btn.getId())) { btn.setDisabled(!adtab.needSave(true, false)); } else if (BTN_CUSTOMIZE_ID.equals(btn.getId())) { btn.setDisabled(!enableCustomize); } else if (BTN_QUICK_FORM_ID.equals(btn.getId())) { btn.setDisabled(!(adtab.isEnableQuickFormButton() && !adtab.getGridTab().isReadOnly())); } else if (BTN_TOGGLE_ID.equals(btn.getId())) { btn.setDisabled(adtab.getGridTab().isSortTab()); } if (windowRestrictList.contains(btn.getId())) { btn.setVisible(false); } else if (tabRestrictList.contains(btn.getId())) { btn.setVisible(false); } else if (tabpanel.toolbarCustomButtons.containsKey(btn)) { ToolbarCustomButton customButton = tabpanel.toolbarCustomButtons.get(btn); customButton.dynamicDisplay(); }else { btn.setVisible(true); } } } //Not use by ADTabpanel, for custom IADTabpanel implementation. adtab.updateDetailToolbar(toolbar); } /** * Update state of Process toolbar button. */ private void updateProcessToolbar() { int index = getSelectedIndex(); if (index < 0 || index >= getTabcount()) return; Tabpanel tabpanel = (Tabpanel) tabbox.getTabpanel(index); Toolbar toolbar = tabpanel.getToolbar(); IADTabpanel adtab = getADTabpanel(index); if (adtab == null) return; for(Component c : toolbar.getChildren()) { if (c instanceof ToolBarButton) { ToolBarButton btn = (ToolBarButton) c; if (BTN_PROCESS_ID.equals(btn.getId())) { if (adtab.getGridTab().isSortTab()) { btn.setDisabled(true); } else { boolean isToolbarDisabled = (adtab.getToolbarButtons() == null || adtab.getToolbarButtons().isEmpty()); btn.setDisabled(isToolbarDisabled); } break; } } } } /** * Edit current record of selected tab.
* This event will make the selected tab becomes the new header tab, i.e become the selected tab of {@link CompositeADTabbox}. * @param formView true to force form view. * @throws Exception */ public void onEdit(boolean formView) throws Exception { Event openEvent = new Event(ON_EDIT_EVENT, DetailPane.this, Boolean.valueOf(formView)); eventListener.onEvent(openEvent); } /** * Fire ON_ACTIVATE_DETAIL_EVENT for selected tab. */ public void fireActivateDetailEvent() { int index = tabbox.getSelectedIndex(); IADTabpanel tabPanel = (IADTabpanel) tabbox.getTabpanel(index).getChildren().get(1); Event activateEvent = new Event(ON_ACTIVATE_DETAIL_EVENT, tabPanel, prevSelectedIndex); Events.sendEvent(activateEvent); } /** * Set visibility of tab at tabIndex * @param tabIndex * @param visible */ public void setTabVisibility(int tabIndex, boolean visible) { if (tabIndex < 0 || tabbox.getTabs() == null || tabIndex >= tabbox.getTabs().getChildren().size()) return; Tab tab = (Tab) tabbox.getTabs().getChildren().get(tabIndex); tab.setVisible(visible); if (tab.isSelected()) { tab.setSelected(false); } if (tab.getLinkedPanel() != null) { tab.getLinkedPanel().setVisible(visible); } } /** * Is tab at tabIndex visible * @param tabIndex * @return true if tab at tabIndex is visible */ public boolean isTabVisible(int tabIndex) { if (tabIndex < 0 || tabbox.getTabs() == null || tabIndex >= tabbox.getTabs().getChildren().size()) return false; return tabbox.getTabs().getChildren().get(tabIndex).isVisible(); } /** * Is tab at tabIndex enable * @param tabIndex * @return true if tab at tabIndex is enable */ public boolean isTabEnabled(int tabIndex) { if (tabIndex < 0 || tabbox.getTabs() == null || tabIndex >= tabbox.getTabs().getChildren().size()) return false; Tab tab = (Tab) tabbox.getTabs().getChildren().get(tabIndex); return !tab.isDisabled(); } /** * Enable/disable tab at tabIndex * @param tabIndex * @param enabled */ public void setTabEnabled(int tabIndex, boolean enabled) { if (tabIndex < 0 || tabbox.getTabs() == null || tabIndex >= tabbox.getTabs().getChildren().size()) return; Tab tab = (Tab) tabbox.getTabs().getChildren().get(tabIndex); tab.setDisabled(!enabled); } /** * Disable all toolbar buttons */ public void disableToolbar() { int index = getSelectedIndex(); if (index < 0 || index >= getTabcount()) return; Tabpanel tabpanel = (Tabpanel) tabbox.getTabpanel(index); Toolbar toolbar = tabpanel.getToolbar(); for(Component c : toolbar.getChildren()) { if (c instanceof ToolBarButton) { ToolBarButton btn = (ToolBarButton) c; btn.setDisabled(true); } } } /** * Find first {@link Tabpanel} that own comp. * @param comp * @return {@link Component} */ private Component findTabpanel(Component comp) { Component parent = comp.getParent(); while (parent != null) { if (parent instanceof org.adempiere.webui.component.Tabpanel) return parent; parent = parent.getParent(); } return null; } /** * Add new row * @throws Exception */ public void onNew() throws Exception { Event openEvent = new Event(ON_NEW_EVENT, DetailPane.this); eventListener.onEvent(openEvent); } /** * Handle shortcut key event * @param keyEvent */ private void onCtrlKeyEvent(KeyEvent keyEvent) { ToolBarButton btn = null; if (keyEvent.isAltKey() && !keyEvent.isCtrlKey() && keyEvent.isShiftKey()) { // Shift+Alt key if (keyEvent.getKeyCode() == ADWindowToolbar.VK_N) { // Shift+Alt+N btn = getSelectedPanel().getToolbarButton(BTN_NEW_ID); } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_T) { btn = getSelectedPanel().getToolbarButton(BTN_TOGGLE_ID); } else if (keyEvent.getKeyCode() == KeyEvent.HOME) { btn = getSelectedPanel().getRecordToolbar().btnFirst; } else if (keyEvent.getKeyCode() == KeyEvent.END) { btn = getSelectedPanel().getRecordToolbar().btnLast; } else if (keyEvent.getKeyCode() == KeyEvent.LEFT) { btn = getSelectedPanel().getRecordToolbar().btnPrevious; } else if (keyEvent.getKeyCode() == KeyEvent.RIGHT) { btn = getSelectedPanel().getRecordToolbar().btnNext; } else if (keyEvent.getKeyCode() == KeyEvent.HOME) { btn = getSelectedPanel().getRecordToolbar().btnFirst; } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_E) { btn = getSelectedPanel().getToolbarButton(BTN_EDIT_ID); } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_S) { btn = getSelectedPanel().getToolbarButton(BTN_SAVE_ID); } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_D) { btn = getSelectedPanel().getToolbarButton(BTN_DELETE_ID); } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_O) { btn = getSelectedPanel().getToolbarButton(BTN_PROCESS_ID); } else if (keyEvent.getKeyCode() == ADWindowToolbar.VK_F) { btn = getSelectedPanel().getToolbarButton(BTN_QUICK_FORM_ID); } } if (btn != null) { keyEvent.stopPropagation(); if (!btn.isDisabled() && btn.isVisible()) { Events.sendEvent(btn, new Event(Events.ON_CLICK, btn)); //client side script to close combobox popup String script = "(function(){let w=zk.Widget.$('#" + btn.getUuid()+"'); " + "zWatch.fire('onFloatUp', w);})()"; Clients.response(new AuScript(script)); } } } /** * Custom {@link org.adempiere.webui.component.Tabpanel} implementation for DetailPane. */ public static class Tabpanel extends org.adempiere.webui.component.Tabpanel { /** * generated serial id */ private static final long serialVersionUID = -2502140440194514450L; private ToolBar toolbar; private RecordToolbar recordToolBar; private Div pagingControl; private boolean toggleToFormView = false; private IADTabpanel adTabPanel; private HashMap toolbarCustomButtons = new HashMap(); private A overflowButton; private Popup overflowPopup; public Tabpanel() { setSclass("adwindow-detailpane-tabpanel"); toolbar = new ToolBar(); appendChild(toolbar); } /** * Update toolbar state after toggle between grid and form view */ public void afterToggle() { if (getPagingControl() != null) getPagingControl().setVisible(!toggleToFormView); if (getRecordToolbar() != null) { getRecordToolbar().setVisible(toggleToFormView); if (getRecordToolbar().isVisible()) getRecordToolbar().dynamicDisplay(); } boolean enableCustomize = !adTabPanel.getGridTab().isSortTab() && adTabPanel.isGridView(); Optional optional = getToolbarButtons().stream().filter(e -> BTN_CUSTOMIZE_ID.equals(e.getId())).findFirst(); if (optional.isPresent()) optional.get().setDisabled(!enableCustomize); } /** * Set form view state of selected tab * @param b */ public void setToggleToFormView(boolean b) { toggleToFormView = b; } /** * Is selected tab in form view * @return true if tab have been toggle to form view */ public boolean isToggleToFormView() { return toggleToFormView; } /** * * @param tabPanel */ public void setADTabpanel(IADTabpanel tabPanel) { appendChild(tabPanel); this.adTabPanel = tabPanel; if (tabPanel instanceof ADTabpanel) { tabPanel.addEventListener(ADTabpanel.ON_SWITCH_VIEW_EVENT, e -> { if (recordToolBar != null && tabPanel.isGridView()) { recordToolBar.setVisible(false); } }); } } /** * Get toolbar of the tabpanel * @return {@link ToolBar} */ public ToolBar getToolbar() { return toolbar; } /** * set record navigation toolbar * @param rtb */ public void setRecordToolbar(RecordToolbar rtb) { recordToolBar = rtb; Component parent = overflowPopup != null ? overflowPopup : toolbar; parent.appendChild(rtb); rtb.setVisible(false); if (overflowPopup == null) rtb.setSclass("adwindow-detailpane-adtab-grid-south"); } /** * * @return {@link RecordToolbar} */ public RecordToolbar getRecordToolbar() { return recordToolBar; } /** * Set paging control * @param pagingControl */ public void setPagingControl(Div pagingControl) { Component parent = overflowPopup != null ? overflowPopup : toolbar; if ( pagingControl.getParent() != parent) { parent.appendChild(pagingControl); ZKUpdateUtil.setHflex(pagingControl, "0"); if (overflowPopup == null) pagingControl.setSclass("adwindow-detailpane-adtab-grid-south"); } this.pagingControl = pagingControl; } /** * * @return paging control */ public Div getPagingControl() { return pagingControl; } /** * Get toolbar button by id * @param id * @return {@link ToolBarButton} */ public ToolBarButton getToolbarButton(String id) { Optional optional = getToolbarButtons().stream().filter(e -> e.getId().equals(id)).findFirst(); return optional.isPresent() ? optional.get() : null; } /** * * @return toolbar buttons from the detail toolbar */ private List getToolbarButtons() { List list = new ArrayList<>(); for (Component c : toolbar.getChildren()) { if (c instanceof ToolBarButton) list.add((ToolBarButton) c); } return list; } /** * Create overflow button (show more) for mobile client. */ private void createOverflowButton() { overflowButton = new A(); overflowButton.setTooltiptext(Msg.getMsg(Env.getCtx(), "ShowMore")); overflowButton.setIconSclass("z-icon-ShowMore"); overflowButton.setSclass("font-icon-toolbar-button toolbar-button mobile-overflow-link"); toolbar.appendChild(overflowButton); newOverflowPopup(); toolbar.appendChild(overflowPopup); overflowButton.addEventListener(Events.ON_CLICK, e -> { Long ts = (Long) overflowPopup.removeAttribute("popup.close"); if (ts != null) { if (System.currentTimeMillis() - ts.longValue() < 500) { return; } } overflowPopup.open(overflowButton, "after_end"); }); } /** * Create new overflow popup */ private void newOverflowPopup() { overflowPopup = new Popup(); overflowPopup.setHflex("min"); overflowPopup.setVflex("min"); } } /** * Record navigation toolbar * @author hengsin * */ private static class RecordToolbar extends Hlayout { /** * generated serial id */ private static final long serialVersionUID = -3369063577339438823L; private ToolBarButton btnFirst; private ToolBarButton btnPrevious; private ToolBarButton btnRecordInfo; private ToolBarButton btnNext; private ToolBarButton btnLast; private GridTab gridTab; /** * @param gridTab */ private RecordToolbar(GridTab gridTab) { this.gridTab = gridTab; btnFirst = createButton("First", "First", "First"); btnFirst.setTooltiptext(btnFirst.getTooltiptext()+" Shift+Alt+Home"); appendChild(btnFirst); btnFirst.addEventListener(Events.ON_CLICK, e -> { Event ne = new Event(DetailPane.ON_RECORD_NAVIGATE_EVENT, this, "first"); Events.sendEvent(this, ne); }); btnPrevious = createButton("Previous", "Previous", "Previous"); btnPrevious.setTooltiptext(btnPrevious.getTooltiptext()+" Shift+Alt+Left"); appendChild(btnPrevious); btnPrevious.addEventListener(Events.ON_CLICK, e -> { Event ne = new Event(DetailPane.ON_RECORD_NAVIGATE_EVENT, this, "previous"); Events.sendEvent(this, ne); }); btnRecordInfo = new ToolBarButton(); btnRecordInfo.setLabel(""); btnRecordInfo.setTooltiptext(Util.cleanAmp(Msg.getMsg(Env.getCtx(), "Who"))); btnRecordInfo.addEventListener(Events.ON_CLICK, e -> { if (gridTab.isNew() || gridTab.getRowCount() == 0) return; DataStatusEvent dse = new DataStatusEvent(gridTab, gridTab.getRowCount(), gridTab.needSave(true, true), true, false); dse.AD_Table_ID = gridTab.getAD_Table_ID(); gridTab.updateDataStatusEventProperties(dse); dse.setCurrentRow(gridTab.getCurrentRow()); String title = Msg.getMsg(Env.getCtx(), "Who") + btnRecordInfo.getLabel(); new WRecordInfo(title, dse, gridTab); }); btnRecordInfo.setSclass("breadcrumb-record-info link"); btnRecordInfo.setId("recordInfo"); btnRecordInfo.setStyle("float: none; display: inline-flex; width: auto;"); appendChild(btnRecordInfo); btnNext = createButton("Next", "Next", "Next"); btnNext.setTooltiptext(btnNext.getTooltiptext()+" Shift+Alt+Right"); btnNext.addEventListener(Events.ON_CLICK, e -> { Event ne = new Event(DetailPane.ON_RECORD_NAVIGATE_EVENT, this, "next"); Events.sendEvent(this, ne); }); appendChild(btnNext); btnLast = createButton("Last", "Last", "Last"); btnLast.setTooltiptext(btnLast.getTooltiptext()+" Shift+Alt+End"); btnLast.addEventListener(Events.ON_CLICK, e -> { Event ne = new Event(DetailPane.ON_RECORD_NAVIGATE_EVENT, this, "last"); Events.sendEvent(this, ne); }); appendChild(btnLast); this.setValign("middle"); } /** * Create toolbar button * @param name * @param image * @param tooltip * @return {@link ToolBarButton} */ private ToolBarButton createButton(String name, String image, String tooltip) { ToolBarButton btn = new ToolBarButton(""); btn.setName("Btn"+name); btn.setId(name); String suffix = "16.png"; if (ThemeManager.isUseFontIconForImage()) btn.setIconSclass("z-icon-"+image+"Record"); else btn.setImage(ThemeManager.getThemeResource("images/"+image + suffix)); btn.setTooltiptext(Msg.getMsg(Env.getCtx(),tooltip)); btn.setSclass("breadcrumb-toolbar-button"); this.appendChild(btn); //make toolbar button last to receive focus btn.setTabindex(0); btn.setDisabled(true); btn.setStyle("float: none"); return btn; } /** * Dynamic update state of toolbar buttons */ private void dynamicDisplay() { int rowCount = gridTab.getRowCount(); int currentRow = gridTab.getCurrentRow()+1; btnRecordInfo.setLabel(currentRow+"/"+rowCount); btnFirst.setDisabled(currentRow<=1); btnPrevious.setDisabled(currentRow<=1); btnNext.setDisabled(currentRow==rowCount); btnLast.setDisabled(currentRow==rowCount); this.invalidate(); } } }