/****************************************************************************** * Copyright (C) 2008 Low Heng Sin * * 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. * *****************************************************************************/ package org.adempiere.webui.component; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.table.AbstractTableModel; import org.adempiere.webui.LayoutUtils; import org.adempiere.webui.editor.WEditor; import org.adempiere.webui.panel.AbstractADWindowPanel; import org.adempiere.webui.util.SortComparator; import org.compiere.model.GridField; import org.compiere.model.GridTab; import org.compiere.model.GridTable; import org.compiere.model.MSysConfig; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.zkoss.zk.au.out.AuFocus; import org.zkoss.zk.au.out.AuScript; import org.zkoss.zk.ui.AbstractComponent; import org.zkoss.zk.ui.Component; 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.zkex.zul.Borderlayout; import org.zkoss.zkex.zul.Center; import org.zkoss.zkex.zul.South; import org.zkoss.zul.Column; import org.zkoss.zul.Div; import org.zkoss.zul.Paging; import org.zkoss.zul.Row; import org.zkoss.zul.event.ZulEvents; /** * Grid view implemented using the Grid component. * @author Low Heng Sin * */ public class GridPanel extends Borderlayout implements EventListener { /** * generated serial version ID */ private static final long serialVersionUID = -7151423393713654553L; private static final int MIN_COLUMN_WIDTH = 100; private static final int MAX_COLUMN_WIDTH = 300; private static final int MIN_COMBOBOX_WIDTH = 160; private static final int MIN_NUMERIC_COL_WIDTH = 130; private Grid listbox = null; private int pageSize = 100; private GridField[] gridField; private AbstractTableModel tableModel; private int numColumns = 5; private int windowNo; private GridTab gridTab; private boolean init; private GridTableListModel listModel; private Paging paging; private GridTabRowRenderer renderer; private South south; private boolean modeless; private String columnOnClick; private AbstractADWindowPanel windowPanel; public static final String PAGE_SIZE_KEY = "ZK_PAGING_SIZE"; public static final String MODE_LESS_KEY = "ZK_GRID_EDIT_MODELESS"; public GridPanel() { this(0); } /** * @param windowNo */ public GridPanel(int windowNo) { this.windowNo = windowNo; listbox = new Grid(); listbox.setOddRowSclass(null); south = new South(); this.appendChild(south); //default paging size pageSize = MSysConfig.getIntValue(PAGE_SIZE_KEY, 100); //default false for better performance modeless = MSysConfig.getBooleanValue(MODE_LESS_KEY, false); } /** * * @param gridTab */ public void init(GridTab gridTab) { if (init) return; this.gridTab = gridTab; tableModel = gridTab.getTableModel(); numColumns = tableModel.getColumnCount(); gridField = ((GridTable)tableModel).getFields(); setupColumns(); render(); updateListIndex(); this.init = true; } /** * * @return boolean */ public boolean isInit() { return init; } /** * call when tab is activated * @param gridTab */ public void activate(GridTab gridTab) { if (!isInit()) { init(gridTab); } } /** * refresh after switching from form view * @param gridTab */ public void refresh(GridTab gridTab) { if (this.gridTab != gridTab || !isInit()) { init = false; init(gridTab); } else { listbox.setModel(listModel); updateListIndex(); } } /** * Update current row from model */ public void updateListIndex() { if (gridTab == null || !gridTab.isOpen()) return; int rowIndex = gridTab.getCurrentRow(); if (pageSize > 0) { if (paging.getTotalSize() != gridTab.getRowCount()) paging.setTotalSize(gridTab.getRowCount()); int pgIndex = rowIndex >= 0 ? rowIndex % pageSize : 0; int pgNo = rowIndex >= 0 ? (rowIndex - pgIndex) / pageSize : 0; if (listModel.getPage() != pgNo) { listModel.setPage(pgNo); if (renderer.isEditing()) { renderer.stopEditing(false); } } else if (rowIndex == renderer.getCurrentRowIndex()){ if (modeless && !renderer.isEditing()) Events.echoEvent("onPostSelectedRowChanged", this, null); return; } else { if (renderer.isEditing()) { renderer.stopEditing(false); if (((renderer.getCurrentRowIndex() - pgIndex) / pageSize) == pgNo) { listModel.updateComponent(renderer.getCurrentRowIndex() % pageSize); } } } if (paging.getActivePage() != pgNo) { paging.setActivePage(pgNo); } if (rowIndex >= 0 && pgIndex >= 0) { Events.echoEvent("onPostSelectedRowChanged", this, null); } } else { if (rowIndex >= 0) { Events.echoEvent("onPostSelectedRowChanged", this, null); } } } /** * set paging size * @param pageSize */ public void setPageSize(int pageSize) { this.pageSize = pageSize; } public void clear() { this.getChildren().clear(); } /** * toggle visibility * @param bool */ public void showGrid(boolean bool) { if (bool) this.setVisible(true); else this.setVisible(false); } private void setupColumns() { if (init) return; Columns columns = new Columns(); listbox.appendChild(columns); columns.setSizable(true); columns.setMenupopup("auto"); columns.setColumnsgroup(false); Map<Integer, String> colnames = new HashMap<Integer, String>(); int index = 0; for (int i = 0; i < numColumns; i++) { if (gridField[i].isDisplayed()) { colnames.put(index, gridField[i].getHeader()); index++; org.zkoss.zul.Column column = new Column(); column.setSortAscending(new SortComparator(i, true, Env.getLanguage(Env.getCtx()))); column.setSortDescending(new SortComparator(i, false, Env.getLanguage(Env.getCtx()))); column.setLabel(gridField[i].getHeader()); int l = DisplayType.isNumeric(gridField[i].getDisplayType()) ? 120 : gridField[i].getDisplayLength() * 9; if (gridField[i].getHeader().length() * 9 > l) l = gridField[i].getHeader().length() * 9; if (l > MAX_COLUMN_WIDTH) l = MAX_COLUMN_WIDTH; else if ( l < MIN_COLUMN_WIDTH) l = MIN_COLUMN_WIDTH; if (gridField[i].getDisplayType() == DisplayType.Table || gridField[i].getDisplayType() == DisplayType.TableDir) { if (l < MIN_COMBOBOX_WIDTH) l = MIN_COMBOBOX_WIDTH; } else if (DisplayType.isNumeric(gridField[i].getDisplayType())) { if (l < MIN_NUMERIC_COL_WIDTH) l = MIN_NUMERIC_COL_WIDTH; } column.setWidth(Integer.toString(l) + "px"); columns.appendChild(column); } } } private void render() { LayoutUtils.addSclass("adtab-grid-panel", this); listbox.setVflex(true); listbox.setFixedLayout(true); listbox.addEventListener(Events.ON_CLICK, this); updateModel(); Center center = new Center(); center.appendChild(listbox); this.appendChild(center); if (pageSize > 0) { paging = new Paging(); paging.setPageSize(pageSize); paging.setTotalSize(tableModel.getRowCount()); paging.setDetailed(true); south.appendChild(paging); paging.addEventListener(ZulEvents.ON_PAGING, this); renderer.setPaging(paging); } else { south.setVisible(false); } } private void updateModel() { listModel = new GridTableListModel((GridTable)tableModel, windowNo); listModel.setPageSize(pageSize); if (renderer != null && renderer.isEditing()) renderer.stopEditing(false); renderer = new GridTabRowRenderer(gridTab, windowNo); renderer.setGridPanel(this); renderer.setADWindowPanel(windowPanel); listbox.setRowRenderer(renderer); listbox.setModel(listModel); } /** * deactivate panel */ public void deactivate() { if (renderer != null && renderer.isEditing()) renderer.stopEditing(true); } public void onEvent(Event event) throws Exception { if (event == null) return; else if (event.getTarget() == listbox && Events.ON_CLICK.equals(event.getName())) { Object data = event.getData(); org.zkoss.zul.Row row = null; String columnName = null; if (data != null && data instanceof Component) { if (data instanceof org.zkoss.zul.Row) row = (org.zkoss.zul.Row) data; else { AbstractComponent cmp = (AbstractComponent) data; if (cmp.getParent() instanceof org.zkoss.zul.Row) { row = (Row) cmp.getParent(); columnName = (String) cmp.getAttribute("columnName"); } } } if (row != null) { //click on selected row to enter edit mode if (row == renderer.getCurrentRow()) { if (!renderer.isEditing()) { renderer.editCurrentRow(); if (columnName != null && columnName.trim().length() > 0) setFocusToField(columnName); else renderer.setFocusToEditor(); } } else { int index = listbox.getRows().getChildren().indexOf(row); if (index >= 0 ) { columnOnClick = columnName; onSelectedRowChange(index); } } } } else if (event.getTarget() == paging) { int pgNo = paging.getActivePage(); if (pgNo != listModel.getPage()) { listModel.setPage(pgNo); onSelectedRowChange(0); } } } private void onSelectedRowChange(int index) { if (updateModelIndex(index)) { updateListIndex(); } } /** * Event after the current selected row change */ public void onPostSelectedRowChanged() { if (listbox.getRows().getChildren().isEmpty()) return; int rowIndex = gridTab.isOpen() ? gridTab.getCurrentRow() : -1; if (rowIndex >= 0 && pageSize > 0) { int pgIndex = rowIndex >= 0 ? rowIndex % pageSize : 0; org.zkoss.zul.Row row = (org.zkoss.zul.Row) listbox.getRows().getChildren().get(pgIndex); if (!isRowRendered(row, pgIndex)) { listbox.renderRow(row); } else { Row old = renderer.getCurrentRow(); int oldIndex = renderer.getCurrentRowIndex(); renderer.setCurrentRow(row); if (old != null && old != row && oldIndex >= 0 && oldIndex != gridTab.getCurrentRow()) { listModel.updateComponent(oldIndex % pageSize); } } if (modeless && !renderer.isEditing()) { renderer.editCurrentRow(); if (columnOnClick != null && columnOnClick.trim().length() > 0) { setFocusToField(columnOnClick); columnOnClick = null; } else { renderer.setFocusToEditor(); } } else { focusToRow(row); } } else if (rowIndex >= 0) { org.zkoss.zul.Row row = (org.zkoss.zul.Row) listbox.getRows().getChildren().get(rowIndex); if (!isRowRendered(row, rowIndex)) { listbox.renderRow(row); } else { Row old = renderer.getCurrentRow(); int oldIndex = renderer.getCurrentRowIndex(); renderer.setCurrentRow(row); if (old != null && old != row && oldIndex >= 0 && oldIndex != gridTab.getCurrentRow()) { listModel.updateComponent(oldIndex); } } if (modeless && !renderer.isEditing()) { renderer.editCurrentRow(); if (columnOnClick != null && columnOnClick.trim().length() > 0) { setFocusToField(columnOnClick); columnOnClick = null; } else { renderer.setFocusToEditor(); } } else { focusToRow(row); } } } /** * scroll grid to the current focus row */ public void scrollToCurrentRow() { onPostSelectedRowChanged(); } private void focusToRow(org.zkoss.zul.Row row) { if (renderer.isEditing()) { if (columnOnClick != null && columnOnClick.trim().length() > 0) { setFocusToField(columnOnClick); columnOnClick = null; } else { renderer.setFocusToEditor(); } } else { Component cmp = null; List<?> childs = row.getChildren(); for (Object o : childs) { Component c = (Component) o; if (!c.isVisible()) continue; c = c.getFirstChild(); if (c == null) continue; if (c.getNextSibling() != null) { cmp = c.getNextSibling(); break; } } if (cmp != null) Clients.response(new AuScript(null, "scrollToRow('" + cmp.getUuid() + "');")); if (columnOnClick != null && columnOnClick.trim().length() > 0) { List<?> list = row.getChildren(); for(Object element : list) { if (element instanceof Div) { Div div = (Div) element; if (columnOnClick.equals(div.getAttribute("columnName"))) { cmp = div.getFirstChild().getNextSibling(); Clients.response(new AuScript(null, "scrollToRow('" + cmp.getUuid() + "');")); break; } } } columnOnClick = null; } } } private boolean isRowRendered(org.zkoss.zul.Row row, int index) { if (row.getChildren().size() == 0) { return false; } else if (row.getChildren().size() == 1) { if (!(row.getChildren().get(0) instanceof Div)) { return false; } } return true; } private boolean updateModelIndex(int rowIndex) { if (pageSize > 0) { int start = listModel.getPage() * listModel.getPageSize(); rowIndex = start + rowIndex; } if (gridTab.getCurrentRow() != rowIndex) { gridTab.navigate(rowIndex); return true; } return false; } /** * @return Grid */ public Grid getListbox() { return listbox; } /** * Validate display properties of fields of current row * @param col */ public void dynamicDisplay(int col) { if (gridTab == null || !gridTab.isOpen()) { return; } // Selective if (col > 0) { GridField changedField = gridTab.getField(col); String columnName = changedField.getColumnName(); ArrayList<?> dependants = gridTab.getDependantFields(columnName); if (dependants.size() == 0 && changedField.getCallout().length() > 0) { return; } } boolean noData = gridTab.getRowCount() == 0; List<WEditor> list = renderer.getEditors(); for (WEditor comp : list) { GridField mField = comp.getGridField(); if (mField != null && mField.getIncluded_Tab_ID() <= 0) { if (noData) { comp.setReadWrite(false); } else { comp.dynamicDisplay(); boolean rw = mField.isEditable(true); // r/w - check Context comp.setReadWrite(rw); } comp.setVisible(mField.isDisplayed(true)); } } // all components } /** * * @param windowNo */ public void setWindowNo(int windowNo) { this.windowNo = windowNo; } @Override public void focus() { if (renderer != null && renderer.isEditing()) { renderer.setFocusToEditor(); } } /** * Handle enter key event */ public boolean onEnterKey() { if (!modeless && renderer != null && !renderer.isEditing()) { renderer.editCurrentRow(); renderer.setFocusToEditor(); return true; } return false; } /** * @param columnName */ public void setFocusToField(String columnName) { boolean found = false; for (WEditor editor : renderer.getEditors()) { if (found) editor.setHasFocus(false); else if (columnName.equals(editor.getColumnName())) { editor.setHasFocus(true); Clients.response(new AuFocus(editor.getComponent())); found = true; } } } /** * @param winPanel */ public void setADWindowPanel(AbstractADWindowPanel winPanel) { windowPanel = winPanel; if (renderer != null) renderer.setADWindowPanel(windowPanel); } }