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.field;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.commons.collections.CollectionUtils;
023import org.apache.commons.lang.StringUtils;
024import org.kuali.rice.core.api.data.DataType;
025import org.kuali.rice.core.api.util.ConcreteKeyValue;
026import org.kuali.rice.core.api.util.KeyValue;
027import org.kuali.rice.core.api.util.type.TypeUtils;
028import org.kuali.rice.krad.bo.DataObjectRelationship;
029import org.kuali.rice.krad.datadictionary.AttributeDefinition;
030import org.kuali.rice.krad.datadictionary.parse.BeanTag;
031import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
032import org.kuali.rice.krad.datadictionary.parse.BeanTags;
033import org.kuali.rice.krad.datadictionary.state.StateMapping;
034import org.kuali.rice.krad.datadictionary.validation.constraint.CaseConstraint;
035import org.kuali.rice.krad.datadictionary.validation.constraint.MustOccurConstraint;
036import org.kuali.rice.krad.datadictionary.validation.constraint.PrerequisiteConstraint;
037import org.kuali.rice.krad.datadictionary.validation.constraint.SimpleConstraint;
038import org.kuali.rice.krad.datadictionary.validation.constraint.ValidCharactersConstraint;
039import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
040import org.kuali.rice.krad.datadictionary.validator.Validator;
041import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
042import org.kuali.rice.krad.service.DataDictionaryService;
043import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
044import org.kuali.rice.krad.uif.CssConstants;
045import org.kuali.rice.krad.uif.UifConstants;
046import org.kuali.rice.krad.uif.UifPropertyPaths;
047import org.kuali.rice.krad.uif.component.Component;
048import org.kuali.rice.krad.uif.component.DelayedCopy;
049import org.kuali.rice.krad.uif.control.Control;
050import org.kuali.rice.krad.uif.control.MultiValueControlBase;
051import org.kuali.rice.krad.uif.control.SelectControl;
052import org.kuali.rice.krad.uif.control.TextAreaControl;
053import org.kuali.rice.krad.uif.control.TextControl;
054import org.kuali.rice.krad.uif.control.UifKeyValuesFinder;
055import org.kuali.rice.krad.uif.element.Action;
056import org.kuali.rice.krad.uif.element.FieldValidationMessages;
057import org.kuali.rice.krad.uif.element.Label;
058import org.kuali.rice.krad.uif.element.Link;
059import org.kuali.rice.krad.uif.element.Message;
060import org.kuali.rice.krad.uif.element.ValidationMessages;
061import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
062import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
063import org.kuali.rice.krad.uif.lifecycle.ViewPostMetadata;
064import org.kuali.rice.krad.uif.util.ClientValidationUtils;
065import org.kuali.rice.krad.uif.util.ComponentFactory;
066import org.kuali.rice.krad.uif.util.ComponentUtils;
067import org.kuali.rice.krad.uif.util.ConstraintStateUtils;
068import org.kuali.rice.krad.uif.util.ContextUtils;
069import org.kuali.rice.krad.uif.util.LifecycleElement;
070import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
071import org.kuali.rice.krad.uif.util.ViewModelUtils;
072import org.kuali.rice.krad.uif.view.View;
073import org.kuali.rice.krad.uif.view.ViewModel;
074import org.kuali.rice.krad.uif.widget.QuickFinder;
075import org.kuali.rice.krad.uif.widget.Suggest;
076import org.kuali.rice.krad.util.KRADConstants;
077import org.kuali.rice.krad.util.KRADUtils;
078import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
079import org.kuali.rice.krad.web.form.UifFormBase;
080
081/**
082 * Field that encapsulates data input/output captured by an attribute within the
083 * application
084 *
085 * <p>
086 * The {@code InputField} provides the majority of the data input/output
087 * for the screen. Through these fields the model can be displayed and updated.
088 * For data input, the field contains a {@link Control} instance will
089 * render an HTML control element(s). The input field also contains a
090 * {@link Label}, summary, and widgets such as a quickfinder (for
091 * looking up values) and inquiry (for getting more information on the value).
092 * {@code InputField} instances can have associated messages (errors)
093 * due to invalid input or business rule failures. Security can also be
094 * configured to restrict who may view the fields valnue.
095 * </p>
096 *
097 * @author Kuali Rice Team (rice.collab@kuali.org)
098 */
099@BeanTags({@BeanTag(name = "input", parent = "Uif-InputField"),
100        @BeanTag(name = "inputLabelTop", parent = "Uif-InputField-LabelTop"),
101        @BeanTag(name = "inputLabelRight", parent = "Uif-InputField-LabelRight"),
102        @BeanTag(name = "checkboxInput", parent = "Uif-CheckboxInputField")})
103public class InputFieldBase extends DataFieldBase implements InputField {
104    private static final long serialVersionUID = -3703656713706343840L;
105
106    // constraint variables
107    private ValidCharactersConstraint validCharactersConstraint;
108    private CaseConstraint caseConstraint;
109    private List<PrerequisiteConstraint> dependencyConstraints;
110    private List<MustOccurConstraint> mustOccurConstraints;
111    private SimpleConstraint simpleConstraint;
112    private DataType dataType;
113
114    // display props
115    private Control control;
116
117    private boolean inlineEdit;
118    private boolean ajaxInlineEdit;
119
120    private KeyValuesFinder optionsFinder;
121
122    private boolean uppercaseValue;
123    private boolean disableNativeAutocomplete;
124
125    @DelayedCopy
126    private FieldValidationMessages validationMessages;
127
128    // messages
129    private String constraintText;
130    private String instructionalText;
131
132    private Message constraintMessage;
133    private Message instructionalMessage;
134
135    private String helperText;
136
137    private AttributeQuery attributeQuery;
138
139    // widgets
140    private boolean enableAutoDirectInquiry;
141
142    private QuickFinder quickfinder;
143    private boolean enableAutoQuickfinder;
144
145    private Suggest suggest;
146
147    private boolean widgetInputOnly;
148
149    private boolean renderInputAddonGroup;
150    private List<String> postInputCssClasses;
151    private List<Component> postInputAddons;
152
153    public InputFieldBase() {
154        super();
155
156        simpleConstraint = new SimpleConstraint();
157
158        enableAutoDirectInquiry = true;
159        enableAutoQuickfinder = true;
160    }
161
162    /**
163     * {@inheritDoc}
164     */
165    @Override
166    public void performInitialization(Object model) {
167        super.performInitialization(model);
168
169        if ((StringUtils.isNotBlank(constraintText) || (getPropertyExpression("constraintText") != null)) && (
170                constraintMessage
171                        == null)) {
172            constraintMessage = ComponentFactory.getConstraintMessage();
173        }
174
175        if ((StringUtils.isNotBlank(instructionalText) || (getPropertyExpression("instructionalText") != null)) && (
176                instructionalMessage
177                        == null)) {
178            instructionalMessage = ComponentFactory.getInstructionalMessage();
179        }
180    }
181
182    /**
183     * {@inheritDoc}
184     */
185    @Override
186    public void afterEvaluateExpression() {
187        // populate readOnly from parent before calling super, to prevent DataField
188        // from forcing to true.
189        if (getReadOnly() == null) {
190            Component parent = ViewLifecycle.getPhase().getParent();
191            setReadOnly(parent == null ? null : parent.getReadOnly());
192        }
193
194        super.afterEvaluateExpression();
195    }
196
197    /**
198     * {@inheritDoc}
199     */
200    @Override
201    public void performApplyModel(Object model, LifecycleElement parent) {
202        super.performApplyModel(model, parent);
203
204        // Done in apply model so we have the message text for additional rich message processing in Message
205        // Sets message
206        if (StringUtils.isNotBlank(instructionalText) && instructionalMessage != null && StringUtils.isBlank(
207                instructionalMessage.getMessageText())) {
208            instructionalMessage.setMessageText(instructionalText);
209        }
210
211        // Sets constraints
212        if (StringUtils.isNotBlank(constraintText) && constraintMessage != null && StringUtils.isBlank(
213                constraintMessage.getMessageText())) {
214            constraintMessage.setMessageText(constraintText);
215        }
216
217        // invoke options finder if options not configured on the control
218        List<KeyValue> fieldOptions = new ArrayList<KeyValue>();
219
220        // use options directly configured on the control first
221        if ((control != null) && control instanceof MultiValueControlBase) {
222            MultiValueControlBase multiValueControl = (MultiValueControlBase) control;
223            if ((multiValueControl.getOptions() != null) && !multiValueControl.getOptions().isEmpty()) {
224                fieldOptions = multiValueControl.getOptions();
225            }
226        }
227
228        // set multiLineReadOnlyDisplay to true to preserve text formatting
229        if (control instanceof TextAreaControl) {
230            setMultiLineReadOnlyDisplay(true);
231        }
232
233        // if options not configured on the control, invoke configured options finder
234        if (fieldOptions.isEmpty() && (optionsFinder != null)) {
235            if (optionsFinder instanceof UifKeyValuesFinder) {
236                fieldOptions = ((UifKeyValuesFinder) optionsFinder).getKeyValues((ViewModel) model, this);
237
238                // check if blank option should be added
239                if (((UifKeyValuesFinder) optionsFinder).isAddBlankOption()) {
240                    fieldOptions.add(0, new ConcreteKeyValue("", ""));
241                }
242            } else {
243                fieldOptions = optionsFinder.getKeyValues();
244            }
245
246            if ((control != null) && control instanceof MultiValueControlBase) {
247                ((MultiValueControlBase) control).setOptions(fieldOptions);
248            }
249        }
250
251        if (enableAutoDirectInquiry && this.getInquiry() == null && hasAutoInquiryRelationship()) {
252            setInquiry(ComponentFactory.getInquiry());
253        }
254
255        if (enableAutoQuickfinder && this.getQuickfinder() == null && hasAutoQuickfinderRelationship()) {
256            setQuickfinder(ComponentFactory.getQuickFinder());
257            ContextUtils.pushAllToContextDeep(quickfinder, this.getContext());
258        }
259
260        // if read only do key/value translation if necessary (if alternative and additional properties not set)
261        if (Boolean.TRUE.equals(getReadOnly())
262                && !fieldOptions.isEmpty()
263                && StringUtils.isBlank(getReadOnlyDisplayReplacement())
264                && StringUtils.isBlank(getReadOnlyDisplaySuffix())
265                && StringUtils.isBlank(getReadOnlyDisplayReplacementPropertyName())
266                && StringUtils.isBlank(getReadOnlyDisplaySuffixPropertyName())) {
267
268            Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
269
270            // TODO: can we translate Collections? (possibly combining output with delimiter
271            if ((fieldValue != null) && (TypeUtils.isSimpleType(fieldValue.getClass()))) {
272                for (KeyValue keyValue : fieldOptions) {
273                    if (StringUtils.equals(fieldValue.toString(), keyValue.getKey())) {
274                        setReadOnlyDisplayReplacement(keyValue.getValue());
275                        break;
276                    }
277                }
278            }
279        }
280
281        if(control != null && quickfinder != null && quickfinder.getQuickfinderAction() != null) {
282            String disabledExpression = control.getPropertyExpression("disabled");
283            if(StringUtils.isNotBlank(disabledExpression)) {
284                quickfinder.getQuickfinderAction().getPropertyExpressions().put("disabled", disabledExpression);
285            }  else {
286                quickfinder.getQuickfinderAction().setDisabled(control.isDisabled());
287            }
288        }
289
290    }
291
292    /**
293     * {@inheritDoc}
294     */
295    @Override
296    public void performFinalize(Object model, LifecycleElement parent) {
297        super.performFinalize(model, parent);
298
299        setupIds();
300
301        this.addDataAttribute(UifConstants.DataAttributes.ROLE, UifConstants.RoleTypes.INPUT_FIELD);
302
303        boolean ajaxInlineEditRefresh = ajaxInlineEdit && ((UifFormBase)model).getUpdateComponentId() != null &&
304                ((UifFormBase)model).getUpdateComponentId().equals(this.getId());
305
306        // if read only or the control is null no input can be given so no need to setup validation
307        if ((Boolean.TRUE.equals(getReadOnly()) && !inlineEdit && !ajaxInlineEditRefresh) || getControl() == null) {
308            return;
309        }
310
311        if (StringUtils.isNotBlank(helperText) && (getControl() != null)) {
312            getControl().getCssClasses().add(CssConstants.Classes.HAS_HELPER);
313        }
314
315        DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
316        if (this.getDictionaryObjectEntry() != null && this.getDictionaryAttributeName() != null) {
317            AttributeDefinition ad = dataDictionaryService.getAttributeDefinition(this.getDictionaryObjectEntry(),
318                    this.getDictionaryAttributeName());
319
320            Map<String, String> propertyExpressions = this.getPropertyExpressions();
321
322            if (propertyExpressions.containsKey(UifPropertyPaths.DICTIONARY_ATTR_NAME)) {
323                // call copyFromAttributeDefinition since the dictionaryAttributeName expression had not yet
324                // been evaluated when this field was initialized.
325                copyFromAttributeDefinition(ad);
326            }
327
328            if (ad.getForceUppercase() || uppercaseValue) {
329                Object currentPropertyValue = ObjectPropertyUtils.getPropertyValue(model,
330                        getBindingInfo().getBindingPath());
331                if (currentPropertyValue instanceof String) {
332                    String uppercasedValue = ((String) currentPropertyValue).toUpperCase();
333                    ObjectPropertyUtils.setPropertyValue(model, getBindingInfo().getBindingPath(), uppercasedValue);
334                }
335            }
336        }
337
338        // browser's native autocomplete causes issues with the suggest plugin
339        if ((suggest != null) && suggest.isSuggestConfigured()) {
340            setDisableNativeAutocomplete(true);
341        }
342
343        // adjust paths on PrerequisiteConstraint property names
344        adjustPrerequisiteConstraintBinding(dependencyConstraints);
345
346        // adjust paths on MustOccurConstraints property names
347        adjustMustOccurConstraintBinding(mustOccurConstraints);
348
349        // adjust paths on CaseConstraint property names
350        if (caseConstraint != null) {
351            String propertyName = getBindingInfo().getPropertyAdjustedBindingPath(caseConstraint.getPropertyName());
352            caseConstraint.setPropertyName(propertyName);
353        }
354
355        View view = ViewLifecycle.getView();
356
357        setupFieldQuery(view);
358
359        // special requiredness indicator handling, if this was previously not required reset its required
360        // message to be ** for indicating required in the next state
361        String path = view.getStateObjectBindingPath();
362        Object stateObject;
363
364        if (StringUtils.isNotBlank(path)) {
365            stateObject = ObjectPropertyUtils.getPropertyValue(model, path);
366        } else {
367            stateObject = model;
368        }
369        StateMapping stateMapping = view.getStateMapping();
370        String nextStateReqIndicator = (String) KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
371                UifConstants.REQUIRED_NEXT_STATE_INDICATOR_ID);
372
373        if (stateMapping != null) {
374            String validationState = ConstraintStateUtils.getClientViewValidationState(model, view);
375            SimpleConstraint appliedSimpleConstraint = ConstraintStateUtils.getApplicableConstraint(
376                    this.getSimpleConstraint(), validationState, stateMapping);
377
378            if (appliedSimpleConstraint != null
379                    && appliedSimpleConstraint.getRequired() != null
380                    && appliedSimpleConstraint.getRequired()) {
381                SimpleConstraint prevConstraint = ConstraintStateUtils.getApplicableConstraint(
382                        this.getSimpleConstraint(), stateMapping.getCurrentState(stateObject), stateMapping);
383                if (prevConstraint == null || prevConstraint.getRequired() == null || !prevConstraint.getRequired()) {
384                    this.getFieldLabel().setRequiredIndicator(nextStateReqIndicator);
385                }
386            }
387        }
388
389        ClientValidationUtils.processAndApplyConstraints(this, view, model);
390
391        if (inlineEdit || ajaxInlineEdit) {
392            this.addDataAttribute(UifConstants.DataAttributes.INLINE_EDIT, "true");
393        }
394
395        // Generate validation messages
396        if (validationMessages != null) {
397            // Messages will not use tooltip for inline edit cases
398            if (inlineEdit || ajaxInlineEdit) {
399                validationMessages.setUseTooltip(false);
400            }
401
402            validationMessages.generateMessages(view, model, this);
403        }
404
405        addComponentPostMetadata();
406
407        if (this.getHelp() != null && StringUtils.isNotBlank(this.getHelp().getExternalHelpUrl())) {
408            this.setRenderInputAddonGroup(true);
409        }
410    }
411
412    /**
413     * Invoked during the finalize phase to capture state of the component needs to support post operations.
414     */
415    protected void addComponentPostMetadata() {
416        ViewPostMetadata viewPostMetadata = ViewLifecycle.getViewPostMetadata();
417
418        viewPostMetadata.getInputFieldIds().add(this.getId());
419
420        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.LABEL, this.getLabel());
421
422        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.PATH, this.getName());
423
424        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.SIMPLE_CONSTRAINT,
425                this.getSimpleConstraint());
426
427        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.VALID_CHARACTER_CONSTRAINT,
428                this.getValidCharactersConstraint());
429
430        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.CASE_CONSTRAINT, this.getCaseConstraint());
431
432        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.MUST_OCCUR_CONSTRAINTS,
433                this.getMustOccurConstraints());
434
435        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.PREREQ_CONSTSTRAINTS,
436                this.getPrerequisiteConstraints());
437
438        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.INPUT_FIELD_ATTRIBUTE_QUERY,
439                attributeQuery);
440
441        if (this.suggest != null) {
442            viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.SUGGEST, this.suggest.getPostData());
443        }
444
445        viewPostMetadata.addComponentPostData(this, UifConstants.PostMetadata.INPUT_FIELD_IS_UPPERCASE,
446                isUppercaseValue());
447
448        if ((isRender() || StringUtils.isNotBlank(getProgressiveRender())) && !isHidden() && (!Boolean.TRUE.equals(
449                getReadOnly()) || inlineEdit || ajaxInlineEdit)) {
450            viewPostMetadata.addAccessibleBindingPath(getBindingInfo().getBindingPath());
451        }
452    }
453
454    /**
455     * Overrides processReadOnlyListDisplay to handle MultiValueControls by creating the list of values from values
456     * instead of the keys of the options selected (makes the list "human-readable").  Otherwise it just passes the
457     * list ahead as normal if this InputField does not use a MultiValueControl.
458     *
459     * @param model the model
460     * @param originalList originalList of values
461     */
462    @Override
463    protected void processReadOnlyListDisplay(Object model, List<?> originalList) {
464        //Special handling for option based fields
465        if ((control != null) && control instanceof MultiValueControlBase) {
466            List<String> newList = new ArrayList<String>();
467            List<KeyValue> fieldOptions = ((MultiValueControlBase) control).getOptions();
468
469            if (fieldOptions == null || fieldOptions.isEmpty()) {
470                return;
471            }
472
473            for (Object fieldValue : originalList) {
474                for (KeyValue keyValue : fieldOptions) {
475                    if (fieldValue != null && StringUtils.equals(fieldValue.toString(), keyValue.getKey())) {
476                        newList.add(keyValue.getValue());
477                        break;
478                    }
479                }
480            }
481            this.setReadOnlyDisplayReplacement(super.generateReadOnlyListDisplayReplacement(newList));
482        } else {
483            this.setReadOnlyDisplayReplacement(super.generateReadOnlyListDisplayReplacement(originalList));
484        }
485    }
486
487    /**
488     * Overriding to check quickfinder when masked is being applied. If quickfinder is configured set the component
489     * to widgetInputOnly, else set to readOnly
490     *
491     * {@inheritDoc}
492     */
493    @Override
494    protected void setAlternateAndAdditionalDisplayValue(View view, Object model) {
495        // if alternate or additional display values set don't override
496        if (StringUtils.isNotBlank(getReadOnlyDisplayReplacement()) || StringUtils.isNotBlank(
497                getReadOnlyDisplaySuffix())) {
498            return;
499        }
500
501        if (isApplyMask()) {
502            if ((this.quickfinder != null) && StringUtils.isNotBlank(this.quickfinder.getDataObjectClassName())) {
503                setWidgetInputOnly(true);
504            } else {
505                String maintenanceAction = null;
506                if (view.getViewTypeName().equals(UifConstants.ViewType.MAINTENANCE)) {
507                    maintenanceAction =((MaintenanceDocumentForm) model).getMaintenanceAction();
508                }
509
510                if ((!view.getViewTypeName().equals(UifConstants.ViewType.LOOKUP)) &&
511                    (!KRADConstants.MAINTENANCE_COPY_ACTION.equals(maintenanceAction))) {
512                        setReadOnly(true);
513                }
514            }
515        }
516
517        super.setAlternateAndAdditionalDisplayValue(view, model);
518    }
519
520    /**
521     * Adjust paths on the must occur constrain bindings
522     *
523     * @param mustOccurConstraints
524     */
525    protected void adjustMustOccurConstraintBinding(List<MustOccurConstraint> mustOccurConstraints) {
526        if (mustOccurConstraints != null) {
527            for (MustOccurConstraint mustOccurConstraint : mustOccurConstraints) {
528                adjustPrerequisiteConstraintBinding(mustOccurConstraint.getPrerequisiteConstraints());
529                adjustMustOccurConstraintBinding(mustOccurConstraint.getMustOccurConstraints());
530            }
531        }
532    }
533
534    /**
535     * Adjust paths on the prerequisite constraint bindings
536     *
537     * @param prerequisiteConstraints
538     */
539    protected void adjustPrerequisiteConstraintBinding(List<PrerequisiteConstraint> prerequisiteConstraints) {
540        if (prerequisiteConstraints != null) {
541            for (PrerequisiteConstraint prerequisiteConstraint : prerequisiteConstraints) {
542                String propertyName = getBindingInfo().getPropertyAdjustedBindingPath(
543                        prerequisiteConstraint.getPropertyName());
544                prerequisiteConstraint.setPropertyName(propertyName);
545            }
546        }
547    }
548
549    /**
550     * Performs setup of the field attribute query and informational display properties.
551     *
552     * <p>Paths are adjusted to match the binding for the this field, and the necessary onblur script for
553     * triggering the query client side is constructed</p>
554     *
555     * @param view view instance the input field is associated with
556     */
557    protected void setupFieldQuery(View view) {
558        if (getAttributeQuery() != null) {
559            getAttributeQuery().defaultQueryTarget(view.getViewHelperService());
560
561            // adjust paths on query mappings
562            getAttributeQuery().updateQueryFieldMapping(getBindingInfo());
563            getAttributeQuery().updateReturnFieldMapping(getBindingInfo());
564            getAttributeQuery().updateQueryMethodArgumentFieldList(getBindingInfo());
565
566            // build onblur script for field query
567            String script = "executeFieldQuery('" + getControl().getId() + "',";
568            script += "'" + getId() + "'," + getAttributeQuery().getQueryFieldMappingJsString() + ",";
569            script += getAttributeQuery().getQueryMethodArgumentFieldsJsString() + ",";
570            script += getAttributeQuery().getReturnFieldMappingJsString() + ");";
571
572            // show the span wich will contain the info
573            this.setRenderInfoMessageSpan(true);
574
575            if (StringUtils.isNotBlank(getControl().getOnBlurScript())) {
576                script = getControl().getOnBlurScript() + script;
577            }
578            getControl().setOnBlurScript(script);
579        }
580    }
581
582    /**
583     * Sets the ids on all components the input field uses so they will all
584     * contain this input field's id in their ids. This is useful for jQuery
585     * manipulation.
586     */
587    protected void setupIds() {
588        // update ids so they all match the attribute
589
590        setNestedComponentIdAndSuffix(getControl(), UifConstants.IdSuffixes.CONTROL);
591        setNestedComponentIdAndSuffix(getFieldLabel(), UifConstants.IdSuffixes.LABEL);
592        setNestedComponentIdAndSuffix(getInstructionalMessage(), UifConstants.IdSuffixes.INSTRUCTIONAL);
593        setNestedComponentIdAndSuffix(getConstraintMessage(), UifConstants.IdSuffixes.CONSTRAINT);
594        setNestedComponentIdAndSuffix(getQuickfinder(), UifConstants.IdSuffixes.QUICK_FINDER);
595        setNestedComponentIdAndSuffix(getSuggest(), UifConstants.IdSuffixes.SUGGEST);
596
597        if (this.getControl() != null) {
598            this.getControl().addDataAttribute(UifConstants.DataAttributes.CONTROL_FOR, this.getId());
599        }
600    }
601
602    /**
603     * {@inheritDoc}
604     */
605    @Override
606    public void copyFromAttributeDefinition(AttributeDefinition attributeDefinition) {
607        super.copyFromAttributeDefinition(attributeDefinition);
608
609        // max length
610        if (getMaxLength() == null) {
611            setMaxLength(attributeDefinition.getMaxLength());
612        }
613
614        // min length
615        if (getMinLength() == null) {
616            setMinLength(attributeDefinition.getMinLength());
617        }
618
619        // valid characters
620        if (getValidCharactersConstraint() == null) {
621            setValidCharactersConstraint(attributeDefinition.getValidCharactersConstraint());
622        }
623
624        if (getCaseConstraint() == null) {
625            setCaseConstraint(attributeDefinition.getCaseConstraint());
626        }
627
628        if (getDependencyConstraints() == null) {
629            setDependencyConstraints(attributeDefinition.getPrerequisiteConstraints());
630        }
631
632        if (getMustOccurConstraints() == null) {
633            setMustOccurConstraints(attributeDefinition.getMustOccurConstraints());
634        }
635
636        // required
637        if (getRequired() == null) {
638            setRequired(attributeDefinition.isRequired());
639
640            //if still null, default to false
641            if (getRequired() == null) {
642                setRequired(Boolean.FALSE);
643            }
644        }
645
646        if (getDataType() == null) {
647            setDataType(attributeDefinition.getDataType());
648            //Assume date if dataType is still null and using a DatePicker
649            if (getDataType() == null
650                    && control instanceof TextControl
651                    && ((TextControl) control).getDatePicker() != null) {
652                setDataType(DataType.DATE);
653            }
654        }
655
656        // control
657        if ((getControl() == null) && (attributeDefinition.getControlField() != null)) {
658            Control control = ComponentUtils.copy(attributeDefinition.getControlField());
659            setControl(control);
660        }
661
662        // constraint
663        if (StringUtils.isEmpty(getConstraintText())) {
664            setConstraintText(attributeDefinition.getConstraintText());
665
666            if (constraintMessage == null) {
667                constraintMessage = ComponentFactory.getConstraintMessage();
668            }
669
670            getConstraintMessage().setMessageText(attributeDefinition.getConstraintText());
671        }
672
673        // options
674        if (getOptionsFinder() == null) {
675            setOptionsFinder(attributeDefinition.getOptionsFinder());
676        }
677
678        // copy over simple constraint information because we cannot directly use simpleConstraint from
679        // attributeDefinition because the settings in InputField take precedence
680        if (this.getSimpleConstraint().getConstraintStateOverrides() == null) {
681            this.getSimpleConstraint().setConstraintStateOverrides(
682                    attributeDefinition.getSimpleConstraint().getConstraintStateOverrides());
683        }
684
685        if (this.getSimpleConstraint().getStates().isEmpty()) {
686            this.getSimpleConstraint().setStates(attributeDefinition.getSimpleConstraint().getStates());
687        }
688
689        if (this.getSimpleConstraint().getMessageKey() == null) {
690            this.getSimpleConstraint().setMessageKey(attributeDefinition.getSimpleConstraint().getMessageKey());
691        }
692
693        if (this.getSimpleConstraint().getApplyClientSide() == null) {
694            this.getSimpleConstraint().setApplyClientSide(
695                    attributeDefinition.getSimpleConstraint().getApplyClientSide());
696        }
697
698        if (this.getSimpleConstraint().getValidationMessageParams() == null) {
699            this.getSimpleConstraint().setValidationMessageParams(
700                    attributeDefinition.getSimpleConstraint().getValidationMessageParams());
701        }
702    }
703
704    /**
705     * {@inheritDoc}
706     */
707    @Override
708    public boolean isInputAllowed() {
709        return true;
710    }
711
712    /**
713     * {@inheritDoc}
714     */
715    @Override
716    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.BYTYPE)
717    public Control getControl() {
718        return this.control;
719    }
720
721    /**
722     * {@inheritDoc}
723     */
724    @Override
725    public void setControl(Control control) {
726        this.control = control;
727    }
728
729    /**
730     * {@inheritDoc}
731     */
732    @BeanTagAttribute
733    public boolean isInlineEdit() {
734        return inlineEdit;
735    }
736
737    /**
738     * {@inheritDoc}
739     */
740    public void setInlineEdit(boolean inlineEdit) {
741        this.inlineEdit = inlineEdit;
742    }
743
744    /**
745     * {@inheritDoc}
746     */
747    @BeanTagAttribute
748    public boolean isAjaxInlineEdit() {
749        return ajaxInlineEdit;
750    }
751
752    /**
753     * {@inheritDoc}
754     */
755    public void setAjaxInlineEdit(boolean ajaxInlineEdit) {
756        this.ajaxInlineEdit = ajaxInlineEdit;
757    }
758
759    /**
760     * {@inheritDoc}
761     */
762    @Override
763    @ViewLifecycleRestriction
764    @BeanTagAttribute
765    public FieldValidationMessages getValidationMessages() {
766        return this.validationMessages;
767    }
768
769    /**
770     * {@inheritDoc}
771     */
772    @Override
773    public void setValidationMessages(FieldValidationMessages validationMessages) {
774        this.validationMessages = validationMessages;
775    }
776
777    /**
778     * {@inheritDoc}
779     */
780    @Override
781    @BeanTagAttribute
782    public KeyValuesFinder getOptionsFinder() {
783        return this.optionsFinder;
784    }
785
786    /**
787     * {@inheritDoc}
788     */
789    @Override
790    public void setOptionsFinder(KeyValuesFinder optionsFinder) {
791        this.optionsFinder = optionsFinder;
792    }
793
794    /**
795     * {@inheritDoc}
796     */
797    @Override
798    @BeanTagAttribute
799    public Class<? extends KeyValuesFinder> getOptionsFinderClass() {
800        if (this.optionsFinder != null) {
801            return this.optionsFinder.getClass();
802        } else {
803            return null;
804        }
805    }
806
807    /**
808     * {@inheritDoc}
809     */
810    @Override
811    public void setOptionsFinderClass(Class<? extends KeyValuesFinder> optionsFinderClass) {
812        this.optionsFinder = KRADUtils.createNewObjectFromClass(optionsFinderClass);
813    }
814
815    /**
816     * {@inheritDoc}
817     */
818    @Override
819    @BeanTagAttribute
820    public boolean isEnableAutoDirectInquiry() {
821        return enableAutoDirectInquiry;
822    }
823
824    /**
825     * {@inheritDoc}
826     */
827    @Override
828    public void setEnableAutoDirectInquiry(boolean enableAutoDirectInquiry) {
829        this.enableAutoDirectInquiry = enableAutoDirectInquiry;
830    }
831
832    /**
833     * {@inheritDoc}
834     */
835    @Override
836    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
837    public QuickFinder getQuickfinder() {
838        return this.quickfinder;
839    }
840
841    /**
842     * {@inheritDoc}
843     */
844    @Override
845    public void setQuickfinder(QuickFinder quickfinder) {
846        this.quickfinder = quickfinder;
847    }
848
849    /**
850     * {@inheritDoc}
851     */
852    @Override
853    @BeanTagAttribute
854    public boolean isEnableAutoQuickfinder() {
855        return enableAutoQuickfinder;
856    }
857
858    /**
859     * {@inheritDoc}
860     */
861    @Override
862    public void setEnableAutoQuickfinder(boolean enableAutoQuickfinder) {
863        this.enableAutoQuickfinder = enableAutoQuickfinder;
864    }
865
866    /**
867     * {@inheritDoc}
868     */
869    @Override
870    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
871    public Suggest getSuggest() {
872        return suggest;
873    }
874
875    /**
876     * {@inheritDoc}
877     */
878    @Override
879    public void setSuggest(Suggest suggest) {
880        this.suggest = suggest;
881    }
882
883    /**
884     * {@inheritDoc}
885     */
886    @Override
887    @BeanTagAttribute
888    public boolean isWidgetInputOnly() {
889        return this.widgetInputOnly;
890    }
891
892    /**
893     * {@inheritDoc}
894     */
895    @Override
896    public void setWidgetInputOnly(boolean widgetInputOnly) {
897        this.widgetInputOnly = widgetInputOnly;
898    }
899
900    /**
901     * {@inheritDoc}
902     */
903    @Override
904    @BeanTagAttribute
905    public boolean isRenderInputAddonGroup() {
906        return renderInputAddonGroup;
907    }
908
909    /**
910     * {@inheritDoc}
911     */
912    @Override
913    public void setRenderInputAddonGroup(boolean renderInputAddonGroup) {
914        this.renderInputAddonGroup = renderInputAddonGroup;
915    }
916
917    /**
918     * {@inheritDoc}
919     */
920    @Override
921    @BeanTagAttribute
922    public List<String> getPostInputCssClasses() {
923        return postInputCssClasses;
924    }
925
926    /**
927     * {@inheritDoc}
928     */
929    @Override
930    public String getPostInputCssClassesAsString() {
931        if (postInputCssClasses != null) {
932            return StringUtils.join(postInputCssClasses, " ");
933        }
934
935        return "";
936    }
937
938    /**
939     * {@inheritDoc}
940     */
941    @Override
942    public void setPostInputCssClasses(List<String> postInputCssClasses) {
943        this.postInputCssClasses = postInputCssClasses;
944    }
945
946    /**
947     * {@inheritDoc}
948     */
949    @Override
950    @BeanTagAttribute
951    public List<Component> getPostInputAddons() {
952        return postInputAddons;
953    }
954
955    /**
956     * {@inheritDoc}
957     */
958    @Override
959    public void setPostInputAddons(List<Component> postInputAddons) {
960        this.postInputAddons = postInputAddons;
961    }
962
963    /**
964     * {@inheritDoc}
965     */
966    @Override
967    public void addPostInputAddon(Component addOn) {
968        if (postInputAddons == null) {
969            postInputAddons = new ArrayList<Component>();
970        }
971
972        postInputAddons.add(addOn);
973    }
974
975    /**
976     * {@inheritDoc}
977     */
978    @Override
979    @BeanTagAttribute
980    public String getInstructionalText() {
981        return this.instructionalText;
982    }
983
984    /**
985     * {@inheritDoc}
986     */
987    @Override
988    public void setInstructionalText(String instructionalText) {
989        this.instructionalText = instructionalText;
990    }
991
992    /**
993     * {@inheritDoc}
994     */
995    @Override
996    @BeanTagAttribute
997    public Message getInstructionalMessage() {
998        return this.instructionalMessage;
999    }
1000
1001    /**
1002     * {@inheritDoc}
1003     */
1004    @Override
1005    public void setInstructionalMessage(Message instructionalMessage) {
1006        this.instructionalMessage = instructionalMessage;
1007    }
1008
1009    /**
1010     * {@inheritDoc}
1011     */
1012    @Override
1013    @BeanTagAttribute
1014    public String getHelperText() {
1015        return helperText;
1016    }
1017
1018    /**
1019     * {@inheritDoc}
1020     */
1021    @Override
1022    public void setHelperText(String helperText) {
1023        this.helperText = helperText;
1024    }
1025
1026    /**
1027     * {@inheritDoc}
1028     */
1029    @Override
1030    @BeanTagAttribute
1031    public String getConstraintText() {
1032        return this.constraintText;
1033    }
1034
1035    /**
1036     * {@inheritDoc}
1037     */
1038    @Override
1039    public void setConstraintText(String constraintText) {
1040        this.constraintText = constraintText;
1041    }
1042
1043    /**
1044     * {@inheritDoc}
1045     */
1046    @Override
1047    @BeanTagAttribute
1048    public Message getConstraintMessage() {
1049        return this.constraintMessage;
1050    }
1051
1052    /**
1053     * {@inheritDoc}
1054     */
1055    @Override
1056    public void setConstraintMessage(Message constraintMessage) {
1057        this.constraintMessage = constraintMessage;
1058    }
1059
1060    /**
1061     * {@inheritDoc}
1062     */
1063    @Override
1064    @BeanTagAttribute
1065    public ValidCharactersConstraint getValidCharactersConstraint() {
1066        return this.validCharactersConstraint;
1067    }
1068
1069    /**
1070     * {@inheritDoc}
1071     */
1072    @Override
1073    public void setValidCharactersConstraint(ValidCharactersConstraint validCharactersConstraint) {
1074        this.validCharactersConstraint = validCharactersConstraint;
1075    }
1076
1077    /**
1078     * {@inheritDoc}
1079     */
1080    @Override
1081    @BeanTagAttribute
1082    public CaseConstraint getCaseConstraint() {
1083        return this.caseConstraint;
1084    }
1085
1086    /**
1087     * {@inheritDoc}
1088     */
1089    @Override
1090    public void setCaseConstraint(CaseConstraint caseConstraint) {
1091        this.caseConstraint = caseConstraint;
1092    }
1093
1094    /**
1095     * {@inheritDoc}
1096     */
1097    @Override
1098    @BeanTagAttribute
1099    public List<PrerequisiteConstraint> getDependencyConstraints() {
1100        return this.dependencyConstraints;
1101    }
1102
1103    /**
1104     * {@inheritDoc}
1105     */
1106    @Override
1107    public void setDependencyConstraints(List<PrerequisiteConstraint> dependencyConstraints) {
1108        this.dependencyConstraints = dependencyConstraints;
1109    }
1110
1111    /**
1112     * {@inheritDoc}
1113     */
1114    @Override
1115    public List<PrerequisiteConstraint> getPrerequisiteConstraints() {
1116        return dependencyConstraints;
1117    }
1118
1119    /**
1120     * {@inheritDoc}
1121     */
1122    @Override
1123    @BeanTagAttribute
1124    public List<MustOccurConstraint> getMustOccurConstraints() {
1125        return this.mustOccurConstraints;
1126    }
1127
1128    /**
1129     * {@inheritDoc}
1130     */
1131    @Override
1132    public void setMustOccurConstraints(List<MustOccurConstraint> mustOccurConstraints) {
1133        this.mustOccurConstraints = mustOccurConstraints;
1134    }
1135
1136    /**
1137     * {@inheritDoc}
1138     */
1139    @Override
1140    @BeanTagAttribute
1141    public SimpleConstraint getSimpleConstraint() {
1142        return this.simpleConstraint;
1143    }
1144
1145    /**
1146     * {@inheritDoc}
1147     */
1148    @Override
1149    public void setSimpleConstraint(SimpleConstraint simpleConstraint) {
1150        this.simpleConstraint = simpleConstraint;
1151    }
1152
1153    /**
1154     * {@inheritDoc}
1155     */
1156    @Override
1157    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1158    public DataType getDataType() {
1159        return this.simpleConstraint.getDataType();
1160    }
1161
1162    /**
1163     * {@inheritDoc}
1164     */
1165    @Override
1166    public void setDataType(DataType dataType) {
1167        this.simpleConstraint.setDataType(dataType);
1168    }
1169
1170    /**
1171     * {@inheritDoc}
1172     */
1173    @Override
1174    public void setDataType(String dataType) {
1175        this.simpleConstraint.setDataType(DataType.valueOf(dataType));
1176    }
1177
1178    /**
1179     * {@inheritDoc}
1180     */
1181    @Override
1182    @BeanTagAttribute
1183    public Integer getMaxLength() {
1184        return simpleConstraint.getMaxLength();
1185    }
1186
1187    /**
1188     * {@inheritDoc}
1189     */
1190    @Override
1191    public void setMaxLength(Integer maxLength) {
1192        simpleConstraint.setMaxLength(maxLength);
1193    }
1194
1195    /**
1196     * {@inheritDoc}
1197     */
1198    @Override
1199    @BeanTagAttribute
1200    public Integer getMinLength() {
1201        return simpleConstraint.getMinLength();
1202    }
1203
1204    /**
1205     * {@inheritDoc}
1206     */
1207    @Override
1208    public void setMinLength(Integer minLength) {
1209        simpleConstraint.setMinLength(minLength);
1210    }
1211
1212    /**
1213     * {@inheritDoc}
1214     */
1215    @Override
1216    @BeanTagAttribute
1217    public Boolean getRequired() {
1218        return this.simpleConstraint.getRequired();
1219    }
1220
1221    /**
1222     * {@inheritDoc}
1223     */
1224    @Override
1225    public void setRequired(Boolean required) {
1226        this.simpleConstraint.setRequired(required);
1227    }
1228
1229    /**
1230     * {@inheritDoc}
1231     */
1232    @Override
1233    @BeanTagAttribute
1234    public String getExclusiveMin() {
1235        return simpleConstraint.getExclusiveMin();
1236    }
1237
1238    /**
1239     * {@inheritDoc}
1240     */
1241    @Override
1242    public void setExclusiveMin(String exclusiveMin) {
1243        simpleConstraint.setExclusiveMin(exclusiveMin);
1244    }
1245
1246    /**
1247     * {@inheritDoc}
1248     */
1249    @Override
1250    @BeanTagAttribute
1251    public String getInclusiveMax() {
1252        return simpleConstraint.getInclusiveMax();
1253    }
1254
1255    /**
1256     * {@inheritDoc}
1257     */
1258    @Override
1259    public void setInclusiveMax(String inclusiveMax) {
1260        simpleConstraint.setInclusiveMax(inclusiveMax);
1261    }
1262
1263    /**
1264     * {@inheritDoc}
1265     */
1266    @Override
1267    @BeanTagAttribute(type = BeanTagAttribute.AttributeType.DIRECTORBYTYPE)
1268    public AttributeQuery getAttributeQuery() {
1269        return attributeQuery;
1270    }
1271
1272    /**
1273     * {@inheritDoc}
1274     */
1275    @Override
1276    public void setAttributeQuery(AttributeQuery attributeQuery) {
1277        this.attributeQuery = attributeQuery;
1278    }
1279
1280    /**
1281     * {@inheritDoc}
1282     */
1283    @Override
1284    @BeanTagAttribute
1285    public boolean isUppercaseValue() {
1286        return uppercaseValue;
1287    }
1288
1289    /**
1290     * {@inheritDoc}
1291     */
1292    @Override
1293    public void setUppercaseValue(boolean uppercaseValue) {
1294        this.uppercaseValue = uppercaseValue;
1295    }
1296
1297    /**
1298     * {@inheritDoc}
1299     */
1300    @Override
1301    @BeanTagAttribute
1302    public boolean isDisableNativeAutocomplete() {
1303        return disableNativeAutocomplete;
1304    }
1305
1306    /**
1307     * {@inheritDoc}
1308     */
1309    @Override
1310    public void setDisableNativeAutocomplete(boolean disableNativeAutocomplete) {
1311        this.disableNativeAutocomplete = disableNativeAutocomplete;
1312    }
1313
1314    /**
1315     * {@inheritDoc}
1316     */
1317    @Override
1318    public boolean isRenderFieldset() {
1319        return super.isRenderFieldset() || (quickfinder != null
1320                && quickfinder.isRender()
1321                && quickfinder.getQuickfinderAction() != null
1322                && quickfinder.getQuickfinderAction().isRender());
1323    }
1324
1325    /**
1326     * {@inheritDoc}
1327     */
1328    @Override
1329    public void completeValidation(ValidationTrace tracer) {
1330        tracer.addBean(this);
1331
1332        // Checks that the control is set
1333        if (getControl() == null) {
1334            if (Validator.checkExpressions(this, "control")) {
1335                String currentValues[] = {"propertyName =" + getPropertyName()};
1336                tracer.createWarning("Control should be set", currentValues);
1337            }
1338        }
1339
1340
1341        if (getControl() != null && !(getControl() instanceof TextControl
1342                || getControl() instanceof TextAreaControl
1343                || getControl() instanceof SelectControl)){
1344
1345            if (CollectionUtils.isNotEmpty(this.getPostInputAddons())) {
1346                String currentValues[] = {"propertyName =" + getPropertyName()};
1347                tracer.createWarning("Inputs which are not text or select should not use post input addons for "
1348                        + "user experience reasons", currentValues);
1349            }
1350        }
1351
1352        super.completeValidation(tracer.getCopy());
1353    }
1354
1355
1356    /**
1357     * Determines wheter or not to create an automatic quickfinder widget for this field within the current lifecycle.
1358     *
1359     * @return True if an automatic quickfinder widget should be created for this field on the current lifecycle.
1360     */
1361    protected boolean hasAutoQuickfinderRelationship() {
1362        String propertyName = getBindingInfo().getBindingName();
1363
1364        // get object instance and class for parent
1365        View view = ViewLifecycle.getView();
1366        Object model = ViewLifecycle.getModel();
1367        Object parentObject = ViewModelUtils.getParentObjectForMetadata(view, model, this);
1368        Class<?> parentObjectClass = null;
1369        if (parentObject != null) {
1370            parentObjectClass = parentObject.getClass();
1371        }
1372
1373        // get relationship from metadata service
1374        @SuppressWarnings("deprecation")
1375        DataObjectRelationship relationship = null;
1376        if (parentObject != null) {
1377            relationship = KRADServiceLocatorWeb.getLegacyDataAdapter().getDataObjectRelationship(parentObject,
1378                    parentObjectClass, propertyName, "", true, true, false);
1379        }
1380
1381        return relationship != null;
1382    }
1383
1384}