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