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