001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krad.uif.view;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.krad.datadictionary.parse.BeanTag;
020    import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021    import org.kuali.rice.krad.uif.UifConstants.ViewType;
022    import org.kuali.rice.krad.uif.UifPropertyPaths;
023    import org.kuali.rice.krad.uif.container.CollectionGroup;
024    import org.kuali.rice.krad.uif.container.Group;
025    import org.kuali.rice.krad.uif.component.Component;
026    import org.kuali.rice.krad.uif.component.RequestParameter;
027    import org.kuali.rice.krad.uif.control.Control;
028    import org.kuali.rice.krad.uif.control.TextAreaControl;
029    import org.kuali.rice.krad.uif.control.TextControl;
030    import org.kuali.rice.krad.uif.element.Link;
031    import org.kuali.rice.krad.uif.field.Field;
032    import org.kuali.rice.krad.uif.field.FieldGroup;
033    import org.kuali.rice.krad.uif.field.InputField;
034    import org.kuali.rice.krad.uif.field.LookupInputField;
035    import org.kuali.rice.krad.uif.util.ComponentFactory;
036    import org.kuali.rice.krad.uif.util.ComponentUtils;
037    import org.kuali.rice.krad.util.KRADConstants;
038    import org.kuali.rice.krad.web.form.LookupForm;
039    
040    import java.util.ArrayList;
041    import java.util.Arrays;
042    import java.util.HashMap;
043    import java.util.List;
044    
045    /**
046     * View type for Maintenance documents
047     *
048     * <p>
049     * Supports doing a search against a data object class or performing a more advanced query. The view
050     * type is primarily made up of two groups, the search (or criteria) group and the results group. Many
051     * options are supported on the view to enable/disable certain features, like what actions are available
052     * on the search results.
053     * </p>
054     *
055     * <p>
056     * Works in conjunction with <code>LookupableImpl</code> which customizes the view and carries out the
057     * business functionality
058     * </p>
059     *
060     * @author Kuali Rice Team (rice.collab@kuali.org)
061     */
062    @BeanTag(name = "lookupView-bean", parent = "Uif-LookupView")
063    public class LookupView extends FormView {
064        private static final long serialVersionUID = 716926008488403616L;
065    
066        private Class<?> dataObjectClassName;
067    
068        private Group criteriaGroup;
069        private CollectionGroup resultsGroup;
070    
071        private FieldGroup resultsActionsFieldGroup;
072        private Field resultsReturnField;
073    
074        private List<Component> criteriaFields;
075        private List<Component> resultFields;
076        private List<String> defaultSortAttributeNames;
077    
078        protected boolean defaultSortAscending = true;
079    
080        @RequestParameter
081        private boolean hideReturnLinks = false;
082        @RequestParameter
083        private boolean suppressActions = false;
084        @RequestParameter
085        private boolean showMaintenanceLinks = false;
086        @RequestParameter
087        private boolean multipleValuesSelect = false;
088    
089        @RequestParameter
090        private String returnTarget;
091    
092        @RequestParameter
093        private boolean returnByScript;
094    
095        private boolean lookupCriteriaEnabled = true;
096        private boolean supplementalActionsEnabled = false;
097        private boolean disableSearchButtons = false;
098    
099        private Integer resultSetLimit = null;
100    
101        private String maintenanceUrlMapping;
102    
103        private FieldGroup rangeFieldGroupPrototype;
104    
105        public LookupView() {
106            super();
107    
108            setViewTypeName(ViewType.LOOKUP);
109            setApplyDirtyCheck(false);
110        }
111    
112        /**
113         * The following initialization is performed:
114         *
115         * <ul>
116         * <li>Set the abstractTypeClasses map for the lookup object path</li>
117         * </ul>
118         *
119         * @see org.kuali.rice.krad.uif.container.ContainerBase#performInitialization(org.kuali.rice.krad.uif.view.View,
120         *      java.lang.Object)
121         */
122        @Override
123        public void performInitialization(View view, Object model) {
124            initializeGroups();
125            if (getItems().isEmpty()) {
126                setItems(Arrays.asList(getCriteriaGroup(), getResultsGroup()));
127            }
128    
129            super.performInitialization(view, model);
130    
131            // if this is a multi-value lookup, don't show return column
132            if (multipleValuesSelect) {
133                hideReturnLinks = true;
134            }
135    
136            getObjectPathToConcreteClassMapping().put(UifPropertyPaths.LOOKUP_CRITERIA, getDataObjectClassName());
137            if (StringUtils.isNotBlank(getDefaultBindingObjectPath())) {
138                getObjectPathToConcreteClassMapping().put(getDefaultBindingObjectPath(), getDataObjectClassName());
139            }
140        }
141    
142        protected void initializeGroups() {
143            if ((getCriteriaGroup() != null) && (getCriteriaGroup().getItems().isEmpty())) {
144                getCriteriaGroup().setItems(getCriteriaFields());
145            }
146    
147            if (getResultsGroup() != null) {
148                if ((getResultsGroup().getItems().isEmpty()) && (getResultFields() != null)) {
149                    getResultsGroup().setItems(getResultFields());
150                }
151                if (getResultsGroup().getCollectionObjectClass() == null) {
152                    getResultsGroup().setCollectionObjectClass(getDataObjectClassName());
153                }
154            }
155        }
156    
157        /**
158         * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(View, Object,
159         * org.kuali.rice.krad.uif.component.Component)
160         */
161        @Override
162        public void performApplyModel(View view, Object model, Component parent) {
163            LookupForm lookupForm = (LookupForm) model;
164    
165            // TODO: need to check lookupForm.isAtLeastOneRowHasActions() somewhere
166            if (!isSuppressActions() && isShowMaintenanceLinks()) {
167                ((List<Component>) getResultsGroup().getItems()).add(0, getResultsActionsFieldGroup());
168            }
169    
170            if (StringUtils.isNotBlank(lookupForm.getReturnFormKey()) &&
171                    StringUtils.isNotBlank(lookupForm.getReturnLocation()) && !isHideReturnLinks()) {
172                ((List<Component>) getResultsGroup().getItems()).add(0, getResultsReturnField());
173            }
174    
175            setupLookupCriteriaFields();
176    
177            super.performApplyModel(view, model, parent);
178        }
179    
180        /**
181         * Helper method to do any lookup specific changes to the criteria fields
182         */
183        private void setupLookupCriteriaFields() {
184    
185            int rangeIndex = 0;
186            HashMap<Integer, Component> dateRangeFieldMap = new HashMap<Integer, Component>();
187    
188            for (Component criteriaField : criteriaGroup.getItems()) {
189    
190                // Set the max length on the controls to allow for wildcards
191                Control control = ((InputField)criteriaField).getControl();
192                if (control instanceof TextControl) {
193                    ((TextControl) control).setMaxLength(null);
194                } else if (control instanceof TextAreaControl) {
195                    ((TextAreaControl) control).setMaxLength(null);
196                }
197    
198                if (((LookupInputField)criteriaField).isRanged()) {
199                    // Create field group
200                    FieldGroup rangeFieldGroup = ComponentUtils.copy(rangeFieldGroupPrototype, criteriaField.getId());
201                    rangeFieldGroup.setLabel(((LookupInputField)criteriaField).getLabel());
202                    List<Component> fieldGroupItems = new ArrayList<Component>();
203    
204                    // Create a new from date field
205                    LookupInputField fromDate = (LookupInputField)ComponentUtils.copy(criteriaField, KRADConstants.LOOKUP_DEFAULT_RANGE_SEARCH_LOWER_BOUND_LABEL);
206                    fromDate.getBindingInfo().setBindingName(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + fromDate.getPropertyName());
207                    fromDate.setPropertyName(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + fromDate.getPropertyName());
208    
209                    // Set the criteria fields labels
210                    fromDate.setLabel("");
211                    fromDate.getFieldLabel().setRenderColon(false);
212                    ((LookupInputField)criteriaField).setLabel("to");
213                    ((LookupInputField)criteriaField).getFieldLabel().setRenderColon(false);
214    
215                    // Add the cirteria fields to the field group
216                    fieldGroupItems.add(fromDate);
217                    fieldGroupItems.add(criteriaField);
218                    rangeFieldGroup.setItems(fieldGroupItems);
219    
220                    // Add fieldgroup to map with index as key
221                    dateRangeFieldMap.put(rangeIndex, rangeFieldGroup);
222                }
223                rangeIndex++;
224            }
225    
226            // Replace original fields with range fieldgroups
227            List<Component> itemList = (List<Component>)criteriaGroup.getItems();
228            for (Integer index : dateRangeFieldMap.keySet()) {
229                itemList.set(index, dateRangeFieldMap.get(index));
230            }
231    
232            criteriaGroup.setItems(itemList);
233        }
234    
235        /**
236         * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
237         */
238        @Override
239        public List<Component> getComponentPrototypes() {
240            List<Component> components = super.getComponentPrototypes();
241    
242            components.add(criteriaGroup);
243            components.add(resultsGroup);
244            components.add(resultsActionsFieldGroup);
245            components.add(resultsReturnField);
246            components.addAll(criteriaFields);
247            components.addAll(resultFields);
248    
249            components.add(rangeFieldGroupPrototype);
250    
251            return components;
252        }
253    
254        public void applyConditionalLogicForFieldDisplay() {
255            // TODO: work into view lifecycle
256            //          LookupViewHelperService lookupViewHelperService = (LookupViewHelperService) getViewHelperService();
257            //              Set<String> readOnlyFields = lookupViewHelperService.getConditionallyReadOnlyPropertyNames();
258            //              Set<String> requiredFields = lookupViewHelperService.getConditionallyRequiredPropertyNames();
259            //              Set<String> hiddenFields = lookupViewHelperService.getConditionallyHiddenPropertyNames();
260            //              if ( (readOnlyFields != null && !readOnlyFields.isEmpty()) ||
261            //                       (requiredFields != null && !requiredFields.isEmpty()) ||
262            //                       (hiddenFields != null && !hiddenFields.isEmpty())
263            //                      ) {
264            //                      for (Field field : getResultsGroup().getItems()) {
265            //                              if (InputField.class.isAssignableFrom(field.getClass())) {
266            //                                      InputField attributeField = (InputField) field;
267            //                                      if (readOnlyFields != null && readOnlyFields.contains(attributeField.getBindingInfo().getBindingName())) {
268            //                                              attributeField.setReadOnly(true);
269            //                                      }
270            //                                      if (requiredFields != null && requiredFields.contains(attributeField.getBindingInfo().getBindingName())) {
271            //                                              attributeField.setRequired(Boolean.TRUE);
272            //                                      }
273            //                                      if (hiddenFields != null && hiddenFields.contains(attributeField.getBindingInfo().getBindingName())) {
274            //                                              attributeField.setControl(LookupInquiryUtils.generateCustomLookupControlFromExisting(HiddenControl.class, null));
275            //                                      }
276            //                              }
277            //              }
278            //              }
279        }
280    
281        /**
282         * Class name for the object the lookup applies to
283         *
284         * <p>
285         * The object class name is used to pick up a dictionary entry which will
286         * feed the attribute field definitions and other configuration. In addition
287         * it is to configure the <code>Lookupable</code> which will carry out the
288         * lookup action
289         * </p>
290         *
291         * @return Class<?> lookup data object class
292         */
293        @BeanTagAttribute(name="dataObjectClassName")
294        public Class<?> getDataObjectClassName() {
295            return this.dataObjectClassName;
296        }
297    
298        /**
299         * Setter for the object class name
300         *
301         * @param dataObjectClassName
302         */
303        public void setDataObjectClassName(Class<?> dataObjectClassName) {
304            this.dataObjectClassName = dataObjectClassName;
305        }
306    
307        /**
308         * @return the hideReturnLinks
309         */
310        @BeanTagAttribute(name="hideReturnLinks")
311        public boolean isHideReturnLinks() {
312            return this.hideReturnLinks;
313        }
314    
315        /**
316         * @param hideReturnLinks the hideReturnLinks to set
317         */
318        public void setHideReturnLinks(boolean hideReturnLinks) {
319            this.hideReturnLinks = hideReturnLinks;
320        }
321    
322        /**
323         * @return the suppressActions
324         */
325        @BeanTagAttribute(name="isSuppressActions")
326        public boolean isSuppressActions() {
327            return this.suppressActions;
328        }
329    
330        /**
331         * @param suppressActions the suppressActions to set
332         */
333        public void setSuppressActions(boolean suppressActions) {
334            this.suppressActions = suppressActions;
335        }
336    
337        /**
338         * @return the showMaintenanceLinks
339         */
340        @BeanTagAttribute(name="showMaintenanceLinks")
341        public boolean isShowMaintenanceLinks() {
342            return this.showMaintenanceLinks;
343        }
344    
345        /**
346         * @param showMaintenanceLinks the showMaintenanceLinks to set
347         */
348        public void setShowMaintenanceLinks(boolean showMaintenanceLinks) {
349            this.showMaintenanceLinks = showMaintenanceLinks;
350        }
351    
352        /**
353         * Indicates whether multiple values select should be enabled for the lookup
354         *
355         * <p>
356         * When set to true, the select field is enabled for the lookup results group that allows the user
357         * to select one or more rows for returning
358         * </p>
359         *
360         * @return boolean true if multiple values should be enabled, false otherwise
361         */
362        @BeanTagAttribute(name="multipleValueSelect")
363        public boolean isMultipleValuesSelect() {
364            return multipleValuesSelect;
365        }
366    
367        /**
368         * Setter for the multiple values select indicator
369         *
370         * @param multipleValuesSelect
371         */
372        public void setMultipleValuesSelect(boolean multipleValuesSelect) {
373            this.multipleValuesSelect = multipleValuesSelect;
374        }
375    
376        /**
377         * @return the resultsActionsField
378         */
379        @BeanTagAttribute(name="resultActionsFieldGroup",type= BeanTagAttribute.AttributeType.SINGLEBEAN)
380        public FieldGroup getResultsActionsFieldGroup() {
381            return this.resultsActionsFieldGroup;
382        }
383    
384        /**
385         * @param resultsActionsFieldGroup the resultsActionsField to set
386         */
387        public void setResultsActionsFieldGroup(FieldGroup resultsActionsFieldGroup) {
388            this.resultsActionsFieldGroup = resultsActionsFieldGroup;
389        }
390    
391        /**
392         * @return the resultsReturnField
393         */
394        @BeanTagAttribute(name="resultReturnField",type= BeanTagAttribute.AttributeType.SINGLEBEAN)
395        public Field getResultsReturnField() {
396            return this.resultsReturnField;
397        }
398    
399        /**
400         * @param resultsReturnField the resultsReturnField to set
401         */
402        public void setResultsReturnField(Field resultsReturnField) {
403            this.resultsReturnField = resultsReturnField;
404        }
405    
406        @BeanTagAttribute(name="criteriaGroup",type = BeanTagAttribute.AttributeType.SINGLEBEAN)
407        public Group getCriteriaGroup() {
408            return this.criteriaGroup;
409        }
410    
411        public void setCriteriaGroup(Group criteriaGroup) {
412            this.criteriaGroup = criteriaGroup;
413        }
414    
415        @BeanTagAttribute(name="resultsGroup",type= BeanTagAttribute.AttributeType.SINGLEBEAN)
416        public CollectionGroup getResultsGroup() {
417            return this.resultsGroup;
418        }
419    
420        public void setResultsGroup(CollectionGroup resultsGroup) {
421            this.resultsGroup = resultsGroup;
422        }
423    
424        @BeanTagAttribute(name="criteriaFields",type= BeanTagAttribute.AttributeType.LISTBEAN)
425        public List<Component> getCriteriaFields() {
426            return this.criteriaFields;
427        }
428    
429        public void setCriteriaFields(List<Component> criteriaFields) {
430            this.criteriaFields = criteriaFields;
431        }
432    
433        @BeanTagAttribute(name="resultFields",type= BeanTagAttribute.AttributeType.LISTBEAN)
434        public List<Component> getResultFields() {
435            return this.resultFields;
436        }
437    
438        public void setResultFields(List<Component> resultFields) {
439            this.resultFields = resultFields;
440        }
441    
442        @BeanTagAttribute(name="defaultSortAttributeNames",type= BeanTagAttribute.AttributeType.LISTVALUE)
443        public List<String> getDefaultSortAttributeNames() {
444            return this.defaultSortAttributeNames;
445        }
446    
447        public void setDefaultSortAttributeNames(List<String> defaultSortAttributeNames) {
448            this.defaultSortAttributeNames = defaultSortAttributeNames;
449        }
450    
451        @BeanTagAttribute(name="defaultSortAscending")
452        public boolean isDefaultSortAscending() {
453            return this.defaultSortAscending;
454        }
455    
456        public void setDefaultSortAscending(boolean defaultSortAscending) {
457            this.defaultSortAscending = defaultSortAscending;
458        }
459    
460        /**
461         * Retrieves the maximum number of records that will be listed
462         * as a result of the lookup search
463         *
464         * @return Integer result set limit
465         */
466        @BeanTagAttribute(name="resultSetLimit")
467        public Integer getResultSetLimit() {
468            return resultSetLimit;
469        }
470    
471        /**
472         * Setter for the result list limit
473         *
474         * @param resultSetLimit Integer specifying limit
475         */
476        public void setResultSetLimit(Integer resultSetLimit) {
477            this.resultSetLimit = resultSetLimit;
478        }
479    
480        /**
481         * Indicates whether a result set limit has been specified for the
482         * view
483         *
484         * @return true if this instance has a result set limit
485         */
486        public boolean hasResultSetLimit() {
487            return (resultSetLimit != null);
488        }
489    
490        /**
491         * @param returnTarget the returnTarget to set
492         */
493        public void setReturnTarget(String returnTarget) {
494            this.returnTarget = returnTarget;
495        }
496    
497        /**
498         * @return the returnTarget
499         */
500        @BeanTagAttribute(name="returnTarget")
501        public String getReturnTarget() {
502            return returnTarget;
503        }
504    
505        /**
506         * @return the returnByScript
507         */
508        @BeanTagAttribute(name="returnByScript")
509        public boolean isReturnByScript() {
510            return returnByScript;
511        }
512    
513        /**
514         * Setter for the flag to indicate that lookups will return the value
515         * by script and not a post
516         *
517         * @param returnByScript the returnByScript flag
518         */
519        public void setReturnByScript(boolean returnByScript) {
520            this.returnByScript = returnByScript;
521        }
522    
523        /**
524         * String that maps to the maintenance controller for the maintenance document (if any) associated with the
525         * lookup data object class
526         *
527         * <p>
528         * Mapping will be used to build the maintenance action links (such as edit, copy, and new). If not given, the
529         * default maintenance mapping will be used
530         * </p>
531         *
532         * @return String mapping string
533         */
534        @BeanTagAttribute(name="maintenanceUrlMapping")
535        public String getMaintenanceUrlMapping() {
536            return maintenanceUrlMapping;
537        }
538    
539        /**
540         * Setter for the URL mapping string that will be used to build up maintenance action URLs
541         *
542         * @param maintenanceUrlMapping
543         */
544        public void setMaintenanceUrlMapping(String maintenanceUrlMapping) {
545            this.maintenanceUrlMapping = maintenanceUrlMapping;
546        }
547    
548        /**
549         * The field group prototype that will be copied and used for range fields
550         *
551         * @return FieldGroup
552         */
553        public FieldGroup getRangeFieldGroupPrototype() {
554            return rangeFieldGroupPrototype;
555        }
556    
557        /**
558         * Setter for the range FieldGroup prototype
559         *
560         * @param rangeFieldGroupPrototype
561         */
562        public void setRangeFieldGroupPrototype(FieldGroup rangeFieldGroupPrototype) {
563            this.rangeFieldGroupPrototype = rangeFieldGroupPrototype;
564        }
565    }