001/** 002 * Copyright 2005-2018 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.container; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.regex.Matcher; 024import java.util.regex.Pattern; 025 026import org.apache.commons.lang.StringUtils; 027import org.kuali.rice.krad.datadictionary.parse.BeanTag; 028import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 029import org.kuali.rice.krad.datadictionary.parse.BeanTags; 030import org.kuali.rice.krad.uif.UifConstants; 031import org.kuali.rice.krad.uif.component.BindingInfo; 032import org.kuali.rice.krad.uif.component.Component; 033import org.kuali.rice.krad.uif.component.DataBinding; 034import org.kuali.rice.krad.uif.control.CheckboxControl; 035import org.kuali.rice.krad.uif.control.Control; 036import org.kuali.rice.krad.uif.control.SelectControl; 037import org.kuali.rice.krad.uif.control.TextControl; 038import org.kuali.rice.krad.uif.element.Action; 039import org.kuali.rice.krad.uif.element.Image; 040import org.kuali.rice.krad.uif.element.Label; 041import org.kuali.rice.krad.uif.element.Link; 042import org.kuali.rice.krad.uif.element.Message; 043import org.kuali.rice.krad.uif.field.DataField; 044import org.kuali.rice.krad.uif.field.Field; 045import org.kuali.rice.krad.uif.field.FieldGroup; 046import org.kuali.rice.krad.uif.field.InputField; 047import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 048import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils; 049import org.kuali.rice.krad.uif.util.ComponentUtils; 050import org.kuali.rice.krad.uif.util.LifecycleElement; 051import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 052import org.kuali.rice.krad.uif.view.ExpressionEvaluator; 053import org.kuali.rice.krad.uif.view.View; 054import org.kuali.rice.krad.uif.widget.Inquiry; 055import org.kuali.rice.krad.uif.widget.RichTable; 056import org.kuali.rice.krad.uif.widget.Tooltip; 057import org.kuali.rice.krad.util.KRADConstants; 058import org.kuali.rice.krad.util.KRADUtils; 059import org.kuali.rice.krad.web.form.UifFormBase; 060 061/** 062 * LightTable is a light-weight collection table implementation that supports a subset of features, 063 * Current known supported features are: 064 * 065 * <ul> 066 * <li>DataField</li> 067 * <li>InputField with TextControl, CheckboxControl, or single SelectControl</li> 068 * <li>MessageField</li> 069 * <li>LinkField</li> 070 * <li>ActionField</li> 071 * <li>ImageField</li> 072 * <li>most RichTable options</li> 073 * <li>FieldGroup containing only Actions, Image, Messages, or Links</li> 074 * <li>SpringEL for String properties on supported components only</li> 075 * <li>SpringEL specifically for the render property</li> 076 * </ul> 077 * 078 * Other features are not guaranteed to work, but may work at your own risk. The intent of this table is to be a 079 * light-weight alternative to the fully featured table already available in KRAD and it is more suited to displaying 080 * large sets of simple data to the user. 081 * 082 * @author Kuali Rice Team (rice.collab@kuali.org) 083 */ 084@BeanTags({@BeanTag(name = "lightTable", parent = "Uif-LightTableGroup"), 085 @BeanTag(name = "lightTableSection", parent = "Uif-LightTableSection"), 086 @BeanTag(name = "lightTableSubSection", parent = "Uif-LightTableSubSection")}) 087public class LightTable extends GroupBase implements DataBinding { 088 private static final long serialVersionUID = -8930885219866835711L; 089 090 private static final String VALUE_TOKEN = "@v@"; 091 private static final String EXPRESSION_TOKEN = "@e@"; 092 private static final String RENDER = "render"; 093 private static final String ID_TOKEN = "@id@"; 094 private static final String A_TOKEN = "@"; 095 private static final String ROW_CLASS = "@rowClass@"; 096 private static final String SORT_VALUE = "@sortVal"; 097 private static final String SEPARATOR = "@@@"; 098 099 private String propertyName; 100 private BindingInfo bindingInfo; 101 private List<Label> headerLabels; 102 private RichTable richTable; 103 private Map<String, String> conditionalRowCssClasses; 104 105 private Map<String, String> expressionConversionMap; 106 private List<String> initialComponentIds; 107 private Map<String, String> renderIdExpressionMap; 108 private boolean emptyTable; 109 private String currentColumnValue; 110 111 /** 112 * LightTable constructor 113 */ 114 public LightTable() { 115 expressionConversionMap = new HashMap<String, String>(); 116 initialComponentIds = new ArrayList<String>(); 117 renderIdExpressionMap = new HashMap<String, String>(); 118 } 119 120 /** 121 * Initialization override that sets up DataField value placeholders for parsing and populates the 122 * expressionConversionMap 123 */ 124 @Override 125 public void performInitialization(Object model) { 126 super.performInitialization(model); 127 richTable.setForceLocalJsonData(true); 128 129 //init binding info 130 if (bindingInfo != null) { 131 bindingInfo.setDefaults(ViewLifecycle.getView(), getPropertyName()); 132 } 133 134 List<? extends Component> items = getItems(); 135 136 ComponentUtils.clearAndAssignIds(items); 137 138 //iterate over this collections items to initialize 139 for (Component item : this.getItems()) { 140 initialComponentIds.add(item.getId()); 141 142 //if data field, setup a forced placeholder value 143 if (item instanceof DataField) { 144 ((DataField) item).setForcedValue(VALUE_TOKEN + item.getId() + VALUE_TOKEN); 145 } 146 147 ///populate expression map 148 expressionConversionMap = buildExpressionMap(item, expressionConversionMap); 149 } 150 } 151 152 /** 153 * Builds the expression map which contains "propertyName@@@id" and the expression. Also fills the 154 * renderIdExpressionMap which contains all the component ids and expressions for render conditions, and overrides 155 * ids with a placeholder id. This method is recursive for child components which match certain supported types. 156 * 157 * @param item the item to iterate on 158 * @param expressionMap the map holding the expressions for the items of this collection 159 * @return the expressionMap with expressions populated 160 */ 161 protected Map<String, String> buildExpressionMap(Component item, Map<String, String> expressionMap) { 162 if (item == null) { 163 return expressionMap; 164 } 165 166 List<String> toRemove = new ArrayList<String>(); 167 168 if (item.getExpressionGraph() != null && !item.getExpressionGraph().isEmpty()) { 169 for (String name : item.getExpressionGraph().keySet()) { 170 processExpression(name, item, expressionMap, toRemove); 171 } 172 } 173 174 //id placeholder 175 item.setId(ID_TOKEN + item.getId() + ID_TOKEN); 176 177 if (item instanceof Group) { 178 ((Group) item).getLayoutManager().setId(ID_TOKEN + ((Group) item).getLayoutManager().getId() + ID_TOKEN); 179 } 180 181 expressionMap = addChildExpressions(ViewLifecycleUtils.getElementsForLifecycle(item).values(), expressionMap); 182 183 for (String name : toRemove) { 184 item.getExpressionGraph().remove(name); 185 } 186 187 return expressionMap; 188 } 189 190 /** 191 * Process the expression for the item by putting placeholder values in for String properties and adding markers 192 * for render expressions to the component; adds the original expression to the expressionMap 193 * 194 * @param name the property name 195 * @param item the component this expressio is on 196 * @param expressionMap the map to add expressions to 197 * @param toRemove the property name is added this map to be removed later 198 */ 199 public void processExpression(String name, Component item, Map<String, String> expressionMap, 200 List<String> toRemove) { 201 Class<?> clazz = ObjectPropertyUtils.getPropertyType(item, name); 202 if (clazz == null) { 203 return; 204 } 205 206 if (clazz.isAssignableFrom(String.class)) { 207 //add expressions for string properties only 208 expressionMap.put(name + SEPARATOR + item.getId(), item.getExpressionGraph().get(name)); 209 toRemove.add(name); 210 ObjectPropertyUtils.setPropertyValue(item, name, 211 EXPRESSION_TOKEN + name + SEPARATOR + item.getId() + EXPRESSION_TOKEN); 212 213 } else if (name.endsWith(RENDER) && clazz.isAssignableFrom(boolean.class)) { 214 //setup render tokens to be able to determine where to remove content for render false, if needed 215 Component renderComponent = item; 216 217 //check for nested render (child element) 218 if (!name.equals(RENDER)) { 219 renderComponent = ObjectPropertyUtils.getPropertyValue(item, StringUtils.removeEnd(name, ".render")); 220 } 221 222 //add render expression to the map 223 renderIdExpressionMap.put(renderComponent.getId(), item.getExpressionGraph().get(name)); 224 toRemove.add(name); 225 226 String renderMarker = A_TOKEN + RENDER + A_TOKEN + renderComponent.getId() + A_TOKEN; 227 228 //setup pre render content token 229 String pre = renderComponent.getPreRenderContent() == null ? "" : renderComponent.getPreRenderContent(); 230 renderComponent.setPreRenderContent(renderMarker + pre); 231 232 //setup post render content token 233 String post = renderComponent.getPostRenderContent() == null ? "" : renderComponent.getPostRenderContent(); 234 renderComponent.setPostRenderContent(post + renderMarker); 235 236 //force render to true 237 ObjectPropertyUtils.setPropertyValue(item, name, true); 238 } 239 } 240 241 /** 242 * Add expressions to the expression map for nested components of specific types 243 * 244 * @param components the child components 245 * @param expressionMap the map to add expressions to 246 * @return the map with child component expressions added 247 */ 248 protected Map<String, String> addChildExpressions(Collection<? extends LifecycleElement> components, 249 Map<String, String> expressionMap) { 250 for (LifecycleElement comp : components) { 251 if (comp != null && (comp instanceof Action 252 || comp instanceof Image 253 || comp instanceof Message 254 || comp instanceof Link 255 || comp instanceof Inquiry 256 || comp instanceof Group 257 || comp instanceof Tooltip 258 || comp instanceof InputField 259 || comp instanceof CheckboxControl 260 || comp instanceof TextControl 261 || comp instanceof SelectControl)) { 262 expressionMap = buildExpressionMap((Component) comp, expressionMap); 263 } 264 } 265 266 return expressionMap; 267 } 268 269 /** 270 * performFinalize override corrects the binding path for the DataFields and turns off rendering on some components 271 */ 272 @Override 273 public void performFinalize(Object model, LifecycleElement parent) { 274 super.performFinalize(model, parent); 275 276 headerLabels = new ArrayList<Label>(); 277 for (Component item : this.getItems()) { 278 //get the header labels 279 if (item instanceof Field) { 280 headerLabels.add(ComponentUtils.copy(((Field) item).getFieldLabel())); 281 ((Field) item).getFieldLabel().setRender(false); 282 } else { 283 headerLabels.add(null); 284 } 285 286 if (item instanceof FieldGroup) { 287 ((FieldGroup) item).getGroup().setValidationMessages(null); 288 289 } 290 291 if (item instanceof DataField) { 292 ((DataField) item).getBindingInfo().setBindByNamePrefix(this.getBindingInfo().getBindingPath() + "[0]"); 293 } 294 295 if (item instanceof InputField) { 296 ViewLifecycle.getViewPostMetadata().addAccessibleBindingPath(this.getBindingInfo().getBindingPath() + "[*]." + ((DataField) item).getPropertyName()); 297 } 298 } 299 300 Object collectionValue = ObjectPropertyUtils.getPropertyValue(model, bindingInfo.getBindingPath()); 301 302 //set emptyTable true if null, empty, or not valid collection 303 if (collectionValue == null || !(collectionValue instanceof Collection) || 304 ((Collection<?>) collectionValue).isEmpty()) { 305 emptyTable = true; 306 } 307 } 308 309 /** 310 * Build the rows from the rowTemplate passed in. This method uses regex to locate pieces of the row that need 311 * to be replaced with row specific content per row. 312 * 313 * @param view the view instance the table is being built within 314 * @param rowTemplate the first row of the collection in html generated from the ftl 315 * @param model the model 316 */ 317 public void buildRows(View view, String rowTemplate, UifFormBase model) { 318 if (StringUtils.isBlank(rowTemplate)) { 319 return; 320 } 321 322 rowTemplate = StringUtils.removeEnd(rowTemplate, ","); 323 rowTemplate = rowTemplate.replace("\n", ""); 324 rowTemplate = rowTemplate.replace("\r", ""); 325 326 StringBuffer rows = new StringBuffer(); 327 List<Object> collectionObjects = ObjectPropertyUtils.getPropertyValue(model, bindingInfo.getBindingPath()); 328 329 //uncheck any checked checkboxes globally for this row 330 rowTemplate = rowTemplate.replace("checked=\"checked\"", ""); 331 332 //init token patterns 333 Pattern idPattern = Pattern.compile(ID_TOKEN + "(.*?)" + ID_TOKEN); 334 Pattern expressionPattern = Pattern.compile(EXPRESSION_TOKEN + "(.*?)" + EXPRESSION_TOKEN); 335 336 ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator(); 337 expressionEvaluator.initializeEvaluationContext(model); 338 339 int lineIndex = 0; 340 for (Object obj : collectionObjects) { 341 //add line index to all ids 342 String row = idPattern.matcher(rowTemplate).replaceAll("$1" + UifConstants.IdSuffixes.LINE + lineIndex); 343 344 //create the expanded context 345 Map<String, Object> expandedContext = new HashMap<String, Object>(); 346 expandedContext.put(UifConstants.ContextVariableNames.LINE, obj); 347 expandedContext.put(UifConstants.ContextVariableNames.INDEX, lineIndex); 348 expandedContext.put(UifConstants.ContextVariableNames.VIEW, view); 349 350 currentColumnValue = ""; 351 352 int itemIndex = 0; 353 for (Component item : this.getItems()) { 354 //determine original id for this component 355 String originalId = initialComponentIds.get(itemIndex); 356 357 //special DataField handling 358 row = handleDataFieldInRow(item, obj, row, lineIndex, originalId); 359 360 //special InputField handling 361 row = handleInputFieldInRow(item, obj, row, lineIndex, originalId); 362 363 //add item context 364 if (item.getContext() != null) { 365 expandedContext.putAll(item.getContext()); 366 } 367 368 //evaluate expressions found by the pattern 369 row = evaluateAndReplaceExpressionValues(row, lineIndex, model, expandedContext, expressionPattern, 370 expressionEvaluator); 371 372 if (currentColumnValue == null) { 373 currentColumnValue = ""; 374 } 375 376 row = row.replace(SORT_VALUE + itemIndex + A_TOKEN, currentColumnValue); 377 378 itemIndex++; 379 } 380 381 // get rowCss class 382 boolean isOdd = lineIndex % 2 == 0; 383 String rowCss = KRADUtils.generateRowCssClassString(conditionalRowCssClasses, lineIndex, isOdd, 384 expandedContext, expressionEvaluator); 385 386 row = row.replace("\"", "\\\""); 387 row = row.replace(ROW_CLASS, rowCss); 388 row = "{" + row + "},"; 389 390 //special render property expression handling 391 row = evaluateRenderExpressions(row, lineIndex, model, expandedContext, expressionEvaluator); 392 393 //append row 394 rows.append(row); 395 lineIndex++; 396 } 397 398 StringBuffer tableToolsColumnOptions = new StringBuffer("["); 399 for (int index = 0; index < this.getItems().size(); index++) { 400 String colOptions = richTable.constructTableColumnOptions(index, true, false, String.class, null); 401 tableToolsColumnOptions.append(colOptions + " , "); 402 } 403 404 String aoColumnDefs = StringUtils.removeEnd(tableToolsColumnOptions.toString(), " , ") + "]"; 405 Map<String, String> rtTemplateOptions = richTable.getTemplateOptions(); 406 407 if (rtTemplateOptions == null) { 408 richTable.setTemplateOptions(rtTemplateOptions = new HashMap<String, String>()); 409 } 410 411 rtTemplateOptions.put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS, aoColumnDefs); 412 413 // construct aaData option to set data in dataTable options (speed enhancement) 414 String aaData = StringUtils.removeEnd(rows.toString(), ","); 415 aaData = "[" + aaData + "]"; 416 aaData = aaData.replace(KRADConstants.QUOTE_PLACEHOLDER, "\""); 417 418 //set the aaData option on datatable for faster rendering 419 rtTemplateOptions.put(UifConstants.TableToolsKeys.AA_DATA, aaData); 420 421 //make sure deferred rendering is forced whether set or not 422 rtTemplateOptions.put(UifConstants.TableToolsKeys.DEFER_RENDER, 423 UifConstants.TableToolsValues.TRUE); 424 } 425 426 /** 427 * Evaluate expressions and replace content found by the expressionPattern in the row 428 * 429 * @param row the row being modified 430 * @param index the line index 431 * @param model the model 432 * @param expandedContext the context to evaluate expressions against 433 * @param expressionPattern the expression pattern used to find expression tokens for value replacement 434 * @param expressionEvaluator the expression service to use for evaluation 435 * @return the modified row 436 */ 437 protected String evaluateAndReplaceExpressionValues(String row, int index, Object model, 438 Map<String, Object> expandedContext, Pattern expressionPattern, ExpressionEvaluator expressionEvaluator) { 439 440 Matcher matcher = expressionPattern.matcher(row); 441 442 while (matcher.find()) { 443 String matchingGroup = matcher.group(1); 444 String expression = expressionConversionMap.get(matchingGroup); 445 446 //adjust prefix for evaluation 447 expression = expression.replace(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX, 448 this.getBindingInfo().getBindingPath() + "[" + index + "]."); 449 450 //get expression result 451 Object value = expressionEvaluator.evaluateExpressionTemplate(expandedContext, expression); 452 453 if (value != null) { 454 row = row.replace(matcher.group(), value.toString()); 455 } else { 456 row = row.replace(matcher.group(), ""); 457 } 458 } 459 460 return row; 461 } 462 463 /** 464 * Evaluates the render expressions for the row and removes the content if render is evaluated false 465 * 466 * @param row the row being modified 467 * @param index the line index 468 * @param model the model 469 * @param expandedContext the context to evaluate expressions against 470 * @param expressionEvaluator the expression service to use for evaluation 471 * @return the modified row 472 */ 473 protected String evaluateRenderExpressions(String row, int index, Object model, Map<String, Object> expandedContext, 474 ExpressionEvaluator expressionEvaluator) { 475 for (String id : renderIdExpressionMap.keySet()) { 476 String expression = renderIdExpressionMap.get(id); 477 478 //adjust prefix for evaluation 479 expression = expression.replace(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX, 480 this.getBindingInfo().getBindingPath() + "[" + index + "]."); 481 482 //get expression result 483 Object value = expressionEvaluator.evaluateExpressionTemplate(expandedContext, expression); 484 485 String wrap = A_TOKEN + RENDER + A_TOKEN + id + A_TOKEN; 486 487 if (value != null && value instanceof String && Boolean.parseBoolean((String) value) == false) { 488 //do not render this component - remove content between render wrappers 489 row = row.replaceAll(wrap + "(.|\\s)*?" + wrap, ""); 490 } else { 491 //remove render wrappers only - keep content 492 row = row.replaceAll(wrap, ""); 493 } 494 } 495 496 return row; 497 } 498 499 /** 500 * Special handling of the DataField in the row, replaces necessary content with row specific content 501 * 502 * @param item the item being processed 503 * @param obj the row's object model 504 * @param row the row in html 505 * @param index the current row index 506 * @param originalId the original id of the component item 507 * @return the updated row 508 */ 509 protected String handleDataFieldInRow(Component item, Object obj, String row, int index, String originalId) { 510 if (!(item instanceof DataField)) { 511 return row; 512 } 513 514 String currentValue = ObjectPropertyUtils.getPropertyValueAsText(obj, ((DataField) item).getPropertyName()); 515 516 if (currentValue == null) { 517 currentValue = ""; 518 } 519 520 //for readOnly DataFields replace the value marked with the value on the current object 521 row = row.replaceAll(VALUE_TOKEN + originalId + VALUE_TOKEN, currentValue); 522 currentColumnValue = currentValue; 523 524 Inquiry dataFieldInquiry = ((DataField) item).getInquiry(); 525 if (dataFieldInquiry != null && dataFieldInquiry.getInquiryParameters() != null 526 && dataFieldInquiry.getInquiryLink() != null) { 527 528 String inquiryLinkId = dataFieldInquiry.getInquiryLink().getId().replace(ID_TOKEN, "") 529 + UifConstants.IdSuffixes.LINE + index; 530 531 // process each Inquiry link parameter by replacing each in the inquiry url with their current value 532 for (String key : dataFieldInquiry.getInquiryParameters().keySet()) { 533 String name = dataFieldInquiry.getInquiryParameters().get(key); 534 535 //omit the binding prefix from the key to get the path relative to the current object 536 key = key.replace(((DataField) item).getBindingInfo().getBindByNamePrefix() + ".", ""); 537 538 if (ObjectPropertyUtils.isReadableProperty(obj, key)) { 539 String value = ObjectPropertyUtils.getPropertyValueAsText(obj, key); 540 row = row.replaceFirst("(" + inquiryLinkId + "(.|\\s)*?" + name + ")=.*?([&|\"])", 541 "$1=" + value + "$3"); 542 } 543 } 544 } 545 546 return row; 547 } 548 549 /** 550 * Special handling of the InputField in the row, replaces necessary content with row specific content 551 * 552 * @param item the item being processed 553 * @param obj the row's object model 554 * @param row the row in html 555 * @param index the current row index 556 * @param originalId the original id of the component item 557 * @return the updated row 558 */ 559 protected String handleInputFieldInRow(Component item, Object obj, String row, int index, String originalId) { 560 if (!(item instanceof InputField) || ((InputField) item).getControl() == null) { 561 return row; 562 } 563 564 Control control = ((InputField) item).getControl(); 565 566 //updates the name path to the current path for any instance this item's propertyName with 567 //a collection binding prefix 568 row = row.replace("name=\"" + ((InputField) item).getBindingInfo().getBindingPath() + "\"", 569 "name=\"" + this.getBindingInfo().getBindingPath() + "[" + index + "]." + ((InputField) item) 570 .getPropertyName() + "\""); 571 572 Object value = ObjectPropertyUtils.getPropertyValue(obj, ((InputField) item).getPropertyName()); 573 String stringValue = ""; 574 575 if (value == null) { 576 stringValue = ""; 577 } else if (value.getClass().isAssignableFrom(boolean.class)) { 578 stringValue = "" + value; 579 } else if (!(value instanceof Collection)) { 580 stringValue = ObjectPropertyUtils.getPropertyValueAsText(obj, ((InputField) item).getPropertyName()); 581 } 582 583 String controlId = originalId + "_line" + index + UifConstants.IdSuffixes.CONTROL; 584 585 if (control instanceof CheckboxControl && stringValue.equalsIgnoreCase("true")) { 586 //CheckboxControl handling - only replace if true with a checked attribute appended 587 row = row.replaceAll("(id(\\s)*?=(\\s)*?\"" + controlId + "\")", "$1 checked=\"checked\" "); 588 } else if (control instanceof TextControl) { 589 //TextControl handling - replace with 590 row = row.replaceAll("(id(\\s)*?=(\\s)*?\"" + controlId + "\"(.|\\s)*?value=\")(.|\\s)*?\"", 591 "$1" + stringValue + "\""); 592 } else if (control instanceof SelectControl && !((SelectControl) control).isMultiple()) { 593 //SelectControl handling (single item only) 594 Pattern pattern = Pattern.compile("<select(\\s)*?id(\\s)*?=(\\s)*?\"" + controlId + "\"(.|\\s)*?</select>"); 595 Matcher matcher = pattern.matcher(row); 596 String replacement = ""; 597 598 if (matcher.find()) { 599 //remove selected from select options 600 String selected = "selected=\"selected\""; 601 replacement = matcher.group().replace(selected, ""); 602 603 //put selected on only the selected option 604 String selectedValue = "value=\"" + stringValue + "\""; 605 replacement = replacement.replace(selectedValue, selectedValue + " " + selected); 606 } 607 608 //replace the old select tag with the old one 609 if (StringUtils.isNotBlank(replacement)) { 610 row = matcher.replaceAll(replacement); 611 } 612 } 613 614 currentColumnValue = stringValue; 615 616 return row; 617 } 618 619 /** 620 * The propertyName of the list backing this collection 621 * 622 * @return the propertyName of this collection 623 */ 624 @BeanTagAttribute 625 public String getPropertyName() { 626 return propertyName; 627 } 628 629 /** 630 * Set the propertyName 631 * 632 * @param propertyName 633 */ 634 public void setPropertyName(String propertyName) { 635 this.propertyName = propertyName; 636 } 637 638 /** 639 * The bindingInfo for this collection table, containg the property path and other options 640 * 641 * @return the bindingInfo 642 */ 643 @BeanTagAttribute 644 public BindingInfo getBindingInfo() { 645 return bindingInfo; 646 } 647 648 /** 649 * Set the bindingInfo 650 * 651 * @param bindingInfo 652 */ 653 public void setBindingInfo(BindingInfo bindingInfo) { 654 this.bindingInfo = bindingInfo; 655 } 656 657 /** 658 * The labels for the header derived from the items of this collection (the fields) 659 * 660 * @return the header labels 661 */ 662 public List<Label> getHeaderLabels() { 663 return headerLabels; 664 } 665 666 /** 667 * The richTable widget definition for this table for setting dataTable javascript options 668 * 669 * @return the RichTable widget 670 */ 671 @BeanTagAttribute 672 public RichTable getRichTable() { 673 return richTable; 674 } 675 676 /** 677 * Set the richTable widget 678 * 679 * @param richTable 680 */ 681 public void setRichTable(RichTable richTable) { 682 this.richTable = richTable; 683 } 684 685 /** 686 * The row css classes for the rows of this layout 687 * 688 * <p>To set a css class on all rows, use "all" as a key. To set a 689 * class for even rows, use "even" as a key, for odd rows, use "odd". 690 * Use a one-based index to target a specific row by index. SpringEL can be 691 * used as a key and the expression will be evaluated; if evaluated to true, the 692 * class(es) specified will be applied.</p> 693 * 694 * @return a map which represents the css classes of the rows of this layout 695 */ 696 @BeanTagAttribute 697 public Map<String, String> getConditionalRowCssClasses() { 698 return conditionalRowCssClasses; 699 } 700 701 /** 702 * Set the conditionalRowCssClasses 703 * 704 * @param conditionalRowCssClasses 705 */ 706 public void setConditionalRowCssClasses(Map<String, String> conditionalRowCssClasses) { 707 this.conditionalRowCssClasses = conditionalRowCssClasses; 708 } 709 710 /** 711 * True if this table is empty, false otherwise 712 * 713 * @return true if the collection backing this table is empty 714 */ 715 public boolean isEmptyTable() { 716 return emptyTable; 717 } 718 719 public void setHeaderLabels(List<Label> headerLabels) { 720 this.headerLabels = headerLabels; 721 } 722 723 public void setExpressionConversionMap(Map<String, String> expressionConversionMap) { 724 this.expressionConversionMap = expressionConversionMap; 725 } 726 727 public Map<String, String> getExpressionConversionMap() { 728 return expressionConversionMap; 729 } 730 731 public List<String> getInitialComponentIds() { 732 return initialComponentIds; 733 } 734 735 public Map<String, String> getRenderIdExpressionMap() { 736 return renderIdExpressionMap; 737 } 738 739 public void setInitialComponentIds(List<String> initialComponentIds) { 740 this.initialComponentIds = initialComponentIds; 741 } 742 743 public void setRenderIdExpressionMap(Map<String, String> renderIdExpressionMap) { 744 this.renderIdExpressionMap = renderIdExpressionMap; 745 } 746 747 public void setEmptyTable(boolean emptyTable) { 748 this.emptyTable = emptyTable; 749 } 750 751 /** 752 * 753 * @return the current column value 754 */ 755 @BeanTagAttribute(name = "currentColumnValue") 756 protected String getCurrentColumnValue() { 757 return currentColumnValue; 758 } 759 760 /** 761 * Set the current column value 762 * 763 * @param currentColumnValue 764 */ 765 protected void setCurrentColumnValue(String currentColumnValue) { 766 this.currentColumnValue = currentColumnValue; 767 } 768}