View Javadoc
1   /**
2    * Copyright 2014 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   *
15   * Created by venkat on 9/3/14
16   */
17  package org.kuali.student.cm.course.modifiers;
18  
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.UifPropertyPaths;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.container.CollectionGroup;
24  import org.kuali.rice.krad.uif.container.Group;
25  import org.kuali.rice.krad.uif.element.Header;
26  import org.kuali.rice.krad.uif.field.DataField;
27  import org.kuali.rice.krad.uif.field.DataFieldBase;
28  import org.kuali.rice.krad.uif.field.Field;
29  import org.kuali.rice.krad.uif.field.SpaceField;
30  import org.kuali.rice.krad.uif.layout.GridLayoutManager;
31  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
32  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
33  import org.kuali.rice.krad.uif.modifier.ComparableInfo;
34  import org.kuali.rice.krad.uif.modifier.CompareFieldCreateModifier;
35  import org.kuali.rice.krad.uif.util.ComponentFactory;
36  import org.kuali.rice.krad.uif.util.ComponentUtils;
37  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
38  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
39  import org.kuali.rice.krad.uif.view.View;
40  
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  
46  /**
47   * Modifier which runs at APPLY_MODEL lifecycle phase and it clones all the items in a group and sets the  binding path
48   * accordingly. Also, it sets the highlight CSS if data is different between the two group items.
49   *
50   * @author Kuali Student Team
51   */
52  public class CMCourseFieldCompareModifier extends CompareFieldCreateModifier{
53  
54      /**
55       * Properties which should not get highlighting or compare-to data.
56       */
57      private List<String> excludeProperties = new ArrayList<>();
58  
59      /**
60       * This method clones all the items in a group, sets proper binding path and mark for hightlight if the model
61       * data is different. This method is a direct copy of the base class method but has a lot of tweaks.
62       *
63       * @see CompareFieldCreateModifier#performModification(Object, org.kuali.rice.krad.uif.component.Component)
64       * @param model
65       * @param component
66       */
67      @Override
68      public void performModification(Object model, Component component) {
69          if ((component != null) && !(component instanceof Group)) {
70              throw new IllegalArgumentException(
71                      "Compare field initializer only support Group components, found type: " + component.getClass());
72          }
73  
74          if (component == null) {
75              return;
76          }
77  
78          Group group = (Group) component;
79  
80          // list to hold the generated compare items
81          List<Component> comparisonItems = new ArrayList<Component>();
82  
83          // sort comparables by their order property
84          List<ComparableInfo> groupComparables = (List<ComparableInfo>) ComponentUtils.sort(getComparables(),
85                  getDefaultOrderSequence());
86  
87          // evaluate expressions on comparables
88          Map<String, Object> context = new HashMap<String, Object>();
89  
90          View view = ViewLifecycle.getView();
91  
92          Map<String, Object> viewContext = view.getContext();
93          if (viewContext != null) {
94              context.putAll(view.getContext());
95          }
96  
97          context.put(UifConstants.ContextVariableNames.COMPONENT, component);
98  
99          ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
100 
101         for (ComparableInfo comparable : groupComparables) {
102             expressionEvaluator.evaluateExpressionsOnConfigurable(view, comparable, context);
103         }
104 
105         // generate compare header
106         if (isGenerateCompareHeaders()) {
107             // add space field for label column
108             SpaceField spaceField = ComponentFactory.getSpaceField();
109             comparisonItems.add(spaceField);
110 
111             for (ComparableInfo comparable : groupComparables) {
112                 Header compareHeaderField = ComponentUtils.copy(getHeaderFieldPrototype(), comparable.getIdSuffix());
113                 compareHeaderField.setHeaderText(comparable.getHeaderText());
114                 comparisonItems.add(compareHeaderField);
115             }
116 
117             // if group is using grid layout, make first row a header
118             if (group.getLayoutManager() instanceof GridLayoutManager) {
119                 ((GridLayoutManager) group.getLayoutManager()).setRenderFirstRowHeader(true);
120             }
121         }
122 
123         // find the comparable to use for comparing value changes (if
124         // configured)
125         boolean performValueChangeComparison = false;
126         String compareValueObjectBindingPath = null;
127         for (ComparableInfo comparable : groupComparables) {
128             if (comparable.isCompareToForValueChange()) {
129                 performValueChangeComparison = true;
130                 compareValueObjectBindingPath = comparable.getBindingObjectPath();
131             }
132         }
133 
134         // generate the compare items from the configured group
135         int index = 0;
136         List<String> rowCssClasses = new ArrayList<>();
137 
138         for (Component item : group.getItems()) {
139 
140             //  Determine if this property is in the exclude list.
141             boolean excluded = false;
142             if (item instanceof DataFieldBase) {
143                 String bindingPath = ((DataFieldBase) item).getBindingInfo().getBindingPath();
144                 if (StringUtils.isNotBlank(bindingPath)) {
145                     for (String exclude : getExcludeProperties()) {
146                         if (bindingPath.endsWith(exclude)) {
147                             excluded = true;
148                             break;
149                         }
150                     }
151                 }
152             }
153 
154             String rowCSS = "";
155             int defaultSuffix = 0;
156             boolean suppressLabel = false;
157 
158             for (ComparableInfo comparable : groupComparables) {
159                 String idSuffix = comparable.getIdSuffix();
160                 if (StringUtils.isBlank(idSuffix)) {
161                     idSuffix = UifConstants.IdSuffixes.COMPARE + defaultSuffix;
162                 }
163 
164                 Component compareItem = ComponentUtils.copy(item, idSuffix);
165 
166                 ComponentUtils.setComponentPropertyDeep(compareItem, UifPropertyPaths.BIND_OBJECT_PATH,
167                         comparable.getBindingObjectPath());
168 
169 
170                 List<CollectionGroup> collectionGroups = ViewLifecycleUtils.getElementsOfTypeDeep(compareItem, CollectionGroup.class);
171 
172                 for (CollectionGroup collectionGroup : collectionGroups){
173                     updateCollectionGroupBindingPath(collectionGroup,comparable.getBindingObjectPath());
174                 }
175 
176                 if (comparable.isReadOnly()) {
177                     compareItem.setReadOnly(true);
178                     if (compareItem.getPropertyExpressions().containsKey("readOnly")) {
179                         compareItem.getPropertyExpressions().remove("readOnly");
180                     }
181                 }
182 
183                 // label will be enabled for first comparable only
184                 if (suppressLabel && (compareItem instanceof Field)) {
185                     ((Field) compareItem).getFieldLabel().setRender(false);
186                 }
187 
188                 // Do value comparison if, among other things, this component hasn't been excluded.
189                 if (performValueChangeComparison && comparable.isHighlightValueChange() && !comparable
190                         .isCompareToForValueChange() && ! excluded) {
191                     boolean valueChanged = performValueComparison(group, compareItem, model,
192                             compareValueObjectBindingPath);
193 
194                     if (valueChanged){
195                         rowCSS = "cm-compare-highlighter";
196                     }
197                 }
198 
199                 //  If this is an excluded component then don't display the right-column/compare-to data.
200                 //  Typically it won't exist but if it does it won't be displayed.
201                 if (excluded) {
202                     item.setStyle("display: none;");
203                 }
204 
205                 comparisonItems.add(compareItem);
206 
207                 defaultSuffix++;
208 
209                 suppressLabel = true;
210             }
211 
212             rowCssClasses.add(rowCSS);
213 
214             index++;
215         }
216 
217         if (group.getLayoutManager() instanceof GridLayoutManager) {
218             ((GridLayoutManager) group.getLayoutManager()).setRowCssClasses(rowCssClasses);
219         }
220 
221         // update the group's list of components
222         group.setItems(comparisonItems);
223     }
224 
225     /**
226      * This method updates the binding path for all the items in a collection group and all its subcolletions
227      * recursively.
228      *
229      * @param collectionGroup
230      * @param bindingPath
231      */
232     protected void updateCollectionGroupBindingPath(CollectionGroup collectionGroup,String bindingPath){
233 
234         ComponentUtils.setComponentPropertyDeep(collectionGroup, UifPropertyPaths.BIND_OBJECT_PATH,bindingPath);
235         ComponentUtils.setComponentPropertyDeep(collectionGroup, "fieldBindingObjectPath",bindingPath);
236 
237         List<CollectionGroup> subCollection = collectionGroup.getSubCollections();
238         for (CollectionGroup subCollectionGroup1 : subCollection){
239             updateCollectionGroupBindingPath(subCollectionGroup1, bindingPath);
240         }
241 
242         List<? extends Component> dataFields = collectionGroup.getItems();
243         for (Component field : dataFields) {
244             ComponentUtils.setComponentPropertyDeep(field, UifPropertyPaths.BIND_OBJECT_PATH,bindingPath);
245             ComponentUtils.setComponentPropertyDeep(field, "fieldBindingObjectPath",bindingPath);
246             if (field instanceof DataField) {
247                 ((DataField)field).getBindingInfo().setBindingObjectPath(bindingPath);
248             }
249         }
250     }
251 
252     /**
253      * Copy of the base class implementation but we dont need setting 'showChangeIcon()' JS as the default
254      * implementation does.
255      *
256      * @param group
257      * @param compareItem
258      * @param model
259      * @param compareValueObjectBindingPath
260      * @return
261      */
262     @Override
263     protected boolean performValueComparison(Group group, Component compareItem, Object model,
264             String compareValueObjectBindingPath) {
265         // get any attribute fields for the item so we can compare the values
266         List<DataField> itemFields = ViewLifecycleUtils.getElementsOfTypeDeep(compareItem, DataField.class);
267         boolean valueChanged = false;
268         for (DataField field : itemFields) {
269             String fieldBindingPath = field.getBindingInfo().getBindingPath();
270             Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, fieldBindingPath);
271 
272             String compareBindingPath = StringUtils.replaceOnce(fieldBindingPath,
273                     field.getBindingInfo().getBindingObjectPath(), compareValueObjectBindingPath);
274             Object compareValue = ObjectPropertyUtils.getPropertyValue(model, compareBindingPath);
275 
276             if (!((fieldValue == null) && (compareValue == null))) {
277                 // if one is null then value changed
278                 if ((fieldValue == null) || (compareValue == null)) {
279                     valueChanged = true;
280                 } else {
281                     // both not null, compare values
282                     valueChanged = !fieldValue.equals(compareValue);
283                 }
284             }
285         }
286         return valueChanged;
287     }
288 
289     /**
290      * @see #setExcludeProperties(java.util.List)
291      * @return
292      */
293     public List<String> getExcludeProperties() {
294         return excludeProperties;
295     }
296 
297     /**
298      * List of fields to exclude for comparsion and marking the fields for hightlight.
299      * @param excludeProperties
300      */
301     public void setExcludeProperties(List<String> excludeProperties) {
302         this.excludeProperties = excludeProperties;
303     }
304 
305 }