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.uif.UifConstants;
24  import org.kuali.rice.krad.uif.component.Component;
25  import org.kuali.rice.krad.uif.container.CollectionGroup;
26  import org.kuali.rice.krad.uif.control.CheckboxControl;
27  import org.kuali.rice.krad.uif.control.CheckboxGroupControl;
28  import org.kuali.rice.krad.uif.control.Control;
29  import org.kuali.rice.krad.uif.control.RadioGroupControl;
30  import org.kuali.rice.krad.uif.control.SelectControl;
31  import org.kuali.rice.krad.uif.field.DataField;
32  import org.kuali.rice.krad.uif.field.FieldGroup;
33  import org.kuali.rice.krad.uif.field.InputField;
34  import org.kuali.rice.krad.uif.layout.LayoutManager;
35  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
36  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
37  import org.kuali.rice.krad.uif.view.View;
38  import org.kuali.rice.krad.web.form.UifFormBase;
39  
40  import java.sql.Timestamp;
41  import java.util.Set;
42  
43  /**
44   * Decorates a HTML Table client side with various tools
45   *
46   * <p>
47   * Decorations implemented depend on widget implementation. Examples are
48   * sorting, paging and skinning.
49   * </p>
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public class RichTable extends WidgetBase {
54      private static final long serialVersionUID = 4671589690877390070L;
55  
56      private String emptyTableMessage;
57      private boolean disableTableSort;
58  
59      private Set<String> hiddenColumns;
60      private Set<String> sortableColumns;
61  
62      private String ajaxSource;
63  
64      private boolean showSearchAndExportOptions = true;
65  
66      public RichTable() {
67          super();
68      }
69  
70      /**
71       * The following initialization is performed:
72       *
73       * <ul>
74       * <li>Initializes component options for empty table message</li>
75       * </ul>
76       */
77      @Override
78      public void performFinalize(View view, Object model, Component component) {
79          super.performFinalize(view, model, component);
80  
81          UifFormBase formBase = (UifFormBase) model;
82  
83          if (isRender()) {
84              if (StringUtils.isNotBlank(getEmptyTableMessage()) && !getTemplateOptions().containsKey(
85                      UifConstants.TableToolsKeys.LANGUAGE)) {
86                  getTemplateOptions().put(UifConstants.TableToolsKeys.LANGUAGE,
87                          "{\"" + UifConstants.TableToolsKeys.EMPTY_TABLE + "\" : \"" + getEmptyTableMessage() + "\"}");
88              }
89  
90              if (!isShowSearchAndExportOptions()) {
91                  Object domOption = getTemplateOptions().get(UifConstants.TableToolsKeys.SDOM);
92                  if (domOption instanceof String) {
93                      String sDomOption = (String) domOption;
94  
95                      if (StringUtils.isNotBlank(sDomOption)) {
96                          sDomOption = StringUtils.remove(sDomOption, "T"); //Removes Export option
97                          sDomOption = StringUtils.remove(sDomOption, "f"); //Removes search option
98                          getTemplateOptions().put(UifConstants.TableToolsKeys.SDOM, sDomOption);
99                      }
100                 }
101             }
102 
103             // for add events, disable initial sorting
104             if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent())) {
105                 getTemplateOptions().put(UifConstants.TableToolsKeys.AASORTING, "[]");
106             }
107 
108             if ((component instanceof CollectionGroup)) {// && !getTemplateOptions().containsKey(UifConstants.TableToolsKeys.AO_COLUMNS)) {
109                 buildTableOptions((CollectionGroup) component);
110             }
111 
112             if (isDisableTableSort()) {
113                 getTemplateOptions().put(UifConstants.TableToolsKeys.TABLE_SORT, "false");
114             }
115 
116             if (StringUtils.isNotBlank(ajaxSource)) {
117                 getTemplateOptions().put(UifConstants.TableToolsKeys.SAJAX_SOURCE, ajaxSource);
118             }
119         }
120     }
121 
122     /**
123      * Builds column options for sorting
124      *
125      * @param collectionGroup
126      */
127     protected void buildTableOptions(CollectionGroup collectionGroup) {
128         LayoutManager layoutManager = collectionGroup.getLayoutManager();
129 
130         // if sub collection exists, don't allow the table sortable
131         if (!collectionGroup.getSubCollections().isEmpty()) {
132             setDisableTableSort(true);
133         }
134 
135         if (!isDisableTableSort()) {
136             // if rendering add line, skip that row from col sorting
137             if (collectionGroup.isRenderAddLine()
138                     && !collectionGroup.isReadOnly()
139                     && !((layoutManager instanceof TableLayoutManager) && ((TableLayoutManager) layoutManager)
140                     .isSeparateAddLine())) {
141                 getTemplateOptions().put(UifConstants.TableToolsKeys.SORT_SKIP_ROWS,
142                         "[" + UifConstants.TableToolsValues.ADD_ROW_DEFAULT_INDEX + "]");
143             }
144 
145             StringBuffer tableToolsColumnOptions = new StringBuffer("[");
146 
147             if (layoutManager instanceof TableLayoutManager && ((TableLayoutManager) layoutManager)
148                     .isRenderSequenceField()) {
149                 tableToolsColumnOptions.append(" null ,");
150             }
151 
152             // skip select field if enabled
153             if (collectionGroup.isIncludeLineSelectionField()) {
154                 String colOptions = constructTableColumnOptions(false, null, null);
155                 tableToolsColumnOptions.append(colOptions + " , ");
156             }
157 
158             // if data dictionary defines aoColumns, copy here and skip default sorting/visibility behaviour
159             if (!StringUtils.isEmpty(getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS))) {
160                 // get the contents of the JS array string
161                 String jsArray = getTemplateOptions().get(UifConstants.TableToolsKeys.AO_COLUMNS);
162                 int startBrace = StringUtils.indexOf(jsArray, "[");
163                 int endBrace = StringUtils.lastIndexOf(jsArray, "]");
164                 tableToolsColumnOptions.append(StringUtils.substring(jsArray, startBrace + 1, endBrace) + " , ");
165             } else {
166                 // TODO: does this handle multiple rows correctly?
167                 for (Component component : collectionGroup.getItems()) {
168                     // for FieldGroup, get the first field from that group
169                     if (component instanceof FieldGroup) {
170                         component = ((FieldGroup) component).getItems().get(0);
171                     }
172 
173                     if (component instanceof DataField) {
174                         DataField field = (DataField) component;
175 
176                         // if a field is marked as invisible in hiddenColumns, append options and skip sorting
177                         if (getHiddenColumns() != null && getHiddenColumns().contains(field.getPropertyName())) {
178                             tableToolsColumnOptions.append("{"
179                                     + UifConstants.TableToolsKeys.VISIBLE
180                                     + ": "
181                                     + UifConstants.TableToolsValues.FALSE
182                                     + "}, ");
183                             // if sortableColumns is present and a field is marked as sortable or unspecified
184                         } else if (getSortableColumns() != null && !getSortableColumns().isEmpty()) {
185                             if (getSortableColumns().contains(field.getPropertyName())) {
186                                 tableToolsColumnOptions.append(getDataFieldColumnOptions(collectionGroup, field)
187                                         + ", ");
188                             } else {
189                                 tableToolsColumnOptions.append("{'"
190                                         + UifConstants.TableToolsKeys.SORTABLE
191                                         + "': "
192                                         + UifConstants.TableToolsValues.FALSE
193                                         + "}, ");
194                             }
195                         } else {// sortable columns not defined
196                             String colOptions = getDataFieldColumnOptions(collectionGroup, field);
197                             tableToolsColumnOptions.append(colOptions + " , ");
198                         }
199                     } else {
200                         String colOptions = constructTableColumnOptions(false, null, null);
201                         tableToolsColumnOptions.append(colOptions + " , ");
202                     }
203                 }
204             }
205 
206             if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly()) {
207                 String colOptions = constructTableColumnOptions(false, null, null);
208                 tableToolsColumnOptions.append(colOptions);
209             } else {
210                 tableToolsColumnOptions = new StringBuffer(StringUtils.removeEnd(tableToolsColumnOptions.toString(),
211                         ", "));
212             }
213 
214             tableToolsColumnOptions.append("]");
215 
216             getTemplateOptions().put(UifConstants.TableToolsKeys.AO_COLUMNS, tableToolsColumnOptions.toString());
217         }
218     }
219 
220     /**
221      * Construct the column options for a data field
222      *
223      * @param collectionGroup - the collectionGroup in which the data field is defined
224      * @param field - the field to construction options for
225      * @return - options as valid for datatable
226      */
227     protected String getDataFieldColumnOptions(CollectionGroup collectionGroup, DataField field) {
228         String sortType = null;
229 
230         if (!collectionGroup.isReadOnly()
231                 && (field instanceof InputField)
232                 && ((InputField) field).getControl() != null) {
233             Control control = ((InputField) field).getControl();
234             if (control instanceof SelectControl) {
235                 sortType = UifConstants.TableToolsValues.DOM_SELECT;
236             } else if (control instanceof CheckboxControl || control instanceof CheckboxGroupControl) {
237                 sortType = UifConstants.TableToolsValues.DOM_CHECK;
238             } else if (control instanceof RadioGroupControl) {
239                 sortType = UifConstants.TableToolsValues.DOM_RADIO;
240             } else {
241                 sortType = UifConstants.TableToolsValues.DOM_TEXT;
242             }
243         } else {
244             sortType = UifConstants.TableToolsValues.DOM_TEXT;
245         }
246 
247         Class dataTypeClass = ObjectPropertyUtils.getPropertyType(collectionGroup.getCollectionObjectClass(),
248                 field.getPropertyName());
249 
250         return constructTableColumnOptions(true, dataTypeClass, sortType);
251     }
252 
253     /**
254      * Constructs the sort data type for each data table columns in a format that will be used to initialize the data
255      * table widget via javascript
256      *
257      * @param isSortable - whether a column should be marked as sortable
258      * @param dataTypeClass - the class type of the column value - used determine the sType option - which identifies
259      * the search plugin to use
260      * @param sortDataType - Defines a data source type for the sorting which can be used to read realtime information
261      * from the table
262      * @return a formatted string with data table options for one column
263      */
264     protected String constructTableColumnOptions(boolean isSortable, Class dataTypeClass, String sortDataType) {
265         String colOptions = "null";
266 
267         String sortType = "";
268         if (!isSortable || dataTypeClass == null || sortType == null) {
269             colOptions = "\"" + UifConstants.TableToolsKeys.SORTABLE + "\" : false, \"sType\" : \"string\"";
270         } else {
271             if (ClassUtils.isAssignable(dataTypeClass, KualiPercent.class)) {
272                 sortType = UifConstants.TableToolsValues.PERCENT;
273             } else if (ClassUtils.isAssignable(dataTypeClass, KualiInteger.class) || ClassUtils.isAssignable(
274                     dataTypeClass, KualiDecimal.class)) {
275                 sortType = UifConstants.TableToolsValues.CURRENCY;
276             } else if (ClassUtils.isAssignable(dataTypeClass, Timestamp.class)) {
277                 sortType = "date";
278             } else if (ClassUtils.isAssignable(dataTypeClass, java.sql.Date.class) || ClassUtils.isAssignable(
279                     dataTypeClass, java.util.Date.class)) {
280                 sortType = UifConstants.TableToolsValues.DATE;
281             } else if (ClassUtils.isAssignable(dataTypeClass, Number.class)) {
282                 sortType = UifConstants.TableToolsValues.NUMERIC;
283             } else {
284                 sortType = UifConstants.TableToolsValues.STRING;
285             }
286 
287             colOptions = "\"" + UifConstants.TableToolsKeys.SORT_DATA_TYPE + "\" : \"" + sortDataType + "\"";
288             colOptions += " , \"" + UifConstants.TableToolsKeys.SORT_TYPE + "\" : \"" + sortType + "\"";
289         }
290 
291         colOptions = "{" + colOptions + "}";
292 
293         return colOptions;
294     }
295 
296     /**
297      * Returns the text which is used to display text when the table is empty
298      *
299      * @return empty table message
300      */
301     public String getEmptyTableMessage() {
302         return emptyTableMessage;
303     }
304 
305     /**
306      * Setter for a text to be displayed when the table is empty
307      *
308      * @param emptyTableMessage
309      */
310     public void setEmptyTableMessage(String emptyTableMessage) {
311         this.emptyTableMessage = emptyTableMessage;
312     }
313 
314     /**
315      * Returns true if sorting is disabled
316      *
317      * @return the disableTableSort
318      */
319     public boolean isDisableTableSort() {
320         return this.disableTableSort;
321     }
322 
323     /**
324      * Enables/disables the table sorting
325      *
326      * @param disableTableSort the disableTableSort to set
327      */
328     public void setDisableTableSort(boolean disableTableSort) {
329         this.disableTableSort = disableTableSort;
330     }
331 
332     /**
333      * Returns true if search and export options are enabled
334      *
335      * @return the showSearchAndExportOptions
336      */
337     public boolean isShowSearchAndExportOptions() {
338         return this.showSearchAndExportOptions;
339     }
340 
341     /**
342      * Show/Hide the search and export options in tabletools
343      *
344      * @param showSearchAndExportOptions the showSearchAndExportOptions to set
345      */
346     public void setShowSearchAndExportOptions(boolean showSearchAndExportOptions) {
347         this.showSearchAndExportOptions = showSearchAndExportOptions;
348     }
349 
350     /**
351      * Holds propertyNames for the ones meant to be hidden since columns are visible by default
352      *
353      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
354      *
355      * @return a set with propertyNames of columns to be hidden
356      */
357     public Set<String> getHiddenColumns() {
358         return hiddenColumns;
359     }
360 
361     /**
362      * Setter for the hidden columns set
363      *
364      * @param hiddenColumns - a set containing propertyNames
365      */
366     public void setHiddenColumns(Set<String> hiddenColumns) {
367         this.hiddenColumns = hiddenColumns;
368     }
369 
370     /**
371      * Holds the propertyNames for columns that are to be sorted
372      *
373      * <p>Duplicate entries are ignored and the order of entries is not significant</p>
374      *
375      * @return a set of propertyNames with for columns that will be sorted
376      */
377     public Set<String> getSortableColumns() {
378         return sortableColumns;
379     }
380 
381     /**
382      * Setter for sortable columns
383      *
384      * @param sortableColumns - a set containing propertyNames of columns to be sorted
385      */
386     public void setSortableColumns(Set<String> sortableColumns) {
387         this.sortableColumns = sortableColumns;
388     }
389 
390     /**
391      * Specifies a URL for acquiring the table data with ajax
392      *
393      * <p>
394      * When the ajax source URL is specified the rich table plugin will retrieve the data by invoking the URL and
395      * building the table rows from the result. This is different from the standard use of the rich table plugin
396      * with uses progressive enhancement to decorate a table that has already been rendereed
397      * </p>
398      *
399      * @return String URL for ajax source
400      */
401     public String getAjaxSource() {
402         return ajaxSource;
403     }
404 
405     /**
406      * Setter for the Ajax source URL
407      *
408      * @param ajaxSource
409      */
410     public void setAjaxSource(String ajaxSource) {
411         this.ajaxSource = ajaxSource;
412     }
413 }