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