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.field;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.util.ConcreteKeyValue;
020import org.kuali.rice.krad.datadictionary.AttributeDefinition;
021import org.kuali.rice.krad.datadictionary.parse.BeanTag;
022import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
023import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
024import org.kuali.rice.krad.uif.UifConstants;
025import org.kuali.rice.krad.uif.component.Component;
026import org.kuali.rice.krad.uif.control.CheckboxControl;
027import org.kuali.rice.krad.uif.control.Control;
028import org.kuali.rice.krad.uif.control.FilterableLookupCriteriaControl;
029import org.kuali.rice.krad.uif.control.MultiValueControl;
030import org.kuali.rice.krad.uif.control.RadioGroupControl;
031import org.kuali.rice.krad.uif.control.TextAreaControl;
032import org.kuali.rice.krad.uif.element.Message;
033import org.kuali.rice.krad.uif.util.ComponentFactory;
034import org.kuali.rice.krad.uif.util.ComponentUtils;
035import org.kuali.rice.krad.uif.util.KeyMessage;
036import org.kuali.rice.krad.uif.view.View;
037import org.kuali.rice.krad.util.KRADConstants;
038import org.kuali.rice.krad.util.KRADPropertyConstants;
039
040import java.util.Map;
041
042/**
043 * Custom <code>InputField</code> for search fields within a lookup view
044 *
045 * @author Kuali Rice Team (rice.collab@kuali.org)
046 */
047@BeanTag(name = "lookupCriteriaInputField-bean", parent = "Uif-LookupCriteriaInputField")
048public class LookupInputField extends InputField {
049    private static final long serialVersionUID = -8294275596836322699L;
050
051    public static final String CHECKBOX_CONVERTED_RADIO_CONTROL = "Uif-CheckboxConvertedRadioControl";
052
053    private boolean disableWildcardsAndOperators;
054    private boolean addControlSelectAllOption;
055    private boolean triggerOnChange;
056    private boolean ranged;
057
058    public LookupInputField() {
059        super();
060
061        disableWildcardsAndOperators = false;
062        addControlSelectAllOption = false;
063        setTriggerOnChange(false);
064    }
065
066    /**
067     * The following actions are performed:
068     *
069     * <ul>
070     * <li>Add all option if enabled and control is multi-value</li>
071     * </ul>
072     *
073     * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
074     *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
075     */
076    @Override
077    public void performFinalize(View view, Object model, Component parent) {
078        super.performFinalize(view, model, parent);
079
080        // if enabled add option to select all values
081        if (addControlSelectAllOption && (getControl() != null) && getControl() instanceof MultiValueControl) {
082            String allOptionText = KRADServiceLocatorWeb.getMessageService().getMessageText(
083                    UifConstants.MessageKeys.OPTION_ALL);
084
085            MultiValueControl multiValueControl = (MultiValueControl) getControl();
086            if (multiValueControl.getOptions() != null) {
087                multiValueControl.getOptions().add(0, new ConcreteKeyValue("", allOptionText));
088            }
089
090            if (multiValueControl.getRichOptions() != null) {
091                Message message = ComponentFactory.getMessage();
092
093                view.assignComponentIds(message);
094                message.setMessageText(allOptionText);
095                message.setGenerateSpan(false);
096
097                multiValueControl.getRichOptions().add(0, new KeyMessage("", allOptionText, message));
098            }
099        }
100    }
101
102    /**
103     * Override of InputField copy to setup properties necessary to make the field usable for inputting
104     * search criteria
105     *
106     * @param attributeDefinition AttributeDefinition instance the property values should be copied from
107     * @see DataField#copyFromAttributeDefinition(org.kuali.rice.krad.uif.view.View,
108     *      org.kuali.rice.krad.datadictionary.AttributeDefinition)
109     */
110    @Override
111    public void copyFromAttributeDefinition(View view, AttributeDefinition attributeDefinition) {
112        // label
113        if (StringUtils.isEmpty(getLabel())) {
114            setLabel(attributeDefinition.getLabel());
115        }
116
117        // short label
118        if (StringUtils.isEmpty(getShortLabel())) {
119            setShortLabel(attributeDefinition.getShortLabel());
120        }
121
122        // security
123        if (getDataFieldSecurity().getAttributeSecurity() == null) {
124            getDataFieldSecurity().setAttributeSecurity(attributeDefinition.getAttributeSecurity());
125        }
126
127        // options
128        if (getOptionsFinder() == null) {
129            setOptionsFinder(attributeDefinition.getOptionsFinder());
130        }
131
132        // TODO: what about formatter?
133
134        // use control from dictionary if not specified and convert for searching
135        if (getControl() == null) {
136            Control control = convertControlToLookupControl(attributeDefinition);
137            view.assignComponentIds(control);
138
139            setControl(control);
140        }
141
142        // overwrite maxLength to allow for wildcards and ranges
143        setMaxLength(100);
144
145        // set default value for active field to true
146        if (StringUtils.isEmpty(getDefaultValue())) {
147            if ((StringUtils.equals(getPropertyName(), KRADPropertyConstants.ACTIVE))) {
148                setDefaultValue(KRADConstants.YES_INDICATOR_VALUE);
149            }
150        }
151
152        /*
153           * TODO delyea: FieldUtils.createAndPopulateFieldsForLookup used to allow for a set of property names to be passed in via the URL
154           * parameters of the lookup url to set fields as 'read only'
155           */
156
157    }
158
159    /**
160     * If control definition is defined on the given attribute definition, converts to an appropriate control for
161     * searching (if necessary) and returns a copy for setting on the field
162     *
163     * @param attributeDefinition attribute definition instance to retrieve control from
164     * @return Control instance or null if not found
165     */
166    protected static Control convertControlToLookupControl(AttributeDefinition attributeDefinition) {
167        if (attributeDefinition.getControlField() == null) {
168            return null;
169        }
170
171        Control newControl = null;
172
173        // convert checkbox to radio with yes/no/both options
174        if (CheckboxControl.class.isAssignableFrom(attributeDefinition.getControlField().getClass())) {
175            newControl = getCheckboxConvertedRadioControl();
176        }
177        // text areas get converted to simple text inputs
178        else if (TextAreaControl.class.isAssignableFrom(attributeDefinition.getControlField().getClass())) {
179            newControl = ComponentFactory.getTextControl();
180        } else {
181            newControl = ComponentUtils.copy(attributeDefinition.getControlField(), "");
182        }
183
184        return newControl;
185    }
186
187    /**
188     * @return the treatWildcardsAndOperatorsAsLiteral
189     */
190    @BeanTagAttribute(name = "disableWildcardsAndOperators")
191    public boolean isDisableWildcardsAndOperators() {
192        return this.disableWildcardsAndOperators;
193    }
194
195    /**
196     * @param disableWildcardsAndOperators the treatWildcardsAndOperatorsAsLiteral to set
197     */
198    public void setDisableWildcardsAndOperators(boolean disableWildcardsAndOperators) {
199        this.disableWildcardsAndOperators = disableWildcardsAndOperators;
200    }
201
202    /**
203     * Indicates whether the option for all values (blank key, 'All' label) should be added to the lookup
204     * field, note this is only supported for {@link org.kuali.rice.krad.uif.control.MultiValueControl} instance
205     *
206     * @return boolean true if all option should be added, false if not
207     */
208    @BeanTagAttribute(name = "addControlSelectAllOption")
209    public boolean isAddControlSelectAllOption() {
210        return addControlSelectAllOption;
211    }
212
213    /**
214     * Setter for the add all option indicator
215     *
216     * @param addControlSelectAllOption
217     */
218    public void setAddControlSelectAllOption(boolean addControlSelectAllOption) {
219        this.addControlSelectAllOption = addControlSelectAllOption;
220    }
221
222    /**
223     * Indicates that the search must execute on changing of a value in the lookup input field
224     *
225     * @return boolean
226     */
227    public boolean isTriggerOnChange() {
228        return triggerOnChange;
229    }
230
231    /**
232     * Setter for the trigger search on change flag
233     *
234     * @param triggerOnChange
235     */
236    public void setTriggerOnChange(boolean triggerOnChange) {
237        this.triggerOnChange = triggerOnChange;
238    }
239
240    /**
241     * Indicates that a field must be rendered as a from and to value
242     *
243     * @return
244     */
245    public boolean isRanged() {
246        return ranged;
247    }
248
249    /**
250     * Setter for ranged flag to indicate this is a range field
251     *
252     * @param ranged
253     */
254    public void setRanged(boolean ranged) {
255        this.ranged = ranged;
256    }
257
258    /**
259     * Remove any search criteria that are not part of the database lookup
260     *
261     * @param searchCriteria the search criteria to be filtered
262     * @return the filteredSearchCriteria
263     */
264    public Map<String, String> filterSearchCriteria(Map<String, String> searchCriteria) {
265        if (getControl() instanceof FilterableLookupCriteriaControl) {
266            return ((FilterableLookupCriteriaControl) getControl()).filterSearchCriteria(getPropertyName(),
267                    searchCriteria);
268        } else {
269            return searchCriteria;
270        }
271    }
272
273    /**
274     * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
275     */
276    @Override
277    protected <T> void copyProperties(T component) {
278        super.copyProperties(component);
279        LookupInputField lookupInputFieldCopy = (LookupInputField) component;
280        lookupInputFieldCopy.setDisableWildcardsAndOperators(this.disableWildcardsAndOperators);
281        lookupInputFieldCopy.setAddControlSelectAllOption(this.addControlSelectAllOption);
282        lookupInputFieldCopy.setTriggerOnChange(this.triggerOnChange);
283        lookupInputFieldCopy.setRanged(this.ranged);
284    }
285
286    /**
287     * Retrieves a new radio group control instance for converted lookup criteria checkboxes from Spring
288     * (initialized by the bean definition with the given id)
289     *
290     * @return RadioGroupControl
291     */
292    private static RadioGroupControl getCheckboxConvertedRadioControl() {
293        return (RadioGroupControl) ComponentFactory.getNewComponentInstance(CHECKBOX_CONVERTED_RADIO_CONTROL);
294    }
295}