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.component;
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.datadictionary.uif.UifDictionaryBeanBase;
022 import org.kuali.rice.krad.uif.UifConstants;
023 import org.kuali.rice.krad.uif.view.View;
024 import org.kuali.rice.krad.util.ObjectUtils;
025
026 import java.io.Serializable;
027
028 /**
029 * Provides binding configuration for an DataBinding component (attribute or
030 * collection)
031 *
032 * <p>
033 * From the binding configuration the binding path is determined (if not
034 * manually set) and used to set the path in the UI or to get the value from the
035 * model
036 * </p>
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040 @BeanTag(name = "bindingInfo-bean", parent = "Uif-BindingInfo")
041 public class BindingInfo extends UifDictionaryBeanBase implements Serializable {
042 private static final long serialVersionUID = -7389398061672136091L;
043
044 private boolean bindToForm;
045 private boolean bindToMap;
046
047 private String bindingName;
048 private String bindByNamePrefix;
049 private String bindingObjectPath;
050
051 private String collectionPath;
052
053 private String bindingPath;
054
055 public BindingInfo() {
056 super();
057
058 bindToForm = false;
059 bindToMap = false;
060 }
061
062 /**
063 * Sets up some default binding properties based on the view configuration
064 * and the component's property name
065 *
066 * <p>
067 * Sets the bindingName (if not set) to the given property name, and if the
068 * binding object path has not been set uses the default binding object path
069 * setup for the view
070 * </p>
071 *
072 * @param view
073 * - the view instance the component belongs to
074 * @param propertyName
075 * - name of the property (relative to the parent object) the
076 * component binds to
077 */
078 public void setDefaults(View view, String propertyName) {
079 if (StringUtils.isBlank(bindingName)) {
080 bindingName = propertyName;
081 }
082
083 if (StringUtils.isBlank(bindingObjectPath)) {
084 bindingObjectPath = view.getDefaultBindingObjectPath();
085 }
086 }
087
088 /**
089 * Path to the property on the model the component binds to. Uses standard
090 * dot notation for nested properties. If the binding path was manually set
091 * it will be returned as it is, otherwise the path will be formed by using
092 * the binding object path and the bind prefix
093 *
094 * <p>
095 * e.g. Property name 'foo' on a model would have binding path "foo", while
096 * property name 'name' of the nested model property 'account' would have
097 * binding path "account.name"
098 * </p>
099 *
100 * @return String binding path
101 */
102 @BeanTagAttribute(name="bindingPath")
103 public String getBindingPath() {
104 if (StringUtils.isNotBlank(bindingPath)) {
105 return bindingPath;
106 }
107
108 String formedBindingPath = "";
109
110 if (!bindToForm && StringUtils.isNotBlank(bindingObjectPath)) {
111 formedBindingPath = bindingObjectPath;
112 }
113
114 if (StringUtils.isNotBlank(bindByNamePrefix)) {
115 if (!bindByNamePrefix.startsWith("[") && StringUtils.isNotBlank(formedBindingPath)) {
116 formedBindingPath += ".";
117 }
118 formedBindingPath += bindByNamePrefix;
119 }
120
121 if (bindToMap) {
122 formedBindingPath += "[" + bindingName + "]";
123 } else {
124 if (StringUtils.isNotBlank(formedBindingPath)) {
125 formedBindingPath += ".";
126 }
127 formedBindingPath += bindingName;
128 }
129
130 return formedBindingPath;
131 }
132
133 /**
134 * Returns the binding prefix string that can be used to setup the binding
135 * on <code>DataBinding</code> components that are children of the component
136 * that contains the <code>BindingInfo</code>. The binding prefix is formed
137 * like the binding path but without including the object path
138 *
139 * @return String binding prefix for nested components
140 */
141 public String getBindingPrefixForNested() {
142 String bindingPrefix = "";
143
144 if (StringUtils.isNotBlank(bindByNamePrefix)) {
145 bindingPrefix = bindByNamePrefix;
146 }
147
148 if (bindToMap) {
149 bindingPrefix += "['" + bindingName + "']";
150 } else {
151 if (StringUtils.isNotBlank(bindingPrefix)) {
152 bindingPrefix += ".";
153 }
154 bindingPrefix += bindingName;
155 }
156
157 return bindingPrefix;
158 }
159
160 /**
161 * Returns the binding path that is formed by taking the binding configuration
162 * of this <code>BindingInfo</code> instance with the given property path as the
163 * binding name. This can be used to get the binding path when just a property
164 * name is given that is assumed to be on the same parent object of the field with
165 * the configured binding info
166 *
167 * <p>
168 * Special check is done for org.kuali.rice.krad.uif.UifConstants#NO_BIND_ADJUST_PREFIX prefix
169 * on the property name which indicates the property path is the full path and should
170 * not be adjusted. Also, if the property is prefixed with
171 * org.kuali.rice.krad.uif.UifConstants#FIELD_PATH_BIND_ADJUST_PREFIX, this indicates we should only append the
172 * binding object path
173 * </p>
174 *
175 * @param propertyPath - path for property to return full binding path for
176 * @return String full binding path
177 */
178 public String getPropertyAdjustedBindingPath(String propertyPath) {
179 if (propertyPath.startsWith(UifConstants.NO_BIND_ADJUST_PREFIX)) {
180 propertyPath = StringUtils.removeStart(propertyPath, UifConstants.NO_BIND_ADJUST_PREFIX);
181 return propertyPath;
182 }
183
184 BindingInfo bindingInfoCopy = (BindingInfo) ObjectUtils.deepCopy(this);
185
186 // clear the path if explicitly set
187 bindingInfoCopy.setBindingPath("");
188
189 if (propertyPath.startsWith(UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX)) {
190 bindingInfoCopy.setBindByNamePrefix("");
191 propertyPath = StringUtils.removeStart(propertyPath, UifConstants.FIELD_PATH_BIND_ADJUST_PREFIX);
192 }
193 bindingInfoCopy.setBindingName(propertyPath);
194
195 return bindingInfoCopy.getBindingPath();
196 }
197
198 /**
199 * Helper method for adding a path to the binding prefix
200 *
201 * @param bindPrefix - path to add
202 */
203 public void addToBindByNamePrefix(String bindPrefix) {
204 if (StringUtils.isNotBlank(bindByNamePrefix) && StringUtils.isNotBlank(bindPrefix)) {
205 bindByNamePrefix += "." + bindPrefix;
206 } else {
207 bindByNamePrefix = bindPrefix;
208 }
209 }
210
211 /**
212 * Setter for the binding path. Can be left blank in which the path will be
213 * determined from the binding configuration
214 *
215 * @param bindingPath
216 */
217 public void setBindingPath(String bindingPath) {
218 this.bindingPath = bindingPath;
219 }
220
221 /**
222 * Indicates whether the component binds directly to the form (that is its
223 * bindingName gives a property available through the form), or whether is
224 * binds through a nested form object. If bindToForm is false, it is assumed
225 * the component binds to the object given by the form property whose path
226 * is configured by bindingObjectPath.
227 *
228 * @return boolean true if component binds directly to form, false if it
229 * binds to a nested object
230 */
231 @BeanTagAttribute(name="bindToForm")
232 public boolean isBindToForm() {
233 return this.bindToForm;
234 }
235
236 /**
237 * Setter for the bind to form indicator
238 *
239 * @param bindToForm
240 */
241 public void setBindToForm(boolean bindToForm) {
242 this.bindToForm = bindToForm;
243 }
244
245 /**
246 * Gives the name of the property that the component binds to. The name can
247 * be nested but not the full path, just from the parent object or in the
248 * case of binding directly to the form from the form object
249 *
250 * <p>
251 * If blank this will be set from the name field of the component
252 * </p>
253 *
254 * @return String name of the bind property
255 */
256 @BeanTagAttribute(name="bindingName")
257 public String getBindingName() {
258 return this.bindingName;
259 }
260
261 /**
262 * Setter for the bind property name
263 *
264 * @param bindingName
265 */
266 public void setBindingName(String bindingName) {
267 this.bindingName = bindingName;
268 }
269
270 /**
271 * Prefix that will be used to form the binding path from the component
272 * name. Typically used for nested collection properties
273 *
274 * @return String binding prefix
275 */
276 @BeanTagAttribute(name="bindByNamePrefix")
277 public String getBindByNamePrefix() {
278 return this.bindByNamePrefix;
279 }
280
281 /**
282 * Setter for the prefix to use for forming the binding path by name
283 *
284 * @param bindByNamePrefix
285 */
286 public void setBindByNamePrefix(String bindByNamePrefix) {
287 this.bindByNamePrefix = bindByNamePrefix;
288 }
289
290 /**
291 * If field is part of a collection field, gives path to collection
292 *
293 * <p>
294 * This is used for metadata purposes when getting finding the attribute
295 * definition from the dictionary and is not used in building the final
296 * binding path
297 * </p>
298 *
299 * @return String path to collection
300 */
301 public String getCollectionPath() {
302 return this.collectionPath;
303 }
304
305 /**
306 * Setter for the field's collection path (if part of a collection)
307 *
308 * @param collectionPath
309 */
310 public void setCollectionPath(String collectionPath) {
311 this.collectionPath = collectionPath;
312 }
313
314 /**
315 * For attribute fields that do not belong to the default form object (given
316 * by the view), this field specifies the path to the object (on the form)
317 * the attribute does belong to.
318 *
319 * <p>
320 * e.g. Say we have an attribute field with property name 'number', that
321 * belongs to the object given by the 'account' property on the form. The
322 * form object path would therefore be set to 'account'. If the property
323 * belonged to the object given by the 'document.header' property of the
324 * form, the binding object path would be set to 'document.header'. Note if
325 * the binding object path is not set for an attribute field (or any
326 * <code>DataBinding</code> component), the binding object path configured
327 * on the <code>View</code> will be used (unless bindToForm is set to true,
328 * where is assumed the property is directly available from the form).
329 * </p>
330 *
331 * @return String path to object from form
332 */
333 @BeanTagAttribute(name="bindingObjectPath")
334 public String getBindingObjectPath() {
335 return this.bindingObjectPath;
336 }
337
338 /**
339 * Setter for the object path on the form
340 *
341 * @param bindingObjectPath
342 */
343 public void setBindingObjectPath(String bindingObjectPath) {
344 this.bindingObjectPath = bindingObjectPath;
345 }
346
347 /**
348 * Indicates whether the parent object for the property that we are binding
349 * to is a Map. If true the binding path will be adjusted to use the map key
350 * syntax
351 *
352 * @return boolean true if the property binds to a map, false if it does not
353 */
354 @BeanTagAttribute(name="bindToMap")
355 public boolean isBindToMap() {
356 return this.bindToMap;
357 }
358
359 /**
360 * Setter for the bind to map indicator
361 *
362 * @param bindToMap
363 */
364 public void setBindToMap(boolean bindToMap) {
365 this.bindToMap = bindToMap;
366 }
367
368 }