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    }