1 /*
2 * Copyright 2007 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.container;
12
13 import org.apache.commons.lang.StringUtils;
14 import org.kuali.rice.core.api.mo.common.active.ImmutableInactivatable;
15 import org.kuali.rice.krad.uif.UifConstants;
16 import org.kuali.rice.krad.uif.UifParameters;
17 import org.kuali.rice.krad.uif.core.ActiveCollectionFilter;
18 import org.kuali.rice.krad.uif.core.BindingInfo;
19 import org.kuali.rice.krad.uif.core.CollectionFilter;
20 import org.kuali.rice.krad.uif.core.Component;
21 import org.kuali.rice.krad.uif.core.DataBinding;
22 import org.kuali.rice.krad.uif.field.ActionField;
23 import org.kuali.rice.krad.uif.field.AttributeField;
24 import org.kuali.rice.krad.uif.field.Field;
25 import org.kuali.rice.krad.uif.field.LabelField;
26 import org.kuali.rice.krad.uif.util.ComponentUtils;
27
28 import java.util.ArrayList;
29 import java.util.List;
30
31 /**
32 * Group that holds a collection of objects and configuration for presenting the
33 * collection in the UI. Supports functionality such as add line, line actions,
34 * and nested collections.
35 *
36 * <p>
37 * Note the standard header/footer can be used to give a header to the
38 * collection as a whole, or to provide actions that apply to the entire
39 * collection
40 * </p>
41 *
42 * <p>
43 * For binding purposes the binding path of each row field is indexed. The name
44 * property inherited from <code>ComponentBase</code> is used as the collection
45 * name. The collectionObjectClass property is used to lookup attributes from
46 * the data dictionary.
47 * </p>
48 *
49 * @author Kuali Rice Team (rice.collab@kuali.org)
50 */
51 public class CollectionGroup extends Group implements DataBinding {
52 private static final long serialVersionUID = -6496712566071542452L;
53
54 private Class<?> collectionObjectClass;
55
56 private String propertyName;
57 private BindingInfo bindingInfo;
58
59 private boolean renderAddLine;
60 private String addLinePropertyName;
61 private BindingInfo addLineBindingInfo;
62 private LabelField addLineLabelField;
63 private List<? extends Field> addLineFields;
64 private List<ActionField> addLineActionFields;
65
66 private boolean renderLineActions;
67 private List<ActionField> actionFields;
68
69 private boolean showInactive;
70 private CollectionFilter activeCollectionFilter;
71
72 private List<CollectionGroup> subCollections;
73
74 private CollectionGroupBuilder collectionGroupBuilder;
75
76 public CollectionGroup() {
77 renderAddLine = true;
78 renderLineActions = true;
79 showInactive = false;
80
81 actionFields = new ArrayList<ActionField>();
82 addLineFields = new ArrayList<Field>();
83 addLineActionFields = new ArrayList<ActionField>();
84 subCollections = new ArrayList<CollectionGroup>();
85 }
86
87 /**
88 * The following actions are performed:
89 *
90 * <ul>
91 * <li>Set fieldBindModelPath to the collection model path (since the fields
92 * have to belong to the same model as the collection)</li>
93 * <li>Set defaults for binding</li>
94 * <li>Default add line field list to groups items list</li>
95 * <li>Sets default active collection filter if not set</li>
96 * <li>Sets the dictionary entry (if blank) on each of the items to the
97 * collection class</li>
98 * </ul>
99 *
100 * @see org.kuali.rice.krad.uif.core.ComponentBase#performInitialization(org.kuali.rice.krad.uif.container.View)
101 */
102 @Override
103 public void performInitialization(View view) {
104 setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
105
106 super.performInitialization(view);
107
108 if (bindingInfo != null) {
109 bindingInfo.setDefaults(view, getPropertyName());
110 }
111
112 if (addLineBindingInfo != null) {
113 // add line binds to model property
114 if (StringUtils.isNotBlank(addLinePropertyName)) {
115 addLineBindingInfo.setDefaults(view, getPropertyName());
116 addLineBindingInfo.setBindingName(addLinePropertyName);
117 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
118 addLineBindingInfo.setBindByNamePrefix(getFieldBindByNamePrefix());
119 }
120 }
121 }
122
123 for (Component item : getItems()) {
124 if (item instanceof AttributeField) {
125 AttributeField field = (AttributeField) item;
126
127 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
128 field.setDictionaryObjectEntry(collectionObjectClass.getName());
129 }
130 }
131 }
132
133 if ((addLineFields == null) || addLineFields.isEmpty()) {
134 addLineFields = getItems();
135 }
136
137 // if active collection filter not set use default
138 if (this.activeCollectionFilter == null) {
139 activeCollectionFilter = new ActiveCollectionFilter();
140 }
141
142 // set static collection path on items
143 String collectionPath = "";
144 if (StringUtils.isNotBlank(getBindingInfo().getCollectionPath())) {
145 collectionPath += getBindingInfo().getCollectionPath() + ".";
146 }
147 if (StringUtils.isNotBlank(getBindingInfo().getBindByNamePrefix())) {
148 collectionPath += getBindingInfo().getBindByNamePrefix() + ".";
149 }
150 collectionPath += getBindingInfo().getBindingName();
151
152 List<AttributeField> collectionFields = ComponentUtils.getComponentsOfTypeDeep(getItems(), AttributeField.class);
153 for (AttributeField collectionField : collectionFields) {
154 collectionField.getBindingInfo().setCollectionPath(collectionPath);
155 }
156
157 for (CollectionGroup collectionGroup : getSubCollections()) {
158 collectionGroup.getBindingInfo().setCollectionPath(collectionPath);
159 view.getViewHelperService().performComponentInitialization(view, collectionGroup);
160 }
161
162 // add collection entry to abstract classes
163 if (!view.getAbstractTypeClasses().containsKey(collectionPath)) {
164 view.getAbstractTypeClasses().put(collectionPath, getCollectionObjectClass());
165 }
166
167 // initialize container items and sub-collections (since they are not in
168 // child list)
169 for (Component item : getItems()) {
170 view.getViewHelperService().performComponentInitialization(view, item);
171 }
172 }
173
174 /**
175 * Calls the configured <code>CollectionGroupBuilder</code> to build the
176 * necessary components based on the collection data
177 *
178 * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(org.kuali.rice.krad.uif.container.View,
179 * java.lang.Object)
180 */
181 @Override
182 public void performApplyModel(View view, Object model, Component parent) {
183 super.performApplyModel(view, model, parent);
184
185 pushCollectionGroupToReference();
186
187 performCollectionFiltering(view, model);
188
189 getCollectionGroupBuilder().build(view, model, this);
190
191 pushCollectionGroupToReference();
192 }
193
194 /**
195 * Sets a reference in the context map for all nested components to the collection group
196 * instance, and sets name as parameter for an action fields in the group
197 */
198 protected void pushCollectionGroupToReference() {
199 List<Component> components = this.getNestedComponents();
200
201 ComponentUtils
202 .pushObjectToContext(components, UifConstants.ContextVariableNames.COLLECTION_GROUP,
203 this);
204
205 List<ActionField> actionFields =
206 ComponentUtils.getComponentsOfTypeDeep(components, ActionField.class);
207 for (ActionField actionField : actionFields) {
208 actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
209 this.getBindingInfo().getBindingPath());
210 }
211 }
212
213 /**
214 * Performs any filtering necessary on the collection before building the collection fields
215 *
216 * <p>
217 * If showInactive is set to false and the collection line type implements <code>Inactivatable</code>,
218 * invokes the active collection filer
219 * </p>
220 *
221 * @param model - object containing the views data, from which the collection will be pulled
222 */
223 protected void performCollectionFiltering(View view, Object model) {
224 if (ImmutableInactivatable.class.isAssignableFrom(this.collectionObjectClass) && !showInactive) {
225 this.activeCollectionFilter.filter(view, model, this);
226 }
227 }
228
229 /**
230 * New collection lines are handled in the framework by maintaining a map on
231 * the form. The map contains as a key the collection name, and as value an
232 * instance of the collection type. An entry is created here for the
233 * collection represented by the <code>CollectionGroup</code> if an instance
234 * is not available (clearExistingLine will force a new instance). The given
235 * model must be a subclass of <code>UifFormBase</code> in order to find the
236 * Map.
237 *
238 * @param model
239 * - Model instance that contains the new collection lines Map
240 * @param clearExistingLine
241 * - boolean that indicates whether the line should be set to a
242 * new instance if it already exists
243 */
244 public void initializeNewCollectionLine(View view, Object model, CollectionGroup collectionGroup,
245 boolean clearExistingLine) {
246 getCollectionGroupBuilder().initializeNewCollectionLine(view, model, collectionGroup, clearExistingLine);
247 }
248
249 /**
250 * @see org.kuali.rice.krad.uif.container.ContainerBase#getNestedComponents()
251 */
252 @Override
253 public List<Component> getNestedComponents() {
254 List<Component> components = super.getNestedComponents();
255
256 components.add(addLineLabelField);
257 components.addAll(actionFields);
258 components.addAll(addLineActionFields);
259
260 // remove the containers items because we don't want them as children
261 // (they will become children of the layout manager as the rows are
262 // created)
263 // TODO: is this necessary?
264 for (Component item : getItems()) {
265 if (components.contains(item)) {
266 components.remove(item);
267 }
268 }
269
270 return components;
271 }
272
273 /**
274 * Object class the collection maintains. Used to get dictionary information
275 * in addition to creating new instances for the collection when necessary
276 *
277 * @return Class<?> collection object class
278 */
279 public Class<?> getCollectionObjectClass() {
280 return this.collectionObjectClass;
281 }
282
283 /**
284 * Setter for the collection object class
285 *
286 * @param collectionObjectClass
287 */
288 public void setCollectionObjectClass(Class<?> collectionObjectClass) {
289 this.collectionObjectClass = collectionObjectClass;
290 }
291
292 /**
293 * @see org.kuali.rice.krad.uif.core.DataBinding#getPropertyName()
294 */
295 public String getPropertyName() {
296 return this.propertyName;
297 }
298
299 /**
300 * Setter for the collections property name
301 *
302 * @param propertyName
303 */
304 public void setPropertyName(String propertyName) {
305 this.propertyName = propertyName;
306 }
307
308 /**
309 * Determines the binding path for the collection. Used to get the
310 * collection value from the model in addition to setting the binding path
311 * for the collection attributes
312 *
313 * @see org.kuali.rice.krad.uif.core.DataBinding#getBindingInfo()
314 */
315 public BindingInfo getBindingInfo() {
316 return this.bindingInfo;
317 }
318
319 /**
320 * Setter for the binding info instance
321 *
322 * @param bindingInfo
323 */
324 public void setBindingInfo(BindingInfo bindingInfo) {
325 this.bindingInfo = bindingInfo;
326 }
327
328 /**
329 * Action fields that should be rendered for each collection line. Example
330 * line action is the delete action
331 *
332 * @return List<ActionField> line action fields
333 */
334 public List<ActionField> getActionFields() {
335 return this.actionFields;
336 }
337
338 /**
339 * Setter for the line action fields list
340 *
341 * @param actionFields
342 */
343 public void setActionFields(List<ActionField> actionFields) {
344 this.actionFields = actionFields;
345 }
346
347 /**
348 * Indicates whether the action column for the collection should be rendered
349 *
350 * @return boolean true if the actions should be rendered, false if not
351 * @see #getActionFields()
352 */
353 public boolean isRenderLineActions() {
354 return this.renderLineActions;
355 }
356
357 /**
358 * Setter for the render line actions indicator
359 *
360 * @param renderLineActions
361 */
362 public void setRenderLineActions(boolean renderLineActions) {
363 this.renderLineActions = renderLineActions;
364 }
365
366 /**
367 * Indicates whether an add line should be rendered for the collection
368 *
369 * @return boolean true if add line should be rendered, false if it should
370 * not be
371 */
372 public boolean isRenderAddLine() {
373 return this.renderAddLine;
374 }
375
376 /**
377 * Setter for the render add line indicator
378 *
379 * @param renderAddLine
380 */
381 public void setRenderAddLine(boolean renderAddLine) {
382 this.renderAddLine = renderAddLine;
383 }
384
385 /**
386 * Convenience getter for the add line label field text. The text is used to
387 * label the add line when rendered and its placement depends on the
388 * <code>LayoutManager</code>.
389 * <p>
390 * For the <code>TableLayoutManager</code> the label appears in the sequence
391 * column to the left of the add line fields. For the
392 * <code>StackedLayoutManager</code> the label is placed into the group
393 * header for the line.
394 * </p>
395 *
396 * @return String add line label
397 */
398 public String getAddLineLabel() {
399 if (getAddLineLabelField() != null) {
400 return getAddLineLabelField().getLabelText();
401 }
402
403 return null;
404 }
405
406 /**
407 * Setter for the add line label text
408 *
409 * @param addLineLabel
410 */
411 public void setAddLineLabel(String addLineLabel) {
412 if (getAddLineLabelField() != null) {
413 getAddLineLabelField().setLabelText(addLineLabel);
414 }
415 }
416
417 /**
418 * <code>LabelField</code> instance for the add line label
419 *
420 * @return LabelField add line label field
421 * @see #getAddLineLabel()
422 */
423 public LabelField getAddLineLabelField() {
424 return this.addLineLabelField;
425 }
426
427 /**
428 * Setter for the <code>LabelField</code> instance for the add line label
429 *
430 * @param addLineLabelField
431 * @see #getAddLineLabel()
432 */
433 public void setAddLineLabelField(LabelField addLineLabelField) {
434 this.addLineLabelField = addLineLabelField;
435 }
436
437 /**
438 * Name of the property that contains an instance for the add line. If set
439 * this is used with the binding info to create the path to the add line.
440 * Can be left blank in which case the framework will manage the add line
441 * instance in a generic map.
442 *
443 * @return String add line property name
444 */
445 public String getAddLinePropertyName() {
446 return this.addLinePropertyName;
447 }
448
449 /**
450 * Setter for the add line property name
451 *
452 * @param addLinePropertyName
453 */
454 public void setAddLinePropertyName(String addLinePropertyName) {
455 this.addLinePropertyName = addLinePropertyName;
456 }
457
458 /**
459 * <code>BindingInfo</code> instance for the add line property used to
460 * determine the full binding path. If add line name given
461 * {@link #getAddLineLabel()} then it is set as the binding name on the
462 * binding info. Add line label and binding info are not required, in which
463 * case the framework will manage the new add line instances through a
464 * generic map (model must extend UifFormBase)
465 *
466 * @return BindingInfo add line binding info
467 */
468 public BindingInfo getAddLineBindingInfo() {
469 return this.addLineBindingInfo;
470 }
471
472 /**
473 * Setter for the add line binding info
474 *
475 * @param addLineBindingInfo
476 */
477 public void setAddLineBindingInfo(BindingInfo addLineBindingInfo) {
478 this.addLineBindingInfo = addLineBindingInfo;
479 }
480
481 /**
482 * List of <code>Field</code> instances that should be rendered for the
483 * collection add line (if enabled). If not set, the default group's items
484 * list will be used
485 *
486 * @return List<? extends Field> add line field list
487 */
488 public List<? extends Field> getAddLineFields() {
489 return this.addLineFields;
490 }
491
492 /**
493 * Setter for the add line field list
494 *
495 * @param addLineFields
496 */
497 public void setAddLineFields(List<? extends Field> addLineFields) {
498 this.addLineFields = addLineFields;
499 }
500
501 /**
502 * Action fields that should be rendered for the add line. This is generally
503 * the add action (button) but can be configured to contain additional
504 * actions
505 *
506 * @return List<ActionField> add line action fields
507 */
508 public List<ActionField> getAddLineActionFields() {
509 return this.addLineActionFields;
510 }
511
512 /**
513 * Setter for the add line action fields
514 *
515 * @param addLineActionFields
516 */
517 public void setAddLineActionFields(List<ActionField> addLineActionFields) {
518 this.addLineActionFields = addLineActionFields;
519 }
520
521 /**
522 * Indicates whether inactive collections lines should be displayed
523 *
524 * <p>
525 * Setting only applies when the collection line type implements the
526 * <code>Inactivatable</code> interface. If true and showInactive is
527 * set to false, the collection will be filtered to remove any items
528 * whose active status returns false
529 * </p>
530 *
531 * @return boolean true to show inactive records, false to not render inactive records
532 */
533 public boolean isShowInactive() {
534 return showInactive;
535 }
536
537 /**
538 * Setter for the show inactive indicator
539 *
540 * @param boolean show inactive
541 */
542 public void setShowInactive(boolean showInactive) {
543 this.showInactive = showInactive;
544 }
545
546 /**
547 * Collection filter instance for filtering the collection data when the
548 * showInactive flag is set to false
549 *
550 * @return CollectionFilter
551 */
552 public CollectionFilter getActiveCollectionFilter() {
553 return activeCollectionFilter;
554 }
555
556 /**
557 * Setter for the collection filter to use for filter inactive records from the
558 * collection
559 *
560 * @param activeCollectionFilter - CollectionFilter instance
561 */
562 public void setActiveCollectionFilter(CollectionFilter activeCollectionFilter) {
563 this.activeCollectionFilter = activeCollectionFilter;
564 }
565
566 /**
567 * List of <code>CollectionGroup</code> instances that are sub-collections
568 * of the collection represented by this collection group
569 *
570 * @return List<CollectionGroup> sub collections
571 */
572 public List<CollectionGroup> getSubCollections() {
573 return this.subCollections;
574 }
575
576 /**
577 * Setter for the sub collection list
578 *
579 * @param subCollections
580 */
581 public void setSubCollections(List<CollectionGroup> subCollections) {
582 this.subCollections = subCollections;
583 }
584
585 /**
586 * <code>CollectionGroupBuilder</code> instance that will build the
587 * components dynamically for the collection instance
588 *
589 * @return CollectionGroupBuilder instance
590 */
591 public CollectionGroupBuilder getCollectionGroupBuilder() {
592 if (this.collectionGroupBuilder == null) {
593 this.collectionGroupBuilder = new CollectionGroupBuilder();
594 }
595 return this.collectionGroupBuilder;
596 }
597
598 /**
599 * Setter for the collection group building instance
600 *
601 * @param collectionGroupBuilder
602 */
603 public void setCollectionGroupBuilder(CollectionGroupBuilder collectionGroupBuilder) {
604 this.collectionGroupBuilder = collectionGroupBuilder;
605 }
606
607 /**
608 * @see org.kuali.rice.krad.uif.container.ContainerBase#getItems()
609 */
610 @SuppressWarnings("unchecked")
611 @Override
612 public List<? extends Field> getItems() {
613 return (List<? extends Field>) super.getItems();
614 }
615
616 }