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.widget;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.uif.component.BindingInfo;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.component.MethodInvokerConfig;
24  import org.kuali.rice.krad.uif.field.AttributeQuery;
25  import org.kuali.rice.krad.uif.field.InputField;
26  import org.kuali.rice.krad.uif.util.ScriptUtils;
27  import org.kuali.rice.krad.uif.view.View;
28  
29  import java.util.List;
30  
31  /**
32   * Widget that provides dynamic select options to the user as they
33   * are entering the value (also known as auto-complete)
34   *
35   * <p>
36   * Widget is backed by an <code>AttributeQuery</code> that provides
37   * the configuration for executing a query server side that will retrieve
38   * the valid option values
39   * </p>
40   *
41   * @author Kuali Rice Team (rice.collab@kuali.org)
42   */
43  @BeanTag(name = "suggest")
44  public class Suggest extends WidgetBase {
45      private static final long serialVersionUID = 7373706855319347225L;
46  
47      private AttributeQuery suggestQuery;
48  
49      private String sourcePropertyName;
50      private boolean sourceQueryMethodResults;
51  
52      private boolean retrieveAllSuggestions;
53      private List<Object> suggestOptions;
54  
55      private String suggestOptionsJsString;
56  
57      public Suggest() {
58          super();
59      }
60  
61      /**
62       * The following actions are performed:
63       *
64       * <ul>
65       * <li>Adjusts the query field mappings on the query based on the binding configuration of the field</li>
66       * <li>TODO: determine query if render is true and query is not set</li>
67       * </ul>
68       *
69       * @see org.kuali.rice.krad.uif.component.ComponentBase#performFinalize(org.kuali.rice.krad.uif.view.View,
70       *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
71       */
72      @Override
73      public void performFinalize(View view, Object model, Component parent) {
74          super.performFinalize(view, model, parent);
75  
76          // if source property name or query method or options not set then we can't render the Suggest widget
77          if (StringUtils.isBlank(sourcePropertyName) &&
78                  !suggestQuery.hasConfiguredMethod() &&
79                  (suggestOptions == null || suggestOptions.isEmpty())) {
80              setRender(false);
81          }
82  
83          if (!isRender()) {
84              return;
85          }
86  
87          if (retrieveAllSuggestions) {
88              if (suggestOptions == null || suggestOptions.isEmpty()) {
89                  // execute query method to retrieve up front suggestions
90                  if (suggestQuery.hasConfiguredMethod()) {
91                      retrieveSuggestOptions(view);
92                  }
93              } else {
94                  suggestOptionsJsString = ScriptUtils.translateValue(suggestOptions);
95              }
96          } else {
97              // adjust from side on query field mapping to match parent fields path
98              InputField field = (InputField) parent;
99  
100             BindingInfo bindingInfo = field.getBindingInfo();
101             suggestQuery.updateQueryFieldMapping(bindingInfo);
102         }
103     }
104 
105     /**
106      * Invokes the configured query method and sets the returned method value as the suggest options or
107      * suggest options JS string
108      *
109      * @param view view instance the suggest belongs to, used to get the view helper service if needed
110      */
111     protected void retrieveSuggestOptions(View view) {
112         String queryMethodToCall = suggestQuery.getQueryMethodToCall();
113         MethodInvokerConfig queryMethodInvoker = suggestQuery.getQueryMethodInvokerConfig();
114 
115         if (queryMethodInvoker == null) {
116             queryMethodInvoker = new MethodInvokerConfig();
117         }
118 
119         // if method not set on invoker, use queryMethodToCall, note staticMethod could be set(don't know since
120         // there is not a getter), if so it will override the target method in prepare
121         if (StringUtils.isBlank(queryMethodInvoker.getTargetMethod())) {
122             queryMethodInvoker.setTargetMethod(queryMethodToCall);
123         }
124 
125         // if target class or object not set, use view helper service
126         if ((queryMethodInvoker.getTargetClass() == null) && (queryMethodInvoker.getTargetObject() == null)) {
127             queryMethodInvoker.setTargetObject(view.getViewHelperService());
128         }
129 
130         try {
131             queryMethodInvoker.prepare();
132 
133             Object methodResult = queryMethodInvoker.invoke();
134             if (methodResult instanceof String) {
135                 suggestOptionsJsString = (String) methodResult;
136             } else if (methodResult instanceof List) {
137                 suggestOptions = (List<Object>) methodResult;
138                 suggestOptionsJsString = ScriptUtils.translateValue(suggestOptions);
139             } else {
140                 throw new RuntimeException("Suggest query method did not return List<String> for suggestions");
141             }
142         } catch (Exception e) {
143             throw new RuntimeException("Unable to invoke query method: " + queryMethodInvoker.getTargetMethod(), e);
144         }
145     }
146 
147     /**
148      * Attribute query instance the will be executed to provide
149      * the suggest options
150      *
151      * @return AttributeQuery
152      */
153     @BeanTagAttribute(name = "suggestQuery", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
154     public AttributeQuery getSuggestQuery() {
155         return suggestQuery;
156     }
157 
158     /**
159      * Setter for the suggest attribute query
160      *
161      * @param suggestQuery
162      */
163     public void setSuggestQuery(AttributeQuery suggestQuery) {
164         this.suggestQuery = suggestQuery;
165     }
166 
167     /**
168      * Name of the property on the query result object that provides
169      * the options for the suggest, values from this field will be
170      * collected and sent back on the result to provide as suggest options
171      *
172      * @return String source property name
173      */
174     @BeanTagAttribute(name = "sourcePropertyName")
175     public String getSourcePropertyName() {
176         return sourcePropertyName;
177     }
178 
179     /**
180      * Setter for the source property name
181      *
182      * @param sourcePropertyName
183      */
184     public void setSourcePropertyName(String sourcePropertyName) {
185         this.sourcePropertyName = sourcePropertyName;
186     }
187 
188     /**
189      * When set to true the results of a query method will be used directly as the suggestions (
190      * it will not be assumed the method returns objects from which the source property name is then used
191      * to pull out the suggestions)
192      *
193      * <p>
194      * Note this is not supported for auto queries (only custom method queries). The query method can return
195      * a list of Strings which will be used for the suggestions, a list of object with 'label' and 'value' properties,
196      * or a custom object (if the plugin has been customized to handle the object)
197      * </p>
198      *
199      * @return boolean true if the query method results should be used as the suggestions, false to assume
200      *         objects are returned and suggestions are formed using the source property name
201      */
202     @BeanTagAttribute(name = "sourceQueryMethodResults")
203     public boolean isSourceQueryMethodResults() {
204         return sourceQueryMethodResults;
205     }
206 
207     /**
208      * Setter for the source query method results indicator
209      *
210      * @param sourceQueryMethodResults
211      */
212     public void setSourceQueryMethodResults(boolean sourceQueryMethodResults) {
213         this.sourceQueryMethodResults = sourceQueryMethodResults;
214     }
215 
216     /**
217      * Indicates whether all suggest options should be retrieved up front and provide to the suggest
218      * widget as options locally
219      *
220      * <p>
221      * Use this for a small list of options to improve performance. The query will be performed on the client
222      * to filter the provider options based on the users input instead of doing a query each time
223      * </p>
224      *
225      * <p>
226      * When a query method is configured and this option set to true the method will be invoked to set the
227      * options. The query method should not take any arguments and should return the suggestion options
228      * List or the JS String as a result. If a query method is not configured the suggest options can be
229      * set through configuration or a view helper method (for example a component finalize method)
230      * </p>
231      *
232      * @return boolean true to provide the suggest options initially, false to use ajax retrieval based on the
233      *         user's input
234      */
235     @BeanTagAttribute(name = "retrieveAllSuggestions")
236     public boolean isRetrieveAllSuggestions() {
237         return retrieveAllSuggestions;
238     }
239 
240     /**
241      * Setter for the retrieve all suggestions indicator
242      *
243      * @param retrieveAllSuggestions
244      */
245     public void setRetrieveAllSuggestions(boolean retrieveAllSuggestions) {
246         this.retrieveAllSuggestions = retrieveAllSuggestions;
247     }
248 
249     /**
250      * When {@link #isRetrieveAllSuggestions()} is true, this list provides the full list of suggestions
251      *
252      * <p>
253      * If a query method is configured that method will be invoked to populate this list, otherwise the
254      * list should be populated through configuration or the view helper
255      * </p>
256      *
257      * <p>
258      * The suggest options can either be a list of Strings, in which case the strings will be the suggested
259      * values. Or a list of objects. If the object does not have 'label' and 'value' properties, a custom render
260      * and select method must be provided
261      * </p>
262      *
263      * @return List<Object> list of suggest options
264      */
265     @BeanTagAttribute(name = "suggestOptions", type = BeanTagAttribute.AttributeType.LISTBEAN)
266     public List<Object> getSuggestOptions() {
267         return suggestOptions;
268     }
269 
270     /**
271      * Setter for the list of suggest options
272      *
273      * @param suggestOptions
274      */
275     public void setSuggestOptions(List<Object> suggestOptions) {
276         this.suggestOptions = suggestOptions;
277     }
278 
279     /**
280      * Returns the suggest options as a JS String (set by the framework from method invocation)
281      *
282      * @return String suggest options JS string
283      */
284     public String getSuggestOptionsJsString() {
285         if (StringUtils.isNotBlank(suggestOptionsJsString)) {
286             return this.suggestOptionsJsString;
287         }
288 
289         return "null";
290     }
291 }