View Javadoc

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