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