View Javadoc
1   /**
2    * Copyright 2005-2015 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.component;
17  
18  import java.io.Serializable;
19  
20  import org.apache.commons.lang.StringUtils;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
23  import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.view.View;
26  
27  /**
28   * Provides binding configuration for an DataBinding component (attribute or
29   * collection)
30   *
31   * <p>
32   * From the binding configuration the binding path is determined (if not
33   * manually set) and used to set the path in the UI or to get the value from the
34   * model
35   * </p>
36   *
37   * @author Kuali Rice Team (rice.collab@kuali.org)
38   */
39  @BeanTag(name = "bindingInfo", parent = "Uif-BindingInfo")
40  public class BindingInfo extends UifDictionaryBeanBase implements Serializable {
41      private static final long serialVersionUID = -7389398061672136091L;
42  
43      private boolean bindToForm;
44      private boolean bindToMap;
45  
46      private String bindingName;
47      private String bindByNamePrefix;
48      private String bindingObjectPath;
49  
50      private String collectionPath;
51  
52      private String bindingPath;
53  
54      public BindingInfo() {
55          super();
56  
57          bindToForm = false;
58          bindToMap = false;
59      }
60  
61      /**
62       * Sets up some default binding properties based on the view configuration
63       * and the component's property name
64       *
65       * <p>
66       * Sets the bindingName (if not set) to the given property name, and if the
67       * binding object path has not been set uses the default binding object path
68       * setup for the view
69       * </p>
70       *
71       * @param view the view instance the component belongs to
72       * @param propertyName name of the property (relative to the parent object) the component binds to
73       */
74      public void setDefaults(View view, String propertyName) {
75          if (StringUtils.isBlank(bindingName)) {
76              bindingName = propertyName;
77          }
78  
79          if (StringUtils.isBlank(bindingObjectPath)) {
80              bindingObjectPath = view.getDefaultBindingObjectPath();
81          }
82      }
83  
84      /**
85       * Path to the property on the model the component binds to. Uses standard
86       * dot notation for nested properties. If the binding path was manually set
87       * it will be returned as it is, otherwise the path will be formed by using
88       * the binding object path and the bind prefix
89       *
90       * <p>
91       * e.g. Property name 'foo' on a model would have binding path "foo", while
92       * property name 'name' of the nested model property 'account' would have
93       * binding path "account.name"
94       * </p>
95       *
96       * @return binding path
97       */
98      @BeanTagAttribute
99      public String getBindingPath() {
100         if (StringUtils.isNotBlank(bindingPath)) {
101             return bindingPath;
102         }
103 
104         String formedBindingPath = getBindingPathPrefix();
105 
106         if (StringUtils.isNotBlank(bindingName)) {
107             if (bindToMap) {
108                 formedBindingPath += "[" + bindingName + "]";
109             } else {
110                 if (StringUtils.isNotBlank(formedBindingPath)) {
111                     formedBindingPath += ".";
112                 }
113                 formedBindingPath += bindingName;
114             }
115         }
116 
117         return formedBindingPath;
118     }
119 
120     /**
121      * Returns the binding prefix string that can be used to setup the binding
122      * on <code>DataBinding</code> components that are children of the component
123      * that contains the <code>BindingInfo</code>. The binding prefix is formed
124      * like the binding path but without including the object path
125      *
126      * @return binding prefix for nested components
127      */
128     public String getBindingPrefixForNested() {
129         String bindingPrefix = "";
130 
131         if (StringUtils.isNotBlank(bindByNamePrefix)) {
132             bindingPrefix = bindByNamePrefix;
133         }
134 
135         if (bindToMap) {
136             bindingPrefix += "[" + bindingName + "]";
137         } else {
138             if (StringUtils.isNotBlank(bindingPrefix)) {
139                 bindingPrefix += ".";
140             }
141             bindingPrefix += bindingName;
142         }
143 
144         return bindingPrefix;
145     }
146 
147     /**
148      * Returns the binding path that is formed by taking the binding configuration
149      * of this <code>BindingInfo</code> instance with the given property path as the
150      * binding name. This can be used to get the binding path when just a property
151      * name is given that is assumed to be on the same parent object of the field with
152      * the configured binding info
153      *
154      * <p>
155      * Special check is done for org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX prefix
156      * on the property name which indicates the property path is the full path and should
157      * not be adjusted. Also, if the property is prefixed with
158      * org.kuali.rice.krad.uif.UifConstants#FIELD_PATH_BIND_ADJUST_PREFIX, this indicates we should only append the
159      * binding object path
160      * </p>
161      *
162      * @param propertyPath path for property to return full binding path for
163      * @return full binding path
164      */
165     public String getPropertyAdjustedBindingPath(String propertyPath) {
166         if (propertyPath.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
167             propertyPath = StringUtils.removeStart(propertyPath, UifConstants.NO_BIND_ADJUST_PREFIX);
168 
169             return propertyPath;
170         }
171 
172         if (propertyPath.startsWith(UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX)) {
173             propertyPath = StringUtils.removeStart(propertyPath, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX);
174         }
175 
176         String formedBindingPath = getBindingPathPrefix();
177 
178         //KULRICE-13096 avoid duplicate property paths
179         if (propertyPath.startsWith(formedBindingPath)) {
180             return propertyPath;
181         }
182 
183         if (bindToMap) {
184             formedBindingPath += "[" + propertyPath + "]";
185         } else {
186             if (StringUtils.isNotBlank(formedBindingPath)) {
187                 formedBindingPath += ".";
188             }
189             formedBindingPath += propertyPath;
190         }
191 
192         return formedBindingPath;
193     }
194 
195     /**
196      * Returns the binding path prefix to affix before the binding path or property adjusted binding path.
197      *
198      * @return the binding path prefix to affix before a binding path.
199      */
200     public String getBindingPathPrefix() {
201         String bindingPathPrefix = "";
202 
203         if (!bindToForm && StringUtils.isNotBlank(bindingObjectPath)) {
204             bindingPathPrefix = bindingObjectPath;
205         }
206 
207         if (StringUtils.isNotBlank(bindByNamePrefix)) {
208             if (!bindByNamePrefix.startsWith("[") && StringUtils.isNotBlank(bindingPathPrefix)) {
209                 bindingPathPrefix += ".";
210             }
211 
212             bindingPathPrefix += bindByNamePrefix;
213         }
214 
215         return bindingPathPrefix;
216     }
217 
218     /**
219      * Helper method for adding a path to the binding prefix
220      *
221      * @param bindPrefix path to add
222      */
223     public void addToBindByNamePrefix(String bindPrefix) {
224         if (StringUtils.isNotBlank(bindByNamePrefix) && StringUtils.isNotBlank(bindPrefix)) {
225             bindByNamePrefix += "." + bindPrefix;
226         } else {
227             bindByNamePrefix = bindPrefix;
228         }
229     }
230 
231     /**
232      * Setter for the binding path. Can be left blank in which the path will be
233      * determined from the binding configuration
234      *
235      * @param bindingPath
236      */
237     public void setBindingPath(String bindingPath) {
238         this.bindingPath = bindingPath;
239     }
240 
241     /**
242      * Indicates whether the component binds directly to the form (that is its
243      * bindingName gives a property available through the form), or whether is
244      * binds through a nested form object. If bindToForm is false, it is assumed
245      * the component binds to the object given by the form property whose path
246      * is configured by bindingObjectPath.
247      *
248      * @return true if component binds directly to form, false if it
249      *         binds to a nested object
250      */
251     @BeanTagAttribute
252     public boolean isBindToForm() {
253         return this.bindToForm;
254     }
255 
256     /**
257      * Setter for the bind to form indicator
258      *
259      * @param bindToForm
260      */
261     public void setBindToForm(boolean bindToForm) {
262         this.bindToForm = bindToForm;
263     }
264 
265     /**
266      * Gives the name of the property that the component binds to. The name can
267      * be nested but not the full path, just from the parent object or in the
268      * case of binding directly to the form from the form object
269      *
270      * <p>
271      * If blank this will be set from the name field of the component
272      * </p>
273      *
274      * @return name of the bind property
275      */
276     @BeanTagAttribute
277     public String getBindingName() {
278         return this.bindingName;
279     }
280 
281     /**
282      * Setter for the bind property name
283      *
284      * @param bindingName
285      */
286     public void setBindingName(String bindingName) {
287         this.bindingName = bindingName;
288     }
289 
290     /**
291      * Prefix that will be used to form the binding path from the component
292      * name. Typically used for nested collection properties
293      *
294      * @return binding prefix
295      */
296     @BeanTagAttribute
297     public String getBindByNamePrefix() {
298         return this.bindByNamePrefix;
299     }
300 
301     /**
302      * Setter for the prefix to use for forming the binding path by name
303      *
304      * @param bindByNamePrefix
305      */
306     public void setBindByNamePrefix(String bindByNamePrefix) {
307         this.bindByNamePrefix = bindByNamePrefix;
308     }
309 
310     /**
311      * If field is part of a collection field, gives path to collection
312      *
313      * <p>
314      * This is used for metadata purposes when getting finding the attribute
315      * definition from the dictionary and is not used in building the final
316      * binding path
317      * </p>
318      *
319      * @return path to collection
320      */
321     public String getCollectionPath() {
322         return this.collectionPath;
323     }
324 
325     /**
326      * Setter for the field's collection path (if part of a collection)
327      *
328      * @param collectionPath
329      */
330     public void setCollectionPath(String collectionPath) {
331         this.collectionPath = collectionPath;
332     }
333 
334     /**
335      * For attribute fields that do not belong to the default form object (given
336      * by the view), this field specifies the path to the object (on the form)
337      * the attribute does belong to.
338      *
339      * <p>
340      * e.g. Say we have an attribute field with property name 'number', that
341      * belongs to the object given by the 'account' property on the form. The
342      * form object path would therefore be set to 'account'. If the property
343      * belonged to the object given by the 'document.header' property of the
344      * form, the binding object path would be set to 'document.header'. Note if
345      * the binding object path is not set for an attribute field (or any
346      * <code>DataBinding</code> component), the binding object path configured
347      * on the <code>View</code> will be used (unless bindToForm is set to true,
348      * where is assumed the property is directly available from the form).
349      * </p>
350      *
351      * @return path to object from form
352      */
353     @BeanTagAttribute
354     public String getBindingObjectPath() {
355         return this.bindingObjectPath;
356     }
357 
358     /**
359      * Setter for the object path on the form
360      *
361      * @param bindingObjectPath
362      */
363     public void setBindingObjectPath(String bindingObjectPath) {
364         this.bindingObjectPath = bindingObjectPath;
365     }
366 
367     /**
368      * Indicates whether the parent object for the property that we are binding
369      * to is a Map. If true the binding path will be adjusted to use the map key
370      * syntax
371      *
372      * @return true if the property binds to a map, false if it does not
373      */
374     @BeanTagAttribute
375     public boolean isBindToMap() {
376         return this.bindToMap;
377     }
378 
379     /**
380      * Setter for the bind to map indicator
381      *
382      * @param bindToMap
383      */
384     public void setBindToMap(boolean bindToMap) {
385         this.bindToMap = bindToMap;
386     }
387 }