View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.widget;
17  
18  import org.apache.commons.lang.ClassUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.util.type.KualiDecimal;
21  import org.kuali.rice.core.api.util.type.KualiInteger;
22  import org.kuali.rice.core.api.util.type.KualiPercent;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.container.CollectionGroup;
28  import org.kuali.rice.krad.uif.control.CheckboxControl;
29  import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
30  import org.kuali.rice.krad.uif.control.Control;
31  import org.kuali.rice.krad.uif.control.RadioGroupControl;
32  import org.kuali.rice.krad.uif.control.SelectControl;
33  import org.kuali.rice.krad.uif.field.DataField;
34  import org.kuali.rice.krad.uif.field.FieldGroup;
35  import org.kuali.rice.krad.uif.field.InputField;
36  import org.kuali.rice.krad.uif.field.MessageField;
37  import org.kuali.rice.krad.uif.layout.LayoutManager;
38  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
39  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
40  import org.kuali.rice.krad.uif.view.View;
41  import org.kuali.rice.krad.web.form.UifFormBase;
42  
43  import java.sql.Timestamp;
44  import java.util.List;
45  import java.util.Set;
46  
47  /**
48   * Decorates a HTML Table client side with various tools
49   *
50   * <p>
51   * Decorations implemented depend on widget implementation. Examples are
52   * sorting, paging and skinning.
53   * </p>
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  @BeanTag(name="richTable")
58  public class RichTable extends WidgetBase {
59      private static final long serialVersionUID = 4671589690877390070L;
60  
61      private String emptyTableMessage;
62      private boolean disableTableSort;
63  
64      private boolean forceAoColumnDefsOverride;
65  
66      private Set<String> hiddenColumns;
67      private Set<String> sortableColumns;
68  
69      private String ajaxSource;
70  
71      private boolean showSearchAndExportOptions = true;
72  
73      private String groupingOptionsJSString;
74  
75      public RichTable() {
76          super();
77          groupingOptionsJSString = "null";
78      }
79  
80      /**
81       * The following initialization is performed:
82       *
83       * <ul>
84       * <li>Initializes component options for empty table message</li>
85       * </ul>
86       */
87      @Override
88      public void performFinalize(View view, Object model, Component component) {
89          super.performFinalize(view, model, component);
90  
91          UifFormBase formBase = (UifFormBase) model;
92  
93          if (isRender()) {
94              if (StringUtils.isNotBlank(getEmptyTableMessage()) && !getTemplateOptions().containsKey(
95                      UifConstants.TableToolsKeys.LANGUAGE)) {
96                  getTemplateOptions().put(UifConstants.TableToolsKeys.LANGUAGE,
97                          "{\"" + UifConstants.TableToolsKeys.EMPTY_TABLE + "\" : \"" + getEmptyTableMessage() + "\"}");
98              }
99  
100             if (!isShowSearchAndExportOptions()) {
101                 Object domOption = getTemplateOptions().get(UifConstants.TableToolsKeys.SDOM);
102                 if (domOption instanceof String) {
103                     String sDomOption = (String) domOption;
104 
105                     if (StringUtils.isNotBlank(sDomOption)) {
106                         sDomOption = StringUtils.remove(sDomOption, "T"); //Removes Export option
107                         sDomOption = StringUtils.remove(sDomOption, "f"); //Removes search option
108                         getTemplateOptions().put(UifConstants.TableToolsKeys.SDOM, sDomOption);
109                     }
110                 }
111             }
112 
113             // for add events, disable initial sorting
114             if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent()) || UifConstants.ActionEvents
115                     .ADD_BLANK_LINE.equals(formBase.getActionEvent())) {
116                 getTemplateOptions().put(UifConstants.TableToolsKeys.AASORTING, "[]");
117             }
118 
119             if ((component instanceof CollectionGroup)) {
120                 buildTableOptions((CollectionGroup) component);
121                 setTotalOptions((CollectionGroup) component);
122             }
123 
124             if (isDisableTableSort()) {
125                 getTemplateOptions().put(UifConstants.TableToolsKeys.TABLE_SORT, "false");
126             }
127 
128             if (StringUtils.isNotBlank(ajaxSource)) {
129                 getTemplateOptions().put(UifConstants.TableToolsKeys.SAJAX_SOURCE, ajaxSource);
130             }
131         }
132     }
133 
134     /**
135      * Builds the footer callback template option for column totals
136      *
137      * @param collectionGroup - the collection group
138      */
139     private void setTotalOptions(CollectionGroup collectionGroup) {
140         LayoutManager layoutManager = collectionGroup.getLayoutManager();
141 
142         if (layoutManager instanceof TableLayoutManager) {
143 
144             List<String> totalColumns = ((TableLayoutManager) layoutManager).getColumnsToCalculate();
145 
146             if (totalColumns.size() > 0) {
147                 String array = "[";
148                 for (String i : totalColumns) {
149                     array = array + i + ",";
150                 }
151                 array = StringUtils.removeEnd(array, ",");
152                 array = array + "]";
153                 getTemplateOptions().put(UifConstants.TableToolsKeys.FOOTER_CALLBACK,
154                         "function (nRow, aaData, iStart, iEnd, aiDisplay) {initializeTotalsFooter (nRow, aaData, iStart, iEnd, aiDisplay, "
155                                 + array
156                                 + " )}");
157             }
158         }
159     }
160 
161     /**
162      * Builds column options for sorting
163      *
164      * @param collectionGroup
165      */
166     protected void buildTableOptions(CollectionGroup collectionGroup) {
167         LayoutManager layoutManager = collectionGroup.getLayoutManager();
168 
169         // if sub collection exists, don't allow the table sortable
170         if (!collectionGroup.getSubCollections().isEmpty()) {
171             setDisableTableSort(true);
172         }
173 
174         if (!isDisableTableSort()) {
175             // if rendering add line, skip that row from col sorting
176             if (collectionGroup.isRenderAddLine()
177                     && !collectionGroup.isReadOnly()
178                     && !((layoutManager instanceof TableLayoutManager) && ((TableLayoutManager) layoutManager)
179                     .isSeparateAddLine())) {
180                 getTemplateOptions().put(UifConstants.TableToolsKeys.SORT_SKIP_ROWS,
181                         "[" + UifConstants.TableToolsValues.ADD_ROW_DEFAULT_INDEX + "]");
182             }
183 
184             StringBuffer tableToolsColumnOptions = new StringBuffer("[");
185 
186             int columnIndex = 0;
187             int actionIndex = -1;
188             boolean actionFieldVisible = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly();
189 
190             if (layoutManager instanceof TableLayoutManager) {
191                 actionIndex = ((TableLayoutManager) layoutManager).getActionColumnIndex();
192             }
193 
194             if (actionIndex == 1 && actionFieldVisible) {
195                 String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
196                 tableToolsColumnOptions.append(actionColOptions + " , ");
197                 columnIndex++;
198             }
199 
200             if (layoutManager instanceof TableLayoutManager && ((TableLayoutManager) layoutManager)
201                     .isRenderSequenceField()) {
202                 tableToolsColumnOptions.append("{\""
203                         + UifConstants.TableToolsKeys.SORT_TYPE
204                         + "\" : \""
205                         + UifConstants.TableToolsValues.NUMERIC
206                         + "\", "
207                         + "\""
208                         + UifConstants.TableToolsKeys.TARGETS
209                         + "\": ["
210                         + columnIndex
211                         + "]}, ");
212                 columnIndex++;
213                 if (actionIndex == 2 && actionFieldVisible) {
214                     String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
215                     tableToolsColumnOptions.append(actionColOptions + " , ");
216                     columnIndex++;
217                 }
218             }
219 
220             // skip select field if enabled
221             if (collectionGroup.isIncludeLineSelectionField()) {
222                 String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
223                 tableToolsColumnOptions.append(colOptions + " , ");
224                 columnIndex++;
225             }
226 
227             // if data dictionary defines aoColumns, copy here and skip default sorting/visibility behaviour
228             if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS))) {
229                 // get the contents of the JS array string
230                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS);
231                 int startBrace = StringUtils.indexOf(jsArray, "[");
232                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
233                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
234 
235                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
236                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
237                     tableToolsColumnOptions.append(actionColOptions);
238                 } else {
239                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
240                             ", "));
241                 }
242 
243                 tableToolsColumnOptions.append("]");
244                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMNS, tableToolsColumnOptions.toString());
245             } else if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))
246                     && forceAoColumnDefsOverride) {
247                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS);
248                 int startBrace = StringUtils.indexOf(jsArray, "[");
249                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
250                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + ", ");
251 
252                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
253                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
254                     tableToolsColumnOptions.append(actionColOptions);
255                 } else {
256                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
257                             ", "));
258                 }
259 
260                 tableToolsColumnOptions.append("]");
261                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
262                         tableToolsColumnOptions.toString());
263             } else {
264 
265                 // TODO: does this handle multiple rows correctly?
266                 for (Component component : collectionGroup.getItems()) {
267                     if (actionFieldVisible && columnIndex + 1 == actionIndex) {
268                         String actionColOptions = constructTableColumnOptions(columnIndex, false, null, null);
269                         tableToolsColumnOptions.append(actionColOptions + " , ");
270                         columnIndex++;
271                     }
272                     // for FieldGroup, get the first field from that group
273                     if (component instanceof FieldGroup) {
274                         component = ((FieldGroup) component).getItems().get(0);
275                     }
276 
277                     if (component instanceof DataField) {
278                         DataField field = (DataField) component;
279 
280                         // if a field is marked as invisible in hiddenColumns, append options and skip sorting
281                         if (getHiddenColumns() != null && getHiddenColumns().contains(field.getPropertyName())) {
282                             tableToolsColumnOptions.append("{"
283                                     + UifConstants.TableToolsKeys.VISIBLE
284                                     + ": "
285                                     + UifConstants.TableToolsValues.FALSE
286                                     + ", \""
287                                     + UifConstants.TableToolsKeys.TARGETS
288                                     + "\": ["
289                                     + columnIndex
290                                     + "]"
291                                     + "}, ");
292                             // if sortableColumns is present and a field is marked as sortable or unspecified
293                         } else if (getSortableColumns() != null && !getSortableColumns().isEmpty()) {
294                             if (getSortableColumns().contains(field.getPropertyName())) {
295                                 tableToolsColumnOptions.append(getDataFieldColumnOptions(columnIndex, collectionGroup,
296                                         field) + ", ");
297                             } else {
298                                 tableToolsColumnOptions.append("{'"
299                                         + UifConstants.TableToolsKeys.SORTABLE
300                                         + "': "
301                                         + UifConstants.TableToolsValues.FALSE
302                                         + ", \""
303                                         + UifConstants.TableToolsKeys.TARGETS
304                                         + "\": ["
305                                         + columnIndex
306                                         + "]"
307                                         + "}, ");
308                             }
309                         } else {// sortable columns not defined
310                             String colOptions = getDataFieldColumnOptions(columnIndex, collectionGroup, field);
311                             tableToolsColumnOptions.append(colOptions + " , ");
312                         }
313                         columnIndex++;
314                     } else if (component instanceof MessageField
315                             && component.getDataAttributes().get("role") != null
316                             && component.getDataAttributes().get("role").equals("grouping")) {
317                         //Grouping column is never shown, so skip
318                         tableToolsColumnOptions.append("{"
319                                 + UifConstants.TableToolsKeys.VISIBLE
320                                 + ": "
321                                 + UifConstants.TableToolsValues.FALSE
322                                 + ", \""
323                                 + UifConstants.TableToolsKeys.TARGETS
324                                 + "\": ["
325                                 + columnIndex
326                                 + "]"
327                                 + "}, ");
328                         columnIndex++;
329                     } else {
330                         String colOptions = constructTableColumnOptions(columnIndex, false, null, null);
331                         tableToolsColumnOptions.append(colOptions + " , ");
332                         columnIndex++;
333                     }
334 
335                 }
336 
337                 if (actionFieldVisible && (actionIndex == -1 || actionIndex >= columnIndex)) {
338                     String actionColOptions = constructTableColumnOptions(actionIndex, false, null, null);
339                     tableToolsColumnOptions.append(actionColOptions);
340                 } else {
341                     tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
342                             ", "));
343                 }
344                 //merge the aoColumnDefs passed in
345                 if(!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS))){
346                     String origAoOptions = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMN_DEFS).trim();
347                     origAoOptions = StringUtils.removeStart(origAoOptions, "[");
348                     origAoOptions = StringUtils.removeEnd(origAoOptions, "]");
349                     tableToolsColumnOptions.append("," + origAoOptions);
350                 }
351                 tableToolsColumnOptions.append("]");
352                 getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS,
353                         tableToolsColumnOptions.toString());
354             }
355         }
356     }
357 
358     /**
359      * Construct the column options for a data field
360      *
361      * @param collectionGroup - the collectionGroup in which the data field is defined
362      * @param field - the field to construction options for
363      * @return - options as valid for datatable
364      */
365     protected String getDataFieldColumnOptions(int target, CollectionGroup collectionGroup, DataField field) {
366         String sortType = null;
367 
368         if (!collectionGroup.isReadOnly()
369                 && (field instanceof InputField)
370                 && ((InputField) field).getControl() != null) {
371             Control control = ((InputField) field).getControl();
372             if (control instanceof SelectControl) {
373                 sortType = UifConstants.TableToolsValues.DOM_SELECT;
374             } else if (control instanceof CheckboxControl || control instanceof CheckboxGroupControl) {
375                 sortType = UifConstants.TableToolsValues.DOM_CHECK;
376             } else if (control instanceof RadioGroupControl) {
377                 sortType = UifConstants.TableToolsValues.DOM_RADIO;
378             } else {
379                 sortType = UifConstants.TableToolsValues.DOM_TEXT;
380             }
381         } else {
382             sortType = UifConstants.TableToolsValues.DOM_TEXT;
383         }
384 
385         Class dataTypeClass = ObjectPropertyUtils.getPropertyType(collectionGroup.getCollectionObjectClass(),
386                 field.getPropertyName());
387 
388         return constructTableColumnOptions(target, true, dataTypeClass, sortType);
389     }
390 
391     /**
392      * Constructs the sort data type for each data table columns in a format that will be used to initialize the data
393      * table widget via javascript
394      *
395      * @param isSortable - whether a column should be marked as sortable
396      * @param dataTypeClass - the class type of the column value - used determine the sType option - which identifies
397      * the search plugin to use
398      * @param sortDataType - Defines a data source type for the sorting which can be used to read realtime information
399      * from the table
400      * @return a formatted string with data table options for one column
401      */
402     protected String constructTableColumnOptions(int target, boolean isSortable, Class dataTypeClass,
403             String sortDataType) {
404         String colOptions = "null";
405 
406         String sortType = "";
407         if (!isSortable || dataTypeClass == null || sortType == null) {
408             colOptions = "\"" + UifConstants.TableToolsKeys.SORTABLE + "\" : false, \"sType\" : \"string\"";
409         } else {
410             if (ClassUtils.isAssignable(dataTypeClass, KualiPercent.class)) {
411                 sortType = UifConstants.TableToolsValues.PERCENT;
412             } else if (ClassUtils.isAssignable(dataTypeClass, KualiInteger.class) || ClassUtils.isAssignable(
413                     dataTypeClass, KualiDecimal.class)) {
414                 sortType = UifConstants.TableToolsValues.CURRENCY;
415             } else if (ClassUtils.isAssignable(dataTypeClass, Timestamp.class)) {
416                 sortType = "date";
417             } else if (ClassUtils.isAssignable(dataTypeClass, java.sql.Date.class) || ClassUtils.isAssignable(
418                     dataTypeClass, java.util.Date.class)) {
419                 sortType = UifConstants.TableToolsValues.DATE;
420             } else if (ClassUtils.isAssignable(dataTypeClass, Number.class)) {
421                 sortType = UifConstants.TableToolsValues.NUMERIC;
422             } else {
423                 sortType = UifConstants.TableToolsValues.STRING;
424             }
425 
426             colOptions = "\"" + UifConstants.TableToolsKeys.SORT_DATA_TYPE + "\" : \"" + sortDataType + "\"";
427             colOptions += " , \"" + UifConstants.TableToolsKeys.SORT_TYPE + "\" : \"" + sortType + "\"";
428         }
429 
430         if (!colOptions.equals("null")) {
431             colOptions = "{" + colOptions + ", \"" + UifConstants.TableToolsKeys.TARGETS + "\": [" + target + "]}";
432         } else {
433             colOptions = "{" + colOptions + "}";
434         }
435 
436         return colOptions;
437     }
438 
439     /**
440      * Returns the text which is used to display text when the table is empty
441      *
442      * @return empty table message
443      */
444     @BeanTagAttribute(name="emptyTableMessage")
445     public String getEmptyTableMessage() {
446         return emptyTableMessage;
447     }
448 
449     /**
450      * Setter for a text to be displayed when the table is empty
451      *
452      * @param emptyTableMessage
453      */
454     public void setEmptyTableMessage(String emptyTableMessage) {
455         this.emptyTableMessage = emptyTableMessage;
456     }
457 
458     /**
459      * Returns true if sorting is disabled
460      *
461      * @return the disableTableSort
462      */
463     @BeanTagAttribute(name="disableTableSort")
464     public boolean isDisableTableSort() {
465         return this.disableTableSort;
466     }
467 
468     /**
469      * Enables/disables the table sorting
470      *
471      * @param disableTableSort the disableTableSort to set
472      */
473     public void setDisableTableSort(boolean disableTableSort) {
474         this.disableTableSort = disableTableSort;
475     }
476 
477     /**
478      * Returns true if search and export options are enabled
479      *
480      * @return the showSearchAndExportOptions
481      */
482     @BeanTagAttribute(name="showSearchAndExportOptions")
483     public boolean isShowSearchAndExportOptions() {
484         return this.showSearchAndExportOptions;
485     }
486 
487     /**
488      * Show/Hide the search and export options in tabletools
489      *
490      * @param showSearchAndExportOptions the showSearchAndExportOptions to set
491      */
492     public void setShowSearchAndExportOptions(boolean showSearchAndExportOptions) {
493         this.showSearchAndExportOptions = showSearchAndExportOptions;
494     }
495 
496     /**
497      * Holds propertyNames for the ones meant to be hidden since columns are visible by default
498      *
499      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
500      *
501      * @return a set with propertyNames of columns to be hidden
502      */
503     @BeanTagAttribute(name="hiddenColumns",type= BeanTagAttribute.AttributeType.SETVALUE)
504     public Set<String> getHiddenColumns() {
505         return hiddenColumns;
506     }
507 
508     /**
509      * Setter for the hidden columns set
510      *
511      * @param hiddenColumns - a set containing propertyNames
512      */
513     public void setHiddenColumns(Set<String> hiddenColumns) {
514         this.hiddenColumns = hiddenColumns;
515     }
516 
517     /**
518      * Holds the propertyNames for columns that are to be sorted
519      *
520      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
521      *
522      * @return a set of propertyNames with for columns that will be sorted
523      */
524     @BeanTagAttribute(name="sortableColumns",type= BeanTagAttribute.AttributeType.SETVALUE)
525     public Set<String> getSortableColumns() {
526         return sortableColumns;
527     }
528 
529     /**
530      * Setter for sortable columns
531      *
532      * @param sortableColumns - a set containing propertyNames of columns to be sorted
533      */
534     public void setSortableColumns(Set<String> sortableColumns) {
535         this.sortableColumns = sortableColumns;
536     }
537 
538     /**
539      * Specifies a URL for acquiring the table data with ajax
540      *
541      * <p>
542      * When the ajax source URL is specified the rich table plugin will retrieve the data by invoking the URL and
543      * building the table rows from the result. This is different from the standard use of the rich table plugin
544      * with uses progressive enhancement to decorate a table that has already been rendereed
545      * </p>
546      *
547      * @return String URL for ajax source
548      */
549     @BeanTagAttribute(name="ajaxSource")
550     public String getAjaxSource() {
551         return ajaxSource;
552     }
553 
554     /**
555      * Setter for the Ajax source URL
556      *
557      * @param ajaxSource
558      */
559     public void setAjaxSource(String ajaxSource) {
560         this.ajaxSource = ajaxSource;
561     }
562 
563     /**
564      * Get groupingOption
565      *
566      * @return
567      */
568     public String getGroupingOptionsJSString() {
569         return groupingOptionsJSString;
570     }
571 
572     /**
573      * Set the groupingOptions js data.  <b>This should not be set through XML configuration.</b>
574      *
575      * @param groupingOptionsJSString
576      */
577     public void setGroupingOptionsJSString(String groupingOptionsJSString) {
578         this.groupingOptionsJSString = groupingOptionsJSString;
579     }
580 
581     public boolean isForceAoColumnDefsOverride() {
582         return forceAoColumnDefsOverride;
583     }
584 
585     public void setForceAoColumnDefsOverride(boolean forceAoColumnDefsOverride) {
586         this.forceAoColumnDefsOverride = forceAoColumnDefsOverride;
587     }
588 }