View Javadoc

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