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 }