/****************************************************************************** * 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.sql.Timestamp; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.adempiere.webui.apps.AEnv; import org.adempiere.webui.editor.WButtonEditor; import org.adempiere.webui.editor.WEditor; import org.adempiere.webui.editor.WEditorPopupMenu; import org.adempiere.webui.editor.WebEditorFactory; import org.adempiere.webui.event.ContextMenuListener; import org.adempiere.webui.panel.AbstractADWindowPanel; import org.adempiere.webui.session.SessionManager; import org.adempiere.webui.util.GridTabDataBinder; import org.adempiere.webui.window.ADWindow; import org.compiere.model.GridField; import org.compiere.model.GridTab; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.NamePair; import org.zkoss.xml.XMLs; import org.zkoss.zk.au.out.AuFocus; import org.zkoss.zk.ui.Component; 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.Div; import org.zkoss.zul.Grid; import org.zkoss.zul.Paging; import org.zkoss.zul.RendererCtrl; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.RowRendererExt; import org.zkoss.zhtml.Input; import org.zkoss.zhtml.Label; import org.zkoss.zhtml.Text; /** * Row renderer for GridTab grid. * @author hengsin * * @author Teo Sarca, teo.sarca@gmail.com * <li>BF [ 2996608 ] GridPanel is not displaying time * https://sourceforge.net/tracker/?func=detail&aid=2996608&group_id=176962&atid=955896 */ public class GridTabRowRenderer implements RowRenderer, RowRendererExt, RendererCtrl { private static final String CURRENT_ROW_STYLE = "border-top: 2px solid #6f97d2; border-bottom: 2px solid #6f97d2"; private static final int MAX_TEXT_LENGTH = 60; private GridTab gridTab; private int windowNo; private GridTabDataBinder dataBinder; private Map<GridField, WEditor> editors = new LinkedHashMap<GridField, WEditor>(); private Paging paging; private Map<String, Map<Object, String>> lookupCache = null; private RowListener rowListener; private Grid grid = null; private GridPanel gridPanel = null; private Row currentRow; private Object[] currentValues; private boolean editing = false; private int currentRowIndex = -1; private AbstractADWindowPanel m_windowPanel; /** * * @param gridTab * @param windowNo */ public GridTabRowRenderer(GridTab gridTab, int windowNo) { this.gridTab = gridTab; this.windowNo = windowNo; this.dataBinder = new GridTabDataBinder(gridTab); } private WEditor getEditorCell(GridField gridField, Object object, int i) { WEditor editor = editors.get(gridField); if (editor != null) { if (editor instanceof WButtonEditor) { if (m_windowPanel != null) { ((WButtonEditor)editor).addActionListener(m_windowPanel); } else { Object window = SessionManager.getAppDesktop().findWindow(windowNo); if (window != null && window instanceof ADWindow) { AbstractADWindowPanel windowPanel = ((ADWindow)window).getADWindowPanel(); ((WButtonEditor)editor).addActionListener(windowPanel); } } } else { editor.addValueChangeListener(dataBinder); } gridField.removePropertyChangeListener(editor); gridField.addPropertyChangeListener(editor); editor.setValue(gridField.getValue()); //streach component to fill grid cell if (editor.getComponent() instanceof Textbox) ((HtmlBasedComponent)editor.getComponent()).setWidth("98%"); else editor.fillHorizontal(); } return editor; } private int getColumnIndex(GridField field) { GridField[] fields = gridTab.getFields(); for(int i = 0; i < fields.length; i++) { if (fields[i] == field) return i; } return 0; } private Component createReadonlyCheckbox(Object value) { Checkbox checkBox = new Checkbox(); if (value != null && "true".equalsIgnoreCase(value.toString())) checkBox.setChecked(true); else checkBox.setChecked(false); checkBox.setDisabled(true); return checkBox; } private String getDisplayText(Object value, GridField gridField) { if (value == null) return ""; if (gridField.isEncryptedField()) { return "********"; } else if (gridField.isLookup()) { if (lookupCache != null) { Map<Object, String> cache = lookupCache.get(gridField.getColumnName()); if (cache != null && cache.size() >0) { String text = cache.get(value); if (text != null) { return text; } } } NamePair namepair = gridField.getLookup().get(value); if (namepair != null) { String text = namepair.getName(); if (lookupCache != null) { Map<Object, String> cache = lookupCache.get(gridField.getColumnName()); if (cache == null) { cache = new HashMap<Object, String>(); lookupCache.put(gridField.getColumnName(), cache); } cache.put(value, text); } return text; } else return ""; } else if (gridTab.getTableModel().getColumnClass(getColumnIndex(gridField)).equals(Timestamp.class)) { int displayType = DisplayType.Date; if (gridField != null && gridField.getDisplayType() == DisplayType.DateTime) displayType = DisplayType.DateTime; SimpleDateFormat dateFormat = DisplayType.getDateFormat(displayType, AEnv.getLanguage(Env.getCtx())); return dateFormat.format((Timestamp)value); } else if (DisplayType.isNumeric(gridField.getDisplayType())) { return DisplayType.getNumberFormat(gridField.getDisplayType(), AEnv.getLanguage(Env.getCtx())).format(value); } else if (DisplayType.Button == gridField.getDisplayType()) { return ""; } else if (DisplayType.Image == gridField.getDisplayType()) { if (value == null || (Integer)value <= 0) return ""; else return "..."; } else return value.toString(); } private Component getDisplayComponent(Object value, GridField gridField) { Component component; if (gridField.getDisplayType() == DisplayType.YesNo) { component = createReadonlyCheckbox(value); } else { String text = getDisplayText(value, gridField); Label label = new Label(); setLabelText(text, label); component = label; } return component; } /** * @param text * @param label */ private void setLabelText(String text, Label label) { String display = text; if (text != null && text.length() > MAX_TEXT_LENGTH) display = text.substring(0, MAX_TEXT_LENGTH - 3) + "..."; if (display != null) display = XMLs.encodeText(display); label.appendChild(new Text(display)); if (text != null && text.length() > MAX_TEXT_LENGTH) label.setDynamicProperty("title", text); else label.setDynamicProperty("title", ""); } /** * * @return active editor list */ public List<WEditor> getEditors() { List<WEditor> editorList = new ArrayList<WEditor>(); if (!editors.isEmpty()) editorList.addAll(editors.values()); return editorList; } /** * @param paging */ public void setPaging(Paging paging) { this.paging = paging; } /** * Detach all editor and optionally set the current value of the editor as cell label. * @param updateCellLabel */ public void stopEditing(boolean updateCellLabel) { if (!editing) { return; } else { editing = false; } Row row = null; for (Entry<GridField, WEditor> entry : editors.entrySet()) { if (entry.getValue().getComponent().getParent() != null) { Component child = entry.getValue().getComponent(); Div div = null; while (div == null && child != null) { Component parent = child.getParent(); if (parent instanceof Div && parent.getParent() instanceof Row) div = (Div)parent; else child = parent; } Component component = div.getFirstChild(); if (updateCellLabel) { if (component instanceof Label) { Label label = (Label)component; label.getChildren().clear(); String text = getDisplayText(entry.getValue().getValue(), entry.getValue().getGridField()); setLabelText(text, label); } else if (component instanceof Checkbox) { Checkbox checkBox = (Checkbox)component; Object value = entry.getValue().getValue(); if (value != null && "true".equalsIgnoreCase(value.toString())) checkBox.setChecked(true); else checkBox.setChecked(false); } } component.setVisible(true); if (row == null) row = ((Row)div.getParent()); entry.getValue().getComponent().detach(); entry.getKey().removePropertyChangeListener(entry.getValue()); entry.getValue().removeValuechangeListener(dataBinder); } } GridTableListModel model = (GridTableListModel) grid.getModel(); model.setEditing(false); } /** * @param row * @param data * @see RowRenderer#render(Row, Object) */ public void render(Row row, Object data) throws Exception { //don't render if not visible if (gridPanel != null && !gridPanel.isVisible()) { return; } if (grid == null) grid = (Grid) row.getParent().getParent(); if (rowListener == null) rowListener = new RowListener((Grid)row.getParent().getParent()); currentValues = (Object[])data; int columnCount = gridTab.getTableModel().getColumnCount(); GridField[] gridField = gridTab.getFields(); Grid grid = (Grid) row.getParent().getParent(); org.zkoss.zul.Columns columns = grid.getColumns(); int rowIndex = row.getParent().getChildren().indexOf(row); if (paging != null && paging.getPageSize() > 0) { rowIndex = (paging.getActivePage() * paging.getPageSize()) + rowIndex; } int colIndex = -1; int compCount = 0; for (int i = 0; i < columnCount; i++) { if (!gridField[i].isDisplayed()) { continue; } colIndex ++; Div div = new Div(); String divStyle = "border: none; width: 100%; height: 100%;"; org.zkoss.zul.Column column = (org.zkoss.zul.Column) columns.getChildren().get(colIndex); if (column.isVisible()) { compCount++; Component component = getDisplayComponent(currentValues[i], gridField[i]); div.appendChild(component); // if (compCount == 1) { //add hidden input component to help focusing to row div.appendChild(createAnchorInput()); // } if (DisplayType.YesNo == gridField[i].getDisplayType() || DisplayType.Image == gridField[i].getDisplayType()) { divStyle += "text-align:center; "; } else if (DisplayType.isNumeric(gridField[i].getDisplayType())) { divStyle += "text-align:right; "; } } div.setStyle(divStyle); div.setAttribute("columnName", gridField[i].getColumnName()); div.addEventListener(Events.ON_CLICK, rowListener); div.addEventListener(Events.ON_DOUBLE_CLICK, rowListener); row.appendChild(div); } if (rowIndex == gridTab.getCurrentRow()) { setCurrentRow(row); } row.addEventListener(Events.ON_OK, rowListener); } /** * @param component * @return */ private Input createAnchorInput() { Input input = new Input(); input.setDynamicProperty("type", "text"); input.setValue(""); input.setDynamicProperty("readonly", "readonly"); input.setStyle("border: none; display: none; width: 3px;"); return input; } /** * @param row */ public void setCurrentRow(Row row) { if (currentRow != null && currentRow.getParent() != null && currentRow != row) { currentRow.setStyle(null); } currentRow = row; currentRow.setStyle(CURRENT_ROW_STYLE); if (currentRowIndex == gridTab.getCurrentRow()) { if (editing) { stopEditing(false); editCurrentRow(); } } else { currentRowIndex = gridTab.getCurrentRow(); if (editing) { stopEditing(false); } } } /** * @return Row */ public Row getCurrentRow() { return currentRow; } /** * @return current row index ( absolute ) */ public int getCurrentRowIndex() { return currentRowIndex; } /** * Enter edit mode */ public void editCurrentRow() { if (currentRow != null && currentRow.getParent() != null && currentRow.isVisible() && grid != null && grid.isVisible() && grid.getParent() != null && grid.getParent().isVisible()) { int columnCount = gridTab.getTableModel().getColumnCount(); GridField[] gridField = gridTab.getFields(); org.zkoss.zul.Columns columns = grid.getColumns(); int colIndex = -1; for (int i = 0; i < columnCount; i++) { if (!gridField[i].isDisplayed()) { continue; } colIndex ++; if (editors.get(gridField[i]) == null) editors.put(gridField[i], WebEditorFactory.getEditor(gridField[i], true)); org.zkoss.zul.Column column = (org.zkoss.zul.Column) columns.getChildren().get(colIndex); if (column.isVisible()) { Div div = (Div) currentRow.getChildren().get(colIndex); WEditor editor = getEditorCell(gridField[i], currentValues[i], i); div.appendChild(editor.getComponent()); WEditorPopupMenu popupMenu = editor.getPopupMenu(); if (popupMenu != null) { popupMenu.addMenuListener((ContextMenuListener)editor); div.appendChild(popupMenu); } div.getFirstChild().setVisible(false); //check context if (!gridField[i].isDisplayed(true)) { editor.setVisible(false); } editor.setReadWrite(gridField[i].isEditable(true)); } } editing = true; GridTableListModel model = (GridTableListModel) grid.getModel(); model.setEditing(true); } } /** * @see RowRendererExt#getControls() */ public int getControls() { return DETACH_ON_RENDER; } /** * @see RowRendererExt#newCell(Row) */ public Component newCell(Row row) { return null; } /** * @see RowRendererExt#newRow(Grid) */ public Row newRow(Grid grid) { return null; } /** * @see RendererCtrl#doCatch(Throwable) */ public void doCatch(Throwable ex) throws Throwable { lookupCache = null; } /** * @see RendererCtrl#doFinally() */ public void doFinally() { lookupCache = null; } /** * @see RendererCtrl#doTry() */ public void doTry() { lookupCache = new HashMap<String, Map<Object,String>>(); } /** * set focus to first active editor */ public void setFocusToEditor() { if (currentRow != null && currentRow.getParent() != null) { WEditor toFocus = null; WEditor firstEditor = null; for (WEditor editor : getEditors()) { if (editor.isHasFocus() && editor.isVisible() && editor.getComponent().getParent() != null) { toFocus = editor; break; } if (editor.isVisible() && editor.getComponent().getParent() != null) { if (toFocus == null && editor.isReadWrite()) { toFocus = editor; } if (firstEditor == null) firstEditor = editor; } } if (toFocus != null) { Component c = toFocus.getComponent(); if (c instanceof EditorBox) { c = ((EditorBox)c).getTextbox(); } Clients.response(new AuFocus(c)); } else if (firstEditor != null) { Component c = firstEditor.getComponent(); if (c instanceof EditorBox) { c = ((EditorBox)c).getTextbox(); } Clients.response(new AuFocus(c)); } } } /** * * @param gridPanel */ public void setGridPanel(GridPanel gridPanel) { this.gridPanel = gridPanel; } class RowListener implements EventListener { private Grid _grid; public RowListener(Grid grid) { _grid = grid; } public void onEvent(Event event) throws Exception { if (Events.ON_CLICK.equals(event.getName())) { Event evt = new Event(Events.ON_CLICK, _grid, event.getTarget()); Events.sendEvent(_grid, evt); } else if (Events.ON_DOUBLE_CLICK.equals(event.getName())) { Event evt = new Event(Events.ON_DOUBLE_CLICK, _grid, _grid); Events.sendEvent(_grid, evt); } else if (Events.ON_OK.equals(event.getName())) { Event evt = new Event(Events.ON_OK, _grid, _grid); Events.sendEvent(_grid, evt); } } } /** * @return boolean */ public boolean isEditing() { return editing; } /** * @param windowPanel */ public void setADWindowPanel(AbstractADWindowPanel windowPanel) { this.m_windowPanel = windowPanel; } }