001/**
002 * Copyright 2005-2015 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.control;
017
018import org.kuali.rice.core.api.util.ConcreteKeyValue;
019import org.kuali.rice.core.api.util.KeyValue;
020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.component.Component;
023import org.kuali.rice.krad.uif.container.Container;
024import org.kuali.rice.krad.uif.element.Message;
025import org.kuali.rice.krad.uif.field.InputField;
026import org.kuali.rice.krad.uif.util.ComponentUtils;
027import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
028import org.kuali.rice.krad.uif.util.ComponentFactory;
029import org.kuali.rice.krad.uif.util.ExpressionUtils;
030import org.kuali.rice.krad.uif.util.KeyMessage;
031import org.kuali.rice.krad.uif.util.UrlInfo;
032import org.kuali.rice.krad.uif.util.UifKeyValueLocation;
033import org.kuali.rice.krad.uif.view.View;
034
035import java.util.ArrayList;
036import java.util.List;
037
038/**
039 * Base class for controls that accept/display multiple values
040 *
041 * @author Kuali Rice Team (rice.collab@kuali.org)
042 */
043public abstract class MultiValueControlBase extends ControlBase implements MultiValueControl {
044    private static final long serialVersionUID = -8691367056245775455L;
045
046    private List<KeyValue> options;
047    private List<KeyMessage> richOptions;
048    private List<Component> inlineComponents;
049
050    private boolean locationSelect = false;
051
052    public MultiValueControlBase() {
053        super();
054    }
055
056    /**
057     * Process rich message content that may be in the options, by creating and initializing the richOptions
058     *
059     * @see org.kuali.rice.krad.uif.component.ComponentBase#performApplyModel(org.kuali.rice.krad.uif.view.View,
060     *      Object, org.kuali.rice.krad.uif.component.Component)
061     */
062    @Override
063    public void performApplyModel(View view, Object model, Component parent) {
064        super.performApplyModel(view, model, parent);
065
066        if (options != null && richOptions == null) {
067            richOptions = new ArrayList<KeyMessage>();
068
069            for (KeyValue option : options) {
070                Message message = ComponentFactory.getMessage();
071                view.assignComponentIds(message);
072                message.setMessageText(option.getValue());
073                message.setInlineComponents(inlineComponents);
074                message.setGenerateSpan(false);
075
076                view.getViewHelperService().performComponentInitialization(view, model, message);
077                richOptions.add(new KeyMessage(option.getKey(), option.getValue(), message));
078            }
079        }
080    }
081
082    /**
083     * Adds appropriate parent data to inputs internal to the controls that may be in rich content of options
084     *
085     * @see org.kuali.rice.krad.uif.component.Component#performFinalize(org.kuali.rice.krad.uif.view.View, Object,
086     *      org.kuali.rice.krad.uif.component.Component)
087     */
088    @Override
089    public void performFinalize(View view, Object model, Component parent) {
090        super.performFinalize(view, model, parent);
091
092        ExpressionEvaluator expressionEvaluator = view.getViewHelperService().getExpressionEvaluator();
093
094        if (options != null && !options.isEmpty()) {
095            for (KeyValue option : options) {
096                if (option instanceof UifKeyValueLocation) {
097                    locationSelect = true;
098
099                    UrlInfo url = ((UifKeyValueLocation) option).getLocation();
100
101                    ExpressionUtils.populatePropertyExpressionsFromGraph(url, false);
102                    expressionEvaluator.evaluateExpressionsOnConfigurable(view, url, view.getContext());
103                }
104            }
105        }
106
107        if (richOptions == null || richOptions.isEmpty()) {
108            return;
109        }
110
111        //Messages included in options which have have rich message content need to be aware of their parent for
112        //validation purposes
113        for (KeyMessage richOption : richOptions) {
114            List<Component> components = richOption.getMessage().getMessageComponentStructure();
115
116            if (components != null && !components.isEmpty()) {
117                for (Component c : components) {
118                    if (c instanceof Container || c instanceof InputField) {
119                        c.addDataAttribute(UifConstants.DataAttributes.PARENT, parent.getId());
120                    }
121                }
122            }
123        }
124
125    }
126
127    /**
128     * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
129     */
130    @Override
131    public List<Component> getComponentsForLifecycle() {
132        List<Component> components = super.getComponentsForLifecycle();
133
134        if (richOptions != null) {
135            for (KeyMessage richOption : richOptions) {
136                components.add(richOption.getMessage());
137            }
138        }
139
140        return components;
141    }
142
143    /**
144     * @see MultiValueControl#getOptions()
145     */
146    @BeanTagAttribute(name = "options", type = BeanTagAttribute.AttributeType.LISTBEAN)
147    public List<KeyValue> getOptions() {
148        return this.options;
149    }
150
151    /**
152     * @see MultiValueControl#setOptions(java.util.List<org.kuali.rice.core.api.util.KeyValue>)
153     */
154    public void setOptions(List<KeyValue> options) {
155        this.options = options;
156    }
157
158    /**
159     * Gets the inlineComponents which represent components that can be referenced in an option's value
160     * by index
161     *
162     * @return the components that can be used in rich values of options
163     */
164    @BeanTagAttribute(name = "inlineComponents", type = BeanTagAttribute.AttributeType.LISTBEAN)
165    public List<Component> getInlineComponents() {
166        return inlineComponents;
167    }
168
169    /**
170     * Sets the inlineComponents which represent components that can be referenced in an option's value
171     * by index
172     *
173     * @param inlineComponents
174     */
175    public void setInlineComponents(List<Component> inlineComponents) {
176        this.inlineComponents = inlineComponents;
177    }
178
179    /**
180     * @see MultiValueControl#getRichOptions()
181     */
182    public List<KeyMessage> getRichOptions() {
183        return richOptions;
184    }
185
186    /**
187     * Sets the richOptions.  This will always override/ignore options if set.
188     *
189     * <p><b>Messages MUST be defined</b> when using this setter, do not use this setter for most cases
190     * as setting options through setOptions, with a richMessage value, is appropriate in MOST cases.  This
191     * setter is only available for full control.</p>
192     *
193     * @param richOptions with their messages predefined
194     */
195    public void setRichOptions(List<KeyMessage> richOptions) {
196        this.richOptions = richOptions;
197    }
198
199    /**
200     * If true, this select represents a location select (navigate on select of option)
201     *
202     * @return true if this is a location select
203     */
204    public boolean isLocationSelect() {
205        return locationSelect;
206    }
207
208    /**
209     * Sets the location select (navigate on select of option)
210     *
211     * @param locationSelect
212     */
213    protected void setLocationSelect(boolean locationSelect) {
214        this.locationSelect = locationSelect;
215    }
216
217    /**
218     * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
219     */
220    @Override
221    protected <T> void copyProperties(T component) {
222        super.copyProperties(component);
223        MultiValueControlBase multiValueControlBaseCopy = (MultiValueControlBase) component;
224
225        try {
226            if (options != null) {
227                List<KeyValue> optionsCopy = new ArrayList<KeyValue>();
228                for (KeyValue option : options) {
229
230                    KeyValue keyValue = null;
231                    if (option != null) {
232                        Class<? extends KeyValue> optionClass = option.getClass();
233                        keyValue = optionClass.getDeclaredConstructor(String.class, String.class).newInstance(
234                                option.getKey(), option.getValue());
235                        if (keyValue instanceof UifKeyValueLocation) {
236                            ((UifKeyValueLocation) keyValue).setLocation(
237                                    (UrlInfo) ((UifKeyValueLocation) option).getLocation().copy());
238                        }
239                    }
240
241                    optionsCopy.add(keyValue);
242                }
243                multiValueControlBaseCopy.setOptions(optionsCopy);
244            }
245        } catch (Exception e) {
246            throw new RuntimeException("Failed to copy options in MultiValueControlBase", e);
247        }
248
249        if (richOptions != null) {
250            List<KeyMessage> richOptionsCopy = new ArrayList<KeyMessage>();
251            for (KeyMessage richOption : richOptions) {
252                KeyMessage keyMessage = new KeyMessage(richOption.getKey(), richOption.getValue(),
253                        richOption.getMessage());
254                richOptionsCopy.add(keyMessage);
255            }
256            multiValueControlBaseCopy.setRichOptions(richOptionsCopy);
257        }
258
259        if (inlineComponents != null) {
260            List<Component> inlineComponentsCopy = new ArrayList<Component>();
261            for (Component inlineComponent : inlineComponents) {
262                inlineComponentsCopy.add((Component) inlineComponent.copy());
263            }
264            multiValueControlBaseCopy.setInlineComponents(inlineComponentsCopy);
265        }
266
267        multiValueControlBaseCopy.setLocationSelect(this.locationSelect);
268    }
269}