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