BrightSide Workbench Full Report + Source Code
EditableGrid.java
Go to the documentation of this file.
1 /*
2  * TurrĂ³ i Cutiller Foundation. License notice.
3  * Copyright (C) 2011 Lluis TurrĂ³ Cutiller <http://www.turro.org/>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Affero General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 package org.turro.zkoss.grid;
19 
20 import java.util.ArrayList;
21 import java.util.Date;
22 import java.util.Iterator;
23 import java.util.List;
24 import org.turro.string.Strings;
25 import org.turro.elephant.impl.util.StringParser;
26 import org.turro.elephant.util.BooleanFormats;
27 import org.turro.elephant.util.DateFormats;
28 import org.turro.elephant.util.DecimalFormats;
29 import org.turro.elephant.util.Messages;
30 import org.turro.i18n.I_;
31 import org.turro.zkoss.input.*;
32 import org.zkoss.zk.ui.Component;
33 import org.zkoss.zk.ui.HtmlBasedComponent;
34 import org.zkoss.zk.ui.event.Event;
35 import org.zkoss.zk.ui.event.EventListener;
36 import org.zkoss.zk.ui.event.Events;
37 import org.zkoss.zk.ui.ext.AfterCompose;
38 import org.zkoss.zul.*;
39 import org.zkoss.zul.impl.InputElement;
40 import org.zkoss.zul.impl.XulElement;
41 
46 public abstract class EditableGrid<V> extends PagingGrid implements AfterCompose {
47 
53  public boolean allowInsertions, allowDeletions,
54  selectInput = true, readOnly = false,
55  showZeros = false;
56 
58  protected String ctrlKeys;
59  protected Row lastRow;
60 
61  private boolean onlyRenderer = false;
62 
63  public EditableGrid() {
64  setSclass("editableGrid");
65  }
66 
67  public boolean isAllowDeletions() {
68  return allowDeletions;
69  }
70 
71  public void setAllowDeletions(boolean allowDeletions) {
72  this.allowDeletions = allowDeletions;
73  }
74 
75  public boolean isAllowInsertions() {
76  return allowInsertions;
77  }
78 
79  public void setAllowInsertions(boolean allowInsertions) {
80  this.allowInsertions = allowInsertions;
81  }
82 
84  return currentCell;
85  }
86 
87  public Row getLastRow() {
88  return lastRow;
89  }
90 
91  public boolean isReadOnly() {
92  return readOnly;
93  }
94 
95  public void setReadOnly(boolean readOnly) {
96  this.readOnly = readOnly;
97  }
98 
99  public boolean isSelectInput() {
100  return selectInput;
101  }
102 
103  public void setSelectInput(boolean selectInput) {
104  this.selectInput = selectInput;
105  }
106 
107  public boolean isShowZeros() {
108  return showZeros;
109  }
110 
111  public void setShowZeros(boolean showZeros) {
112  this.showZeros = showZeros;
113  }
114 
115  public void clearTable() {
116  clearColumns();
117  clearRows();
118  clearFoot();
119  }
120 
121  public void clearFoot() {
122  if(getFoot() != null) {
123  getFoot().getChildren().clear();
124  }
125  }
126 
127  public void clearRows() {
128  if(getRows() != null) {
129  getRows().getChildren().clear();
130  }
131  }
132 
133  public void clearValidRows() {
134  if(getRows() != null) {
135  Iterator it = getRows().getChildren().iterator();
136  while(it.hasNext()) {
137  Row r = (Row) it.next();
138  if(isValid((V) r.getValue())) {
139  it.remove();
140  }
141  }
142  }
143  }
144 
145  public void clearColumns() {
146  if(getColumns() != null) {
147  getColumns().getChildren().clear();
148  }
149  }
150 
151  public List<V> getValues() {
152  List<V> l = new ArrayList<V>();
153  for(Component row : getRows().getChildren()) {
154  if(isValid((V) ((Row)row).getValue())) {
155  l.add((V) ((Row)row).getValue());
156  }
157  }
158  return l;
159  }
160 
161  public List<V> getAllValues() {
162  List<V> l = new ArrayList<V>();
163  for(Component row : getRows().getChildren()) {
164  l.add((V) ((Row)row).getValue());
165  }
166  return l;
167  }
168 
169  public EventListener getEditListener() {
170  if(editListener == null) {
171  editListener = new EditEventListener(this);
172  }
173  return editListener;
174  }
175 
176  public EventListener getNextListener() {
177  if(nextListener == null) {
178  nextListener = new NextEventListener(this);
179  }
180  return nextListener;
181  }
182 
183  public EventListener getKeyListener() {
184  if(keyListener == null) {
185  keyListener = new KeyEventListener(this);
186  }
187  return keyListener;
188  }
189 
190  public EventListener getBlurListener() {
191  if(blurListener == null) {
192  blurListener = new BlurEventListener(this);
193  }
194  return blurListener;
195  }
196 
197  public EventListener getCancelListener() {
198  if(cancelListener == null) {
200  }
201  return cancelListener;
202  }
203 
204  public void processEdition(EditableCell editableCell) {
205  if(currentCell != null) {
206  if(currentCell.getEditor() != null && !currentCell.isReadOnly()) {
207  Object value = getEditorValue(currentCell);
208  if(isCellValid(currentCell, value)) {
209  setCellValue(currentCell, value);
210  } else {
211  return;
212  }
213  }
215  if(editableCell == null || !lastRow.equals(editableCell.getRow())) {
216  Events.postEvent(new Event(Events.ON_CHANGE, this, editableCell));
217  processRowSclass(lastRow);
219  lastRow = null;
220  }
221  currentCell = null;
222  }
223  if(editableCell != null) {
224  currentCell = editableCell;
225  currentCell.edit();
227  }
228  }
229 
230  public void cancelEdition() {
231  if(currentCell != null) {
233  currentCell = null;
234  }
235  }
236 
237  public HtmlBasedComponent getEditor(EditableCell editableCell) {
238  HtmlBasedComponent hbc = createEditor(editableCell);
239  hbc.setSclass("grid-editor");
240  hbc.addEventListener(Events.ON_OK, getNextListener());
241  hbc.addEventListener(Events.ON_CANCEL, getCancelListener());
242  hbc.addEventListener(Events.ON_BLUR, getBlurListener());
243  hbc.addEventListener(Events.ON_CTRL_KEY, getKeyListener());
244  hbc.setWidgetListener("onKeyDown", "if(event.keyCode == 9 && !(event.ctrlKey || event.shiftKey)) event.keyCode = 13;");
245  ((XulElement) hbc).setCtrlKeys(
246  (!Strings.isBlank(ctrlKeys) ? ctrlKeys : "") +
247  (hbc instanceof Listbox ? "" : "#down#up"));
248  if(!canEditRow(editableCell.getRow())) {
249  if(hbc instanceof Datebox) {
250  ((Datebox) hbc).setButtonVisible(false);
251  ((InputElement) hbc).setReadonly(true);
252  } else if(hbc instanceof InputElement) {
253  ((InputElement) hbc).setReadonly(true);
254  } else if(hbc instanceof Listbox) {
255  ((Listbox) hbc).setDisabled(true);
256  } else if(hbc instanceof Checkbox) {
257  ((Checkbox) hbc).setDisabled(true);
258  }
259  }
260  return hbc;
261  }
262 
263  public Row appendValue() {
264  Row row = (Row) getRows().getLastChild();
265  if(row != null && !isValid((V) row.getValue())) {
266  return row;
267  }
268  return addNewRow();
269  }
270 
271  public Row addNewRow() {
272  Row row = new Row();
273  row.setSclass("invalid");
274  if(getRows().appendChild(row)) {
275  setRowCount(getRows().getChildren().size());
276  initiateRow(row, null);
277  initiateRowListeners(row);
278  return row;
279  }
280  return null;
281  }
282 
283  public void deleteSelectedRow(final Row row) {
284  if(canDeleteRow(row)) {
285  Messages.confirmDeletion().show(() -> {
286  if(deleteRow(row)) {
287  row.detach();
288  Events.postEvent(new Event(Events.ON_CHANGE, EditableGrid.this));
289  }
290  });
291  }
292  }
293 
294  public void insertInSelectedRow(Row row) {
295  Row newRow = new Row();
296  newRow.setSclass("invalid");
297  getRows().insertBefore(newRow, row);
298  initiateRow(newRow, null);
299  initiateRowListeners(newRow);
300  setRowCount(getRows().getChildren().size());
301  }
302 
303  public Column getEditableColumn(int cellIndex) {
304  Columns cols = getColumns(true);
305  if(cols != null && cellIndex < cols.getChildren().size()) {
306  return (Column) cols.getChildren().get(cellIndex);
307  }
308  return null;
309  }
310 
311  public void updateRow(Row row) {
312  onlyRenderer = true;
313  processRowSclass(row);
314  try {
315  for(Component c : (List<Component>) row.getChildren()) {
316  if(!(c instanceof Detail)) {
317  EditableCell ec = new EditableCell(this, row, c);
318  setCellValue(ec, getCellValue(ec));
319  }
320  }
321  } finally {
322  onlyRenderer = false;
323  }
324  }
325 
326  public void updateRows() {
327  for(Component row : getRows().getChildren()) {
328  updateRow(((Row)row));
329  }
330  }
331 
332  public EditableColumn addColumn(String label, Class javaClass, String property,
333  String format, int scale, boolean onlyDate, boolean readOnly) {
334  EditableColumn ec = new EditableColumn();
335  ec.setLabel(label);
336  ec.setFormat(format);
337  ec.setJavaClass(javaClass);
338  ec.setOnlyDate(onlyDate);
339  ec.setProperty(property);
340  ec.setReadOnly(readOnly);
341  ec.setScale(scale);
342  if(ec.isNumber()) {
343  ec.setAlign("right");
344  if(Strings.isBlank(format)) {
346  }
347  }
348  getColumns(true).appendChild(ec);
349  return ec;
350  }
351 
352  public EditableColumn addColumn(String label, String javaClass, String property,
353  String format, int scale, boolean onlyDate, boolean readOnly) throws ClassNotFoundException {
354  EditableColumn ec = new EditableColumn();
355  ec.setLabel(label);
356  ec.setFormat(format);
357  ec.setJavaClass(javaClass);
358  ec.setOnlyDate(onlyDate);
359  ec.setProperty(property);
360  ec.setReadOnly(readOnly);
361  ec.setScale(scale);
362  if(ec.isNumber()) {
363  ec.setAlign("right");
364  if(Strings.isBlank(format)) {
366  }
367  }
368  getColumns(true).appendChild(ec);
369  return ec;
370  }
371 
372  public EditableColumn addColumn(String image, String tooltip, Class javaClass, String property,
373  String format, int scale, boolean onlyDate, boolean readOnly) {
374  EditableColumn ec = new EditableColumn();
375  ec.setImage(image);
376  ec.setTooltiptext(tooltip);
377  ec.setFormat(format);
378  ec.setJavaClass(javaClass);
379  ec.setOnlyDate(onlyDate);
380  ec.setProperty(property);
381  ec.setReadOnly(readOnly);
382  ec.setScale(scale);
383  if(ec.isNumber()) {
384  ec.setAlign("right");
385  if(Strings.isBlank(format)) {
387  }
388  }
389  getColumns(true).appendChild(ec);
390  return ec;
391  }
392 
393  public EditableColumn addColumn(String image, String tooltip, String javaClass, String property,
394  String format, int scale, boolean onlyDate, boolean readOnly) throws ClassNotFoundException {
395  EditableColumn ec = new EditableColumn();
396  ec.setImage(image);
397  ec.setTooltiptext(tooltip);
398  ec.setFormat(format);
399  ec.setJavaClass(javaClass);
400  ec.setOnlyDate(onlyDate);
401  ec.setProperty(property);
402  ec.setReadOnly(readOnly);
403  ec.setScale(scale);
404  if(ec.isNumber()) {
405  ec.setAlign("right");
406  if(Strings.isBlank(format)) {
408  }
409  }
410  getColumns(true).appendChild(ec);
411  return ec;
412  }
413 
414  // default - returns text, boolean
415  protected Object getCellValue(EditableCell editableCell) {
416  if(editableCell.getRow().getValue() != null) {
417  return StringParser.getValueFrom(editableCell.getProperty(), editableCell.getRow().getValue());
418  } else {
419  HtmlBasedComponent hbc = (HtmlBasedComponent) editableCell.getRenderer();
420  if(hbc instanceof Checkbox) {
421  return ((Checkbox) hbc).isChecked();
422  } else if(hbc instanceof Label) {
423  return ((Label) hbc).getValue();
424  }
425  }
426  return null;
427  }
428 
429  // default - returns text, boolean, number, date
430  protected Object getEditorValue(EditableCell editableCell) {
431  HtmlBasedComponent hbc = (HtmlBasedComponent) editableCell.getEditor();
432  if(hbc instanceof Checkbox) {
433  return ((Checkbox) hbc).isChecked();
434  } else if(hbc instanceof Percentbox) {
435  return ((Percentbox) hbc).getValue();
436  } else if(hbc instanceof Decimalbox) {
437  return StringParser.convertToClass(editableCell.getJavaClass(), ((Decimalbox) hbc).getValue());
438  } else if(hbc instanceof ExpressionInput) {
439  return StringParser.convertToClass(editableCell.getJavaClass(), ((ExpressionInput) hbc).getNumber(editableCell.getScale()));
440  } else if(hbc instanceof Longbox) {
441  return StringParser.convertToClass(editableCell.getJavaClass(), ((Longbox) hbc).getValue());
442  } else if(hbc instanceof Datebox) {
443  return ((Datebox) hbc).getValue();
444  } else if(hbc instanceof Timebox) {
445  return ((Timebox) hbc).getValue();
446  } else if(hbc instanceof KnowsID) {
447  return ((KnowsID) hbc).getObjectId();
448  } else if(hbc instanceof GenericListbox) {
449  return ((GenericListbox) hbc).getObjectValue();
450  } else if(hbc instanceof GenericCombobox) {
451  return ((GenericCombobox) hbc).getObjectValue();
452  } else if(hbc instanceof GenericBandbox) {
453  return ((GenericBandbox) hbc).getObjectValue();
454  } else if(hbc instanceof Listbox) {
455  return ((Listbox) hbc).getSelectedItem().getValue();
456  } else if(hbc instanceof Textbox) {
457  return ((Textbox) hbc).getValue();
458  } else {
459  return null;
460  }
461  }
462 
463  // default - sets text or checks
464  protected void setCellValue(EditableCell editableCell, Object value) {
465  if(!onlyRenderer) {
466  if(editableCell.getProperty() != null && editableCell.getRow().getValue() != null) {
467  StringParser.setValueFrom(editableCell.getProperty(), editableCell.getRow().getValue(),
468  editableCell.getJavaClass(), value);
469  }
470  }
471  HtmlBasedComponent hbc = (HtmlBasedComponent) editableCell.getRenderer();
472  if(hbc instanceof Checkbox) {
473  ((Checkbox) hbc).setChecked((Boolean) value);
474  } else if(hbc instanceof Label) {
475  ((Label) hbc).setValue((String) formatCell(editableCell, value));
476  makeItEditable(hbc);
477  }
478  if(!onlyRenderer) {
479  cellChanged(editableCell, value);
480  }
481  }
482 
483  protected String formatCell(EditableCell editableCell, Object value) {
484  if(value instanceof Boolean) {
485  return BooleanFormats.format((Boolean) value);
486  } else if(value instanceof Number) {
487  if(((Number) value).doubleValue() == 0 && showZeros == false) {
488  return "";
489  }
490  return editableCell.getFormat() != null ?
491  DecimalFormats.format((Number) value, editableCell.getFormat()) :
492  DecimalFormats.format((Number) value);
493  } else if(value instanceof Date) {
494  if(editableCell.isOnlyTime()) {
495  return DateFormats.formatTime((Date) value);
496  } else {
497  return DateFormats.format((Date) value, editableCell.isOnlyDate());
498  }
499  } else if(value instanceof Enum) {
500  return I_.byKey(((Enum) value).toString());
501  } else if(editableCell.getScale() > 0) {
502  return Multilinebox.formatValue((String) value);
503  } else {
504  return (String) value;
505  }
506  }
507 
508  // default - textbox, checkbox, decimalbox, datebox
509  protected HtmlBasedComponent createEditor(EditableCell editableCell) {
510  Object value = getCellValue(editableCell);
511  if(editableCell.isBoolean()) {
512  Checkbox cb = new Checkbox();
513  if(!editableCell.isReadOnly() &&
514  editableCell.getRenderer() instanceof Checkbox &&
515  canEditRow(editableCell.getRow())) {
516  cb.setChecked(((Checkbox)editableCell.getRenderer()).isChecked());
517  } else {
518  cb.setChecked(value != null ? (Boolean) value : false);
519  }
520  //TODO: http://www.zkoss.org/forum/listComment/12889;jsessionid=9462603FC918116CEAC38826F5F75066.zkzh
521  //cb.setWidgetOverride("focus", "function () {this.$n('real').focus();}");
522  return cb;
523  } else if(editableCell.isNumber()) {
524  ExpressionInput ei = new ExpressionInput();
525  ei.setValue(value);
526  //ei.setStyle("padding:0px;margin:0px;");
527  ei.setReadonly(editableCell.isReadOnly());
528  return ei;
529 // Decimalbox db = new Decimalbox((BigDecimal) StringParser.convertToClass(BigDecimal.class, value));
530 // db.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
531 // db.setFormat(editableCell.getFormat());
532 // db.setStyle("padding:0px;margin:0px;");
533 // db.setWidth("100%");
534 // db.setReadonly(editableCell.isReadOnly());
535 // return db;
536  } else if(editableCell.isDate()) {
537  if(editableCell.isOnlyTime()) {
538  Timebox tb = new Timebox((Date) value);
539  tb.setFormat(editableCell.getFormat());
540  tb.setReadonly(editableCell.isReadOnly());
541  tb.setButtonVisible(false);
542  return tb;
543  } else {
544  DateboxShort db = new DateboxShort((Date) value);
545  db.setWithTime(!editableCell.isOnlyDate());
546  db.setFormat(editableCell.getFormat());
547  db.setReadonly(editableCell.isReadOnly());
548  return db;
549  }
550  } else if(editableCell.getScale() > 0) {
551  Multilinebox mb = new Multilinebox((String) value, editableCell.getScale());
552  mb.setReadonly(editableCell.isReadOnly() || mb.isReadonly());
553  return mb;
554  } else {
555  Textbox tb = new Textbox((String) value);
556  //tb.setStyle("padding:0px;margin:0px;");
557  tb.setReadonly(editableCell.isReadOnly());
558  tb.setMaxlength(255);
559  return tb;
560  }
561  }
562 
563  // default - label, checkbox
564  protected HtmlBasedComponent createRenderer(EditableCell editableCell) {
565  Object value = getCellValue(editableCell);
566  if(value instanceof Boolean) {
567  Checkbox cb = new Checkbox();
568  cb.setChecked((Boolean) value);
569  return cb;
570  } else {
571  Label l = new Label(formatCell(editableCell, value));
572  //l.setMultiline(true);
573  makeItEditable(l);
574  return l;
575  }
576  }
577 
578  // default - posts a changing event
579  protected void cellChanged(EditableCell editableCell, Object value) {
580  Events.postEvent(new Event(Events.ON_CHANGING, this, editableCell));
581  }
582 
583  protected boolean isCellValid(EditableCell editableCell, Object value) {
584  return true;
585  }
586 
587  protected boolean canDeleteRow(Row row) {
588  return true;
589  }
590 
591  protected boolean canEditRow(Row row) {
592  return true;
593  }
594 
595  @Override
596  public void afterCompose() {
597  ctrlKeys = "";
598  if(allowDeletions) {
599  ctrlKeys += "^#del";
600  }
601  if(allowInsertions) {
602  ctrlKeys += "^#ins";
603  }
604  initiateListeners();
605  }
606 
607  protected abstract void initiateRow(Row row, V value);
608  protected abstract boolean deleteRow(Row row);
609  protected abstract void rowChanged(Row row);
610  protected abstract boolean isValid(V v);
611 
612  private void initiateListeners() {
613  if(getRows() == null) return;
614  setRowCount(getRows().getChildren().size());
615  for(Component row : getRows().getChildren()) {
616  initiateRowListeners(((Row)row));
617  }
618  }
619 
620  private void initiateRowListeners(Row r) {
621  int ccount = 0;
622  for(Component c : getColumns(true).getChildren()) {
623  if(c instanceof EditableColumn) {
624  r.appendChild(createRenderer(new EditableCell(this, r, ccount)));
625  }
626  ccount++;
627  }
628  for(Component c : (List<Component>) r.getChildren()) {
629  if(readOnly) {
630 
631  } else if(c instanceof Detail) {
632  // nothing
633  } else if(c instanceof Checkbox) {
634  c.addEventListener(Events.ON_CHECK, getEditListener());
635  } else {
636  c.addEventListener(Events.ON_CLICK, getEditListener());
637  }
638  }
639  }
640 
641  private void processRowSclass(Row row) {
642  if(row.getValue() != null && isValid((V) row.getValue())) {
643  row.setSclass(null);
644  } else {
645  row.setSclass("invalid");
646  }
647  }
648 
649  private void makeItEditable(HtmlBasedComponent hbc) {
650  ((Label) hbc).setStyle("display:block;");
651  if (Strings.isBlank(((Label) hbc).getValue())) {
652  ((Label) hbc).setValue("*****");
653  ((Label) hbc).setStyle("color:#eee;display:block;");
654  }
655  }
656 
657 }
static void setValueFrom(String pathToValue, Object object, Class javaClass, Object value)
static Object getValueFrom(String pathToValue, Object object)
static Object convertToClass(Class javaClass, Object value)
static String format(boolean value)
static final String format(Date d, boolean dateOnly)
static String formatTime(Date d)
static String format(Number value, String pattern)
static String getStringFormat(int fractionDigits)
static Messages confirmDeletion()
Definition: Messages.java:87
static String byKey(String key)
Definition: I_.java:83
void setReadOnly(boolean readOnly)
void setOnlyDate(boolean onlyDate)
void setCellValue(EditableCell editableCell, Object value)
void processEdition(EditableCell editableCell)
void setAllowDeletions(boolean allowDeletions)
EditableColumn addColumn(String image, String tooltip, Class javaClass, String property, String format, int scale, boolean onlyDate, boolean readOnly)
EditableColumn addColumn(String label, Class javaClass, String property, String format, int scale, boolean onlyDate, boolean readOnly)
EditableColumn addColumn(String image, String tooltip, String javaClass, String property, String format, int scale, boolean onlyDate, boolean readOnly)
void setAllowInsertions(boolean allowInsertions)
abstract boolean deleteRow(Row row)
Object getEditorValue(EditableCell editableCell)
abstract void rowChanged(Row row)
void cellChanged(EditableCell editableCell, Object value)
abstract void initiateRow(Row row, V value)
void setShowZeros(boolean showZeros)
void deleteSelectedRow(final Row row)
Object getCellValue(EditableCell editableCell)
CancelEventListener cancelListener
EditableColumn addColumn(String label, String javaClass, String property, String format, int scale, boolean onlyDate, boolean readOnly)
boolean isCellValid(EditableCell editableCell, Object value)
void setReadOnly(boolean readOnly)
HtmlBasedComponent createRenderer(EditableCell editableCell)
String formatCell(EditableCell editableCell, Object value)
void setSelectInput(boolean selectInput)
HtmlBasedComponent getEditor(EditableCell editableCell)
HtmlBasedComponent createEditor(EditableCell editableCell)
abstract boolean isValid(V v)
Column getEditableColumn(int cellIndex)
Foot getFoot(boolean create)
Columns getColumns(boolean create)
Rows getRows(boolean create)
void setWithTime(boolean withTime)
static String formatValue(String value)