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