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 }