View Javadoc

1   /**
2    * Copyright 2005-2013 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.modifier;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
21  import org.kuali.rice.krad.uif.UifConstants;
22  import org.kuali.rice.krad.uif.UifPropertyPaths;
23  import org.kuali.rice.krad.uif.container.Group;
24  import org.kuali.rice.krad.uif.field.DataField;
25  import org.kuali.rice.krad.uif.view.View;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.field.HeaderField;
28  import org.kuali.rice.krad.uif.util.ComponentUtils;
29  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
30  
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  /**
39   * Generates <code>Field</code> instances to produce a comparison view among
40   * objects of the same type
41   *
42   * <p>
43   * Modifier is initialized with a List of <code>ComparableInfo</code> instances.
44   * For each comparable info, a copy of the configured group field is made and
45   * adjusted to the binding object path for the comparable. The comparison fields
46   * are ordered based on the configured order property of the comparable. In
47   * addition, a <code>HeaderField<code> can be generated to label each group
48   * of comparison fields.
49   * </p>
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public class CompareFieldCreateModifier extends ComponentModifierBase {
54      private static final Logger LOG = Logger.getLogger(CompareFieldCreateModifier.class);
55  
56      private static final long serialVersionUID = -6285531580512330188L;
57  
58      private int defaultOrderSequence;
59      private boolean generateCompareHeaders;
60  
61      private HeaderField headerFieldPrototype;
62      private List<ComparableInfo> comparables;
63  
64      public CompareFieldCreateModifier() {
65          defaultOrderSequence = 1;
66          generateCompareHeaders = true;
67  
68          comparables = new ArrayList<ComparableInfo>();
69      }
70  
71      /**
72       * Calls <code>ViewHelperService</code> to initialize the header field prototype
73       *
74       * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#performInitialization(org.kuali.rice.krad.uif.view.View,
75       *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
76       */
77      @Override
78      public void performInitialization(View view, Object model, Component component) {
79          super.performInitialization(view, model, component);
80  
81          if (headerFieldPrototype != null) {
82              view.getViewHelperService().performComponentInitialization(view, model, headerFieldPrototype);
83          }
84      }
85  
86      /**
87       * Generates the comparison fields
88       *
89       * <p>
90       * First the configured List of <code>ComparableInfo</code> instances are
91       * sorted based on their order property. Then if generateCompareHeaders is
92       * set to true, a <code>HeaderField</code> is created for each comparable
93       * using the headerFieldPrototype and the headerText given by the
94       * comparable. Finally for each field configured on the <code>Group</code>,
95       * a corresponding comparison field is generated for each comparable and
96       * adjusted to the binding object path given by the comparable in addition
97       * to suffixing the id and setting the readOnly property
98       * </p>
99       *
100      * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#performModification(org.kuali.rice.krad.uif.view.View,
101      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
102      */
103     @SuppressWarnings("unchecked")
104     @Override
105     public void performModification(View view, Object model, Component component) {
106         if ((component != null) && !(component instanceof Group)) {
107             throw new IllegalArgumentException(
108                     "Compare field initializer only support Group components, found type: " + component.getClass());
109         }
110 
111         if (component == null) {
112             return;
113         }
114 
115         // list to hold the generated compare items
116         List<Component> comparisonItems = new ArrayList<Component>();
117 
118         // sort comparables by their order property
119         List<ComparableInfo> groupComparables = (List<ComparableInfo>) ComponentUtils.sort(comparables,
120                 defaultOrderSequence);
121 
122         // evaluate expressions on comparables
123         Map<String, Object> context = new HashMap<String, Object>();
124         context.putAll(view.getContext());
125         context.put(UifConstants.ContextVariableNames.COMPONENT, component);
126 
127         for (ComparableInfo comparable : groupComparables) {
128             KRADServiceLocatorWeb.getExpressionEvaluatorService().evaluateObjectExpressions(comparable, model,
129                     context);
130         }
131 
132         // generate compare header
133         if (isGenerateCompareHeaders()) {
134             for (ComparableInfo comparable : groupComparables) {
135                 HeaderField compareHeaderField = ComponentUtils.copy(headerFieldPrototype, comparable.getIdSuffix());
136                 compareHeaderField.setHeaderText(comparable.getHeaderText());
137 
138                 comparisonItems.add(compareHeaderField);
139             }
140         }
141 
142         // find the comparable to use for comparing value changes (if
143         // configured)
144         boolean performValueChangeComparison = false;
145         String compareValueObjectBindingPath = null;
146         for (ComparableInfo comparable : groupComparables) {
147             if (comparable.isCompareToForValueChange()) {
148                 performValueChangeComparison = true;
149                 compareValueObjectBindingPath = comparable.getBindingObjectPath();
150             }
151         }
152 
153         // generate the compare items from the configured group
154         Group group = (Group) component;
155         for (Component item : group.getItems()) {
156             int defaultSuffix = 0;
157             for (ComparableInfo comparable : groupComparables) {
158                 String idSuffix = comparable.getIdSuffix();
159                 if (StringUtils.isBlank(idSuffix)) {
160                     idSuffix = UifConstants.IdSuffixes.COMPARE + defaultSuffix;
161                 }
162 
163                 Component compareItem = ComponentUtils.copy(item, idSuffix);
164 
165                 ComponentUtils.setComponentPropertyDeep(compareItem, UifPropertyPaths.BIND_OBJECT_PATH,
166                         comparable.getBindingObjectPath());
167                 if (comparable.isReadOnly()) {
168                     compareItem.setReadOnly(true);
169                     if (compareItem.getPropertyExpressions().containsKey("readOnly")) {
170                         compareItem.getPropertyExpressions().remove("readOnly");
171                     }
172                 }
173 
174                 // do value comparison
175                 if (performValueChangeComparison && comparable.isHighlightValueChange() && !comparable
176                         .isCompareToForValueChange()) {
177                     performValueComparison(group, compareItem, model, compareValueObjectBindingPath);
178                 }
179 
180                 comparisonItems.add(compareItem);
181                 defaultSuffix++;
182             }
183         }
184 
185         // update the group's list of components
186         group.setItems(comparisonItems);
187     }
188 
189     /**
190      * For each attribute field in the compare item, retrieves the field value and compares against the value for the
191      * main comparable. If the value is different, adds script to the field on ready event to add the change icon to
192      * the field and the containing group header
193      *
194      * @param group - group that contains the item and whose header will be highlighted for changes
195      * @param compareItem - the compare item being generated and to pull attribute fields from
196      * @param model - object containing the data
197      * @param compareValueObjectBindingPath - object path for the comparison item
198      */
199     protected void performValueComparison(Group group, Component compareItem, Object model,
200             String compareValueObjectBindingPath) {
201         // get any attribute fields for the item so we can compare the values
202         List<DataField> itemFields = ComponentUtils.getComponentsOfTypeDeep(compareItem, DataField.class);
203         for (DataField field : itemFields) {
204             String fieldBindingPath = field.getBindingInfo().getBindingPath();
205             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, fieldBindingPath);
206 
207             String compareBindingPath = StringUtils.replaceOnce(fieldBindingPath,
208                     field.getBindingInfo().getBindingObjectPath(), compareValueObjectBindingPath);
209             Object compareValue = ObjectPropertyUtils.getPropertyValue(model, compareBindingPath);
210 
211             boolean valueChanged = false;
212             if (!((fieldValue == null) && (compareValue == null))) {
213                 // if one is null then value changed
214                 if ((fieldValue == null) || (compareValue == null)) {
215                     valueChanged = true;
216                 } else {
217                     // both not null, compare values
218                     valueChanged = !fieldValue.equals(compareValue);
219                 }
220             }
221 
222             // add script to show change icon
223             if (valueChanged) {
224                 String onReadyScript = "showChangeIcon('" + field.getId() + "');";
225 
226                 // add icon to group header
227                 Component headerField = group.getHeader();
228                 onReadyScript += "showChangeIconOnHeader('" + headerField.getId() + "');";
229 
230                 field.setOnDocumentReadyScript(onReadyScript);
231             }
232 
233             // TODO: add script for value changed?
234         }
235     }
236 
237     /**
238      * Generates an id suffix for the comparable item
239      *
240      * <p>
241      * If the idSuffix to use if configured on the <code>ComparableInfo</code>
242      * it will be used, else the given integer index will be used with an
243      * underscore
244      * </p>
245      *
246      * @param comparable - comparable info to check for id suffix
247      * @param index - sequence integer
248      * @return String id suffix
249      * @see org.kuali.rice.krad.uif.modifier.ComparableInfo.getIdSuffix()
250      */
251     protected String getIdSuffix(ComparableInfo comparable, int index) {
252         String idSuffix = comparable.getIdSuffix();
253         if (StringUtils.isBlank(idSuffix)) {
254             idSuffix = "_" + index;
255         }
256 
257         return idSuffix;
258     }
259 
260     /**
261      * @see org.kuali.rice.krad.uif.modifier.ComponentModifier#getSupportedComponents()
262      */
263     @Override
264     public Set<Class<? extends Component>> getSupportedComponents() {
265         Set<Class<? extends Component>> components = new HashSet<Class<? extends Component>>();
266         components.add(Group.class);
267 
268         return components;
269     }
270 
271     /**
272      * @see org.kuali.rice.krad.uif.modifier.ComponentModifierBase#getComponentPrototypes()
273      */
274     public List<Component> getComponentPrototypes() {
275         List<Component> components = new ArrayList<Component>();
276 
277         components.add(headerFieldPrototype);
278 
279         return components;
280     }
281 
282     /**
283      * Indicates the starting integer sequence value to use for
284      * <code>ComparableInfo</code> instances that do not have the order property
285      * set
286      *
287      * @return int default sequence starting value
288      */
289     public int getDefaultOrderSequence() {
290         return this.defaultOrderSequence;
291     }
292 
293     /**
294      * Setter for the default sequence starting value
295      *
296      * @param defaultOrderSequence
297      */
298     public void setDefaultOrderSequence(int defaultOrderSequence) {
299         this.defaultOrderSequence = defaultOrderSequence;
300     }
301 
302     /**
303      * Indicates whether a <code>HeaderField</code> should be created for each
304      * group of comparison fields
305      *
306      * <p>
307      * If set to true, for each group of comparison fields a header field will
308      * be created using the headerFieldPrototype configured on the modifier with
309      * the headerText property of the comparable
310      * </p>
311      *
312      * @return boolean true if the headers should be created, false if no
313      *         headers should be created
314      */
315     public boolean isGenerateCompareHeaders() {
316         return this.generateCompareHeaders;
317     }
318 
319     /**
320      * Setter for the generate comparison headers indicator
321      *
322      * @param generateCompareHeaders
323      */
324     public void setGenerateCompareHeaders(boolean generateCompareHeaders) {
325         this.generateCompareHeaders = generateCompareHeaders;
326     }
327 
328     /**
329      * Prototype instance to use for creating the <code>HeaderField</code> for
330      * each group of comparison fields (if generateCompareHeaders is true)
331      *
332      * @return HeaderField header field prototype
333      */
334     public HeaderField getHeaderFieldPrototype() {
335         return this.headerFieldPrototype;
336     }
337 
338     /**
339      * Setter for the header field prototype
340      *
341      * @param headerFieldPrototype
342      */
343     public void setHeaderFieldPrototype(HeaderField headerFieldPrototype) {
344         this.headerFieldPrototype = headerFieldPrototype;
345     }
346 
347     /**
348      * List of <code>ComparableInfo</code> instances the compare fields should
349      * be generated for
350      *
351      * <p>
352      * For each comparable, a copy of the fields configured for the
353      * <code>Group</code> will be created for the comparison view
354      * </p>
355      *
356      * @return List<ComparableInfo> comparables to generate fields for
357      */
358     public List<ComparableInfo> getComparables() {
359         return this.comparables;
360     }
361 
362     /**
363      * Setter for the list of comparable info instances
364      *
365      * @param comparables
366      */
367     public void setComparables(List<ComparableInfo> comparables) {
368         this.comparables = comparables;
369     }
370 
371 }