/******************************************************************************
* Product: Posterita Ajax UI *
* Copyright (C) 2007 Posterita Ltd. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius *
* or via info@posterita.org or http://www.posterita.org/ *
*****************************************************************************/
package org.adempiere.webui.adwindow;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import org.adempiere.util.Callback;
import org.adempiere.webui.adwindow.DetailPane.Tabpanel;
import org.adempiere.webui.component.ADTabListModel;
import org.adempiere.webui.component.ADTabListModel.ADTabLabel;
import org.adempiere.webui.component.Tabbox;
import org.adempiere.webui.util.ZKUpdateUtil;
import org.adempiere.webui.window.Dialog;
import org.compiere.model.DataStatusEvent;
import org.compiere.model.DataStatusListener;
import org.compiere.model.GridField;
import org.compiere.model.GridTab;
import org.compiere.model.MTab;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.Evaluator;
import org.compiere.util.Msg;
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.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Center;
import org.zkoss.zul.Label;
import org.zkoss.zul.Menuitem;
import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Vlayout;
/**
* Header and detail UI for AD_Tabs.
* This class manage a list of tabs with the current selected tab as the visible {@link ADTabpanel} instance.
* Child tabs of selected tab is shown in {@link DetailPane} using {@link Tabbox}.
*
* @author Ashley G Ramdass
* @author Low Heng Sin
* @date Feb 25, 2007
* @version $Revision: 0.10 $
*/
public class CompositeADTabbox extends AbstractADTabbox
{
/**
* DetailPane attribute to hold list of child tabs.
* List of Object[] of tabIndex, tabPanel, tabLabel, enable.
*/
private static final String DETAILPANE_TABLIST_ATTR = "detailpane.tablist";
/** Execution attribute to hold reference to detail ADTabpanel that's handling onEditDetail event **/
public static final String AD_TABBOX_ON_EDIT_DETAIL_ATTRIBUTE = "ADTabbox.onEditDetail";
/** after tab selection change event **/
private static final String ON_POST_TAB_SELECTION_CHANGED_EVENT = "onPostTabSelectionChanged";
/** event echo from ON_POST_TAB_SELECTION_CHANGED_EVENT handler **/
private static final String ON_TAB_SELECTION_CHANGED_ECHO_EVENT = "onTabSelectionChangedEcho";
/** tab selection change event **/
public static final String ON_SELECTION_CHANGED_EVENT = "onSelectionChanged";
/** List of all tab **/
private List tabLabelList = new ArrayList();
/** List of all tab panel **/
private List tabPanelList = new ArrayList();
/** main layout component **/
private Vlayout layout;
/** tab selection change listener **/
private EventListener selectionListener;
/** {@link IADTabpanel} instance for selected tab **/
private IADTabpanel headerTab;
/** Index of selected tab **/
private int selectedIndex = 0;
/**
* default constructor
*/
public CompositeADTabbox(){
}
/**
* Create detail panel at bottom
* @return {@link DetailPane}
*/
protected DetailPane createDetailPane() {
DetailPane detailPane = new DetailPane();
detailPane.setEventListener(new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
if (DetailPane.ON_EDIT_EVENT.equals(event.getName())) {
if (headerTab.getGridTab().isNew() && ! headerTab.needSave(true, false)) return;
final int row = getSelectedDetailADTabpanel() != null
? getSelectedDetailADTabpanel().getGridTab().getCurrentRow()
: 0;
final boolean formView = event.getData() != null ? (Boolean)event.getData() : true;
if (getSelectedDetailADTabpanel() != null &&
((getSelectedDetailADTabpanel() == getDirtyADTabpanel()) ||
(getDirtyADTabpanel() == null && getSelectedDetailADTabpanel().getGridTab().isNew()))) {
onEditDetail(row, formView);
} else {
adWindowPanel.saveAndNavigate(new Callback() {
@Override
public void onCallback(Boolean result) {
if (result)
onEditDetail(row, formView);
}
});
}
}
else if (DetailPane.ON_NEW_EVENT.equals(event.getName())) {
if (headerTab.getGridTab().isNew()) return;
final int row = getSelectedDetailADTabpanel() != null
? getSelectedDetailADTabpanel().getGridTab().getCurrentRow()
: 0;
adWindowPanel.saveAndNavigate(new Callback() {
@Override
public void onCallback(Boolean result) {
if (result) {
if (getSelectedDetailADTabpanel().getGridTab().isSingleRow()) {
if (headerTab.isDetailVisible() && headerTab.getDetailPane().getSelectedPanel().isToggleToFormView()) {
if (!getSelectedDetailADTabpanel().getGridTab().isNew()) {
getSelectedDetailADTabpanel().getGridTab().dataNew(false);
getSelectedDetailADTabpanel().dynamicDisplay(0);
focusToTabpanel(getSelectedDetailADTabpanel());
}
} else {
onEditDetail(row, true);
if (!adWindowPanel.getActiveGridTab().isNew())
adWindowPanel.onNew();
}
} else {
if (!getSelectedDetailADTabpanel().getGridTab().isNew()) {
getSelectedDetailADTabpanel().getGridTab().dataNew(false);
if (!headerTab.isDetailVisible()) {
String uuid = headerTab.getDetailPane().getParent().getUuid();
String vid = getSelectedDetailADTabpanel().getGridView().getUuid();
String script = "setTimeout(function(){zk('#"+uuid+"').$().setOpen(true);setTimeout(function(){let v=zk('#" + vid
+ "').$();let e=new zk.Event(v,'onEditCurrentRow',null,{toServer:true});zAu.send(e);},200);},200)";
Clients.response(new AuScript(script));
} else {
boolean isFormView = headerTab.getDetailPane().getSelectedPanel().isToggleToFormView();
if (isFormView) {
getSelectedDetailADTabpanel().dynamicDisplay(0);
focusToTabpanel(getSelectedDetailADTabpanel());
} else {
getSelectedDetailADTabpanel().getGridView().onEditCurrentRow();
}
}
}
}
}
}
});
}
else if (DetailPane.ON_SAVE_EVENT.equals(event.getName())) {
if (headerTab.getGridTab().isNew()) return;
final IADTabpanel tabPanel = getSelectedDetailADTabpanel();
if (!tabPanel.getGridTab().dataSave(true)) {
showLastError();
} else {
tabPanel.getGridTab().dataRefreshAll(true, true);
tabPanel.getGridTab().refreshParentTabs(true);
}
}
else if (DetailPane.ON_DELETE_EVENT.equals(event.getName())) {
onDelete();
}
else if (DetailPane.ON_QUICK_FORM_EVENT.equals(event.getName()))
{
if (headerTab.getGridTab().isNew() && !headerTab.needSave(true, false))
return;
final int row = getSelectedDetailADTabpanel() != null ? getSelectedDetailADTabpanel().getGridTab().getCurrentRow() : 0;
final boolean formView = event.getData() != null ? (Boolean) event.getData() : true;
adWindowPanel.saveAndNavigate(new Callback () {
@Override
public void onCallback(Boolean result)
{
if (result)
{
onEditDetail(row, formView);
adWindowPanel.onQuickForm(true);
}
}
});
}
else if (DetailPane.ON_RECORD_NAVIGATE_EVENT.equals(event.getName())) {
final String action = (String) event.getData();
adWindowPanel.saveAndNavigate(new Callback () {
@Override
public void onCallback(Boolean result)
{
if (result)
{
if ("first".equalsIgnoreCase(action)) {
getSelectedDetailADTabpanel().getGridTab().navigate(0);
} else if ("previous".equalsIgnoreCase(action)) {
getSelectedDetailADTabpanel().getGridTab().navigateRelative(-1);
} else if ("next".equalsIgnoreCase(action)) {
getSelectedDetailADTabpanel().getGridTab().navigateRelative(1);
} else if ("last".equalsIgnoreCase(action)) {
getSelectedDetailADTabpanel().getGridTab().navigate(getSelectedDetailADTabpanel().getGridTab().getRowCount()-1);
}
}
}
});
}
}
/**
* Delete current row of selected detail tab
*/
private void onDelete() {
if (headerTab.getGridTab().isNew()) return;
final IADTabpanel tabPanel = getSelectedDetailADTabpanel();
if (tabPanel != null && tabPanel.getGridTab().getSelection().length > 0) {
onDeleteSelected(tabPanel);
}
else if (tabPanel != null && tabPanel.getGridTab().getRowCount() > 0
&& tabPanel.getGridTab().getCurrentRow() >= 0) {
Dialog.ask(tabPanel.getGridTab().getWindowNo(), "DeleteRecord?", new Callback() {
@Override
public void onCallback(Boolean result) {
if (!result) return;
if (!tabPanel.getGridTab().dataDelete()) {
showLastError();
} else {
adWindowPanel.onRefresh(true);
}
}
});
}
}
/**
* Delete selected rows of selected detail tab
* @param tabPanel
*/
private void onDeleteSelected(final IADTabpanel tabPanel) {
if (tabPanel == null || tabPanel.getGridTab() == null) return;
final int[] indices = tabPanel.getGridTab().getSelection();
if(indices.length > 0) {
StringBuilder sb = new StringBuilder();
sb.append(Env.getContext(Env.getCtx(), tabPanel.getGridTab().getWindowNo(), "_WinInfo_WindowName", false)).append(" - ")
.append(indices.length).append(" ").append(Msg.getMsg(Env.getCtx(), "Selected"));
Dialog.ask(sb.toString(), tabPanel.getGridTab().getWindowNo(),"DeleteSelection", new Callback() {
@Override
public void onCallback(Boolean result) {
if(result){
tabPanel.getGridTab().clearSelection();
Arrays.sort(indices);
int offset = 0;
int count = 0;
for (int i = 0; i < indices.length; i++)
{
tabPanel.getGridTab().navigate(indices[i]-offset);
if (tabPanel.getGridTab().dataDelete())
{
offset++;
count++;
}
}
adWindowPanel.onRefresh(true);
adWindowPanel.getStatusBar().setStatusLine(Msg.getMsg(Env.getCtx(), "Deleted")+": "+count, false);
}
}
});
}
}
});
return detailPane;
}
/**
* defer execution of adTabPanel.focus()
* @param adTabPanel
*/
private void focusToTabpanel(IADTabpanel adTabPanel ) {
if (adTabPanel != null && adTabPanel instanceof HtmlBasedComponent) {
final HtmlBasedComponent comp = (HtmlBasedComponent) adTabPanel;
Executions.schedule(layout.getDesktop(), e -> {comp.focus();}, new Event("onFocusDefer"));
}
}
/**
* Edit current row of selected detail tab.
* Make selected detail tab the new header tab.
* @param row
* @param formView
*/
protected void onEditDetail(int row, boolean formView) {
int oldIndex = selectedIndex;
IADTabpanel selectedPanel = getSelectedDetailADTabpanel();
if (selectedPanel == null) return;
int newIndex = selectedPanel.getTabNo();
selectedPanel.query();
Executions.getCurrent().setAttribute(AD_TABBOX_ON_EDIT_DETAIL_ATTRIBUTE, selectedPanel);
Event selectionChanged = new Event(ON_SELECTION_CHANGED_EVENT, layout, new Object[]{oldIndex, newIndex});
try {
selectionListener.onEvent(selectionChanged);
} catch (Exception e) {
throw new RuntimeException(e);
}
headerTab.setDetailPaneMode(false);
if (formView && headerTab.isGridView()) {
headerTab.switchRowPresentation();
}
if (!headerTab.getGridTab().isSortTab() && headerTab instanceof ADTabpanel)
headerTab.getGridTab().setCurrentRow(row, true);
if (headerTab.isGridView()) {
if (headerTab.getGridTab().isNew() || headerTab.needSave(true, false)) {
headerTab.getGridView().onEditCurrentRow();
}
} else {
((HtmlBasedComponent)headerTab).focus();
}
}
/**
* Create layout and setup listeners for bread crumb.
* Vertical layout with {@link ADTabpanel} as the only child component.
*/
@Override
protected Component doCreatePart(Component parent)
{
layout = new Vlayout();
ZKUpdateUtil.setHeight(layout, "100%");
ZKUpdateUtil.setWidth(layout, "100%");
layout.setStyle("position: relative");
if (parent != null) {
layout.setParent(parent);
} else {
layout.setPage(page);
}
layout.addEventListener(ON_POST_TAB_SELECTION_CHANGED_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
onPostTabSelectionChanged((Boolean)event.getData());
}
});
layout.addEventListener(ON_TAB_SELECTION_CHANGED_ECHO_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
onTabSelectionChangedEcho((Boolean)event.getData());
}
});
BreadCrumb breadCrumb = getBreadCrumb();
breadCrumb.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
//send tab selection change event
int oldIndex = selectedIndex;
if (event.getTarget() instanceof BreadCrumbLink) {
BreadCrumbLink link = (BreadCrumbLink) event.getTarget();
int newIndex = Integer.parseInt(link.getPathId());
Event selectionChanged = new Event(ON_SELECTION_CHANGED_EVENT, layout, new Object[]{oldIndex, newIndex});
selectionListener.onEvent(selectionChanged);
} else if (event.getTarget() instanceof Menuitem) {
Menuitem item = (Menuitem) event.getTarget();
int newIndex = Integer.parseInt(item.getValue());
Event selectionChanged = new Event(ON_SELECTION_CHANGED_EVENT, layout, new Object[]{oldIndex, newIndex});
selectionListener.onEvent(selectionChanged);
}
}
});
return layout;
}
@Override
protected void doAddTab(GridTab gTab, IADTabpanel tabPanel) {
ADTabListModel.ADTabLabel tabLabel = new ADTabListModel.ADTabLabel(gTab.getName(), gTab.getTabLevel(), gTab.getDescription(),
gTab.getWindowNo(), gTab.getAD_Tab_ID());
tabLabelList.add(tabLabel);
tabPanelList.add(tabPanel);
tabPanel.setTabNo(tabPanelList.size()-1);
tabPanel.addEventListener(ADTabpanel.ON_ACTIVATE_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
Boolean b = (Boolean) event.getData();
if (b != null && !b.booleanValue())
return;
IADTabpanel tabPanel = (IADTabpanel) event.getTarget();
//call onActivateDetail if it is detail tab panel
if (tabPanel != headerTab && headerTab.getDetailPane() != null && tabPanel.getTabLevel() > headerTab.getTabLevel()) {
if (b != null && b.booleanValue()) {
onActivateDetail(tabPanel);
if (headerTab instanceof ADTabpanel) {
if (!((ADTabpanel) headerTab).getADWindowContent().focusToLastFocusEditor(true))
((ADTabpanel) headerTab).getADWindowContent().focusToActivePanel();
}
}
}
}
});
tabPanel.addEventListener(DetailPane.ON_ACTIVATE_DETAIL_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
final IADTabpanel tabPanel = (IADTabpanel) event.getTarget();
int oldIndex = (Integer) event.getData();
if (oldIndex != headerTab.getDetailPane().getSelectedIndex()) {
IADTabpanel prevTabPanel = headerTab.getDetailPane().getADTabpanel(oldIndex);
if (prevTabPanel != null && prevTabPanel.needSave(true, true)) {
final int newIndex = headerTab.getDetailPane().getSelectedIndex();
headerTab.getDetailPane().setSelectedIndex(oldIndex);
adWindowPanel.saveAndNavigate(new Callback() {
@Override
public void onCallback(Boolean result) {
if (result) {
headerTab.getDetailPane().setSelectedIndex(newIndex);
tabPanel.activate(true);
}
}
});
} else {
headerTab.getDetailPane().setSelectedIndex(headerTab.getDetailPane().getSelectedIndex());
tabPanel.activate(true);
}
} else {
tabPanel.activate(true);
}
}
});
tabPanel.addEventListener(ADTabpanel.ON_SWITCH_VIEW_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
IADTabpanel tabPanel = (IADTabpanel) event.getTarget();
if (tabPanel == headerTab) {
IADTabpanel detailPanel = getSelectedDetailADTabpanel();
if (detailPanel != null) {
detailPanel.setDetailPaneMode(true);
}
if (headerTab.getDetailPane() != null)
headerTab.getDetailPane().setVflex("true");
}
}
});
tabPanel.addEventListener(ADTabpanel.ON_TOGGLE_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
IADTabpanel tabPanel = (IADTabpanel) event.getTarget();
if (tabPanel == headerTab) {
adWindowPanel.onToggle();
} else {
headerTab.getDetailPane().onEdit(true);
}
}
});
if (tabPanel.getGridView() != null) {
tabPanel.getGridView().addEventListener(DetailPane.ON_EDIT_EVENT, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
GridView gridView = (GridView) event.getTarget();
if (!gridView.isDetailPaneMode()) {
adWindowPanel.onToggle();
}
}
});
}
//add to header or detail pane
if (layout.getChildren().isEmpty()) {
layout.appendChild(tabPanel);
headerTab = tabPanel;
} else if (tabLabel.tabLevel == 1) {
if (headerTab.getDetailPane() == null) {
headerTab.setDetailPane(createDetailPane());
} else
tabPanel.setVisible(false);
ZKUpdateUtil.setHflex(headerTab.getDetailPane(), "1");
headerTab.getDetailPane().addADTabpanel(tabPanel, tabLabel);
tabPanel.setDetailPaneMode(true);
headerTab.getDetailPane().setVflex("true");
} else if (tabLabel.tabLevel > 1){
headerTab.getDetailPane().addADTabpanel(tabPanel, tabLabel, false);
tabPanel.setDetailPaneMode(true);
headerTab.getDetailPane().setVflex("true");
}
HtmlBasedComponent htmlComponent = (HtmlBasedComponent) tabPanel;
ZKUpdateUtil.setVflex(htmlComponent, "1");
ZKUpdateUtil.setWidth(htmlComponent, "100%");
tabPanel.getGridTab().addDataStatusListener(new SyncDataStatusListener(tabPanel));
}
@Override
public boolean updateSelectedIndex(int oldIndex, int newIndex) {
boolean b = super.updateSelectedIndex(oldIndex, newIndex);
if (b) {
BreadCrumb breadcrumb = getBreadCrumb();
if (breadcrumb.isEmpty()) {
updateBreadCrumb();
}
}
return b;
}
/**
* Call {@link ADTabpanel#activateDetailIfVisible()}
*/
private void activateDetailIfVisible() {
if (headerTab instanceof ADTabpanel) {
((ADTabpanel)headerTab).activateDetailIfVisible();
}
}
@Override
protected void updateTabState() {
if (isDetailPaneLoaded())
{
boolean hasChanges = false;
for(int i = 0; i < headerTab.getDetailPane().getTabcount(); i++)
{
IADTabpanel adtab = headerTab.getDetailPane().getADTabpanel(i);
if (adtab.getDisplayLogic() != null && adtab.getDisplayLogic().trim().length() > 0) {
boolean visible = Evaluator.evaluateLogic(headerTab, adtab.getDisplayLogic());
if (headerTab.getDetailPane().isTabVisible(i) != visible) {
headerTab.getDetailPane().setTabVisibility(i, visible);
hasChanges = true;
}
}
}
int selected = headerTab.getDetailPane().getSelectedIndex();
if (headerTab.getDetailPane().getADTabpanel(selected) == null || !headerTab.getDetailPane().isTabVisible(selected)) {
for(int i = 0; i < headerTab.getDetailPane().getTabcount(); i++) {
if (selected == i) continue;
if (headerTab.getDetailPane().isTabVisible(i)) {
headerTab.getDetailPane().setSelectedIndex(i);
if (headerTab instanceof ADTabpanel) {
((ADTabpanel) headerTab).activateDetailIfVisible();
}
break;
}
}
hasChanges = true;
}
if (hasChanges) {
if (headerTab.getDetailPane().getParent() != null)
headerTab.getDetailPane().getParent().invalidate();
}
}
}
/**
* Return the selected Tab Panel
*/
@Override
public IADTabpanel getSelectedTabpanel()
{
return tabPanelList.isEmpty() ? null : tabPanelList.get(selectedIndex);
}
@Override
public int getSelectedIndex() {
return selectedIndex;
}
@Override
public void setSelectionEventListener(EventListener listener) {
selectionListener = listener;
}
@Override
protected void doTabSelectionChanged(int oldIndex, int newIndex) {
selectedIndex = newIndex;
IADTabpanel oldTabpanel = oldIndex >= 0 ? tabPanelList.get(oldIndex) : null;
IADTabpanel newTabpanel = tabPanelList.get(newIndex);
if (oldTabpanel != null) {
oldTabpanel.setVisible(false);
}
newTabpanel.createUI();
newTabpanel.setVisible(true);
headerTab = newTabpanel;
if (headerTab.getParent() != layout)
layout.appendChild(headerTab);
//set state
headerTab.setDetailPaneMode(false);
//show empty path, update later with actual path in onPostTabSelectionChanged
getBreadCrumb().getFirstChild().getChildren().clear();
getBreadCrumb().getFirstChild().appendChild(new Label(""));
Events.sendEvent(new Event(ON_POST_TAB_SELECTION_CHANGED_EVENT, layout, oldIndex > newIndex));
}
/**
* Handle after tab selection change event, echo onTabSelectionChangedEcho event.
* @param back
*/
private void onPostTabSelectionChanged(Boolean back) {
if (headerTab instanceof ADTabpanel && !headerTab.getGridTab().isSortTab()) {
//gather all child tabs (both immediate and not immediate)
//Object[]: tabIndex, tabPanel, tabLabel, enable
List