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.control;
017
018import org.apache.commons.lang.ObjectUtils;
019import org.kuali.rice.core.api.util.AbstractKeyValue;
020import org.kuali.rice.core.api.util.KeyValue;
021import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
022import org.kuali.rice.krad.uif.UifConstants;
023import org.kuali.rice.krad.uif.component.Component;
024import org.kuali.rice.krad.uif.container.Container;
025import org.kuali.rice.krad.uif.element.Message;
026import org.kuali.rice.krad.uif.field.InputField;
027import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
028import org.kuali.rice.krad.uif.util.ComponentFactory;
029import org.kuali.rice.krad.uif.util.ComponentUtils;
030import org.kuali.rice.krad.uif.util.KeyMessage;
031import org.kuali.rice.krad.uif.util.LifecycleElement;
032import org.kuali.rice.krad.uif.util.UifKeyValueLocation;
033import org.kuali.rice.krad.uif.util.UifOptionGroupLabel;
034import org.kuali.rice.krad.uif.util.UrlInfo;
035import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
036import org.kuali.rice.krad.uif.view.View;
037
038import java.util.ArrayList;
039import java.util.List;
040
041/**
042 * Base class for controls that accept/display multiple values
043 *
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046public abstract class MultiValueControlBase extends ControlBase implements MultiValueControl {
047    private static final long serialVersionUID = -8691367056245775455L;
048
049    private List<KeyValue> options;
050    private List<KeyMessage> richOptions;
051    private List<Component> inlineComponents;
052
053    private List<Message> internalMessageComponents;
054
055    private boolean locationSelect = false;
056
057    public MultiValueControlBase() {
058        super();
059    }
060
061    /**
062     * Process rich message content that may be in the options, by creating and initializing the richOptions
063     *
064     * {@inheritDoc}
065     */
066    @Override
067    public void performApplyModel(Object model, LifecycleElement parent) {
068        super.performApplyModel(model, parent);
069        getStyleClassesAsString();
070        if (options != null && richOptions == null) {
071            richOptions = new ArrayList<KeyMessage>();
072            internalMessageComponents = new ArrayList<Message>();
073
074            for (KeyValue option : options) {
075
076                // do this??
077                if (option instanceof UifOptionGroupLabel) {
078                    continue;
079                }
080
081                Message message = ComponentFactory.getMessage();
082
083                String key = option.getKey();
084                if (key.contains(UifConstants.EL_PLACEHOLDER_PREFIX)) {
085                    key = ViewLifecycle.getExpressionEvaluator().evaluateExpression(this.getContext(), key).toString();
086                }
087
088                String value = option.getValue();
089                if (value.contains(UifConstants.EL_PLACEHOLDER_PREFIX)) {
090                    value = ViewLifecycle.getExpressionEvaluator().evaluateExpression(this.getContext(), value).toString();
091                }
092
093                message.setMessageText(value);
094                message.setInlineComponents(inlineComponents);
095                message.setRenderWrapperTag(false);
096
097                // if the option is a sub-class of AbstractKeyValue class, then we also include the disabled attribute
098                if(AbstractKeyValue.class.isAssignableFrom(option.getClass()) && ((AbstractKeyValue)option).isDisabled()) {
099                    richOptions.add(new KeyMessage(key, value, message, ((AbstractKeyValue)option).isDisabled()));
100                } else {
101                    richOptions.add(new KeyMessage(key, value, message));
102                }
103
104                internalMessageComponents.add(message);
105            }
106        }
107    }
108
109    /**
110     * Adds appropriate parent data to inputs internal to the controls that may be in rich content of options
111     *
112     * {@inheritDoc}
113     */
114    @Override
115    public void performFinalize(Object model, LifecycleElement parent) {
116        super.performFinalize(model, parent);
117
118        View view = ViewLifecycle.getView();
119        ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
120
121        if (options != null && !options.isEmpty()) {
122            for (KeyValue option : options) {
123                if (option instanceof UifKeyValueLocation) {
124                    locationSelect = true;
125
126                    UrlInfo url = ((UifKeyValueLocation) option).getLocation();
127
128                    ViewLifecycle.getExpressionEvaluator().populatePropertyExpressionsFromGraph(url, false);
129                    expressionEvaluator.evaluateExpressionsOnConfigurable(view, url, view.getContext());
130                }
131            }
132        }
133
134        if (richOptions == null || richOptions.isEmpty()) {
135            return;
136        }
137
138        //Messages included in options which have have rich message content need to be aware of their parent for
139        //validation purposes
140        for (KeyMessage richOption : richOptions) {
141            List<Component> components = richOption.getMessage().getMessageComponentStructure();
142
143            if (components != null && !components.isEmpty()) {
144                for (Component c : components) {
145                    if (c instanceof Container || c instanceof InputField) {
146                        c.addDataAttribute(UifConstants.DataAttributes.PARENT, parent.getId());
147                    }
148                }
149            }
150        }
151
152    }
153
154    /**
155     * @see MultiValueControl#getOptions()
156     */
157    @BeanTagAttribute
158    public List<KeyValue> getOptions() {
159        return this.options;
160    }
161
162    /**
163     * {@inheritDoc}
164     */
165    public void setOptions(List<KeyValue> options) {
166        this.options = options;
167    }
168
169    /**
170     * Gets the inlineComponents which represent components that can be referenced in an option's value
171     * by index
172     *
173     * @return the components that can be used in rich values of options
174     */
175    @BeanTagAttribute
176    public List<Component> getInlineComponents() {
177        return inlineComponents;
178    }
179
180    /**
181     * Sets the inlineComponents which represent components that can be referenced in an option's value
182     * by index
183     *
184     * @param inlineComponents
185     */
186    public void setInlineComponents(List<Component> inlineComponents) {
187        this.inlineComponents = inlineComponents;
188    }
189
190    /**
191     * @see MultiValueControl#getRichOptions()
192     */
193    public List<KeyMessage> getRichOptions() {
194        return richOptions;
195    }
196
197    /**
198     * Sets the richOptions.  This will always override/ignore options if set.
199     *
200     * <p><b>Messages MUST be defined</b> when using this setter, do not use this setter for most cases
201     * as setting options through setOptions, with a richMessage value, is appropriate in MOST cases.  This
202     * setter is only available for full control.</p>
203     *
204     * @param richOptions with their messages predefined
205     */
206    public void setRichOptions(List<KeyMessage> richOptions) {
207        this.richOptions = richOptions;
208    }
209
210    /**
211     * Used by reflection during the lifecycle to get internal message components that may be contained in options
212     *
213     * <p>There are no references to this method in the code, this is intentional.  DO NOT REMOVE.</p>
214     *
215     * @return the internal message components, if any
216     */
217    public List<Message> getInternalMessageComponents() {
218        return internalMessageComponents;
219    }
220
221    /**
222     * If true, this select represents a location select (navigate on select of option)
223     *
224     * @return true if this is a location select
225     */
226    public boolean isLocationSelect() {
227        return locationSelect;
228    }
229
230    /**
231     * Sets the location select (navigate on select of option)
232     *
233     * @param locationSelect
234     */
235    protected void setLocationSelect(boolean locationSelect) {
236        this.locationSelect = locationSelect;
237    }
238}