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}