View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.container;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.regex.Matcher;
24  import java.util.regex.Pattern;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
28  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
29  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
30  import org.kuali.rice.krad.uif.UifConstants;
31  import org.kuali.rice.krad.uif.component.BindingInfo;
32  import org.kuali.rice.krad.uif.component.Component;
33  import org.kuali.rice.krad.uif.component.DataBinding;
34  import org.kuali.rice.krad.uif.control.CheckboxControl;
35  import org.kuali.rice.krad.uif.control.Control;
36  import org.kuali.rice.krad.uif.control.SelectControl;
37  import org.kuali.rice.krad.uif.control.TextControl;
38  import org.kuali.rice.krad.uif.element.Action;
39  import org.kuali.rice.krad.uif.element.Image;
40  import org.kuali.rice.krad.uif.element.Label;
41  import org.kuali.rice.krad.uif.element.Link;
42  import org.kuali.rice.krad.uif.element.Message;
43  import org.kuali.rice.krad.uif.field.DataField;
44  import org.kuali.rice.krad.uif.field.Field;
45  import org.kuali.rice.krad.uif.field.FieldGroup;
46  import org.kuali.rice.krad.uif.field.InputField;
47  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
48  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
49  import org.kuali.rice.krad.uif.util.ComponentUtils;
50  import org.kuali.rice.krad.uif.util.LifecycleElement;
51  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
52  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
53  import org.kuali.rice.krad.uif.view.View;
54  import org.kuali.rice.krad.uif.widget.Inquiry;
55  import org.kuali.rice.krad.uif.widget.RichTable;
56  import org.kuali.rice.krad.uif.widget.Tooltip;
57  import org.kuali.rice.krad.util.KRADConstants;
58  import org.kuali.rice.krad.util.KRADUtils;
59  import org.kuali.rice.krad.web.form.UifFormBase;
60  
61  /**
62   * LightTable is a light-weight collection table implementation that supports a subset of features,
63   * Current known supported features are:
64   *
65   * <ul>
66   * <li>DataField</li>
67   * <li>InputField with TextControl, CheckboxControl, or single SelectControl</li>
68   * <li>MessageField</li>
69   * <li>LinkField</li>
70   * <li>ActionField</li>
71   * <li>ImageField</li>
72   * <li>most RichTable options</li>
73   * <li>FieldGroup containing only Actions, Image, Messages, or Links</li>
74   * <li>SpringEL for String properties on supported components only</li>
75   * <li>SpringEL specifically for the render property</li>
76   * </ul>
77   *
78   * Other features are not guaranteed to work, but may work at your own risk.  The intent of this table is to be a
79   * light-weight alternative to the fully featured table already available in KRAD and it is more suited to displaying
80   * large sets of simple data to the user.
81   *
82   * @author Kuali Rice Team (rice.collab@kuali.org)
83   */
84  @BeanTags({@BeanTag(name = "lightTable", parent = "Uif-LightTableGroup"),
85          @BeanTag(name = "lightTableSection", parent = "Uif-LightTableSection"),
86          @BeanTag(name = "lightTableSubSection", parent = "Uif-LightTableSubSection")})
87  public class LightTable extends GroupBase implements DataBinding {
88      private static final long serialVersionUID = -8930885219866835711L;
89  
90      private static final String VALUE_TOKEN = "@v@";
91      private static final String EXPRESSION_TOKEN = "@e@";
92      private static final String RENDER = "render";
93      private static final String ID_TOKEN = "@id@";
94      private static final String A_TOKEN = "@";
95      private static final String ROW_CLASS = "@rowClass@";
96      private static final String SORT_VALUE = "@sortVal";
97      private static final String SEPARATOR = "@@@";
98  
99      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 
296         Object collectionValue = ObjectPropertyUtils.getPropertyValue(model, bindingInfo.getBindingPath());
297 
298         //set emptyTable true if null, empty, or not valid collection
299         if (collectionValue == null || !(collectionValue instanceof Collection) ||
300                 ((Collection<?>) collectionValue).isEmpty()) {
301             emptyTable = true;
302         }
303     }
304 
305     /**
306      * Build the rows from the rowTemplate passed in.  This method uses regex to locate pieces of the row that need
307      * to be replaced with row specific content per row.
308      *
309      * @param view the view instance the table is being built within
310      * @param rowTemplate the first row of the collection in html generated from the ftl
311      * @param model the model
312      */
313     public void buildRows(View view, String rowTemplate, UifFormBase model) {
314         if (StringUtils.isBlank(rowTemplate)) {
315             return;
316         }
317 
318         rowTemplate = StringUtils.removeEnd(rowTemplate, ",");
319         rowTemplate = rowTemplate.replace("\n", "");
320         rowTemplate = rowTemplate.replace("\r", "");
321 
322         StringBuffer rows = new StringBuffer();
323         List<Object> collectionObjects = ObjectPropertyUtils.getPropertyValue(model, bindingInfo.getBindingPath());
324 
325         //uncheck any checked checkboxes globally for this row
326         rowTemplate = rowTemplate.replace("checked=\"checked\"", "");
327 
328         //init token patterns
329         Pattern idPattern = Pattern.compile(ID_TOKEN + "(.*?)" + ID_TOKEN);
330         Pattern expressionPattern = Pattern.compile(EXPRESSION_TOKEN + "(.*?)" + EXPRESSION_TOKEN);
331 
332         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
333         expressionEvaluator.initializeEvaluationContext(model);
334 
335         int lineIndex = 0;
336         for (Object obj : collectionObjects) {
337             //add line index to all ids
338             String row = idPattern.matcher(rowTemplate).replaceAll("$1" + UifConstants.IdSuffixes.LINE + lineIndex);
339 
340             //create the expanded context
341             Map<String, Object> expandedContext = new HashMap<String, Object>();
342             expandedContext.put(UifConstants.ContextVariableNames.LINE, obj);
343             expandedContext.put(UifConstants.ContextVariableNames.INDEX, lineIndex);
344             expandedContext.put(UifConstants.ContextVariableNames.VIEW, view);
345 
346             currentColumnValue = "";
347 
348             int itemIndex = 0;
349             for (Component item : this.getItems()) {
350                 //determine original id for this component
351                 String originalId = initialComponentIds.get(itemIndex);
352 
353                 //special DataField handling
354                 row = handleDataFieldInRow(item, obj, row, lineIndex, originalId);
355 
356                 //special InputField handling
357                 row = handleInputFieldInRow(item, obj, row, lineIndex, originalId);
358 
359                 //add item context
360                 if (item.getContext() != null) {
361                     expandedContext.putAll(item.getContext());
362                 }
363 
364                 //evaluate expressions found by the pattern
365                 row = evaluateAndReplaceExpressionValues(row, lineIndex, model, expandedContext, expressionPattern,
366                         expressionEvaluator);
367 
368                 if (currentColumnValue == null) {
369                     currentColumnValue = "";
370                 }
371 
372                 row = row.replace(SORT_VALUE + itemIndex + A_TOKEN, currentColumnValue);
373 
374                 itemIndex++;
375             }
376 
377             // get rowCss class
378             boolean isOdd = lineIndex % 2 == 0;
379             String rowCss = KRADUtils.generateRowCssClassString(conditionalRowCssClasses, lineIndex, isOdd,
380                     expandedContext, expressionEvaluator);
381 
382             row = row.replace("\"", "\\\"");
383             row = row.replace(ROW_CLASS, rowCss);
384             row = "{" + row + "},";
385 
386             //special render property expression handling
387             row = evaluateRenderExpressions(row, lineIndex, model, expandedContext, expressionEvaluator);
388 
389             //append row
390             rows.append(row);
391             lineIndex++;
392         }
393 
394         StringBuffer tableToolsColumnOptions = new StringBuffer("[");
395         for (int index = 0; index < this.getItems().size(); index++) {
396             String colOptions = richTable.constructTableColumnOptions(index, true, false, String.class, null);
397             tableToolsColumnOptions.append(colOptions + " , ");
398         }
399 
400         String aoColumnDefs = StringUtils.removeEnd(tableToolsColumnOptions.toString(), " , ") + "]";
401         Map<String, String> rtTemplateOptions = richTable.getTemplateOptions();
402         
403         if (rtTemplateOptions == null) {
404             richTable.setTemplateOptions(rtTemplateOptions = new HashMap<String, String>());
405         }
406         
407         rtTemplateOptions.put(UifConstants.TableToolsKeys.AO_COLUMN_DEFS, aoColumnDefs);
408 
409         // construct aaData option to set data in dataTable options (speed enhancement)
410         String aaData = StringUtils.removeEnd(rows.toString(), ",");
411         aaData = "[" + aaData + "]";
412         aaData = aaData.replace(KRADConstants.QUOTE_PLACEHOLDER, "\"");
413 
414         //set the aaData option on datatable for faster rendering
415         rtTemplateOptions.put(UifConstants.TableToolsKeys.AA_DATA, aaData);
416 
417         //make sure deferred rendering is forced whether set or not
418         rtTemplateOptions.put(UifConstants.TableToolsKeys.DEFER_RENDER,
419                 UifConstants.TableToolsValues.TRUE);
420     }
421 
422     /**
423      * Evaluate expressions and replace content found by the expressionPattern in the row
424      *
425      * @param row the row being modified
426      * @param index the line index
427      * @param model the model
428      * @param expandedContext the context to evaluate expressions against
429      * @param expressionPattern the expression pattern used to find expression tokens for value replacement
430      * @param expressionEvaluator the expression service to use for evaluation
431      * @return the modified row
432      */
433     protected String evaluateAndReplaceExpressionValues(String row, int index, Object model,
434             Map<String, Object> expandedContext, Pattern expressionPattern, ExpressionEvaluator expressionEvaluator) {
435 
436         Matcher matcher = expressionPattern.matcher(row);
437 
438         while (matcher.find()) {
439             String matchingGroup = matcher.group(1);
440             String expression = expressionConversionMap.get(matchingGroup);
441 
442             //adjust prefix for evaluation
443             expression = expression.replace(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX,
444                     this.getBindingInfo().getBindingPath() + "[" + index + "].");
445 
446             //get expression result
447             Object value = expressionEvaluator.evaluateExpressionTemplate(expandedContext, expression);
448 
449             if (value != null) {
450                 row = row.replace(matcher.group(), value.toString());
451             } else {
452                 row = row.replace(matcher.group(), "");
453             }
454         }
455 
456         return row;
457     }
458 
459     /**
460      * Evaluates the render expressions for the row and removes the content if render is evaluated false
461      *
462      * @param row the row being modified
463      * @param index the line index
464      * @param model the model
465      * @param expandedContext the context to evaluate expressions against
466      * @param expressionEvaluator the expression service to use for evaluation
467      * @return the modified row
468      */
469     protected String evaluateRenderExpressions(String row, int index, Object model, Map<String, Object> expandedContext,
470             ExpressionEvaluator expressionEvaluator) {
471         for (String id : renderIdExpressionMap.keySet()) {
472             String expression = renderIdExpressionMap.get(id);
473 
474             //adjust prefix for evaluation
475             expression = expression.replace(UifConstants.LINE_PATH_BIND_ADJUST_PREFIX,
476                     this.getBindingInfo().getBindingPath() + "[" + index + "].");
477 
478             //get expression result
479             Object value = expressionEvaluator.evaluateExpressionTemplate(expandedContext, expression);
480 
481             String wrap = A_TOKEN + RENDER + A_TOKEN + id + A_TOKEN;
482 
483             if (value != null && value instanceof String && Boolean.parseBoolean((String) value) == false) {
484                 //do not render this component - remove content between render wrappers
485                 row = row.replaceAll(wrap + "(.|\\s)*?" + wrap, "");
486             } else {
487                 //remove render wrappers only - keep content
488                 row = row.replaceAll(wrap, "");
489             }
490         }
491 
492         return row;
493     }
494 
495     /**
496      * Special handling of the DataField in the row, replaces necessary content with row specific content
497      *
498      * @param item the item being processed
499      * @param obj the row's object model
500      * @param row the row in html
501      * @param index the current row index
502      * @param originalId the original id of the component item
503      * @return the updated row
504      */
505     protected String handleDataFieldInRow(Component item, Object obj, String row, int index, String originalId) {
506         if (!(item instanceof DataField)) {
507             return row;
508         }
509 
510         String currentValue = ObjectPropertyUtils.getPropertyValueAsText(obj, ((DataField) item).getPropertyName());
511 
512         if (currentValue == null) {
513             currentValue = "";
514         }
515 
516         //for readOnly DataFields replace the value marked with the value on the current object
517         row = row.replaceAll(VALUE_TOKEN + originalId + VALUE_TOKEN, currentValue);
518         currentColumnValue = currentValue;
519 
520         Inquiry dataFieldInquiry = ((DataField) item).getInquiry();
521         if (dataFieldInquiry != null && dataFieldInquiry.getInquiryParameters() != null
522                 && dataFieldInquiry.getInquiryLink() != null) {
523 
524             String inquiryLinkId = dataFieldInquiry.getInquiryLink().getId().replace(ID_TOKEN, "")
525                     + UifConstants.IdSuffixes.LINE + index;
526 
527             // process each Inquiry link parameter by replacing each in the inquiry url with their current value
528             for (String key : dataFieldInquiry.getInquiryParameters().keySet()) {
529                 String name = dataFieldInquiry.getInquiryParameters().get(key);
530 
531                 //omit the binding prefix from the key to get the path relative to the current object
532                 key = key.replace(((DataField) item).getBindingInfo().getBindByNamePrefix() + ".", "");
533 
534                 if (ObjectPropertyUtils.isReadableProperty(obj, key)) {
535                     String value = ObjectPropertyUtils.getPropertyValueAsText(obj, key);
536                     row = row.replaceFirst("(" + inquiryLinkId + "(.|\\s)*?" + name + ")=.*?([&|\"])",
537                             "$1=" + value + "$3");
538                 }
539             }
540         }
541 
542         return row;
543     }
544 
545     /**
546      * Special handling of the InputField in the row, replaces necessary content with row specific content
547      *
548      * @param item the item being processed
549      * @param obj the row's object model
550      * @param row the row in html
551      * @param index the current row index
552      * @param originalId the original id of the component item
553      * @return the updated row
554      */
555     protected String handleInputFieldInRow(Component item, Object obj, String row, int index, String originalId) {
556         if (!(item instanceof InputField) || ((InputField) item).getControl() == null) {
557             return row;
558         }
559 
560         Control control = ((InputField) item).getControl();
561 
562         //updates the name path to the current path for any instance this item's propertyName with
563         //a collection binding prefix
564         row = row.replace("name=\"" + ((InputField) item).getBindingInfo().getBindingPath() + "\"",
565                 "name=\"" + this.getBindingInfo().getBindingPath() + "[" + index + "]." + ((InputField) item)
566                         .getPropertyName() + "\"");
567 
568         Object value = ObjectPropertyUtils.getPropertyValue(obj, ((InputField) item).getPropertyName());
569         String stringValue = "";
570 
571         if (value == null) {
572             stringValue = "";
573         } else if (value.getClass().isAssignableFrom(boolean.class)) {
574             stringValue = "" + value;
575         } else if (!(value instanceof Collection)) {
576             stringValue = ObjectPropertyUtils.getPropertyValueAsText(obj, ((InputField) item).getPropertyName());
577         }
578 
579         String controlId = originalId + "_line" + index + UifConstants.IdSuffixes.CONTROL;
580 
581         if (control instanceof CheckboxControl && stringValue.equalsIgnoreCase("true")) {
582             //CheckboxControl handling - only replace if true with a checked attribute appended
583             row = row.replaceAll("(id(\\s)*?=(\\s)*?\"" + controlId + "\")", "$1 checked=\"checked\" ");
584         } else if (control instanceof TextControl) {
585             //TextControl handling - replace with
586             row = row.replaceAll("(id(\\s)*?=(\\s)*?\"" + controlId + "\"(.|\\s)*?value=\")(.|\\s)*?\"",
587                     "$1" + stringValue + "\"");
588         } else if (control instanceof SelectControl && !((SelectControl) control).isMultiple()) {
589             //SelectControl handling (single item only)
590             Pattern pattern = Pattern.compile("<select(\\s)*?id(\\s)*?=(\\s)*?\"" + controlId + "\"(.|\\s)*?</select>");
591             Matcher matcher = pattern.matcher(row);
592             String replacement = "";
593 
594             if (matcher.find()) {
595                 //remove selected from select options
596                 String selected = "selected=\"selected\"";
597                 replacement = matcher.group().replace(selected, "");
598 
599                 //put selected on only the selected option
600                 String selectedValue = "value=\"" + stringValue + "\"";
601                 replacement = replacement.replace(selectedValue, selectedValue + " " + selected);
602             }
603 
604             //replace the old select tag with the old one
605             if (StringUtils.isNotBlank(replacement)) {
606                 row = matcher.replaceAll(replacement);
607             }
608         }
609 
610         currentColumnValue = stringValue;
611 
612         return row;
613     }
614 
615     /**
616      * The propertyName of the list backing this collection
617      *
618      * @return the propertyName of this collection
619      */
620     @BeanTagAttribute
621     public String getPropertyName() {
622         return propertyName;
623     }
624 
625     /**
626      * Set the propertyName
627      *
628      * @param propertyName
629      */
630     public void setPropertyName(String propertyName) {
631         this.propertyName = propertyName;
632     }
633 
634     /**
635      * The bindingInfo for this collection table, containg the property path and other options
636      *
637      * @return the bindingInfo
638      */
639     @BeanTagAttribute
640     public BindingInfo getBindingInfo() {
641         return bindingInfo;
642     }
643 
644     /**
645      * Set the bindingInfo
646      *
647      * @param bindingInfo
648      */
649     public void setBindingInfo(BindingInfo bindingInfo) {
650         this.bindingInfo = bindingInfo;
651     }
652 
653     /**
654      * The labels for the header derived from the items of this collection (the fields)
655      *
656      * @return the header labels
657      */
658     public List<Label> getHeaderLabels() {
659         return headerLabels;
660     }
661 
662     /**
663      * The richTable widget definition for this table for setting dataTable javascript options
664      *
665      * @return the RichTable widget
666      */
667     @BeanTagAttribute
668     public RichTable getRichTable() {
669         return richTable;
670     }
671 
672     /**
673      * Set the richTable widget
674      *
675      * @param richTable
676      */
677     public void setRichTable(RichTable richTable) {
678         this.richTable = richTable;
679     }
680 
681     /**
682      * The row css classes for the rows of this layout
683      *
684      * <p>To set a css class on all rows, use "all" as a key.  To set a
685      * class for even rows, use "even" as a key, for odd rows, use "odd".
686      * Use a one-based index to target a specific row by index.  SpringEL can be
687      * used as a key and the expression will be evaluated; if evaluated to true, the
688      * class(es) specified will be applied.</p>
689      *
690      * @return a map which represents the css classes of the rows of this layout
691      */
692     @BeanTagAttribute
693     public Map<String, String> getConditionalRowCssClasses() {
694         return conditionalRowCssClasses;
695     }
696 
697     /**
698      * Set the conditionalRowCssClasses
699      *
700      * @param conditionalRowCssClasses
701      */
702     public void setConditionalRowCssClasses(Map<String, String> conditionalRowCssClasses) {
703         this.conditionalRowCssClasses = conditionalRowCssClasses;
704     }
705 
706     /**
707      * True if this table is empty, false otherwise
708      *
709      * @return true if the collection backing this table is empty
710      */
711     public boolean isEmptyTable() {
712         return emptyTable;
713     }
714 
715     public void setHeaderLabels(List<Label> headerLabels) {
716         this.headerLabels = headerLabels;
717     }
718 
719     public void setExpressionConversionMap(Map<String, String> expressionConversionMap) {
720         this.expressionConversionMap = expressionConversionMap;
721     }
722 
723     public Map<String, String> getExpressionConversionMap() {
724         return expressionConversionMap;
725     }
726 
727     public List<String> getInitialComponentIds() {
728         return initialComponentIds;
729     }
730 
731     public Map<String, String> getRenderIdExpressionMap() {
732         return renderIdExpressionMap;
733     }
734 
735     public void setInitialComponentIds(List<String> initialComponentIds) {
736         this.initialComponentIds = initialComponentIds;
737     }
738 
739     public void setRenderIdExpressionMap(Map<String, String> renderIdExpressionMap) {
740         this.renderIdExpressionMap = renderIdExpressionMap;
741     }
742 
743     public void setEmptyTable(boolean emptyTable) {
744         this.emptyTable = emptyTable;
745     }
746 
747     /**
748      *
749      * @return the current column value
750      */
751     @BeanTagAttribute(name = "currentColumnValue")
752     protected String getCurrentColumnValue() {
753         return currentColumnValue;
754     }
755 
756     /**
757      * Set the current column value
758      *
759      * @param currentColumnValue
760      */
761     protected void setCurrentColumnValue(String currentColumnValue) {
762         this.currentColumnValue = currentColumnValue;
763     }
764 }