View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.component.sheet;
23  
24  import java.util.ArrayList;
25  
26  import javax.el.ValueExpression;
27  import javax.faces.FacesException;
28  import javax.faces.component.UIComponent;
29  import javax.faces.component.UIInput;
30  import javax.faces.component.behavior.ClientBehaviorHolder;
31  import javax.faces.context.FacesContext;
32  
33  import org.primefaces.component.api.Widget;
34  import org.primefaces.model.SortOrder;
35  
36  /**
37   * Spreadsheet component wrappering the Handsontable jQuery UI component.
38   *
39   * @author Mark Lassiter / Melloware
40   * @since 6.2
41   */
42  abstract class SheetBase extends UIInput implements ClientBehaviorHolder, Widget {
43  
44      public static final String COMPONENT_FAMILY = "org.primefaces.extensions.component";
45      private static final String DEFAULT_RENDERER = "org.primefaces.extensions.component.SheetRenderer";
46  
47      /**
48       * Properties that are tracked by state saving.
49       */
50      @SuppressWarnings("java:S115")
51      enum PropertyKeys {
52          /**
53           * The local value of this {@link UIComponent}.
54           */
55          value,
56  
57          /**
58           * List to keep the filtered and sorted data.
59           */
60          filteredValue,
61  
62          /**
63           * Flag indicating whether or not this component is valid.
64           */
65          valid,
66  
67          /**
68           * The request scope attribute under which the data object for the current row will be exposed when iterating.
69           */
70          var,
71  
72          /**
73           * The IL8N Locale. Default is en-US.
74           */
75          locale,
76  
77          /**
78           * single, range, multiple
79           */
80          selectionMode,
81  
82          /**
83           * The selected row
84           */
85          selectedRow,
86  
87          /**
88           * The last selected row
89           */
90          selectedLastRow,
91  
92          /**
93           * The selected column
94           */
95          selectedColumn,
96  
97          /**
98           * The last selected column
99           */
100         selectedLastColumn,
101         /**
102          * flag indication whether or not to show column headers
103          */
104         showColumnHeaders,
105 
106         /**
107          * flag indication whether or not to show row headers
108          */
109         showRowHeaders,
110 
111         /**
112          * The custom row header to be used in place of the standard numeric header value
113          */
114         rowHeader,
115 
116         /**
117          * Maximum number of rows.
118          */
119         maxRows,
120 
121         /**
122          * Minimum number of rows.
123          */
124         minRows,
125 
126         /**
127          * Maximum number of columns.
128          */
129         maxCols,
130 
131         /**
132          * Minimum number of columns.
133          */
134         minCols,
135 
136         /**
137          * Fixed rows when scrolling
138          */
139         fixedRows,
140 
141         /**
142          * You can fix the bottom rows of the table, by using the fixedRowsBottom config option. This way, when you're scrolling the table, the fixed rows will
143          * stay at the bottom edge of the table's container.
144          */
145         fixedRowsBottom,
146 
147         /**
148          * Fixed columns when scrolling
149          */
150         fixedCols,
151 
152         /**
153          * Allow rows to be manually resizable
154          */
155         resizableRows,
156 
157         /**
158          * Allow columns to be resizable
159          */
160         resizableCols,
161 
162         /**
163          * Allow rows to be manually moved
164          */
165         movableRows,
166 
167         /**
168          * Allow columns to be manually moved
169          */
170         movableCols,
171 
172         /**
173          * The width of the component in pixels
174          */
175         width,
176 
177         /**
178          * The height of the component in pixels
179          */
180         height,
181 
182         /**
183          * The global error message to be displayed when the sheet is in error
184          */
185         errorMessage,
186 
187         /**
188          * Style of the html container element
189          */
190         style,
191 
192         /**
193          * User style class for sheet
194          */
195         styleClass,
196 
197         /**
198          * The row key, used to unqiuely identify each row for update operations
199          */
200         rowKey,
201 
202         /**
203          * The current sortBy value expression
204          */
205         sortBy,
206 
207         /**
208          * The current direction of the sort
209          */
210         sortOrder,
211 
212         /**
213          * Defines where the null values are placed in ascending sort order. Default value is "1" meaning null values are placed at the end in ascending mode
214          * and at beginning in descending mode. Set to "-1" for the opposite behavior.
215          */
216         nullSortOrder,
217 
218         /**
219          * Case sensitivity for sorting, insensitive by default.
220          */
221         caseSensitiveSort,
222 
223         /**
224          * The ID of the current column to be used for sorting. This is used only internally and not exposed to the consumer of the sheet component.
225          */
226         currentSortBy,
227 
228         /**
229          * The original sort direction saved off for reset
230          */
231         origSortOrder,
232 
233         /**
234          * The Handsontable stretchH value
235          */
236         stretchH,
237 
238         /**
239          * The style class to apply to each row in the sheet (EL expression)
240          */
241         rowStyleClass,
242         /**
243          * Flag indicating whether or not the sheet is read only
244          */
245         readOnly,
246 
247         /**
248          * The message displayed when no records are found
249          */
250         emptyMessage,
251 
252         /**
253          * Active Header style class
254          */
255         activeHeaderStyleClass,
256         /**
257          * Commented cell style class
258          */
259         commentedCellStyleClass,
260         /**
261          * Current column style class
262          */
263         currentColStyleClass,
264         /**
265          * Current header style class
266          */
267         currentHeaderStyleClass,
268         /**
269          * Current row style class
270          */
271         currentRowStyleClass,
272         /**
273          * Invalid cell style class
274          */
275         invalidCellStyleClass,
276         /**
277          * No Word Wrap style class
278          */
279         noWordWrapStyleClass,
280         /**
281          * Placeholder style class
282          */
283         placeholderCellStyleClass,
284         /**
285          * Read only style class
286          */
287         readOnlyCellStyleClass,
288         /**
289          * Allowing tabbing off the sheet when on the first or last cell to focus the next component.
290          */
291         allowTabOffSheet,
292         /**
293          * Keyboard focus tab index.
294          */
295         tabindex,
296         /**
297          * Name of javascript function to extend the options of the underlying Handsontable plugin.
298          */
299         extender
300     }
301 
302     /**
303      * Default constructor
304      */
305     public SheetBase() {
306         setRendererType(DEFAULT_RENDERER);
307     }
308 
309     @Override
310     public String getFamily() {
311         return COMPONENT_FAMILY;
312     }
313 
314     public void setStyleClass(final String styleClass) {
315         getStateHelper().put(PropertyKeys.styleClass, styleClass);
316     }
317 
318     public String getStyleClass() {
319         final Object result = getStateHelper().eval(PropertyKeys.styleClass, null);
320         if (result == null) {
321             return null;
322         }
323         return result.toString();
324     }
325 
326     public void setStretchH(final String value) {
327         getStateHelper().put(PropertyKeys.stretchH, value);
328     }
329 
330     public String getStretchH() {
331         final Object result = getStateHelper().eval(PropertyKeys.stretchH, null);
332         if (result == null) {
333             return null;
334         }
335         return result.toString();
336     }
337 
338     public void setEmptyMessage(final String value) {
339         getStateHelper().put(PropertyKeys.emptyMessage, value);
340     }
341 
342     public String getEmptyMessage() {
343         final Object result = getStateHelper().eval(PropertyKeys.emptyMessage, null);
344         if (result == null) {
345             return null;
346         }
347         return result.toString();
348     }
349 
350     /**
351      * Please note: The return type needs to be {@link Object}. Otherwise, evaluating the {@code sortBy} attribute as a value expression forces it into a
352      * string, and strings are sorted differently than numbers.
353      *
354      * @return The ID of the column to sort by.
355      */
356     public Object getSortBy() {
357         return getStateHelper().get(PropertyKeys.sortBy.name());
358     }
359 
360     public void setSortBy(final Object sortBy) {
361         getStateHelper().put(PropertyKeys.sortBy.name(), sortBy);
362     }
363 
364     public void setShowColumnHeaders(final boolean value) {
365         getStateHelper().put(PropertyKeys.showColumnHeaders, value);
366     }
367 
368     public boolean isShowColumnHeaders() {
369         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.showColumnHeaders, true).toString());
370     }
371 
372     public void setShowRowHeaders(final boolean value) {
373         getStateHelper().put(PropertyKeys.showRowHeaders, value);
374     }
375 
376     public boolean isShowRowHeaders() {
377         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.showRowHeaders, true).toString());
378     }
379 
380     public void setResizableRows(final boolean value) {
381         getStateHelper().put(PropertyKeys.resizableRows, value);
382     }
383 
384     public boolean isResizableRows() {
385         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.resizableRows, false).toString());
386     }
387 
388     public void setResizableCols(final boolean value) {
389         getStateHelper().put(PropertyKeys.resizableCols, value);
390     }
391 
392     public boolean isResizableCols() {
393         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.resizableCols, false).toString());
394     }
395 
396     public void setMovableRows(final boolean value) {
397         getStateHelper().put(PropertyKeys.movableRows, value);
398     }
399 
400     public boolean isMovableRows() {
401         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.movableRows, false).toString());
402     }
403 
404     public void setMovableCols(final boolean value) {
405         getStateHelper().put(PropertyKeys.movableCols, value);
406     }
407 
408     public boolean isMovableCols() {
409         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.movableCols, false).toString());
410     }
411 
412     public void setRowStyleClass(final String styleClass) {
413         getStateHelper().put(PropertyKeys.rowStyleClass, styleClass);
414     }
415 
416     public String getRowStyleClass() {
417         return (String) getStateHelper().eval(PropertyKeys.rowStyleClass, null);
418     }
419 
420     public String getActiveHeaderStyleClass() {
421         return (String) getStateHelper().eval(PropertyKeys.activeHeaderStyleClass, null);
422     }
423 
424     public void setReadOnly(final boolean value) {
425         getStateHelper().put(PropertyKeys.readOnly, value);
426     }
427 
428     public boolean isReadOnly() {
429         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.readOnly, Boolean.FALSE).toString());
430     }
431 
432     public void setActiveHeaderStyleClass(final String value) {
433         getStateHelper().put(PropertyKeys.activeHeaderStyleClass, value);
434     }
435 
436     public String getCommentedCellStyleClass() {
437         return (String) getStateHelper().eval(PropertyKeys.commentedCellStyleClass, null);
438     }
439 
440     public void setCommentedCellStyleClass(final String value) {
441         getStateHelper().put(PropertyKeys.commentedCellStyleClass, value);
442     }
443 
444     public String getCurrentColStyleClass() {
445         return (String) getStateHelper().eval(PropertyKeys.currentColStyleClass, null);
446     }
447 
448     public void setCurrentColStyleClass(final String value) {
449         getStateHelper().put(PropertyKeys.currentColStyleClass, value);
450     }
451 
452     public String getCurrentHeaderStyleClass() {
453         return (String) getStateHelper().eval(PropertyKeys.currentHeaderStyleClass, null);
454     }
455 
456     public void setCurrentHeaderStyleClass(final String value) {
457         getStateHelper().put(PropertyKeys.currentHeaderStyleClass, value);
458     }
459 
460     public String getCurrentRowStyleClass() {
461         return (String) getStateHelper().eval(PropertyKeys.currentRowStyleClass, null);
462     }
463 
464     public void setCurrentRowStyleClass(final String value) {
465         getStateHelper().put(PropertyKeys.currentRowStyleClass, value);
466     }
467 
468     public String getInvalidCellStyleClass() {
469         return (String) getStateHelper().eval(PropertyKeys.invalidCellStyleClass, null);
470     }
471 
472     public void setInvalidCellStyleClass(final String value) {
473         getStateHelper().put(PropertyKeys.invalidCellStyleClass, value);
474     }
475 
476     public String getNoWordWrapStyleClass() {
477         return (String) getStateHelper().eval(PropertyKeys.noWordWrapStyleClass, null);
478     }
479 
480     public void setNoWordWrapStyleClass(final String value) {
481         getStateHelper().put(PropertyKeys.noWordWrapStyleClass, value);
482     }
483 
484     public String getPlaceholderCellStyleClass() {
485         return (String) getStateHelper().eval(PropertyKeys.placeholderCellStyleClass, null);
486     }
487 
488     public void setPlaceholderCellStyleClass(final String value) {
489         getStateHelper().put(PropertyKeys.placeholderCellStyleClass, value);
490     }
491 
492     public String getReadOnlyCellStyleClass() {
493         return (String) getStateHelper().eval(PropertyKeys.placeholderCellStyleClass, null);
494     }
495 
496     public void setReadOnlyCellStyleClass(final String value) {
497         getStateHelper().put(PropertyKeys.readOnlyCellStyleClass, value);
498     }
499 
500     public void setAllowTabOffSheet(final boolean value) {
501         getStateHelper().put(PropertyKeys.allowTabOffSheet, value);
502     }
503 
504     public boolean isAllowTabOffSheet() {
505         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.allowTabOffSheet, false).toString());
506     }
507 
508     public String getTabindex() {
509         return (String) getStateHelper().eval(PropertyKeys.tabindex, "0");
510     }
511 
512     public void setTabindex(final String tabindex) {
513         getStateHelper().put(PropertyKeys.tabindex, tabindex);
514     }
515 
516     public String getExtender() {
517         return (String) getStateHelper().eval(PropertyKeys.extender, null);
518     }
519 
520     public void setExtender(final String extender) {
521         getStateHelper().put(PropertyKeys.extender, extender);
522     }
523 
524     public void setCaseSensitiveSort(final boolean value) {
525         getStateHelper().put(PropertyKeys.caseSensitiveSort, value);
526     }
527 
528     public boolean isCaseSensitiveSort() {
529         return Boolean.valueOf(getStateHelper().eval(PropertyKeys.caseSensitiveSort, Boolean.FALSE).toString());
530     }
531 
532     public Integer getNullSortOrder() {
533         return (Integer) getStateHelper().eval(PropertyKeys.nullSortOrder, Integer.valueOf(1));
534     }
535 
536     public void setNullSortOrder(final Integer value) {
537         getStateHelper().put(PropertyKeys.nullSortOrder, value);
538     }
539 
540     public void setMaxRows(final Integer value) {
541         getStateHelper().put(PropertyKeys.maxRows, value);
542     }
543 
544     public Integer getMaxRows() {
545         final Object result = getStateHelper().eval(PropertyKeys.maxRows, null);
546         if (result == null) {
547             return null;
548         }
549         return Integer.valueOf(result.toString());
550     }
551 
552     public void setMinRows(final Integer value) {
553         getStateHelper().put(PropertyKeys.minRows, value);
554     }
555 
556     public Integer getMinRows() {
557         return (Integer) getStateHelper().eval(PropertyKeys.minRows, Integer.valueOf(0));
558     }
559 
560     public void setMaxCols(final Integer value) {
561         getStateHelper().put(PropertyKeys.maxCols, value);
562     }
563 
564     public Integer getMaxCols() {
565         final Object result = getStateHelper().eval(PropertyKeys.maxCols, null);
566         if (result == null) {
567             return null;
568         }
569         return Integer.valueOf(result.toString());
570     }
571 
572     public void setMinCols(final Integer value) {
573         getStateHelper().put(PropertyKeys.minCols, value);
574     }
575 
576     public Integer getMinCols() {
577         return (Integer) getStateHelper().eval(PropertyKeys.minCols, Integer.valueOf(0));
578     }
579 
580     public void setFixedRows(final Integer value) {
581         getStateHelper().put(PropertyKeys.fixedRows, value);
582     }
583 
584     public Integer getFixedRows() {
585         final Object result = getStateHelper().eval(PropertyKeys.fixedRows, null);
586         if (result == null) {
587             return null;
588         }
589         return Integer.valueOf(result.toString());
590     }
591 
592     public void setFixedRowsBottom(final Integer value) {
593         getStateHelper().put(PropertyKeys.fixedRowsBottom, value);
594     }
595 
596     public Integer getFixedRowsBottom() {
597         final Object result = getStateHelper().eval(PropertyKeys.fixedRowsBottom, null);
598         if (result == null) {
599             return null;
600         }
601         return Integer.valueOf(result.toString());
602     }
603 
604     public void setFixedCols(final Integer value) {
605         getStateHelper().put(PropertyKeys.fixedCols, value);
606     }
607 
608     public Integer getFixedCols() {
609         final Object result = getStateHelper().eval(PropertyKeys.fixedCols, null);
610         if (result == null) {
611             return null;
612         }
613         return Integer.valueOf(result.toString());
614     }
615 
616     public String getLocale() {
617         return (String) getStateHelper().eval(PropertyKeys.locale, "en-US");
618     }
619 
620     public void setLocale(final String locale) {
621         getStateHelper().put(PropertyKeys.locale, locale);
622     }
623 
624     public void setSelectionMode(final String value) {
625         getStateHelper().put(PropertyKeys.selectionMode, value);
626     }
627 
628     public String getSelectionMode() {
629         return (String) getStateHelper().eval(PropertyKeys.selectionMode, "multiple");
630     }
631 
632     /**
633      * The currently selected column.
634      *
635      * @return
636      */
637     public Integer getSelectedColumn() {
638         final Object result = getStateHelper().eval(PropertyKeys.selectedColumn);
639         if (result == null) {
640             return null;
641         }
642         return Integer.valueOf(result.toString());
643     }
644 
645     /**
646      * Updates the selected column.
647      *
648      * @param col
649      */
650     public void setSelectedColumn(final Integer col) {
651         getStateHelper().put(PropertyKeys.selectedColumn, col);
652     }
653 
654     /**
655      * The currently selected column.
656      *
657      * @return
658      */
659     public Integer getSelectedLastColumn() {
660         final Object result = getStateHelper().eval(PropertyKeys.selectedLastColumn);
661         if (result == null) {
662             return null;
663         }
664         return Integer.valueOf(result.toString());
665     }
666 
667     /**
668      * Updates the selected column.
669      *
670      * @param col
671      */
672     public void setSelectedLastColumn(final Integer col) {
673         getStateHelper().put(PropertyKeys.selectedLastColumn, col);
674     }
675 
676     /**
677      * The currently selected row.
678      *
679      * @return
680      */
681     public Integer getSelectedRow() {
682         final Object result = getStateHelper().eval(PropertyKeys.selectedRow);
683         if (result == null) {
684             return null;
685         }
686         return Integer.valueOf(result.toString());
687     }
688 
689     /**
690      * The currently selected row.
691      *
692      * @return
693      */
694     public Integer getSelectedLastRow() {
695         final Object result = getStateHelper().eval(PropertyKeys.selectedLastRow);
696         if (result == null) {
697             return null;
698         }
699         return Integer.valueOf(result.toString());
700     }
701 
702     /**
703      * Updates the selected row.
704      *
705      * @param row
706      */
707     public void setSelectedRow(final Integer row) {
708         getStateHelper().put(PropertyKeys.selectedRow, row);
709     }
710 
711     /**
712      * Updates the selected row.
713      *
714      * @param row
715      */
716     public void setSelectedLastRow(final Integer row) {
717         getStateHelper().put(PropertyKeys.selectedLastRow, row);
718     }
719 
720     public Integer getWidth() {
721         final Object result = getStateHelper().eval(PropertyKeys.width);
722         if (result == null) {
723             return null;
724         }
725         // this will handle any type so long as its convertible to integer
726         return Integer.valueOf(result.toString());
727     }
728 
729     public void setWidth(final Integer value) {
730         getStateHelper().put(PropertyKeys.width, value);
731     }
732 
733     /**
734      * The height of the sheet. Note this is applied to the inner div which is why it is recommend you use this property instead of a style class.
735      *
736      * @return
737      */
738     public Integer getHeight() {
739         final Object result = getStateHelper().eval(PropertyKeys.height);
740         if (result == null) {
741             return null;
742         }
743         // this will handle any type so long as its convertable to integer
744         return Integer.valueOf(result.toString());
745     }
746 
747     /**
748      * Updates the height
749      *
750      * @param value
751      */
752     public void setHeight(final Integer value) {
753         getStateHelper().put(PropertyKeys.height, value);
754     }
755 
756     /**
757      * Return the value of the Sheet. This value must be a java.util.List value at this time.
758      */
759     @Override
760     public Object getValue() {
761         return getStateHelper().eval(PropertyKeys.value);
762     }
763 
764     /**
765      * Holds the filtered and sorted List of values.
766      *
767      * @return a List of sorted and filtered values
768      */
769     public java.util.List getFilteredValue() {
770         return (java.util.List) getStateHelper().eval(PropertyKeys.filteredValue, new ArrayList());
771     }
772 
773     /**
774      * Sets the filtered list.
775      *
776      * @param filteredValue the List to store
777      */
778     public void setFilteredValue(final java.util.List filteredValue) {
779         getStateHelper().put(PropertyKeys.filteredValue, filteredValue);
780     }
781 
782     /**
783      * Set the value of the <code>Sheet</code>. This value must be a java.util.List at this time.
784      *
785      * @param value the new value
786      */
787     @Override
788     public void setValue(final Object value) {
789         getStateHelper().put(PropertyKeys.value, value);
790     }
791 
792     /**
793      * Update the style value for the component
794      *
795      * @param value
796      */
797     public void setStyle(final String value) {
798         getStateHelper().put(PropertyKeys.style, value);
799     }
800 
801     /**
802      * The style value
803      *
804      * @return the style value
805      */
806     public String getStyle() {
807         final Object result = getStateHelper().eval(PropertyKeys.style, null);
808         if (result == null) {
809             return null;
810         }
811         return result.toString();
812     }
813 
814     /**
815      * Gets the rowHeader value expression defined
816      *
817      * @return a value expression for Row Header or null if the expression is not set
818      */
819     protected ValueExpression getRowHeaderValueExpression() {
820         return getValueExpression(PropertyKeys.rowHeader.name());
821     }
822 
823     /**
824      * Gets the rowKey for the current row
825      *
826      * @param context the faces context
827      * @return a row key value or null if the expression is not set
828      */
829     protected Object getRowKeyValue(final FacesContext context) {
830         final ValueExpression veRowKey = getValueExpression(PropertyKeys.rowKey.name());
831         if (veRowKey == null) {
832             throw new FacesException("RowKey required on sheet!");
833         }
834         final Object value = veRowKey.getValue(context.getELContext());
835         if (value == null) {
836             throw new FacesException("RowKey must resolve to non-null value for updates to work properly");
837         }
838         return value;
839     }
840 
841     /**
842      * Return the request-scope attribute under which the data object for the current row will be exposed when iterating. This property is <strong>not</strong>
843      * enabled for value binding expressions.
844      */
845     public String getVar() {
846         // must be a string literal (no eval)
847         return (String) getStateHelper().get(PropertyKeys.var);
848     }
849 
850     /**
851      * Set the request-scope attribute under which the data object for the current row wil be exposed when iterating.
852      *
853      * @param var The new request-scope attribute name
854      */
855     public void setVar(final String var) {
856         getStateHelper().put(PropertyKeys.var, var);
857     }
858 
859     /**
860      * Saves the column by which the sheet is currently sorted (when the user clicks on a column).
861      *
862      * @param columnId ID of the column by which the sheet is currently sorted.
863      */
864     public void saveSortByColumn(final String columnId) {
865         getStateHelper().put(PropertyKeys.currentSortBy.name(), columnId);
866     }
867 
868     /**
869      * The sort direction
870      *
871      * @return
872      */
873     public String getSortOrder() {
874         // if we have a toggled sort in our state, use it
875         return (String) getStateHelper().eval(PropertyKeys.sortOrder, SortOrder.ASCENDING.toString());
876     }
877 
878     /**
879      * Update the sort direction
880      *
881      * @param sortOrder
882      */
883     public void setSortOrder(final java.lang.String sortOrder) {
884         // when updating, make sure we store off the original so it may be
885         // restored
886         final String orig = (String) getStateHelper().get(PropertyKeys.origSortOrder);
887         if (orig == null) {
888             // do not call getSortOrder as it defaults to ascending, we want
889             // null
890             // if this is the first call and there is no previous value.
891             getStateHelper().put(PropertyKeys.origSortOrder, getStateHelper().eval(PropertyKeys.sortOrder));
892         }
893         getStateHelper().put(PropertyKeys.sortOrder, sortOrder);
894     }
895 
896     /**
897      * The error message to display when the sheet is in error.
898      *
899      * @return
900      */
901     public String getErrorMessage() {
902         final Object result = getStateHelper().eval(PropertyKeys.errorMessage);
903         if (result == null) {
904             return null;
905         }
906         return result.toString();
907     }
908 
909     /**
910      * Updates the errorMessage value.
911      *
912      * @param value
913      */
914     public void setErrorMessage(final String value) {
915         getStateHelper().put(PropertyKeys.errorMessage, value);
916     }
917 
918 }