Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
CompareFieldCreateModifier |
|
| 2.7333333333333334;2.733 |
1 | /** | |
2 | * Copyright 2005-2011 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 | 0 | 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 | 0 | public CompareFieldCreateModifier() { |
65 | 0 | defaultOrderSequence = 1; |
66 | 0 | generateCompareHeaders = true; |
67 | ||
68 | 0 | comparables = new ArrayList<ComparableInfo>(); |
69 | 0 | } |
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 | 0 | super.performInitialization(view, model, component); |
80 | ||
81 | 0 | if (headerFieldPrototype != null) { |
82 | 0 | view.getViewHelperService().performComponentInitialization(view, model, headerFieldPrototype); |
83 | } | |
84 | 0 | } |
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 | 0 | if ((component != null) && !(component instanceof Group)) { |
107 | 0 | throw new IllegalArgumentException( |
108 | "Compare field initializer only support Group components, found type: " + component.getClass()); | |
109 | } | |
110 | ||
111 | 0 | if (component == null) { |
112 | 0 | return; |
113 | } | |
114 | ||
115 | // list to hold the generated compare items | |
116 | 0 | List<Component> comparisonItems = new ArrayList<Component>(); |
117 | ||
118 | // sort comparables by their order property | |
119 | 0 | List<ComparableInfo> groupComparables = (List<ComparableInfo>) ComponentUtils.sort(comparables, |
120 | defaultOrderSequence); | |
121 | ||
122 | // evaluate expressions on comparables | |
123 | 0 | Map<String, Object> context = new HashMap<String, Object>(); |
124 | 0 | context.putAll(view.getContext()); |
125 | 0 | context.put(UifConstants.ContextVariableNames.COMPONENT, component); |
126 | ||
127 | 0 | for (ComparableInfo comparable : groupComparables) { |
128 | 0 | KRADServiceLocatorWeb.getExpressionEvaluatorService().evaluateObjectExpressions(comparable, model, |
129 | context); | |
130 | } | |
131 | ||
132 | // generate compare header | |
133 | 0 | if (isGenerateCompareHeaders()) { |
134 | 0 | for (ComparableInfo comparable : groupComparables) { |
135 | 0 | HeaderField compareHeaderField = ComponentUtils.copy(headerFieldPrototype, comparable.getIdSuffix()); |
136 | 0 | compareHeaderField.setHeaderText(comparable.getHeaderText()); |
137 | ||
138 | 0 | comparisonItems.add(compareHeaderField); |
139 | 0 | } |
140 | } | |
141 | ||
142 | // find the comparable to use for comparing value changes (if | |
143 | // configured) | |
144 | 0 | boolean performValueChangeComparison = false; |
145 | 0 | String compareValueObjectBindingPath = null; |
146 | 0 | for (ComparableInfo comparable : groupComparables) { |
147 | 0 | if (comparable.isCompareToForValueChange()) { |
148 | 0 | performValueChangeComparison = true; |
149 | 0 | compareValueObjectBindingPath = comparable.getBindingObjectPath(); |
150 | } | |
151 | } | |
152 | ||
153 | // generate the compare items from the configured group | |
154 | 0 | Group group = (Group) component; |
155 | 0 | for (Component item : group.getItems()) { |
156 | 0 | int defaultSuffix = 0; |
157 | 0 | for (ComparableInfo comparable : groupComparables) { |
158 | 0 | String idSuffix = comparable.getIdSuffix(); |
159 | 0 | if (StringUtils.isBlank(idSuffix)) { |
160 | 0 | idSuffix = UifConstants.IdSuffixes.COMPARE + defaultSuffix; |
161 | } | |
162 | ||
163 | 0 | Component compareItem = ComponentUtils.copy(item, idSuffix); |
164 | ||
165 | 0 | ComponentUtils.setComponentPropertyDeep(compareItem, UifPropertyPaths.BIND_OBJECT_PATH, |
166 | comparable.getBindingObjectPath()); | |
167 | 0 | if (comparable.isReadOnly()) { |
168 | 0 | compareItem.setReadOnly(true); |
169 | 0 | if (compareItem.getPropertyExpressions().containsKey("readOnly")) { |
170 | 0 | compareItem.getPropertyExpressions().remove("readOnly"); |
171 | } | |
172 | } | |
173 | ||
174 | // do value comparison | |
175 | 0 | if (performValueChangeComparison && comparable.isHighlightValueChange() && !comparable |
176 | .isCompareToForValueChange()) { | |
177 | 0 | performValueComparison(group, compareItem, model, compareValueObjectBindingPath); |
178 | } | |
179 | ||
180 | 0 | comparisonItems.add(compareItem); |
181 | 0 | defaultSuffix++; |
182 | 0 | } |
183 | 0 | } |
184 | ||
185 | // update the group's list of components | |
186 | 0 | group.setItems(comparisonItems); |
187 | 0 | } |
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 | 0 | List<DataField> itemFields = ComponentUtils.getComponentsOfTypeDeep(compareItem, DataField.class); |
203 | 0 | for (DataField field : itemFields) { |
204 | 0 | String fieldBindingPath = field.getBindingInfo().getBindingPath(); |
205 | 0 | Object fieldValue = ObjectPropertyUtils.getPropertyValue(model, fieldBindingPath); |
206 | ||
207 | 0 | String compareBindingPath = StringUtils.replaceOnce(fieldBindingPath, |
208 | field.getBindingInfo().getBindingObjectPath(), compareValueObjectBindingPath); | |
209 | 0 | Object compareValue = ObjectPropertyUtils.getPropertyValue(model, compareBindingPath); |
210 | ||
211 | 0 | boolean valueChanged = false; |
212 | 0 | if (!((fieldValue == null) && (compareValue == null))) { |
213 | // if one is null then value changed | |
214 | 0 | if ((fieldValue == null) || (compareValue == null)) { |
215 | 0 | valueChanged = true; |
216 | } else { | |
217 | // both not null, compare values | |
218 | 0 | valueChanged = !fieldValue.equals(compareValue); |
219 | } | |
220 | } | |
221 | ||
222 | // add script to show change icon | |
223 | 0 | if (valueChanged) { |
224 | 0 | String onReadyScript = "showChangeIcon('" + field.getId() + "');"; |
225 | ||
226 | // add icon to group header | |
227 | 0 | Component headerField = group.getHeader(); |
228 | 0 | onReadyScript += "showChangeIconOnHeader('" + headerField.getId() + "');"; |
229 | ||
230 | 0 | field.setOnDocumentReadyScript(onReadyScript); |
231 | } | |
232 | ||
233 | // TODO: add script for value changed? | |
234 | 0 | } |
235 | 0 | } |
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 | 0 | String idSuffix = comparable.getIdSuffix(); |
253 | 0 | if (StringUtils.isBlank(idSuffix)) { |
254 | 0 | idSuffix = "_" + index; |
255 | } | |
256 | ||
257 | 0 | 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 | 0 | Set<Class<? extends Component>> components = new HashSet<Class<? extends Component>>(); |
266 | 0 | components.add(Group.class); |
267 | ||
268 | 0 | return components; |
269 | } | |
270 | ||
271 | /** | |
272 | * @see org.kuali.rice.krad.uif.modifier.ComponentModifierBase#getComponentPrototypes() | |
273 | */ | |
274 | public List<Component> getComponentPrototypes() { | |
275 | 0 | List<Component> components = new ArrayList<Component>(); |
276 | ||
277 | 0 | components.add(headerFieldPrototype); |
278 | ||
279 | 0 | 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 | 0 | 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 | 0 | this.defaultOrderSequence = defaultOrderSequence; |
300 | 0 | } |
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 | 0 | 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 | 0 | this.generateCompareHeaders = generateCompareHeaders; |
326 | 0 | } |
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 | 0 | 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 | 0 | this.headerFieldPrototype = headerFieldPrototype; |
345 | 0 | } |
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 | 0 | 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 | 0 | this.comparables = comparables; |
369 | 0 | } |
370 | ||
371 | } |