View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.widget;
17  
18  import org.apache.commons.lang.ClassUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.config.property.ConfigurationService;
22  import org.kuali.rice.core.api.util.type.KualiDecimal;
23  import org.kuali.rice.core.api.util.type.KualiInteger;
24  import org.kuali.rice.core.api.util.type.KualiPercent;
25  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
26  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
27  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
28  import org.kuali.rice.krad.uif.UifConstants;
29  import org.kuali.rice.krad.uif.UifParameters;
30  import org.kuali.rice.krad.uif.component.Component;
31  import org.kuali.rice.krad.uif.component.ComponentBase;
32  import org.kuali.rice.krad.uif.container.CollectionGroup;
33  import org.kuali.rice.krad.uif.control.CheckboxControl;
34  import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
35  import org.kuali.rice.krad.uif.control.Control;
36  import org.kuali.rice.krad.uif.control.RadioGroupControl;
37  import org.kuali.rice.krad.uif.control.SelectControl;
38  import org.kuali.rice.krad.uif.field.DataField;
39  import org.kuali.rice.krad.uif.field.Field;
40  import org.kuali.rice.krad.uif.field.FieldGroup;
41  import org.kuali.rice.krad.uif.field.InputField;
42  import org.kuali.rice.krad.uif.field.LinkField;
43  import org.kuali.rice.krad.uif.field.MessageField;
44  import org.kuali.rice.krad.uif.layout.LayoutManager;
45  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
46  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
47  import org.kuali.rice.krad.uif.view.View;
48  import org.kuali.rice.krad.util.KRADConstants;
49  import org.kuali.rice.krad.util.KRADUtils;
50  import org.kuali.rice.krad.web.form.UifFormBase;
51  
52  import java.sql.Timestamp;
53  import java.util.ArrayList;
54  import java.util.HashSet;
55  import java.util.List;
56  import java.util.Set;
57  
58  /**
59   * Decorates a HTML Table client side with various tools
60   *
61   * <p>
62   * Decorations implemented depend on widget implementation. Examples are
63   * sorting, paging and skinning.
64   * </p>
65   *
66   * @author Kuali Rice Team (rice.collab@kuali.org)
67   */
68  @BeanTags({@BeanTag(name = "richTable-bean", parent = "Uif-RichTable"),
69          @BeanTag(name = "pagedRichTable-bean", parent = "Uif-PagedRichTable"),
70          @BeanTag(name = "scrollableRichTable-bean", parent = "Uif-ScrollableRichTable")})
71  public class RichTable extends WidgetBase {
72      private static final long serialVersionUID = 4671589690877390070L;
73  
74      private String emptyTableMessage;
75      private boolean disableTableSort;
76  
77      private boolean forceAoColumnDefsOverride;
78  
79      private boolean forceAjaxJsonData;
80      private boolean forceLocalJsonData;
81      private int nestedLevel;
82      private String aaData;
83  
84      private Set<String> hiddenColumns;
85      private Set<String> sortableColumns;
86      private List<String> cellCssClasses;
87  
88      private String ajaxSource;
89  
90      private boolean showSearchAndExportOptions = true;
91      private boolean showSearchOption = false;
92      private boolean showExportOption = false;
93  
94      private String groupingOptionsJSString;
95  
96      public RichTable() {
97          super();
98          groupingOptionsJSString = "null";
99          cellCssClasses = new ArrayList<String>();
100     }
101 
102     /**
103      * The following initialization is performed:
104      *
105      * <ul>
106      * <li>Initializes component options for empty table message</li>
107      * </ul>
108      */
109     @Override
110     public void performFinalize(View view, Object model, Component component) {
111         super.performFinalize(view, model, component);
112 
113         UifFormBase formBase = (UifFormBase) model;
114 
115         if (!isRender()) {
116             return;
117         }
118 
119         if (StringUtils.isNotBlank(getEmptyTableMessage()) && !getTemplateOptions().containsKey(
120                 UifConstants.TableToolsKeys.LANGUAGE)) {
121             getTemplateOptions().put(UifConstants.TableToolsKeys.LANGUAGE,
122                     "{\"" + UifConstants.TableToolsKeys.EMPTY_TABLE + "\" : \"" + getEmptyTableMessage() + "\"}");
123         }
124 
125         if (!isShowSearchAndExportOptions()) {
126             Object domOption = getTemplateOptions().get(UifConstants.TableToolsKeys.SDOM);
127             if (domOption instanceof String) {
128                 String sDomOption = (String) domOption;
129 
130                 if (StringUtils.isNotBlank(sDomOption)) {
131                     if (!isShowExportOption()) {
132                         sDomOption = StringUtils.remove(sDomOption, "T"); //Removes Export option
133                     }
134                     if (!isShowSearchOption()) {
135                         sDomOption = StringUtils.remove(sDomOption, "f"); //Removes search option
136                     }
137                     getTemplateOptions().put(UifConstants.TableToolsKeys.SDOM, sDomOption);
138                 }
139             }
140         }
141 
142         // for add events, disable initial sorting
143         if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent()) || UifConstants.ActionEvents
144                 .ADD_BLANK_LINE.equals(formBase.getActionEvent())) {
145             getTemplateOptions().put(UifConstants.TableToolsKeys.AASORTING, "[]");
146         }
147 
148         if ((component instanceof CollectionGroup)) {
149             LayoutManager layoutManager = ((CollectionGroup) component).getLayoutManager();
150 
151             //if forceAjaxJsonData is true, add the css cell styling to the template options so it can still be used
152             //since this will not go through the grid ftl
153             if (layoutManager instanceof TableLayoutManager && (this.forceAjaxJsonData || this.forceLocalJsonData)) {
154                 addCellStyling((TableLayoutManager) layoutManager);
155             }
156 
157             buildTableOptions((CollectionGroup) component);
158             setTotalOptions((CollectionGroup) component);
159         }
160 
161         if (isDisableTableSort()) {
162             getTemplateOptions().put(UifConstants.TableToolsKeys.TABLE_SORT, "false");
163         }
164 
165         String kradUrl = getConfigurationService().getPropertyValueAsString(UifConstants.ConfigProperties.KRAD_URL);
166         if (StringUtils.isNotBlank(ajaxSource)) {
167             getTemplateOptions().put(UifConstants.TableToolsKeys.SAJAX_SOURCE, ajaxSource);
168         } else if (component instanceof CollectionGroup && this.forceAjaxJsonData) {
169             //build sAjaxSource url to call
170             getTemplateOptions().put(UifConstants.TableToolsKeys.SAJAX_SOURCE, kradUrl
171                     + ((UifFormBase) model).getControllerMapping()
172                     + "?"
173                     + UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME
174                     + "="
175                     + UifConstants.MethodToCallNames.TABLE_JSON
176                     + "&"
177                     + UifParameters.TABLE_ID
178                     + "="
179                     + component.getId()
180                     + "&"
181                     + UifParameters.FORM_KEY
182                     + "="
183                     + ((UifFormBase) model).getFormKey()
184                     + "&"
185                     + UifParameters.AJAX_RETURN_TYPE
186                     + "="
187                     + UifConstants.AjaxReturnTypes.UPDATENONE.getKey()
188                     + "&"
189                     + UifParameters.AJAX_REQUEST
190                     + "="
191                     + "true");
192         }
193 
194         //build sAjaxSource url to call
195         getTemplateOptions().put(UifConstants.TableToolsKeys.SDOWNLOAD_SOURCE, kradUrl
196                 + ((UifFormBase) model).getControllerMapping()
197                 + "?"
198                 + UifConstants.CONTROLLER_METHOD_DISPATCH_PARAMETER_NAME
199                 + "="
200                 + UifConstants.MethodToCallNames.TABLE_DATA
201                 + "&"
202                 + UifParameters.TABLE_ID
203                 + "="
204                 + component.getId()
205                 + "&"
206                 + UifParameters.FORM_KEY
207                 + "="
208                 + ((UifFormBase) model).getFormKey()
209                 + "&"
210                 + UifParameters.AJAX_RETURN_TYPE
211                 + "="
212                 + UifConstants.AjaxReturnTypes.UPDATENONE.getKey()
213                 + "&"
214                 + UifParameters.AJAX_REQUEST
215                 + "="
216                 + "true");
217 
218     }
219 
220     /**
221      * Add the css style to the cellCssClasses by column index, later used by the aoColumnDefs
222      *
223      * @param manager the tableLayoutManager that contains the original fields
224      */
225     private void addCellStyling(TableLayoutManager manager) {
226         if (manager.getAllRowFields() != null) {
227             for (int index = 0; index < manager.getNumberOfColumns(); index++) {
228                 String cellStyleClasses = ((ComponentBase) manager.getAllRowFields().get(index))
229                         .getCellStyleClassesAsString();
230                 cellCssClasses.add(cellStyleClasses);
231             }
232         }
233     }
234 
235     /**
236      * Builds the footer callback template option for column totals
237      *
238      * @param collectionGroup the collection group
239      */
240     private void setTotalOptions(CollectionGroup collectionGroup) {
241         LayoutManager layoutManager = collectionGroup.getLayoutManager();
242 
243         if (layoutManager instanceof TableLayoutManager) {
244             List<String> totalColumns = ((TableLayoutManager) layoutManager).getColumnsToCalculate();
245 
246             if (totalColumns.size() > 0) {
247                 String array = "[";
248 
249                 for (String i : totalColumns) {
250                     array = array + i + ",";
251                 }
252                 array = StringUtils.removeEnd(array, ",");
253                 array = array + "]";
254                 getTemplateOptions().put(UifConstants.TableToolsKeys.FOOTER_CALLBACK,
255                         "function (nRow, aaData, iStart, iEnd, aiDisplay) {initializeTotalsFooter (nRow, aaData, iStart, iEnd, aiDisplay, "
256                                 + array
257                                 + " )}");
258             }
259         }
260     }
261 
262     /**
263      * Builds column options for sorting
264      *
265      * @param collectionGroup
266      */
267     protected void buildTableOptions(CollectionGroup collectionGroup) {
268         LayoutManager layoutManager = collectionGroup.getLayoutManager();
269 
270         // if sub collection exists, don't allow the table sortable
271         if (!collectionGroup.getSubCollections().isEmpty()) {
272             setDisableTableSort(true);
273         }
274 
275         if (!isDisableTableSort()) {
276             // if rendering add line, skip that row from col sorting
277             if (collectionGroup.isRenderAddLine()
278                     && !collectionGroup.isReadOnly()
279                     && !((layoutManager instanceof TableLayoutManager) && ((TableLayoutManager) layoutManager)
280                     .isSeparateAddLine())) {
281                 getTemplateOptions().put(UifConstants.TableToolsKeys.SORT_SKIP_ROWS,
282                         "[" + UifConstants.TableToolsValues.ADD_ROW_DEFAULT_INDEX + "]");
283             }
284 
285             StringBuffer tableToolsColumnOptions = new StringBuffer("[");
286 
287             int columnIndex = 0;
288             int actionIndex = UifConstants.TableLayoutValues.ACTIONS_COLUMN_RIGHT_INDEX;
289             boolean actionFieldVisible = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly();
290 
291             if (layoutManager instanceof TableLayoutManager) {
292                 actionIndex = ((TableLayoutManager) layoutManager).getActionColumnIndex();
293             }
294 
295             if (actionIndex == UifConstants.TableLayoutValues.ACTIONS_COLUMN_LEFT_INDEX && actionFieldVisible) {
296                 String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
297                 tableToolsColumnOptions.append(actionColOptions + " , ");
298                 columnIndex++;
299             }
300 
301             if (layoutManager instanceof TableLayoutManager && ((TableLayoutManager) layoutManager)
302                     .isRenderSequenceField()) {
303 
304                 //add mData handling if using a json data source
305                 String mDataOption = "";
306                 if (this.forceAjaxJsonData || this.forceLocalJsonData) {
307                     mDataOption = "\""
308                             + UifConstants.TableToolsKeys.MDATA
309                             +
310                             "\" : function(row,type,newVal){ return _handleColData(row,type,'c"
311                             + columnIndex
312                             + "',newVal);}, ";
313                 }
314 
315                 tableToolsColumnOptions.append("{\""
316                         + UifConstants.TableToolsKeys.SORT_TYPE
317                         + "\" : \""
318                         + UifConstants.TableToolsValues.NUMERIC
319                         + "\", "
320                         + mDataOption
321                         + "\""
322                         + UifConstants.TableToolsKeys.TARGETS
323                         + "\": ["
324                         + columnIndex
325                         + "]}, ");
326                 columnIndex++;
327                 if (actionIndex == 2 && actionFieldVisible) {
328                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
329                     tableToolsColumnOptions.append(actionColOptions + " , ");
330                     columnIndex++;
331                 }
332             }
333 
334             // skip select field if enabled
335             if (collectionGroup.isIncludeLineSelectionField()) {
336                 String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
337                 tableToolsColumnOptions.append(colOptions + " , ");
338                 columnIndex++;
339             }
340 
341             // if data dictionary defines aoColumns, copy here and skip default sorting/visibility behaviour
342             if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS))) {
343                 // get the contents of the JS array string
344                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS);
345                 int startBrace = StringUtils.indexOf(jsArray, "[");
346                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
347                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
348 
349                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
350                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
351                     tableToolsColumnOptions.append(actionColOptions);
352                 } else {
353                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
354                             ", "));
355                 }
356 
357                 tableToolsColumnOptions.append("]");
358                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMNS, tableToolsColumnOptions.toString());
359             } else if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))
360                     && forceAoColumnDefsOverride) {
361                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS);
362                 int startBrace = StringUtils.indexOf(jsArray, "[");
363                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
364                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
365 
366                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
367                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
368                     tableToolsColumnOptions.append(actionColOptions);
369                 } else {
370                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
371                             ", "));
372                 }
373 
374                 tableToolsColumnOptions.append("]");
375                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
376                         tableToolsColumnOptions.toString());
377             } else if (layoutManager instanceof TableLayoutManager) {
378                 List<Field> rowFields = ((TableLayoutManager) layoutManager).getFirstRowFields();
379 
380                 // build column defs from the the first row of the table
381                 for (Component component : rowFields) {
382                     if (actionFieldVisible && columnIndex + 1 == actionIndex) {
383                         String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
384                         tableToolsColumnOptions.append(actionColOptions + " , ");
385                         columnIndex++;
386                     }
387 
388                     //add mData handling if using a json data source
389                     String mDataOption = "";
390                     if (this.forceAjaxJsonData || this.forceLocalJsonData) {
391                         mDataOption = "\""
392                                 + UifConstants.TableToolsKeys.MDATA
393                                 +
394                                 "\" : function(row,type,newVal){ return _handleColData(row,type,'c"
395                                 + columnIndex
396                                 + "',newVal);}, ";
397                     }
398 
399                     // for FieldGroup, get the first field from that group
400                     if (component instanceof FieldGroup) {
401                         component = ((FieldGroup) component).getItems().get(0);
402                     }
403 
404                     if (component instanceof DataField) {
405                         DataField field = (DataField) component;
406 
407                         // if a field is marked as invisible in hiddenColumns, append options and skip sorting
408                         if (getHiddenColumns() != null && getHiddenColumns().contains(field.getPropertyName())) {
409                             tableToolsColumnOptions.append("{"
410                                     + UifConstants.TableToolsKeys.VISIBLE
411                                     + ": "
412                                     + UifConstants.TableToolsValues.FALSE
413                                     + ", "
414                                     + mDataOption
415                                     + "\""
416                                     + UifConstants.TableToolsKeys.TARGETS
417                                     + "\": ["
418                                     + columnIndex
419                                     + "]"
420                                     + "}, ");
421                             // if sortableColumns is present and a field is marked as sortable or unspecified
422                         } else if (getSortableColumns() != null && !getSortableColumns().isEmpty()) {
423                             if (getSortableColumns().contains(field.getPropertyName())) {
424                                 tableToolsColumnOptions.append(getDataFieldColumnOptions(columnIndex, collectionGroup,
425                                         field) + ", ");
426                             } else {
427                                 tableToolsColumnOptions.append("{'"
428                                         + UifConstants.TableToolsKeys.SORTABLE
429                                         + "': "
430                                         + UifConstants.TableToolsValues.FALSE
431                                         + ", "
432                                         + mDataOption
433                                         + "\""
434                                         + UifConstants.TableToolsKeys.TARGETS
435                                         + "\": ["
436                                         + columnIndex
437                                         + "]"
438                                         + "}, ");
439                             }
440                         } else {// sortable columns not defined
441                             String colOptions = getDataFieldColumnOptions(columnIndex, collectionGroup, field);
442                             tableToolsColumnOptions.append(colOptions + " , ");
443                         }
444                         columnIndex++;
445                     } else if (component instanceof MessageField
446                             && component.getDataAttributes().get("role") != null
447                             && component.getDataAttributes().get("role").equals("grouping")) {
448                         //Grouping column is never shown, so skip
449                         tableToolsColumnOptions.append("{"
450                                 + UifConstants.TableToolsKeys.VISIBLE
451                                 + ": "
452                                 + UifConstants.TableToolsValues.FALSE
453                                 + ", "
454                                 + mDataOption
455                                 + "\""
456                                 + UifConstants.TableToolsKeys.TARGETS
457                                 + "\": ["
458                                 + columnIndex
459                                 + "]"
460                                 + "}, ");
461                         columnIndex++;
462                     } else if (component instanceof LinkField) {
463                         String colOptions = constructTableColumnOptions(columnIndex, true, String.class,
464                                 UifConstants.TableToolsValues.DOM_TEXT);
465                         tableToolsColumnOptions.append(colOptions + " , ");
466                         columnIndex++;
467                     } else {
468                         String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
469                         tableToolsColumnOptions.append(colOptions + " , ");
470                         columnIndex++;
471                     }
472                 }
473 
474                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
475                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
476                     tableToolsColumnOptions.append(actionColOptions);
477                 } else {
478                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
479                             ", "));
480                 }
481 
482                 //merge the aoColumnDefs passed in
483                 if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))) {
484                     String origAoOptions = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS).trim();
485                     origAoOptions = StringUtils.removeStart(origAoOptions, "[");
486                     origAoOptions = StringUtils.removeEnd(origAoOptions, "]");
487                     tableToolsColumnOptions.append("," + origAoOptions);
488                 }
489 
490                 tableToolsColumnOptions.append("]");
491                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
492                         tableToolsColumnOptions.toString());
493             }
494         }
495     }
496 
497     /**
498      * Construct the column options for a data field
499      *
500      * @param collectionGroup the collectionGroup in which the data field is defined
501      * @param field the field to construction options for
502      * @return options as valid for datatable
503      */
504     protected String getDataFieldColumnOptions(int target, CollectionGroup collectionGroup, DataField field) {
505         String sortType = null;
506 
507         if (!collectionGroup.isReadOnly()
508                 && (field instanceof InputField)
509                 && ((InputField) field).getControl() != null) {
510             Control control = ((InputField) field).getControl();
511             if (control instanceof SelectControl) {
512                 sortType = UifConstants.TableToolsValues.DOM_SELECT;
513             } else if (control instanceof CheckboxControl || control instanceof CheckboxGroupControl) {
514                 sortType = UifConstants.TableToolsValues.DOM_CHECK;
515             } else if (control instanceof RadioGroupControl) {
516                 sortType = UifConstants.TableToolsValues.DOM_RADIO;
517             } else {
518                 sortType = UifConstants.TableToolsValues.DOM_TEXT;
519             }
520         } else {
521             sortType = UifConstants.TableToolsValues.DOM_TEXT;
522         }
523 
524         Class dataTypeClass = ObjectPropertyUtils.getPropertyType(collectionGroup.getCollectionObjectClass(),
525                 field.getPropertyName());
526 
527         boolean isSortable = true;
528         if (field.isApplyMask()) {
529             isSortable = false;
530         }
531 
532         return constructTableColumnOptions(target, isSortable, dataTypeClass, sortType);
533     }
534 
535     /**
536      * Constructs the sort data type for each data table columns in a format that will be used to initialize the data
537      * table widget via javascript
538      *
539      * @param isSortable whether a column should be marked as sortable
540      * @param dataTypeClass the class type of the column value - used determine the sType option - which identifies
541      * the search plugin to use
542      * @param sortDataType Defines a data source type for the sorting which can be used to read realtime information
543      * from the table
544      * @return a formatted string with data table options for one column
545      */
546     public String constructTableColumnOptions(int target, boolean isSortable, Class dataTypeClass,
547             String sortDataType) {
548         String colOptions = "null";
549 
550         String sortType = "";
551         if (!isSortable || dataTypeClass == null || sortType == null) {
552             colOptions = "\"" + UifConstants.TableToolsKeys.SORTABLE + "\" : false, \"sType\" : \"string\"";
553         } else {
554             if (ClassUtils.isAssignable(dataTypeClass, KualiPercent.class)) {
555                 sortType = UifConstants.TableToolsValues.PERCENT;
556             } else if (ClassUtils.isAssignable(dataTypeClass, KualiInteger.class) || ClassUtils.isAssignable(
557                     dataTypeClass, KualiDecimal.class)) {
558                 sortType = UifConstants.TableToolsValues.CURRENCY;
559             } else if (ClassUtils.isAssignable(dataTypeClass, Timestamp.class)) {
560                 sortType = "date";
561             } else if (ClassUtils.isAssignable(dataTypeClass, java.sql.Date.class) || ClassUtils.isAssignable(
562                     dataTypeClass, java.util.Date.class)) {
563                 sortType = UifConstants.TableToolsValues.DATE;
564             } else if (ClassUtils.isAssignable(dataTypeClass, Number.class)) {
565                 sortType = UifConstants.TableToolsValues.NUMERIC;
566             } else {
567                 sortType = UifConstants.TableToolsValues.STRING;
568             }
569 
570             colOptions = "\"" + UifConstants.TableToolsKeys.SORT_TYPE + "\" : \"" + sortType + "\"";
571 
572             if (!this.forceAjaxJsonData && !this.forceLocalJsonData) {
573                 colOptions += ", \"" + UifConstants.TableToolsKeys.SORT_DATA_TYPE + "\" : \"" + sortDataType + "\"";
574             }
575         }
576 
577         if (target < cellCssClasses.size() && target >= 0) {
578             colOptions += ", \""
579                     + UifConstants.TableToolsKeys.CELL_CLASS
580                     + "\" : \""
581                     + cellCssClasses.get(target)
582                     + "\"";
583         }
584 
585         // only use the mDataProp when using json data (only relevant for this table type)
586         if (this.forceAjaxJsonData || this.forceLocalJsonData) {
587             colOptions += ", \"" + UifConstants.TableToolsKeys.MDATA +
588                     "\" : function(row,type,newVal){ return _handleColData(row,type,'c" + target + "',newVal);}";
589         }
590 
591         if (!colOptions.equals("null")) {
592             colOptions = "{" + colOptions + ", \"" + UifConstants.TableToolsKeys.TARGETS + "\": [" + target + "]}";
593         } else {
594             colOptions = "{" + colOptions + "}";
595         }
596 
597         return colOptions;
598     }
599 
600     /**
601      * Returns the text which is used to display text when the table is empty
602      *
603      * @return empty table message
604      */
605     @BeanTagAttribute(name = "emptyTableMessage")
606     public String getEmptyTableMessage() {
607         return emptyTableMessage;
608     }
609 
610     /**
611      * Setter for a text to be displayed when the table is empty
612      *
613      * @param emptyTableMessage
614      */
615     public void setEmptyTableMessage(String emptyTableMessage) {
616         this.emptyTableMessage = emptyTableMessage;
617     }
618 
619     /**
620      * Returns true if sorting is disabled
621      *
622      * @return the disableTableSort
623      */
624     @BeanTagAttribute(name = "disableTableSort")
625     public boolean isDisableTableSort() {
626         return this.disableTableSort;
627     }
628 
629     /**
630      * Enables/disables the table sorting
631      *
632      * @param disableTableSort the disableTableSort to set
633      */
634     public void setDisableTableSort(boolean disableTableSort) {
635         this.disableTableSort = disableTableSort;
636     }
637 
638     /**
639      * Returns true if search and export options are enabled
640      *
641      * @return the showSearchAndExportOptions
642      */
643     @BeanTagAttribute(name = "showSearchAndExportOptions")
644     public boolean isShowSearchAndExportOptions() {
645         return this.showSearchAndExportOptions;
646     }
647 
648     /**
649      * Returns true if search option is enabled
650      *
651      * @return the showSearchOption
652      */
653     @BeanTagAttribute(name = "showSearchOption")
654     public boolean isShowSearchOption() {
655         return this.showSearchOption;
656     }
657 
658     /**
659      * Returns true if export option is enabled
660      *
661      * @return the showExportOption
662      */
663     @BeanTagAttribute(name = "showExportOption")
664     public boolean isShowExportOption() {
665         return this.showExportOption;
666     }
667 
668     /**
669      * Show/Hide the search and export options in tabletools.  This option supercedes
670      * the individual 'show search' option and 'show export' option
671      *
672      * @param showSearchAndExportOptions the showSearchAndExportOptions to set
673      */
674     public void setShowSearchAndExportOptions(boolean showSearchAndExportOptions) {
675         this.showSearchAndExportOptions = showSearchAndExportOptions;
676     }
677 
678     /**
679      * Show/Hide the search option in tabletools
680      *
681      * @param showSearchOption the showSearchOptions to set
682      */
683     public void setShowSearchOption(boolean showSearchOption) {
684         this.showSearchOption = showSearchOption;
685     }
686 
687     /**
688      * Show/Hide the search and export option in tabletools
689      *
690      * @param showExportOption the showExportOptions to set
691      */
692     public void setShowExportOption(boolean showExportOption) {
693         this.showExportOption = showExportOption;
694     }
695 
696     /**
697      * Holds propertyNames for the ones meant to be hidden since columns are visible by default
698      *
699      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
700      *
701      * @return a set with propertyNames of columns to be hidden
702      */
703     @BeanTagAttribute(name = "hiddenColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
704     public Set<String> getHiddenColumns() {
705         return hiddenColumns;
706     }
707 
708     /**
709      * Setter for the hidden columns set
710      *
711      * @param hiddenColumns a set containing propertyNames
712      */
713     public void setHiddenColumns(Set<String> hiddenColumns) {
714         this.hiddenColumns = hiddenColumns;
715     }
716 
717     /**
718      * Holds the propertyNames for columns that are to be sorted
719      *
720      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
721      *
722      * @return a set of propertyNames with for columns that will be sorted
723      */
724     @BeanTagAttribute(name = "sortableColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
725     public Set<String> getSortableColumns() {
726         return sortableColumns;
727     }
728 
729     /**
730      * Setter for sortable columns
731      *
732      * @param sortableColumns a set containing propertyNames of columns to be sorted
733      */
734     public void setSortableColumns(Set<String> sortableColumns) {
735         this.sortableColumns = sortableColumns;
736     }
737 
738     /**
739      * Specifies a URL for acquiring the table data with ajax
740      *
741      * <p>
742      * When the ajax source URL is specified the rich table plugin will retrieve the data by invoking the URL and
743      * building the table rows from the result. This is different from the standard use of the rich table plugin
744      * with uses progressive enhancement to decorate a table that has already been rendereed
745      * </p>
746      *
747      * @return URL for ajax source
748      */
749     @BeanTagAttribute(name = "ajaxSource")
750     public String getAjaxSource() {
751         return ajaxSource;
752     }
753 
754     /**
755      * Setter for the Ajax source URL
756      *
757      * @param ajaxSource
758      */
759     public void setAjaxSource(String ajaxSource) {
760         this.ajaxSource = ajaxSource;
761     }
762 
763     /**
764      * Get groupingOption
765      *
766      * @return
767      */
768     public String getGroupingOptionsJSString() {
769         return groupingOptionsJSString;
770     }
771 
772     /**
773      * Set the groupingOptions js data.  <b>This should not be set through XML configuration.</b>
774      *
775      * @param groupingOptionsJSString
776      */
777     public void setGroupingOptionsJSString(String groupingOptionsJSString) {
778         this.groupingOptionsJSString = groupingOptionsJSString;
779     }
780 
781     /**
782      * If set to true and the aoColumnDefs template option is explicitly defined in templateOptions, those aoColumnDefs
783      * will be used for this table.  Otherwise, if false, the aoColumnDefs will attempt to be merged with those that
784      * are automatically generated by RichTable
785      *
786      * @return true if the aoColumnDefs set will completely override those that are generated automatically by
787      *         RichTable
788      */
789     public boolean isForceAoColumnDefsOverride() {
790         return forceAoColumnDefsOverride;
791     }
792 
793     /**
794      * Set forceAoColumnDefsOverride
795      *
796      * @param forceAoColumnDefsOverride
797      */
798     public void setForceAoColumnDefsOverride(boolean forceAoColumnDefsOverride) {
799         this.forceAoColumnDefsOverride = forceAoColumnDefsOverride;
800     }
801 
802     /**
803      * If true, the table will automatically call the tableJsonRetrieval method on the controller with the tableId and
804      * formKey necessary to retrieve the aaData generated by this table
805      *
806      * <p>If set, this will always take precedence over the forceLocalJsonData flag.
807      * This forces the table backed by this RichTable to get its content from an ajax source.  This will turn
808      * the sAjaxSource template option to true automatically, automatically skip row generation in the template, and
809      * cause the table to call a server method asynchronously to render its data after the table is created.  The
810      * effect is faster initial page loading times, but small waits per table for content to be returned which may
811      * lock up some browsers if the view contains multiple visible tables.  It also
812      * allows the table to take advantage of bDeferRender option (automatically set to true)
813      * when this table is a paged table (performance increase).
814      * This option is ignored and not used for the LightTable component implementation.</p>
815      *
816      * @return true if backed by the default ajax source method (also enables the necessary framework automation to
817      *         call this correctly), false otherwise
818      */
819     public boolean isForceAjaxJsonData() {
820         return forceAjaxJsonData;
821     }
822 
823     /**
824      * Set the forceAjaxJsonData flag to force the data to be retrieved after the page loads
825      *
826      * @param forceAjaxJsonData
827      */
828     public void setForceAjaxJsonData(boolean forceAjaxJsonData) {
829         this.forceAjaxJsonData = forceAjaxJsonData;
830     }
831 
832     /**
833      * If true, the table will automatically use row JSON data generated by this widget
834      *
835      * <p>This forces the table backed by this RichTable to get its content from a template option called aaData.  This
836      * will automatically skip row generation in the template, and cause
837      * the table receive its data from the aaData template option automatically generated and set by this RichTable.
838      * This allows the table to take advantage of the bDeferRender option (also automatically set to true)
839      * when this table is a paged table (performance increase for tables that are more than one page).
840      * Note: the forceAjaxJsonData flag will always override this functionality if it is also true.</p>
841      *
842      * @return true if backed by the aaData option in JSON, that is generated during the ftl rendering process by
843      *         this widget for this table
844      */
845     public boolean isForceLocalJsonData() {
846         return forceLocalJsonData;
847     }
848 
849     /**
850      * Set the forceLocalJsonData flag to force this table to use generated row json data
851      *
852      * @param forceLocalJsonData
853      */
854     public void setForceLocalJsonData(boolean forceLocalJsonData) {
855         this.forceLocalJsonData = forceLocalJsonData;
856     }
857 
858     /**
859      * The nestedLevel property represents how many collection tables deep this particular table is
860      *
861      * <p>This property must be manually set if the flag forceLocalJsonData is being used and the collection table this
862      * RichTable represents is a subcollection of a TABLE collection (not stacked collection).  If this is true,
863      * add 1 for each level deep (ex. subCollection would be 1, sub-subCollection would be 2).  If this property is
864      * not set javascript errors will occur on the page, as this determines how deep to escape certain characters.</p>
865      *
866      * @return the nestedLevel representing the
867      */
868     public int getNestedLevel() {
869         return nestedLevel;
870     }
871 
872     /**
873      * Set the nestedLevel for this table - must be set if using forceLocalJsonData and this is a subCollection of
874      * a TableCollection (also using forceLocalJsonData)
875      *
876      * @param nestedLevel
877      */
878     public void setNestedLevel(int nestedLevel) {
879         this.nestedLevel = nestedLevel;
880     }
881 
882     /**
883      * Get the translated aaData array generated by calls to addRowToTableData by the ftl
884      *
885      * <p>This data is in JSON format and expected to be consumed by datatables when utilizing the forceLocalJsonData
886      * option or forceAjaxJsonData options.
887      * This will be populated automatically if either flag is set to true.</p>
888      *
889      * @return the generated aaData
890      */
891     public String getAaData() {
892         return aaData;
893     }
894 
895     /**
896      * Set the translated aaData array
897      *
898      * <p>This data is in JSON format and expected to be consumed by datatables when utilizing the forceLocalJsonData
899      * option or forceAjaxJsonData options.
900      * This setter is required for copyProperties()</p>
901      *
902      * @return the generated aaData
903      */
904     public void setAaData(String aaData) {
905         this.aaData = aaData;
906     }
907 
908     /**
909      * Get the simple value as a string that represents the field's sortable value, to be used as val in the custom
910      * uif json data object (accessed by mDataProp option on datatables - automated by framework) when using the
911      * forceAjaxJsonData or forceLocalJsonData option
912      *
913      * @param model model the current model
914      * @param field the field to retrieve a sortable value from for use in custom json data
915      * @return the value as a String
916      */
917     public String getCellValue(Object model, Field field) {
918         String value = KRADUtils.getSimpleFieldValue(model, field);
919 
920         if (value == null) {
921             value = "null";
922         } else {
923             value = KRADConstants.QUOTE_PLACEHOLDER + value + KRADConstants.QUOTE_PLACEHOLDER;
924         }
925 
926         return value;
927     }
928 
929     /**
930      * Add row content passed from table ftl to the aaData array by converting and escaping the content
931      * to an object (in an array of objects) in JSON format
932      *
933      * <p>The data in aaData is expected to be consumed by a call by the datatables plugin using sAjaxSource or aaData.
934      * The addRowToTableData generation call is additive must be made per a row in the ftl.</p>
935      *
936      * @param row the row of content with each cell content surrounded by the @quot@ token and followed by a comma
937      */
938     public void addRowToTableData(String row) {
939         String escape = "";
940 
941         // if nestedLevel is set add the appropriate amount of escape characters per a level of nesting
942         for (int i = 0; i < nestedLevel && forceLocalJsonData; i++) {
943             escape = escape + "\\";
944         }
945 
946         // remove newlines and replace quotes and single quotes with unicode characters
947         row = row.trim().replace("\"", escape + "\\u0022").replace("'", escape + "\\u0027").replace("\n", "").replace(
948                 "\r", "");
949 
950         // remove hanging comma
951         row = StringUtils.removeEnd(row, ",");
952 
953         // replace all quote placeholders with actual quote characters
954         row = row.replace(KRADConstants.QUOTE_PLACEHOLDER, "\"");
955         row = "{" + row + "}";
956 
957         // if first call create aaData and force defer render option, otherwise append
958         if (StringUtils.isBlank(aaData)) {
959             aaData = "[" + row + "]";
960 
961             if (this.getTemplateOptions().get(UifConstants.TableToolsKeys.DEFER_RENDER) == null) {
962                 //make sure deferred rendering is forced if not explicitly set
963                 this.getTemplateOptions().put(UifConstants.TableToolsKeys.DEFER_RENDER,
964                         UifConstants.TableToolsValues.TRUE);
965             }
966         } else if (StringUtils.isNotBlank(row)) {
967             aaData = aaData.substring(0, aaData.length() - 1) + "," + row + "]";
968         }
969 
970         //force json data use if forceLocalJsonData flag is set and forceAjaxJsonData is not
971         if (forceLocalJsonData && !forceAjaxJsonData) {
972             this.getTemplateOptions().put(UifConstants.TableToolsKeys.AA_DATA, aaData);
973         }
974     }
975 
976     protected ConfigurationService getConfigurationService() {
977         return CoreApiServiceLocator.getKualiConfigurationService();
978     }
979 
980     /**
981      * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
982      */
983     @Override
984     protected <T> void copyProperties(T component) {
985         super.copyProperties(component);
986         RichTable richTableCopy = (RichTable) component;
987         richTableCopy.setEmptyTableMessage(this.getEmptyTableMessage());
988         richTableCopy.setDisableTableSort(this.isDisableTableSort());
989         richTableCopy.setForceAoColumnDefsOverride(this.isForceAoColumnDefsOverride());
990         richTableCopy.setForceAjaxJsonData(this.isForceAjaxJsonData());
991         richTableCopy.setForceLocalJsonData(this.isForceLocalJsonData());
992         richTableCopy.setNestedLevel(this.getNestedLevel());
993         richTableCopy.setAaData(this.getAaData());
994 
995         if (hiddenColumns != null) {
996             richTableCopy.setHiddenColumns(new HashSet<String>(hiddenColumns));
997         }
998 
999         if (sortableColumns != null) {
1000             richTableCopy.setSortableColumns(new HashSet<String>(sortableColumns));
1001         }
1002 
1003         if (cellCssClasses != null) {
1004             richTableCopy.setCssClasses(new ArrayList<String>(this.cellCssClasses));
1005         }
1006 
1007         richTableCopy.setAjaxSource(this.getAjaxSource());
1008         richTableCopy.setShowSearchAndExportOptions(this.isShowSearchAndExportOptions());
1009         richTableCopy.setShowSearchOption(this.isShowSearchOption());
1010         richTableCopy.setShowExportOption(this.isShowExportOption());
1011         richTableCopy.setGroupingOptionsJSString(this.getGroupingOptionsJSString());
1012     }
1013 }