001 /**
002 * Copyright 2005-2011 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.uif.container;
017
018 import java.util.ArrayList;
019 import java.util.List;
020
021 import org.apache.commons.lang.StringUtils;
022 import org.kuali.rice.krad.uif.UifConstants;
023 import org.kuali.rice.krad.uif.UifParameters;
024 import org.kuali.rice.krad.uif.component.BindingInfo;
025 import org.kuali.rice.krad.uif.component.Component;
026 import org.kuali.rice.krad.uif.component.DataBinding;
027 import org.kuali.rice.krad.uif.field.ActionField;
028 import org.kuali.rice.krad.uif.field.DataField;
029 import org.kuali.rice.krad.uif.field.Field;
030 import org.kuali.rice.krad.uif.field.LabelField;
031 import org.kuali.rice.krad.uif.util.ComponentUtils;
032 import org.kuali.rice.krad.uif.view.View;
033 import org.kuali.rice.krad.uif.widget.QuickFinder;
034
035 /**
036 * Group that holds a collection of objects and configuration for presenting the
037 * collection in the UI. Supports functionality such as add line, line actions,
038 * and nested collections.
039 *
040 * <p>
041 * Note the standard header/footer can be used to give a header to the
042 * collection as a whole, or to provide actions that apply to the entire
043 * collection
044 * </p>
045 *
046 * <p>
047 * For binding purposes the binding path of each row field is indexed. The name
048 * property inherited from <code>ComponentBase</code> is used as the collection
049 * name. The collectionObjectClass property is used to lookup attributes from
050 * the data dictionary.
051 * </p>
052 *
053 * @author Kuali Rice Team (rice.collab@kuali.org)
054 */
055 public class CollectionGroup extends Group implements DataBinding {
056 private static final long serialVersionUID = -6496712566071542452L;
057
058 private Class<?> collectionObjectClass;
059
060 private String propertyName;
061 private BindingInfo bindingInfo;
062
063 private boolean renderAddLine;
064 private String addLinePropertyName;
065 private BindingInfo addLineBindingInfo;
066 private LabelField addLineLabelField;
067 private List<? extends Component> addLineFields;
068 private List<ActionField> addLineActionFields;
069
070 private boolean renderLineActions;
071 private List<ActionField> actionFields;
072
073 private boolean renderSelectField;
074 private String selectPropertyName;
075
076 private QuickFinder collectionLookup;
077
078 private boolean showHideInactiveButton;
079 private boolean showInactive;
080 private CollectionFilter activeCollectionFilter;
081 private List<CollectionFilter> filters;
082
083 private List<CollectionGroup> subCollections;
084 private String subCollectionSuffix;
085
086 private CollectionGroupBuilder collectionGroupBuilder;
087 private CollectionGroupSecurity collectionGroupSecurity;
088
089 public CollectionGroup() {
090 renderAddLine = true;
091 renderLineActions = true;
092 showInactive = false;
093 showHideInactiveButton = true;
094 renderSelectField = false;
095
096 collectionGroupSecurity = new CollectionGroupSecurity();
097
098 filters = new ArrayList<CollectionFilter>();
099 actionFields = new ArrayList<ActionField>();
100 addLineFields = new ArrayList<Field>();
101 addLineActionFields = new ArrayList<ActionField>();
102 subCollections = new ArrayList<CollectionGroup>();
103 }
104
105 /**
106 * The following actions are performed:
107 *
108 * <ul>
109 * <li>Set fieldBindModelPath to the collection model path (since the fields
110 * have to belong to the same model as the collection)</li>
111 * <li>Set defaults for binding</li>
112 * <li>Default add line field list to groups items list</li>
113 * <li>Sets default active collection filter if not set</li>
114 * <li>Sets the dictionary entry (if blank) on each of the items to the
115 * collection class</li>
116 * </ul>
117 *
118 * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View,
119 * java.lang.Object)
120 */
121 @Override
122 public void performInitialization(View view, Object model) {
123 setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
124
125 super.performInitialization(view, model);
126
127 if (bindingInfo != null) {
128 bindingInfo.setDefaults(view, getPropertyName());
129 }
130
131 if (addLineBindingInfo != null) {
132 // add line binds to model property
133 if (StringUtils.isNotBlank(addLinePropertyName)) {
134 addLineBindingInfo.setDefaults(view, getPropertyName());
135 addLineBindingInfo.setBindingName(addLinePropertyName);
136 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
137 addLineBindingInfo.setBindByNamePrefix(getFieldBindByNamePrefix());
138 }
139 }
140 }
141
142 for (Component item : getItems()) {
143 if (item instanceof DataField) {
144 DataField field = (DataField) item;
145
146 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
147 field.setDictionaryObjectEntry(collectionObjectClass.getName());
148 }
149 }
150 }
151
152 if ((addLineFields == null) || addLineFields.isEmpty()) {
153 addLineFields = getItems();
154 }
155
156 // if active collection filter not set use default
157 if (this.activeCollectionFilter == null) {
158 activeCollectionFilter = new ActiveCollectionFilter();
159 }
160
161 // set static collection path on items
162 String collectionPath = "";
163 if (StringUtils.isNotBlank(getBindingInfo().getCollectionPath())) {
164 collectionPath += getBindingInfo().getCollectionPath() + ".";
165 }
166 if (StringUtils.isNotBlank(getBindingInfo().getBindByNamePrefix())) {
167 collectionPath += getBindingInfo().getBindByNamePrefix() + ".";
168 }
169 collectionPath += getBindingInfo().getBindingName();
170
171 List<DataField> collectionFields = ComponentUtils.getComponentsOfTypeDeep(getItems(), DataField.class);
172 for (DataField collectionField : collectionFields) {
173 collectionField.getBindingInfo().setCollectionPath(collectionPath);
174 }
175
176 // add collection entry to abstract classes
177 if (!view.getAbstractTypeClasses().containsKey(collectionPath)) {
178 view.getAbstractTypeClasses().put(collectionPath, getCollectionObjectClass());
179 }
180
181 // initialize container items and sub-collections (since they are not in
182 // child list)
183 for (Component item : getItems()) {
184 view.getViewHelperService().performComponentInitialization(view, model, item);
185 }
186
187 for (CollectionGroup collectionGroup : getSubCollections()) {
188 collectionGroup.getBindingInfo().setCollectionPath(collectionPath);
189 view.getViewHelperService().performComponentInitialization(view, model, collectionGroup);
190 }
191 }
192
193 /**
194 * Calls the configured <code>CollectionGroupBuilder</code> to build the
195 * necessary components based on the collection data
196 *
197 * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(org.kuali.rice.krad.uif.view.View,
198 * java.lang.Object, org.kuali.rice.krad.uif.component.Component)
199 */
200 @Override
201 public void performApplyModel(View view, Object model, Component parent) {
202 super.performApplyModel(view, model, parent);
203
204 pushCollectionGroupToReference();
205
206 // if rendering the collection group, build out the lines
207 if (isRender()) {
208 getCollectionGroupBuilder().build(view, model, this);
209 }
210
211 // TODO: is this necessary to call again?
212 pushCollectionGroupToReference();
213 }
214
215 /**
216 * Sets a reference in the context map for all nested components to the collection group
217 * instance, and sets name as parameter for an action fields in the group
218 */
219 protected void pushCollectionGroupToReference() {
220 List<Component> components = this.getComponentsForLifecycle();
221
222 ComponentUtils.pushObjectToContext(components, UifConstants.ContextVariableNames.COLLECTION_GROUP, this);
223
224 List<ActionField> actionFields = ComponentUtils.getComponentsOfTypeDeep(components, ActionField.class);
225 for (ActionField actionField : actionFields) {
226 actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
227 this.getBindingInfo().getBindingPath());
228 }
229 }
230
231 /**
232 * New collection lines are handled in the framework by maintaining a map on
233 * the form. The map contains as a key the collection name, and as value an
234 * instance of the collection type. An entry is created here for the
235 * collection represented by the <code>CollectionGroup</code> if an instance
236 * is not available (clearExistingLine will force a new instance). The given
237 * model must be a subclass of <code>UifFormBase</code> in order to find the
238 * Map.
239 *
240 * @param model - Model instance that contains the new collection lines Map
241 * @param clearExistingLine - 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#getComponentsForLifecycle()
251 */
252 @Override
253 public List<Component> getComponentsForLifecycle() {
254 List<Component> components = super.getComponentsForLifecycle();
255
256 components.add(addLineLabelField);
257 components.add(collectionLookup);
258
259 // remove the containers items because we don't want them as children
260 // (they will become children of the layout manager as the rows are
261 // created)
262 for (Component item : getItems()) {
263 if (components.contains(item)) {
264 components.remove(item);
265 }
266 }
267
268 return components;
269 }
270
271 /**
272 * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
273 */
274 @Override
275 public List<Component> getComponentPrototypes() {
276 List<Component> components = super.getComponentPrototypes();
277
278 components.addAll(actionFields);
279 components.addAll(addLineActionFields);
280 components.addAll(getItems());
281 components.addAll(getSubCollections());
282
283 return components;
284 }
285
286 /**
287 * Object class the collection maintains. Used to get dictionary information
288 * in addition to creating new instances for the collection when necessary
289 *
290 * @return Class<?> collection object class
291 */
292 public Class<?> getCollectionObjectClass() {
293 return this.collectionObjectClass;
294 }
295
296 /**
297 * Setter for the collection object class
298 *
299 * @param collectionObjectClass
300 */
301 public void setCollectionObjectClass(Class<?> collectionObjectClass) {
302 this.collectionObjectClass = collectionObjectClass;
303 }
304
305 /**
306 * @see org.kuali.rice.krad.uif.component.DataBinding#getPropertyName()
307 */
308 public String getPropertyName() {
309 return this.propertyName;
310 }
311
312 /**
313 * Setter for the collections property name
314 *
315 * @param propertyName
316 */
317 public void setPropertyName(String propertyName) {
318 this.propertyName = propertyName;
319 }
320
321 /**
322 * Determines the binding path for the collection. Used to get the
323 * collection value from the model in addition to setting the binding path
324 * for the collection attributes
325 *
326 * @see org.kuali.rice.krad.uif.component.DataBinding#getBindingInfo()
327 */
328 public BindingInfo getBindingInfo() {
329 return this.bindingInfo;
330 }
331
332 /**
333 * Setter for the binding info instance
334 *
335 * @param bindingInfo
336 */
337 public void setBindingInfo(BindingInfo bindingInfo) {
338 this.bindingInfo = bindingInfo;
339 }
340
341 /**
342 * Action fields that should be rendered for each collection line. Example
343 * line action is the delete action
344 *
345 * @return List<ActionField> line action fields
346 */
347 public List<ActionField> getActionFields() {
348 return this.actionFields;
349 }
350
351 /**
352 * Setter for the line action fields list
353 *
354 * @param actionFields
355 */
356 public void setActionFields(List<ActionField> actionFields) {
357 this.actionFields = actionFields;
358 }
359
360 /**
361 * Indicates whether the action column for the collection should be rendered
362 *
363 * @return boolean true if the actions should be rendered, false if not
364 * @see #getActionFields()
365 */
366 public boolean isRenderLineActions() {
367 return this.renderLineActions;
368 }
369
370 /**
371 * Setter for the render line actions indicator
372 *
373 * @param renderLineActions
374 */
375 public void setRenderLineActions(boolean renderLineActions) {
376 this.renderLineActions = renderLineActions;
377 }
378
379 /**
380 * Indicates whether an add line should be rendered for the collection
381 *
382 * @return boolean true if add line should be rendered, false if it should
383 * not be
384 */
385 public boolean isRenderAddLine() {
386 return this.renderAddLine;
387 }
388
389 /**
390 * Setter for the render add line indicator
391 *
392 * @param renderAddLine
393 */
394 public void setRenderAddLine(boolean renderAddLine) {
395 this.renderAddLine = renderAddLine;
396 }
397
398 /**
399 * Convenience getter for the add line label field text. The text is used to
400 * label the add line when rendered and its placement depends on the
401 * <code>LayoutManager</code>.
402 * <p>
403 * For the <code>TableLayoutManager</code> the label appears in the sequence
404 * column to the left of the add line fields. For the
405 * <code>StackedLayoutManager</code> the label is placed into the group
406 * header for the line.
407 * </p>
408 *
409 * @return String add line label
410 */
411 public String getAddLineLabel() {
412 if (getAddLineLabelField() != null) {
413 return getAddLineLabelField().getLabelText();
414 }
415
416 return null;
417 }
418
419 /**
420 * Setter for the add line label text
421 *
422 * @param addLineLabel
423 */
424 public void setAddLineLabel(String addLineLabel) {
425 if (getAddLineLabelField() != null) {
426 getAddLineLabelField().setLabelText(addLineLabel);
427 }
428 }
429
430 /**
431 * <code>LabelField</code> instance for the add line label
432 *
433 * @return LabelField add line label field
434 * @see #getAddLineLabel()
435 */
436 public LabelField getAddLineLabelField() {
437 return this.addLineLabelField;
438 }
439
440 /**
441 * Setter for the <code>LabelField</code> instance for the add line label
442 *
443 * @param addLineLabelField
444 * @see #getAddLineLabel()
445 */
446 public void setAddLineLabelField(LabelField addLineLabelField) {
447 this.addLineLabelField = addLineLabelField;
448 }
449
450 /**
451 * Name of the property that contains an instance for the add line. If set
452 * this is used with the binding info to create the path to the add line.
453 * Can be left blank in which case the framework will manage the add line
454 * instance in a generic map.
455 *
456 * @return String add line property name
457 */
458 public String getAddLinePropertyName() {
459 return this.addLinePropertyName;
460 }
461
462 /**
463 * Setter for the add line property name
464 *
465 * @param addLinePropertyName
466 */
467 public void setAddLinePropertyName(String addLinePropertyName) {
468 this.addLinePropertyName = addLinePropertyName;
469 }
470
471 /**
472 * <code>BindingInfo</code> instance for the add line property used to
473 * determine the full binding path. If add line name given
474 * {@link #getAddLineLabel()} then it is set as the binding name on the
475 * binding info. Add line label and binding info are not required, in which
476 * case the framework will manage the new add line instances through a
477 * generic map (model must extend UifFormBase)
478 *
479 * @return BindingInfo add line binding info
480 */
481 public BindingInfo getAddLineBindingInfo() {
482 return this.addLineBindingInfo;
483 }
484
485 /**
486 * Setter for the add line binding info
487 *
488 * @param addLineBindingInfo
489 */
490 public void setAddLineBindingInfo(BindingInfo addLineBindingInfo) {
491 this.addLineBindingInfo = addLineBindingInfo;
492 }
493
494 /**
495 * List of <code>Component</code> instances that should be rendered for the
496 * collection add line (if enabled). If not set, the default group's items
497 * list will be used
498 *
499 * @return List<? extends Component> add line field list
500 */
501 public List<? extends Component> getAddLineFields() {
502 return this.addLineFields;
503 }
504
505 /**
506 * Setter for the add line field list
507 *
508 * @param addLineFields
509 */
510 public void setAddLineFields(List<? extends Component> addLineFields) {
511 this.addLineFields = addLineFields;
512 }
513
514 /**
515 * Action fields that should be rendered for the add line. This is generally
516 * the add action (button) but can be configured to contain additional
517 * actions
518 *
519 * @return List<ActionField> add line action fields
520 */
521 public List<ActionField> getAddLineActionFields() {
522 return this.addLineActionFields;
523 }
524
525 /**
526 * Setter for the add line action fields
527 *
528 * @param addLineActionFields
529 */
530 public void setAddLineActionFields(List<ActionField> addLineActionFields) {
531 this.addLineActionFields = addLineActionFields;
532 }
533
534 /**
535 * Indicates whether lines of the collection group should be selected by rendering a
536 * field for each line that will allow selection
537 *
538 * <p>
539 * For example, having the select field enabled could allow selecting multiple lines from a search
540 * to return (multi-value lookup)
541 * </p>
542 *
543 * @return boolean true if select field should be rendered, false if not
544 */
545 public boolean isRenderSelectField() {
546 return renderSelectField;
547 }
548
549 /**
550 * Setter for the render selected field indicator
551 *
552 * @param renderSelectField
553 */
554 public void setRenderSelectField(boolean renderSelectField) {
555 this.renderSelectField = renderSelectField;
556 }
557
558 /**
559 * When {@link #isRenderSelectField()} is true, gives the name of the property the select field
560 * should bind to
561 *
562 * <p>
563 * Note if no prefix is given in the property name, such as 'form.', it is assumed the property is
564 * contained on the collection line. In this case the binding path to the collection line will be
565 * appended. In other cases, it is assumed the property is a list or set of String that will hold the
566 * selected identifier strings
567 * </p>
568 *
569 * <p>
570 * This property is not required. If not the set the framework will use a property contained on
571 * <code>UifFormBase</code>
572 * </p>
573 *
574 * @return String property name for select field
575 */
576 public String getSelectPropertyName() {
577 return selectPropertyName;
578 }
579
580 /**
581 * Setter for the property name that will bind to the select field
582 *
583 * @param selectPropertyName
584 */
585 public void setSelectPropertyName(String selectPropertyName) {
586 this.selectPropertyName = selectPropertyName;
587 }
588
589 /**
590 * Instance of the <code>QuickFinder</code> widget that configures a multi-value lookup for the collection
591 *
592 * <p>
593 * If the collection lookup is enabled (by the render property of the quick finder), {@link
594 * #getCollectionObjectClass()} will be used as the data object class for the lookup (if not set). Field
595 * conversions need to be set as usual and will be applied for each line returned
596 * </p>
597 *
598 * @return QuickFinder instance configured for the collection lookup
599 */
600 public QuickFinder getCollectionLookup() {
601 return collectionLookup;
602 }
603
604 /**
605 * Setter for the collection lookup quickfinder instance
606 *
607 * @param collectionLookup
608 */
609 public void setCollectionLookup(QuickFinder collectionLookup) {
610 this.collectionLookup = collectionLookup;
611 }
612
613 /**
614 * Indicates whether inactive collections lines should be displayed
615 *
616 * <p>
617 * Setting only applies when the collection line type implements the
618 * <code>Inactivatable</code> interface. If true and showInactive is
619 * set to false, the collection will be filtered to remove any items
620 * whose active status returns false
621 * </p>
622 *
623 * @return boolean true to show inactive records, false to not render inactive records
624 */
625 public boolean isShowInactive() {
626 return showInactive;
627 }
628
629 /**
630 * Setter for the show inactive indicator
631 *
632 * @param showInactive boolean show inactive
633 */
634 public void setShowInactive(boolean showInactive) {
635 this.showInactive = showInactive;
636 }
637
638 /**
639 * Collection filter instance for filtering the collection data when the
640 * showInactive flag is set to false
641 *
642 * @return CollectionFilter
643 */
644 public CollectionFilter getActiveCollectionFilter() {
645 return activeCollectionFilter;
646 }
647
648 /**
649 * Setter for the collection filter to use for filter inactive records from the
650 * collection
651 *
652 * @param activeCollectionFilter - CollectionFilter instance
653 */
654 public void setActiveCollectionFilter(CollectionFilter activeCollectionFilter) {
655 this.activeCollectionFilter = activeCollectionFilter;
656 }
657
658 /**
659 * List of {@link CollectionFilter} instances that should be invoked to filter the collection before
660 * displaying
661 *
662 * @return List<CollectionFilter>
663 */
664 public List<CollectionFilter> getFilters() {
665 return filters;
666 }
667
668 /**
669 * Setter for the List of collection filters for which the collection will be filtered against
670 *
671 * @param filters
672 */
673 public void setFilters(List<CollectionFilter> filters) {
674 this.filters = filters;
675 }
676
677 /**
678 * List of <code>CollectionGroup</code> instances that are sub-collections
679 * of the collection represented by this collection group
680 *
681 * @return List<CollectionGroup> sub collections
682 */
683 public List<CollectionGroup> getSubCollections() {
684 return this.subCollections;
685 }
686
687 /**
688 * Setter for the sub collection list
689 *
690 * @param subCollections
691 */
692 public void setSubCollections(List<CollectionGroup> subCollections) {
693 this.subCollections = subCollections;
694 }
695
696 /**
697 * Suffix for IDs that identifies the collection line the sub-collection belongs to
698 *
699 * <p>
700 * Built by the framework as the collection lines are being generated
701 * </p>
702 *
703 * @return String id suffix for sub-collection
704 */
705 public String getSubCollectionSuffix() {
706 return subCollectionSuffix;
707 }
708
709 /**
710 * Setter for the sub-collection suffix (used by framework, should not be
711 * set in configuration)
712 *
713 * @param subCollectionSuffix
714 */
715 public void setSubCollectionSuffix(String subCollectionSuffix) {
716 this.subCollectionSuffix = subCollectionSuffix;
717 }
718
719 /**
720 * Collection Security object that indicates what authorization (permissions) exist for the collection
721 *
722 * @return CollectionGroupSecurity instance
723 */
724 @Override
725 public CollectionGroupSecurity getComponentSecurity() {
726 return collectionGroupSecurity;
727 }
728
729 /**
730 * Setter for the collection groups security object
731 *
732 * @param collectionGroupSecurity
733 */
734 public void setComponentSecurity(CollectionGroupSecurity collectionGroupSecurity) {
735 this.collectionGroupSecurity = collectionGroupSecurity;
736 }
737
738 /**
739 * <code>CollectionGroupBuilder</code> instance that will build the
740 * components dynamically for the collection instance
741 *
742 * @return CollectionGroupBuilder instance
743 */
744 public CollectionGroupBuilder getCollectionGroupBuilder() {
745 if (this.collectionGroupBuilder == null) {
746 this.collectionGroupBuilder = new CollectionGroupBuilder();
747 }
748 return this.collectionGroupBuilder;
749 }
750
751 /**
752 * Setter for the collection group building instance
753 *
754 * @param collectionGroupBuilder
755 */
756 public void setCollectionGroupBuilder(CollectionGroupBuilder collectionGroupBuilder) {
757 this.collectionGroupBuilder = collectionGroupBuilder;
758 }
759
760 /**
761 * @param showHideInactiveButton the showHideInactiveButton to set
762 */
763 public void setShowHideInactiveButton(boolean showHideInactiveButton) {
764 this.showHideInactiveButton = showHideInactiveButton;
765 }
766
767 /**
768 * @return the showHideInactiveButton
769 */
770 public boolean isShowHideInactiveButton() {
771 return showHideInactiveButton;
772 }
773
774 }