View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.field;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.uif.DataType;
20  import org.kuali.rice.core.api.util.ConcreteKeyValue;
21  import org.kuali.rice.core.api.util.KeyValue;
22  import org.kuali.rice.core.api.util.type.TypeUtils;
23  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
25  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
26  import org.kuali.rice.krad.datadictionary.state.StateMapping;
27  import org.kuali.rice.krad.datadictionary.validation.capability.CaseConstrainable;
28  import org.kuali.rice.krad.datadictionary.validation.capability.MustOccurConstrainable;
29  import org.kuali.rice.krad.datadictionary.validation.capability.PrerequisiteConstrainable;
30  import org.kuali.rice.krad.datadictionary.validation.capability.SimpleConstrainable;
31  import org.kuali.rice.krad.datadictionary.validation.capability.ValidCharactersConstrainable;
32  import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
33  import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
34  import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
35  import org.kuali.rice.krad.datadictionary.validation.constraint.SimpleConstraint;
36  import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
37  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
38  import org.kuali.rice.krad.datadictionary.validator.Validator;
39  import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
40  import org.kuali.rice.krad.uif.UifConstants;
41  import org.kuali.rice.krad.uif.component.Component;
42  import org.kuali.rice.krad.uif.control.Control;
43  import org.kuali.rice.krad.uif.control.MultiValueControlBase;
44  import org.kuali.rice.krad.uif.control.TextAreaControl;
45  import org.kuali.rice.krad.uif.control.TextControl;
46  import org.kuali.rice.krad.uif.control.UifKeyValuesFinder;
47  import org.kuali.rice.krad.uif.element.Label;
48  import org.kuali.rice.krad.uif.element.Message;
49  import org.kuali.rice.krad.uif.element.ValidationMessages;
50  import org.kuali.rice.krad.uif.util.ClientValidationUtils;
51  import org.kuali.rice.krad.uif.util.ComponentFactory;
52  import org.kuali.rice.krad.uif.util.ComponentUtils;
53  import org.kuali.rice.krad.uif.util.ConstraintStateUtils;
54  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
55  import org.kuali.rice.krad.uif.view.View;
56  import org.kuali.rice.krad.uif.view.ViewModel;
57  import org.kuali.rice.krad.uif.widget.QuickFinder;
58  import org.kuali.rice.krad.uif.widget.Suggest;
59  import org.kuali.rice.krad.util.ObjectUtils;
60  
61  import java.util.ArrayList;
62  import java.util.List;
63  
64  /**
65   * Field that encapsulates data input/output captured by an attribute within the
66   * application
67   *
68   * <p>
69   * The {@code InputField} provides the majority of the data input/output
70   * for the screen. Through these fields the model can be displayed and updated.
71   * For data input, the field contains a {@link Control} instance will
72   * render an HTML control element(s). The input field also contains a
73   * {@link Label}, summary, and widgets such as a quickfinder (for
74   * looking up values) and inquiry (for getting more information on the value).
75   * {@code InputField} instances can have associated messages (errors)
76   * due to invalid input or business rule failures. Security can also be
77   * configured to restrict who may view the fields valnue.
78   * </p>
79   *
80   * @author Kuali Rice Team (rice.collab@kuali.org)
81   */
82  @BeanTag(name = "inputField")
83  public class InputField extends DataField implements SimpleConstrainable, CaseConstrainable, PrerequisiteConstrainable, MustOccurConstrainable, ValidCharactersConstrainable {
84      private static final long serialVersionUID = -3703656713706343840L;
85  
86      // constraint variables
87      private String customValidatorClass;
88      private ValidCharactersConstraint validCharactersConstraint;
89      private CaseConstraint caseConstraint;
90      private List<PrerequisiteConstraint> dependencyConstraints;
91      private List<MustOccurConstraint> mustOccurConstraints;
92      private SimpleConstraint simpleConstraint;
93      private DataType dataType;
94  
95      // display props
96      private Control control;
97      private KeyValuesFinder optionsFinder;
98  
99      private boolean uppercaseValue;
100 
101     private ValidationMessages validationMessages;
102 
103     // messages
104     private String constraintText;
105     private String instructionalText;
106 
107     private Message constraintMessage;
108     private Message instructionalMessage;
109 
110     private AttributeQuery attributeQuery;
111 
112     // widgets
113     private QuickFinder quickfinder;
114     private Suggest suggest;
115 
116     public InputField() {
117         super();
118 
119         simpleConstraint = new SimpleConstraint();
120     }
121 
122     /**
123      * The following initialization is performed:
124      *
125      * <ul>
126      * <li>Initializes instructional and constraint message fields if necessary</li>
127      * </ul>
128      *
129      * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View,
130      *      java.lang.Object)
131      */
132     @Override
133     public void performInitialization(View view, Object model) {
134         super.performInitialization(view, model);
135 
136         if ((StringUtils.isNotBlank(constraintText) || (getPropertyExpression("constraintText") != null)) && (
137                 constraintMessage
138                         == null)) {
139             constraintMessage = ComponentFactory.getConstraintMessage();
140             view.assignComponentIds(constraintMessage);
141         }
142 
143         if ((StringUtils.isNotBlank(instructionalText) || (getPropertyExpression("instructionalText") != null)) && (
144                 instructionalMessage
145                         == null)) {
146             instructionalMessage = ComponentFactory.getInstructionalMessage();
147             view.assignComponentIds(instructionalMessage);
148         }
149 
150     }
151 
152     /**
153      * @see Component#performApplyModel(org.kuali.rice.krad.uif.view.View, Object, org.kuali.rice.krad.uif.component.Component)
154      */
155     @Override
156     public void performApplyModel(View view, Object model, Component parent) {
157         super.performApplyModel(view, model, parent);
158 
159         // Done in apply model so we have the message text for additional rich message processing in Message
160         // Sets message
161         if (StringUtils.isNotBlank(instructionalText) && StringUtils.isBlank(instructionalMessage.getMessageText())) {
162             instructionalMessage.setMessageText(instructionalText);
163         }
164 
165         // Sets constraints
166         if (StringUtils.isNotBlank(constraintText) && StringUtils.isBlank(constraintMessage.getMessageText())) {
167             constraintMessage.setMessageText(constraintText);
168         }
169 
170         // invoke options finder if options not configured on the control
171         List<KeyValue> fieldOptions = new ArrayList<KeyValue>();
172 
173         // use options directly configured on the control first
174         if ((control != null) && control instanceof MultiValueControlBase) {
175             MultiValueControlBase multiValueControl = (MultiValueControlBase) control;
176             if ((multiValueControl.getOptions() != null) && !multiValueControl.getOptions().isEmpty()) {
177                 fieldOptions = multiValueControl.getOptions();
178             }
179         }
180 
181         // set multiLineReadOnlyDisplay to true to preserve text formatting
182         if (control instanceof TextAreaControl) {
183             setMultiLineReadOnlyDisplay(true);
184         }
185         // if options not configured on the control, invoke configured options finder
186         if (fieldOptions.isEmpty() && (optionsFinder != null)) {
187             if (optionsFinder instanceof UifKeyValuesFinder) {
188                 fieldOptions = ((UifKeyValuesFinder) optionsFinder).getKeyValues((ViewModel) model);
189 
190                 // check if blank option should be added
191                 if (((UifKeyValuesFinder) optionsFinder).isAddBlankOption()) {
192                     fieldOptions.add(0, new ConcreteKeyValue("", ""));
193                 }
194             } else {
195                 fieldOptions = optionsFinder.getKeyValues();
196             }
197 
198             if ((control != null) && control instanceof MultiValueControlBase) {
199                 ((MultiValueControlBase) control).setOptions(fieldOptions);
200             }
201         }
202 
203         // if read only do key/value translation if necessary (if alternative and additional properties not set)
204         if (isReadOnly()
205                 && !fieldOptions.isEmpty()
206                 && StringUtils.isBlank(getReadOnlyDisplayReplacement())
207                 && StringUtils.isBlank(getReadOnlyDisplaySuffix())
208                 && StringUtils.isBlank(getReadOnlyDisplayReplacementPropertyName())
209                 && StringUtils.isBlank(getReadOnlyDisplaySuffixPropertyName())) {
210 
211             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
212 
213             // TODO: can we translate Collections? (possibly combining output with delimiter
214             if ((fieldValue != null) && (TypeUtils.isSimpleType(fieldValue.getClass()))) {
215                 for (KeyValue keyValue : fieldOptions) {
216                     if (StringUtils.equals(fieldValue.toString(), keyValue.getKey())) {
217                         setReadOnlyDisplayReplacement(keyValue.getValue());
218                         break;
219                     }
220                 }
221             }
222         }
223     }
224 
225     /**
226      * The following actions are performed:
227      *
228      * <ul>
229      * <li>Set the ids for the various attribute components</li>
230      * <li>Sets up the client side validation for constraints on this field. In
231      * addition, it sets up the messages applied to this field</li>
232      * </ul>
233      *
234      * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
235      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
236      */
237     @Override
238     public void performFinalize(View view, Object model, Component parent) {
239         super.performFinalize(view, model, parent);
240 
241         setupIds();
242         this.addDataAttribute("role", "InputField");
243 
244         // if read only or the control is null no input can be given so no need to setup validation
245         if (isReadOnly() || getControl() == null) {
246             return;
247         }
248 
249         // adjust paths on PrerequisiteConstraint property names
250         adjustPrerequisiteConstraintBinding(dependencyConstraints);
251 
252         // adjust paths on MustOccurConstraints property names
253         adjustMustOccurConstraintBinding(mustOccurConstraints);
254 
255         // adjust paths on CaseConstraint property names
256         if (caseConstraint != null) {
257             String propertyName = getBindingInfo().getPropertyAdjustedBindingPath(caseConstraint.getPropertyName());
258             caseConstraint.setPropertyName(propertyName);
259         }
260 
261         setupFieldQuery();
262 
263         // special requiredness indicator handling, if this was previously not required reset its required
264         // message to be ** for indicating required in the next state
265         String path = view.getStateObjectBindingPath();
266         Object stateObject;
267 
268         if (StringUtils.isNotBlank(path)) {
269             stateObject = ObjectPropertyUtils.getPropertyValue(model, path);
270         } else {
271             stateObject = model;
272         }
273         StateMapping stateMapping = view.getStateMapping();
274 
275         if (stateMapping != null) {
276             String validationState = ConstraintStateUtils.getClientViewValidationState(model, view);
277             SimpleConstraint appliedSimpleConstraint = ConstraintStateUtils.getApplicableConstraint(
278                     this.getSimpleConstraint(), validationState, stateMapping);
279 
280             if (appliedSimpleConstraint != null
281                     && appliedSimpleConstraint.getRequired() != null
282                     && appliedSimpleConstraint.getRequired()) {
283                 SimpleConstraint prevConstraint = ConstraintStateUtils.getApplicableConstraint(
284                         this.getSimpleConstraint(), stateMapping.getCurrentState(stateObject), stateMapping);
285                 if (prevConstraint == null || prevConstraint.getRequired() == null || !prevConstraint.getRequired()) {
286                     this.getFieldLabel().getRequiredMessage().setMessageText("**");
287                 }
288             }
289         }
290 
291         ClientValidationUtils.processAndApplyConstraints(this, view, model);
292     }
293 
294     /**
295      * Overrides processReadOnlyListDisplay to handle MultiValueControls by creating the list of values from values
296      * instead of the keys of the options selected (makes the list "human-readable").  Otherwise it just passes the
297      * list ahead as normal if this InputField does not use a MultiValueControl.
298      *
299      * @param model the model
300      * @param originalList originalList of values
301      */
302     @Override
303     protected void processReadOnlyListDisplay(Object model, List<?> originalList) {
304         //Special handling for option based fields
305         if ((control != null) && control instanceof MultiValueControlBase) {
306             List<String> newList = new ArrayList<String>();
307             List<KeyValue> fieldOptions = ((MultiValueControlBase) control).getOptions();
308 
309             if (fieldOptions == null || fieldOptions.isEmpty()) {
310                 return;
311             }
312 
313             for (Object fieldValue : originalList) {
314                 for (KeyValue keyValue : fieldOptions) {
315                     if (fieldValue != null && StringUtils.equals(fieldValue.toString(), keyValue.getKey())) {
316                         newList.add(keyValue.getValue());
317                         break;
318                     }
319                 }
320             }
321             super.generateReadOnlyListDisplayReplacement(newList);
322         } else {
323             super.generateReadOnlyListDisplayReplacement(originalList);
324         }
325     }
326 
327     /**
328      * Adjust paths on the must occur constrain bindings
329      *
330      * @param mustOccurConstraints
331      */
332     protected void adjustMustOccurConstraintBinding(List<MustOccurConstraint> mustOccurConstraints) {
333         if (mustOccurConstraints != null) {
334             for (MustOccurConstraint mustOccurConstraint : mustOccurConstraints) {
335                 adjustPrerequisiteConstraintBinding(mustOccurConstraint.getPrerequisiteConstraints());
336                 adjustMustOccurConstraintBinding(mustOccurConstraint.getMustOccurConstraints());
337             }
338         }
339     }
340 
341     /**
342      * Adjust paths on the prerequisite constraint bindings
343      *
344      * @param prerequisiteConstraints
345      */
346     protected void adjustPrerequisiteConstraintBinding(List<PrerequisiteConstraint> prerequisiteConstraints) {
347         if (prerequisiteConstraints != null) {
348             for (PrerequisiteConstraint prerequisiteConstraint : prerequisiteConstraints) {
349                 String propertyName = getBindingInfo().getPropertyAdjustedBindingPath(
350                         prerequisiteConstraint.getPropertyName());
351                 prerequisiteConstraint.setPropertyName(propertyName);
352             }
353         }
354     }
355 
356     /**
357      * Performs setup of the field attribute query and informational display properties. Paths
358      * are adjusted to match the binding for the this field, and the necessary onblur script for
359      * triggering the query client side is constructed
360      */
361     protected void setupFieldQuery() {
362         if (getAttributeQuery() != null) {
363             // adjust paths on query mappings
364             getAttributeQuery().updateQueryFieldMapping(getBindingInfo());
365             getAttributeQuery().updateReturnFieldMapping(getBindingInfo());
366             getAttributeQuery().updateQueryMethodArgumentFieldList(getBindingInfo());
367 
368             // build onblur script for field query
369             String script = "executeFieldQuery('" + getControl().getId() + "',";
370             script += "'" + getId() + "'," + getAttributeQuery().getQueryFieldMappingJsString() + ",";
371             script += getAttributeQuery().getQueryMethodArgumentFieldsJsString() + ",";
372             script += getAttributeQuery().getReturnFieldMappingJsString() + ");";
373 
374             if (StringUtils.isNotBlank(getControl().getOnBlurScript())) {
375                 script = getControl().getOnBlurScript() + script;
376             }
377             getControl().setOnBlurScript(script);
378         }
379     }
380 
381     /**
382      * Sets the ids on all components the input field uses so they will all
383      * contain this input field's id in their ids. This is useful for jQuery
384      * manipulation.
385      */
386     protected void setupIds() {
387         // update ids so they all match the attribute
388 
389         setNestedComponentIdAndSuffix(getControl(), UifConstants.IdSuffixes.CONTROL);
390         setNestedComponentIdAndSuffix(getValidationMessages(), UifConstants.IdSuffixes.ERRORS);
391         setNestedComponentIdAndSuffix(getFieldLabel(), UifConstants.IdSuffixes.LABEL);
392         setNestedComponentIdAndSuffix(getInstructionalMessage(), UifConstants.IdSuffixes.INSTRUCTIONAL);
393         setNestedComponentIdAndSuffix(getConstraintMessage(), UifConstants.IdSuffixes.CONSTRAINT);
394         setNestedComponentIdAndSuffix(getQuickfinder(), UifConstants.IdSuffixes.QUICK_FINDER);
395         setNestedComponentIdAndSuffix(getSuggest(), UifConstants.IdSuffixes.SUGGEST);
396 
397         if (this.getControl() != null) {
398             this.getControl().addDataAttribute(UifConstants.DATA_ATTRIBUTE_CONTROL_FOR, this.getId());
399         }
400     }
401 
402     /**
403      * Defaults the properties of the {@code InputField} to the
404      * corresponding properties of its {@code AttributeDefinition}
405      * retrieved from the dictionary (if such an entry exists). If the field
406      * already contains a value for a property, the definitions value is not
407      * used.
408      *
409      * @param view - view instance the field belongs to
410      * @param attributeDefinition - AttributeDefinition instance the property values should be
411      * copied from
412      */
413     @Override
414     public void copyFromAttributeDefinition(View view, AttributeDefinition attributeDefinition) {
415         super.copyFromAttributeDefinition(view, attributeDefinition);
416 
417         // max length
418         if (getMaxLength() == null) {
419             setMaxLength(attributeDefinition.getMaxLength());
420         }
421 
422         // min length
423         if (getMinLength() == null) {
424             setMinLength(attributeDefinition.getMinLength());
425         }
426 
427         // valid characters
428         if (getValidCharactersConstraint() == null) {
429             setValidCharactersConstraint(attributeDefinition.getValidCharactersConstraint());
430         }
431 
432         if (getCaseConstraint() == null) {
433             setCaseConstraint(attributeDefinition.getCaseConstraint());
434         }
435 
436         if (getDependencyConstraints() == null) {
437             setDependencyConstraints(attributeDefinition.getPrerequisiteConstraints());
438         }
439 
440         if (getMustOccurConstraints() == null) {
441             setMustOccurConstraints(attributeDefinition.getMustOccurConstraints());
442         }
443 
444         // required
445         if (getRequired() == null) {
446             setRequired(attributeDefinition.isRequired());
447 
448             //if still null, default to false
449             if (getRequired() == null) {
450                 setRequired(Boolean.FALSE);
451             }
452         }
453 
454         if (getDataType() == null) {
455             setDataType(attributeDefinition.getDataType());
456             //Assume date if dataType is still null and using a DatePicker
457             if (getDataType() == null
458                     && control instanceof TextControl
459                     && ((TextControl) control).getDatePicker() != null) {
460                 setDataType(DataType.DATE);
461             }
462         }
463 
464         // control
465         if ((getControl() == null) && (attributeDefinition.getControlField() != null)) {
466             Control control = attributeDefinition.getControlField();
467             view.assignComponentIds(control);
468 
469             setControl(ComponentUtils.copy(control));
470         }
471 
472         // constraint
473         if (StringUtils.isEmpty(getConstraintText())) {
474             setConstraintText(attributeDefinition.getConstraintText());
475 
476             if (constraintMessage == null) {
477                 constraintMessage = ComponentFactory.getConstraintMessage();
478                 view.assignComponentIds(constraintMessage);
479             }
480             getConstraintMessage().setMessageText(attributeDefinition.getConstraintText());
481         }
482 
483         // options
484         if (getOptionsFinder() == null) {
485             setOptionsFinder(attributeDefinition.getOptionsFinder());
486         }
487 
488         // copy over simple constraint information because we cannot directly use simpleConstraint from
489         // attributeDefinition because the settings in InputField take precedence
490         if (this.getSimpleConstraint().getConstraintStateOverrides() == null) {
491             this.getSimpleConstraint().setConstraintStateOverrides(
492                     attributeDefinition.getSimpleConstraint().getConstraintStateOverrides());
493         }
494 
495         if (this.getSimpleConstraint().getStates().isEmpty()) {
496             this.getSimpleConstraint().setStates(attributeDefinition.getSimpleConstraint().getStates());
497         }
498 
499         if (this.getSimpleConstraint().getMessageKey() == null) {
500             this.getSimpleConstraint().setMessageKey(attributeDefinition.getSimpleConstraint().getMessageKey());
501         }
502 
503         if (this.getSimpleConstraint().getApplyClientSide() == null) {
504             this.getSimpleConstraint().setApplyClientSide(
505                     attributeDefinition.getSimpleConstraint().getApplyClientSide());
506         }
507 
508         if (this.getSimpleConstraint().getValidationMessageParams() == null) {
509             this.getSimpleConstraint().setValidationMessageParams(
510                     attributeDefinition.getSimpleConstraint().getValidationMessageParams());
511         }
512     }
513 
514     /**
515      * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
516      */
517     @Override
518     public List<Component> getComponentsForLifecycle() {
519         List<Component> components = super.getComponentsForLifecycle();
520 
521         components.add(instructionalMessage);
522         components.add(constraintMessage);
523         components.add(control);
524         components.add(validationMessages);
525         components.add(quickfinder);
526         components.add(suggest);
527 
528         return components;
529     }
530 
531     /**
532      * @see DataField#isInputAllowed()
533      */
534     @Override
535     public boolean isInputAllowed() {
536         return true;
537     }
538 
539     /**
540      * {@code Control} instance that should be used to input data for the
541      * field
542      *
543      * <p>
544      * When the field is editable, the control will be rendered so the user can
545      * input a value(s). Controls typically are part of a Form and render
546      * standard HTML control elements such as text input, select, and checkbox
547      * </p>
548      *
549      * @return Control instance
550      */
551     @BeanTagAttribute(name = "control", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
552     public Control getControl() {
553         return this.control;
554     }
555 
556     /**
557      * Setter for the field's control
558      *
559      * @param control
560      */
561     public void setControl(Control control) {
562         this.control = control;
563     }
564 
565     /**
566      * Field that contains the messages (errors) for the input field. The
567      * {@code ValidationMessages} holds configuration on associated messages along
568      * with information on rendering the messages in the user interface
569      *
570      * @return ValidationMessages instance
571      */
572     @BeanTagAttribute(name = "validationMessages", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
573     public ValidationMessages getValidationMessages() {
574         return this.validationMessages;
575     }
576 
577     /**
578      * Setter for the input field's errors field
579      *
580      * @param validationMessages
581      */
582     public void setValidationMessages(ValidationMessages validationMessages) {
583         this.validationMessages = validationMessages;
584     }
585 
586     /**
587      * Instance of {@code KeyValuesFinder} that should be invoked to
588      * provide a List of values the field can have. Generally used to provide
589      * the options for a multi-value control or to validate the submitted field
590      * value
591      *
592      * @return KeyValuesFinder instance
593      */
594     @BeanTagAttribute(name = "optionsFinder", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
595     public KeyValuesFinder getOptionsFinder() {
596         return this.optionsFinder;
597     }
598 
599     /**
600      * Setter for the field's KeyValuesFinder instance
601      *
602      * @param optionsFinder
603      */
604     public void setOptionsFinder(KeyValuesFinder optionsFinder) {
605         this.optionsFinder = optionsFinder;
606     }
607 
608     /**
609      * Setter that takes in the class name for the options finder and creates a
610      * new instance to use as the finder for the input field
611      *
612      * @param optionsFinderClass - the options finder class to set
613      */
614     public void setOptionsFinderClass(Class<? extends KeyValuesFinder> optionsFinderClass) {
615         this.optionsFinder = ObjectUtils.newInstance(optionsFinderClass);
616     }
617 
618     /**
619      * Lookup finder widget for the field
620      *
621      * <p>
622      * The quickfinder widget places a small icon next to the field that allows
623      * the user to bring up a search screen for finding valid field values. The
624      * {@code Widget} instance can be configured to point to a certain
625      * {@code LookupView}, or the framework will attempt to associate the
626      * field with a lookup based on its metadata (in particular its
627      * relationships in the model)
628      * </p>
629      *
630      * @return QuickFinder lookup widget
631      */
632     @BeanTagAttribute(name = "quickfinder", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
633     public QuickFinder getQuickfinder() {
634         return this.quickfinder;
635     }
636 
637     /**
638      * Setter for the lookup widget
639      *
640      * @param quickfinder - the field lookup widget to set
641      */
642     public void setQuickfinder(QuickFinder quickfinder) {
643         this.quickfinder = quickfinder;
644     }
645 
646     /**
647      * Suggest box widget for the input field
648      *
649      * <p>
650      * If enabled (by render flag), as the user inputs data into the
651      * fields control a dynamic query is performed to provide the user
652      * suggestions on values which they can then select
653      * </p>
654      *
655      * <p>
656      * Note the Suggest widget is only valid when using a standard TextControl
657      * </p>
658      *
659      * @return Suggest instance
660      */
661     @BeanTagAttribute(name = "suggest", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
662     public Suggest getSuggest() {
663         return suggest;
664     }
665 
666     /**
667      * Setter for the fields suggest widget
668      *
669      * @param suggest - the field suggest widget to  set
670      */
671     public void setSuggest(Suggest suggest) {
672         this.suggest = suggest;
673     }
674 
675     /**
676      * Instructional text that display an explanation of the field usage
677      *
678      * <p>
679      * Text explaining how to use the field, including things like what values should be selected
680      * in certain cases and so on (instructions)
681      * </p>
682      *
683      * @return String instructional message
684      */
685     @BeanTagAttribute(name = "instructionalText")
686     public String getInstructionalText() {
687         return this.instructionalText;
688     }
689 
690     /**
691      * Setter for the instructional message
692      *
693      * @param instructionalText - the instructional text to set
694      */
695     public void setInstructionalText(String instructionalText) {
696         this.instructionalText = instructionalText;
697     }
698 
699     /**
700      * Message field that displays instructional text
701      *
702      * <p>
703      * This message field can be configured to for adjusting how the instructional text will display. Generally
704      * the styleClasses property will be of most interest
705      * </p>
706      *
707      * @return Message instructional message field
708      */
709     @BeanTagAttribute(name = "instructionalMessage", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
710     public Message getInstructionalMessage() {
711         return this.instructionalMessage;
712     }
713 
714     /**
715      * Setter for the instructional text message field
716      *
717      * <p>
718      * Note this is the setter for the field that will render the instructional text. The actual text can be
719      * set on the field but can also be set using {@link #setInstructionalText(String)}
720      * </p>
721      *
722      * @param instructionalMessage - the instructional message to set
723      */
724     public void setInstructionalMessage(Message instructionalMessage) {
725         this.instructionalMessage = instructionalMessage;
726     }
727 
728     /**
729      * Text that display a restriction on the value a field can hold
730      *
731      * <p>
732      * For example when the value must be a valid format (phone number, email), certain length, min/max value and
733      * so on this text can be used to indicate the constraint to the user. Generally displays with the control so
734      * it is visible when the user tabs to the field
735      * </p>
736      *
737      * @return String text to display for the constraint message
738      */
739     @BeanTagAttribute(name = "constraintText")
740     public String getConstraintText() {
741         return this.constraintText;
742     }
743 
744     /**
745      * Setter for the constraint message text
746      *
747      * @param constraintText - the constraint text to set
748      */
749     public void setConstraintText(String constraintText) {
750         this.constraintText = constraintText;
751     }
752 
753     /**
754      * Message field that displays constraint text
755      *
756      * <p>
757      * This message field can be configured to for adjusting how the constrain text will display. Generally
758      * the styleClasses property will be of most interest
759      * </p>
760      *
761      * @return Message constraint message field
762      */
763     @BeanTagAttribute(name = "constraintMessage", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
764     public Message getConstraintMessage() {
765         return this.constraintMessage;
766     }
767 
768     /**
769      * Setter for the constraint text message field
770      *
771      * <p>
772      * Note this is the setter for the field that will render the constraint text. The actual text can be
773      * set on the field but can also be set using {@link #setConstraintText(String)}
774      * </p>
775      *
776      * @param constraintMessage - the constrain message field to set
777      */
778     public void setConstraintMessage(Message constraintMessage) {
779         this.constraintMessage = constraintMessage;
780     }
781 
782     /**
783      * The {@code ValidCharactersConstraint} that applies to this {@code InputField}
784      *
785      * @return the valid characters constraint for this input field
786      */
787     @Override
788     @BeanTagAttribute(name = "validCharactersConstraint", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
789     public ValidCharactersConstraint getValidCharactersConstraint() {
790         return this.validCharactersConstraint;
791     }
792 
793     /**
794      * Setter for {@code validCharacterConstraint}
795      *
796      * @param validCharactersConstraint - the {@code ValidCharactersConstraint} to set
797      */
798     public void setValidCharactersConstraint(ValidCharactersConstraint validCharactersConstraint) {
799         this.validCharactersConstraint = validCharactersConstraint;
800     }
801 
802     /**
803      * The {@code CaseConstraint} that applies to this {@code InputField}
804      *
805      * @return the case constraint for this input field
806      */
807     @Override
808     @BeanTagAttribute(name = "caseConstraint", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
809     public CaseConstraint getCaseConstraint() {
810         return this.caseConstraint;
811     }
812 
813     /**
814      * Setter for {@code caseConstraint}
815      *
816      * @param caseConstraint - the {@code CaseConstraint} to set
817      */
818     public void setCaseConstraint(CaseConstraint caseConstraint) {
819         this.caseConstraint = caseConstraint;
820     }
821 
822     /**
823      * List of {@code PrerequisiteConstraint} that apply to this {@code InputField}
824      *
825      * @return the dependency constraints for this input field
826      */
827     @BeanTagAttribute(name = "dependencyConstraint", type = BeanTagAttribute.AttributeType.LISTBEAN)
828     public List<PrerequisiteConstraint> getDependencyConstraints() {
829         return this.dependencyConstraints;
830     }
831 
832     /**
833      * Setter for {@code dependencyConstraints}
834      *
835      * @param dependencyConstraints - list of {@code PrerequisiteConstraint} to set
836      */
837     public void setDependencyConstraints(List<PrerequisiteConstraint> dependencyConstraints) {
838         this.dependencyConstraints = dependencyConstraints;
839     }
840 
841     /**
842      * List of {@code MustOccurConstraint} that apply to this {@code InputField}
843      *
844      * @return the must occur constraints for this input field
845      */
846     @Override
847     @BeanTagAttribute(name = "mustOccurConstraints", type = BeanTagAttribute.AttributeType.LISTBEAN)
848     public List<MustOccurConstraint> getMustOccurConstraints() {
849         return this.mustOccurConstraints;
850     }
851 
852     /**
853      * Setter for {@code mustOccurConstraints}
854      *
855      * @param mustOccurConstraints - list of {@code MustOccurConstraint} to set
856      */
857     public void setMustOccurConstraints(List<MustOccurConstraint> mustOccurConstraints) {
858         this.mustOccurConstraints = mustOccurConstraints;
859     }
860 
861     /**
862      * Simple constraints for the input field
863      *
864      * <p>
865      * A simple constraint which store the values for constraints such as required,
866      * min/max length, and min/max value.
867      * </p>
868      *
869      * @return the simple constraint of the input field
870      */
871     @Override
872     @BeanTagAttribute(name = "simpleConstraint", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
873     public SimpleConstraint getSimpleConstraint() {
874         return this.simpleConstraint;
875     }
876 
877     /**
878      * Setter for simple constraint
879      *
880      * <p>
881      * When a simple constraint is set on this object ALL simple validation
882      * constraints set directly will be overridden - recommended to use this or
883      * the other gets/sets for defining simple constraints, not both.
884      * </p>
885      *
886      * @param simpleConstraint - the simple constraint to set
887      */
888     public void setSimpleConstraint(SimpleConstraint simpleConstraint) {
889         this.simpleConstraint = simpleConstraint;
890     }
891 
892     /**
893      * Maximum number of characters the input field value is allowed to have
894      *
895      * <p>
896      * The maximum length determines the maximum allowable length of the value
897      * for data entry editing purposes.  The maximum length is inclusive and can
898      * be smaller or longer than the actual control size.  The constraint
899      * is enforced on all data types (e.g. a numeric data type needs to meet the
900      * maximum length constraint in which digits and symbols are counted).
901      * </p>
902      *
903      * @return the maximum length of the input field
904      */
905     @BeanTagAttribute(name = "maxLength")
906     public Integer getMaxLength() {
907         return simpleConstraint.getMaxLength();
908     }
909 
910     /**
911      * Setter for input field max length
912      *
913      * @param maxLength - the maximum length to set
914      */
915     public void setMaxLength(Integer maxLength) {
916         simpleConstraint.setMaxLength(maxLength);
917     }
918 
919     /**
920      * Minimum number of characters the input field value needs to be
921      *
922      * <p>
923      * The minimum length determines the minimum required length of the value for
924      * data entry editing purposes.  The minimum length is inclusive. The constraint
925      * is enforced on all data types (e.g. a numeric data type needs to meet the
926      * minimum length requirement in which digits and symbols are counted).
927      * </p>
928      *
929      * @return the minimum length of the input field
930      */
931     @BeanTagAttribute(name = "minLength")
932     public Integer getMinLength() {
933         return simpleConstraint.getMinLength();
934     }
935 
936     /**
937      * Setter for input field minimum length
938      *
939      * @param minLength - the minLength to set
940      */
941     public void setMinLength(Integer minLength) {
942         simpleConstraint.setMinLength(minLength);
943     }
944 
945     /**
946      * @see org.kuali.rice.krad.uif.component.ComponentBase#getRequired()
947      */
948     @Override
949     @BeanTagAttribute(name = "required")
950     public Boolean getRequired() {
951         return this.simpleConstraint.getRequired();
952     }
953 
954     /**
955      * @see org.kuali.rice.krad.uif.component.ComponentBase#setRequired(java.lang.Boolean)
956      */
957     @Override
958     public void setRequired(Boolean required) {
959         this.simpleConstraint.setRequired(required);
960     }
961 
962     /**
963      * The exclusive minimum value for numeric or date field.
964      *
965      * <p>
966      * The exclusiveMin element determines the minimum allowable value for data
967      * entry editing purposes. This constrain is supported for numeric and
968      * date fields and to be used in conjunction with the appropriate
969      * {@link ValidCharactersConstraint}.
970      *
971      * For numeric constraint the value can be an integer or decimal such as -.001 or 99.
972      * </p>
973      *
974      * @return the exclusive minimum numeric value of the input field
975      */
976     @BeanTagAttribute(name = "exclusiveMin")
977     public String getExclusiveMin() {
978         return simpleConstraint.getExclusiveMin();
979     }
980 
981     /**
982      * Setter for the field's exclusive minimum value
983      *
984      * @param exclusiveMin - the minimum value to set
985      */
986     public void setExclusiveMin(String exclusiveMin) {
987         simpleConstraint.setExclusiveMin(exclusiveMin);
988     }
989 
990     /**
991      * The inclusive maximum value for numeric or date field.
992      *
993      * <p>
994      * The inclusiveMax element determines the maximum allowable value for data
995      * entry editing purposes. This constrain is supported for numeric and
996      * date fields and to be used in conjunction with the appropriate
997      * {@link ValidCharactersConstraint}.
998      *
999      * For numeric constraint the value can be an integer or decimal such as -.001 or 99.
1000      * </p>
1001      *
1002      * @return the inclusive maximum numeric value of the input field
1003      */
1004     @BeanTagAttribute(name = "inclusiveMax")
1005     public String getInclusiveMax() {
1006         return simpleConstraint.getInclusiveMax();
1007     }
1008 
1009     /**
1010      * Setter for the field's inclusive maximum value
1011      *
1012      * @param inclusiveMax - the maximum value to set
1013      */
1014     public void setInclusiveMax(String inclusiveMax) {
1015         simpleConstraint.setInclusiveMax(inclusiveMax);
1016     }
1017 
1018     /**
1019      * Attribute query instance configured for this field to dynamically pull information back for
1020      * updates other fields or providing messages
1021      *
1022      * <p>
1023      * If field attribute query is not null, associated event script will be generated to trigger the
1024      * query from the UI. This will invoke the {@code AttributeQueryService} to
1025      * execute the query and return an instance of {@code AttributeQueryResult} that is then
1026      * read by the script to update the UI. Typically used to update informational property values or
1027      * other field values
1028      * </p>
1029      *
1030      * @return AttributeQuery instance
1031      */
1032     @BeanTagAttribute(name = "attributeQuery", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1033     public AttributeQuery getAttributeQuery() {
1034         return attributeQuery;
1035     }
1036 
1037     /**
1038      * Setter for this field's attribute query
1039      *
1040      * @param attributeQuery
1041      */
1042     public void setAttributeQuery(AttributeQuery attributeQuery) {
1043         this.attributeQuery = attributeQuery;
1044     }
1045 
1046     /**
1047      * Perform uppercase flag for this field to force input to uppercase.
1048      *
1049      * <p>
1050      * It this flag is set to true the 'text-transform' style on the field will be set to 'uppercase'
1051      * which will automatically change any text input into the field to uppercase.
1052      * </p>
1053      *
1054      * @return performUppercase flag
1055      */
1056     @BeanTagAttribute(name = "uppercaseValue")
1057     public boolean isUppercaseValue() {
1058         return uppercaseValue;
1059     }
1060 
1061     /**
1062      * Setter for this field's performUppercase flag
1063      *
1064      * @param uppercaseValue - boolean flag
1065      */
1066     public void setUppercaseValue(boolean uppercaseValue) {
1067         this.uppercaseValue = uppercaseValue;
1068     }
1069 
1070     /**
1071      * Returns the full binding path (the path used in the name attribute of the input).
1072      * This differs from propertyName in that it uses BindingInfo to determine the path.
1073      *
1074      * @return full binding path name
1075      */
1076     @Override
1077     public String getName() {
1078         return this.getBindingInfo().getBindingPath();
1079     }
1080 
1081     @Override
1082     public List<PrerequisiteConstraint> getPrerequisiteConstraints() {
1083         return dependencyConstraints;
1084     }
1085 
1086     /**
1087      * This does not have to be set, represents the DataType constraint of this field.
1088      * This is only checked during server side validation.
1089      *
1090      * @param dataType the dataType to set
1091      */
1092     public void setDataType(DataType dataType) {
1093         this.simpleConstraint.setDataType(dataType);
1094     }
1095 
1096     public void setDataType(String dataType) {
1097         this.simpleConstraint.setDataType(DataType.valueOf(dataType));
1098     }
1099 
1100     /**
1101      * Gets the DataType of this InputField, note that DataType set to be date
1102      * when this field is using a date picker with a TextControl and hasnt otherwise been
1103      * explicitly set.
1104      *
1105      * @return
1106      */
1107     @BeanTagAttribute(name = "dataType", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1108     public DataType getDataType() {
1109         return this.simpleConstraint.getDataType();
1110     }
1111 
1112     @Override
1113     public boolean isRenderFieldset() {
1114         return super.isRenderFieldset() || (this.isInputAllowed()
1115                 && quickfinder != null
1116                 && quickfinder.isRender()
1117                 && quickfinder.getQuickfinderAction() != null
1118                 && quickfinder.getQuickfinderAction().isRender());
1119     }
1120 
1121     /**
1122      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
1123      */
1124     @Override
1125     public void completeValidation(ValidationTrace tracer) {
1126         tracer.addBean(this);
1127 
1128         // Checks that the control is set
1129         if (getControl() == null) {
1130             if (Validator.checkExpressions(this, "control")) {
1131                 String currentValues[] = {"control =" + getConstraintText()};
1132                 tracer.createWarning("Control should be set", currentValues);
1133             }
1134         }
1135 
1136         super.completeValidation(tracer.getCopy());
1137     }
1138 }