001 /** 002 * Copyright 2005-2012 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 org.apache.commons.collections.ListUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.mo.common.active.Inactivatable; 021 import org.kuali.rice.kim.api.identity.Person; 022 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 023 import org.kuali.rice.krad.uif.UifConstants; 024 import org.kuali.rice.krad.uif.UifParameters; 025 import org.kuali.rice.krad.uif.UifPropertyPaths; 026 import org.kuali.rice.krad.uif.component.Component; 027 import org.kuali.rice.krad.uif.component.ComponentSecurity; 028 import org.kuali.rice.krad.uif.control.Control; 029 import org.kuali.rice.krad.uif.component.DataBinding; 030 import org.kuali.rice.krad.uif.field.ActionField; 031 import org.kuali.rice.krad.uif.field.InputField; 032 import org.kuali.rice.krad.uif.field.Field; 033 import org.kuali.rice.krad.uif.field.FieldGroup; 034 import org.kuali.rice.krad.uif.field.RemoteFieldsHolder; 035 import org.kuali.rice.krad.uif.layout.CollectionLayoutManager; 036 import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService; 037 import org.kuali.rice.krad.uif.util.ComponentUtils; 038 import org.kuali.rice.krad.uif.util.ExpressionUtils; 039 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 040 import org.kuali.rice.krad.uif.view.View; 041 import org.kuali.rice.krad.uif.view.ViewAuthorizer; 042 import org.kuali.rice.krad.uif.view.ViewModel; 043 import org.kuali.rice.krad.uif.view.ViewPresentationController; 044 import org.kuali.rice.krad.util.GlobalVariables; 045 import org.kuali.rice.krad.util.KRADUtils; 046 import org.kuali.rice.krad.util.ObjectUtils; 047 import org.kuali.rice.krad.web.form.UifFormBase; 048 049 import java.io.Serializable; 050 import java.util.ArrayList; 051 import java.util.Collection; 052 import java.util.HashMap; 053 import java.util.List; 054 import java.util.Map; 055 056 /** 057 * Builds out the <code>Field</code> instances for a collection group with a 058 * series of steps that interact with the configured 059 * <code>CollectionLayoutManager</code> to assemble the fields as necessary for 060 * the layout 061 * 062 * @author Kuali Rice Team (rice.collab@kuali.org) 063 */ 064 public class CollectionGroupBuilder implements Serializable { 065 private static final long serialVersionUID = -4762031957079895244L; 066 067 /** 068 * Creates the <code>Field</code> instances that make up the table 069 * 070 * <p> 071 * The corresponding collection is retrieved from the model and iterated 072 * over to create the necessary fields. The binding path for fields that 073 * implement <code>DataBinding</code> is adjusted to point to the collection 074 * line it is apart of. For example, field 'number' of collection 'accounts' 075 * for line 1 will be set to 'accounts[0].number', and for line 2 076 * 'accounts[1].number'. Finally parameters are set on the line's action 077 * fields to indicate what collection and line they apply to. 078 * </p> 079 * 080 * @param view 081 * - View instance the collection belongs to 082 * @param model 083 * - Top level object containing the data 084 * @param collectionGroup 085 * - CollectionGroup component for the collection 086 */ 087 public void build(View view, Object model, CollectionGroup collectionGroup) { 088 // create add line 089 if (collectionGroup.isRenderAddLine() && !collectionGroup.isReadOnly()) { 090 buildAddLine(view, model, collectionGroup); 091 } 092 093 // get the collection for this group from the model 094 List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(model, ((DataBinding) collectionGroup) 095 .getBindingInfo().getBindingPath()); 096 097 if (modelCollection != null) { 098 // filter inactive model 099 List<Integer> showIndexes = performCollectionFiltering(view, model, collectionGroup, modelCollection); 100 101 // for each collection row build the line fields 102 for (int index = 0; index < modelCollection.size(); index++) { 103 // display only records that passed filtering 104 if (showIndexes.contains(index)) { 105 String bindingPathPrefix = collectionGroup.getBindingInfo().getBindingName() + "[" + index + "]"; 106 if (StringUtils.isNotBlank(collectionGroup.getBindingInfo().getBindByNamePrefix())) { 107 bindingPathPrefix = 108 collectionGroup.getBindingInfo().getBindByNamePrefix() + "." + bindingPathPrefix; 109 } 110 111 Object currentLine = modelCollection.get(index); 112 113 List<ActionField> actions = getLineActions(view, model, collectionGroup, currentLine, index); 114 buildLine(view, model, collectionGroup, bindingPathPrefix, actions, false, currentLine, index); 115 } 116 } 117 } 118 } 119 120 /** 121 * Performs any filtering necessary on the collection before building the collection fields 122 * 123 * <p> 124 * If showInactive is set to false and the collection line type implements <code>Inactivatable</code>, 125 * invokes the active collection filter. Then any {@link CollectionFilter} instances configured for the collection 126 * group are invoked to filter the collection. Collections lines must pass all filters in order to be 127 * displayed 128 * </p> 129 * 130 * @param view - view instance that contains the collection 131 * @param model - object containing the views data 132 * @param collectionGroup - collection group component instance that will display the collection 133 * @param collection - collection instance that will be filtered 134 */ 135 protected List<Integer> performCollectionFiltering(View view, Object model, CollectionGroup collectionGroup, 136 Collection<?> collection) { 137 List<Integer> filteredIndexes = new ArrayList<Integer>(); 138 for (int i = 0; i < collection.size(); i++) { 139 filteredIndexes.add(new Integer(i)); 140 } 141 142 if (Inactivatable.class.isAssignableFrom(collectionGroup.getCollectionObjectClass()) && !collectionGroup 143 .isShowInactive()) { 144 List<Integer> activeIndexes = collectionGroup.getActiveCollectionFilter().filter(view, model, 145 collectionGroup); 146 filteredIndexes = ListUtils.intersection(filteredIndexes, activeIndexes); 147 } 148 149 for (CollectionFilter collectionFilter : collectionGroup.getFilters()) { 150 List<Integer> indexes = collectionFilter.filter(view, model, collectionGroup); 151 filteredIndexes = ListUtils.intersection(filteredIndexes, indexes); 152 if (filteredIndexes.isEmpty()) { 153 break; 154 } 155 } 156 157 return filteredIndexes; 158 } 159 160 /** 161 * Builds the fields for holding the collection add line and if necessary 162 * makes call to setup the new line instance 163 * 164 * @param view 165 * - view instance the collection belongs to 166 * @param collectionGroup 167 * - collection group the layout manager applies to 168 * @param model 169 * - Object containing the view data, should extend UifFormBase 170 * if using framework managed new lines 171 */ 172 protected void buildAddLine(View view, Object model, CollectionGroup collectionGroup) { 173 boolean addLineBindsToForm = false; 174 175 // initialize new line if one does not already exist 176 initializeNewCollectionLine(view, model, collectionGroup, false); 177 178 // determine whether the add line binds to the generic form map or a 179 // specified property 180 if (StringUtils.isBlank(collectionGroup.getAddLinePropertyName())) { 181 addLineBindsToForm = true; 182 } 183 184 String addLineBindingPath = collectionGroup.getAddLineBindingInfo().getBindingPath(); 185 List<ActionField> actions = getAddLineActions(view, model, collectionGroup); 186 187 Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLineBindingPath); 188 buildLine(view, model, collectionGroup, addLineBindingPath, actions, addLineBindsToForm, addLine, -1); 189 } 190 191 /** 192 * Builds the field instances for the collection line. A copy of the 193 * configured items on the <code>CollectionGroup</code> is made and adjusted 194 * for the line (id and binding). Then a call is made to the 195 * <code>CollectionLayoutManager</code> to assemble the line as necessary 196 * for the layout 197 * 198 * @param view 199 * - view instance the collection belongs to 200 * @param model 201 * - top level object containing the data 202 * @param collectionGroup 203 * - collection group component for the collection 204 * @param bindingPath 205 * - binding path for the line fields (if DataBinding) 206 * @param actions 207 * - List of actions to set in the lines action column 208 * @param bindToForm 209 * - whether the bindToForm property on the items bindingInfo 210 * should be set to true (needed for add line) 211 * @param currentLine 212 * - object instance for the current line, or null if add line 213 * @param lineIndex 214 * - index of the line in the collection, or -1 if we are 215 * building the add line 216 */ 217 @SuppressWarnings("unchecked") 218 protected void buildLine(View view, Object model, CollectionGroup collectionGroup, String bindingPath, 219 List<ActionField> actions, boolean bindToForm, Object currentLine, int lineIndex) { 220 CollectionLayoutManager layoutManager = (CollectionLayoutManager) collectionGroup.getLayoutManager(); 221 222 // copy group items for new line 223 List<? extends Component> lineItems = null; 224 String lineSuffix = null; 225 if (lineIndex == -1) { 226 lineItems = ComponentUtils.copyComponentList(collectionGroup.getAddLineFields(), null); 227 lineSuffix = UifConstants.IdSuffixes.ADD_LINE; 228 } else { 229 lineItems = ComponentUtils.copyComponentList(collectionGroup.getItems(), null); 230 lineSuffix = UifConstants.IdSuffixes.LINE + Integer.toString(lineIndex); 231 } 232 233 if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) { 234 lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix; 235 } 236 237 // check for remote fields holder 238 List<Field> lineFields = processAnyRemoteFieldsHolder(view, model, collectionGroup, lineItems); 239 240 // copy fields for line and adjust binding to match collection line path 241 lineFields = (List<Field>) ComponentUtils.copyFieldList(lineFields, bindingPath, lineSuffix); 242 243 boolean readOnlyLine = collectionGroup.isReadOnly(); 244 245 // add special css styles to identify the add line client side 246 if (lineIndex == -1) { 247 for (Field f : lineFields) { 248 if (f instanceof InputField) { 249 // sets up - skipping these fields in add area during standard form validation calls 250 // custom addLineToCollection js call will validate these fields manually on an add 251 Control control = ((InputField) f).getControl(); 252 if (control != null) { 253 control.addStyleClass(collectionGroup.getFactoryId() + "-addField"); 254 control.addStyleClass("ignoreValid"); 255 } 256 } 257 } 258 259 // set focus on after the add line submit to first field of add line 260 for (ActionField action : actions) { 261 if (action.getActionParameter(UifParameters.ACTION_TYPE).equals(UifParameters.ADD_LINE)) { 262 action.setFocusOnAfterSubmit(lineFields.get(0).getId()); 263 } 264 } 265 } else { 266 // for existing lines, check view line auth 267 boolean canViewLine = checkViewLineAuthorizationAndPresentationLogic(view, (ViewModel) model, 268 collectionGroup, currentLine); 269 270 // if line is not viewable, just return without calling the layout manager to add the line 271 if (!canViewLine) { 272 return; 273 } 274 275 // check edit line authorization if collection is not read only 276 if (!collectionGroup.isReadOnly()) { 277 readOnlyLine = !checkEditLineAuthorizationAndPresentationLogic(view, (ViewModel) model, collectionGroup, 278 currentLine); 279 } 280 281 ComponentUtils.pushObjectToContext(lineFields, UifConstants.ContextVariableNames.READONLY_LINE, 282 readOnlyLine); 283 ComponentUtils.pushObjectToContext(actions, UifConstants.ContextVariableNames.READONLY_LINE, readOnlyLine); 284 } 285 286 ComponentUtils.updateContextsForLine(lineFields, currentLine, lineIndex); 287 288 // check authorization for line fields 289 applyLineFieldAuthorizationAndPresentationLogic(view, (ViewModel) model, collectionGroup, currentLine, 290 readOnlyLine, lineFields, actions); 291 292 if (bindToForm) { 293 ComponentUtils.setComponentsPropertyDeep(lineFields, UifPropertyPaths.BIND_TO_FORM, new Boolean(true)); 294 } 295 296 // remove fields from the line that have render false 297 lineFields = removeNonRenderLineFields(view, model, collectionGroup, lineFields, currentLine, lineIndex); 298 299 // if not add line build sub-collection field groups 300 List<FieldGroup> subCollectionFields = new ArrayList<FieldGroup>(); 301 if ((lineIndex != -1) && (collectionGroup.getSubCollections() != null)) { 302 for (int subLineIndex = 0; subLineIndex < collectionGroup.getSubCollections().size(); subLineIndex++) { 303 CollectionGroup subCollectionPrototype = collectionGroup.getSubCollections().get(subLineIndex); 304 CollectionGroup subCollectionGroup = ComponentUtils.copy(subCollectionPrototype, lineSuffix); 305 306 // verify the sub-collection should be rendered 307 boolean renderSubCollection = checkSubCollectionRender(view, model, collectionGroup, 308 subCollectionGroup); 309 if (!renderSubCollection) { 310 continue; 311 } 312 313 subCollectionGroup.getBindingInfo().setBindByNamePrefix(bindingPath); 314 if (subCollectionGroup.isRenderAddLine()) { 315 subCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(bindingPath); 316 } 317 318 // set sub-collection suffix on group so it can be used for generated groups 319 String subCollectionSuffix = lineSuffix; 320 if (StringUtils.isNotBlank(subCollectionGroup.getSubCollectionSuffix())) { 321 subCollectionSuffix = subCollectionGroup.getSubCollectionSuffix() + lineSuffix; 322 } 323 subCollectionGroup.setSubCollectionSuffix(subCollectionSuffix); 324 325 FieldGroup fieldGroupPrototype = layoutManager.getSubCollectionFieldGroupPrototype(); 326 327 FieldGroup subCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype, 328 lineSuffix + UifConstants.IdSuffixes.SUB + subLineIndex); 329 subCollectionFieldGroup.setGroup(subCollectionGroup); 330 //subCollectionFieldGroup.setLabel(subCollectionGroup.getTitle()); 331 //subCollectionFieldGroup.getLabelField().setRender(true); 332 333 ComponentUtils.updateContextForLine(subCollectionFieldGroup, currentLine, lineIndex); 334 335 subCollectionFields.add(subCollectionFieldGroup); 336 } 337 } 338 339 // invoke layout manager to build the complete line 340 layoutManager.buildLine(view, model, collectionGroup, lineFields, subCollectionFields, bindingPath, actions, 341 lineSuffix, currentLine, lineIndex); 342 } 343 344 /** 345 * Iterates through the given items checking for <code>RemotableFieldsHolder</code>, if found 346 * the holder is invoked to retrieved the remotable fields and translate to attribute fields. The translated list 347 * is then inserted into the returned list at the position of the holder 348 * 349 * @param view - view instance containing the container 350 * @param model - object instance containing the view data 351 * @param group - collection group instance to check for any remotable fields holder 352 * @param items - list of items to process 353 */ 354 protected List<Field> processAnyRemoteFieldsHolder(View view, Object model, CollectionGroup group, 355 List<? extends Component> items) { 356 List<Field> processedItems = new ArrayList<Field>(); 357 358 // check for holders and invoke to retrieve the remotable fields and translate 359 // translated fields are placed into the processed items list at the position of the holder 360 for (Component item : items) { 361 if (item instanceof RemoteFieldsHolder) { 362 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(view, 363 model, group); 364 processedItems.addAll(translatedFields); 365 } else { 366 processedItems.add((Field) item); 367 } 368 } 369 370 return processedItems; 371 } 372 373 /** 374 * Evaluates the render property for the given list of <code>Field</code> 375 * instances for the line and removes any fields from the returned list that 376 * have render false. The conditional render string is also taken into 377 * account. This needs to be done here as opposed to during the normal 378 * condition evaluation so the the fields are not used while building the 379 * collection lines 380 * 381 * @param view 382 * - view instance the collection group belongs to 383 * @param model 384 * - object containing the view data 385 * @param collectionGroup 386 * - collection group for the line fields 387 * @param lineFields 388 * - list of fields configured for the line 389 * @param currentLine 390 * - object containing the line data 391 * @param lineIndex 392 * - index of the line in the collection 393 * @return List<Field> list of field instances that should be rendered 394 */ 395 protected List<Field> removeNonRenderLineFields(View view, Object model, CollectionGroup collectionGroup, 396 List<Field> lineFields, Object currentLine, int lineIndex) { 397 List<Field> fields = new ArrayList<Field>(); 398 399 for (Field lineField : lineFields) { 400 String conditionalRender = lineField.getPropertyExpression("render"); 401 402 // evaluate conditional render string if set 403 if (StringUtils.isNotBlank(conditionalRender)) { 404 Map<String, Object> context = getContextForField(view, collectionGroup, lineField); 405 406 // Adjust the condition as ExpressionUtils.adjustPropertyExpressions will only be 407 // executed after the collection is built. 408 conditionalRender = ExpressionUtils.replaceBindingPrefixes(view, lineField, conditionalRender); 409 410 Boolean render = (Boolean) getExpressionEvaluatorService().evaluateExpression(model, context, 411 conditionalRender); 412 lineField.setRender(render); 413 } 414 415 // only add line field if set to render or if it is hidden by progressive render 416 if (lineField.isRender() || StringUtils.isNotBlank(lineField.getProgressiveRender())) { 417 fields.add(lineField); 418 } 419 } 420 421 return fields; 422 } 423 424 /** 425 * Invokes the view's configured authorizer and presentation controller to determine if the user has permission 426 * to view the line (if a permission has been established) 427 * 428 * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will 429 * be pulled 430 * @param model - object containing the view's data 431 * @param collectionGroup - collection group containing the line 432 * @param line - object containing the lines data 433 * @return boolean true if the user can view the line, false if not 434 */ 435 protected boolean checkViewLineAuthorizationAndPresentationLogic(View view, ViewModel model, 436 CollectionGroup collectionGroup, Object line) { 437 ViewPresentationController presentationController = view.getPresentationController(); 438 ViewAuthorizer authorizer = view.getAuthorizer(); 439 440 Person user = GlobalVariables.getUserSession().getPerson(); 441 442 // check view line auth 443 boolean canViewLine = authorizer.canViewLine(view, model, collectionGroup, collectionGroup.getPropertyName(), 444 line, user); 445 if (canViewLine) { 446 canViewLine = presentationController.canViewLine(view, model, collectionGroup, 447 collectionGroup.getPropertyName(), line); 448 } 449 450 return canViewLine; 451 } 452 453 /** 454 * Invokes the view's configured authorizer and presentation controller to determine if the user has permission 455 * to edit the line (if a permission has been established) 456 * 457 * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will 458 * be pulled 459 * @param model - object containing the view's data 460 * @param collectionGroup - collection group containing the line 461 * @param line - object containing the lines data 462 * @return boolean true if the user can edit the line, false if not 463 */ 464 protected boolean checkEditLineAuthorizationAndPresentationLogic(View view, ViewModel model, 465 CollectionGroup collectionGroup, Object line) { 466 ViewPresentationController presentationController = view.getPresentationController(); 467 ViewAuthorizer authorizer = view.getAuthorizer(); 468 469 Person user = GlobalVariables.getUserSession().getPerson(); 470 471 // check edit line auth 472 boolean canEditLine = authorizer.canEditLine(view, model, collectionGroup, collectionGroup.getPropertyName(), 473 line, user); 474 if (canEditLine) { 475 canEditLine = presentationController.canEditLine(view, model, collectionGroup, 476 collectionGroup.getPropertyName(), line); 477 } 478 479 return canEditLine; 480 } 481 482 /** 483 * Iterates through the line fields and checks the view field authorization using the view's configured authorizer 484 * and presentation controller. If the field is viewable, then sets the edit field authorization. Finally iterates 485 * through the line actions invoking the authorizer and presentation controller to authorizer the action 486 * 487 * @param view - view instance the collection belongs to and from which the authorizer/presentation controller will 488 * be pulled 489 * @param model - object containing the view's data 490 * @param collectionGroup - collection group containing the line 491 * @param line - object containing the lines data 492 * @param readOnlyLine - flag indicating whether the line has been marked as read only (which will force the fields 493 * to be read only) 494 * @param lineFields - list of fields instances for the line 495 * @param actions - list of action field instances for the line 496 */ 497 protected void applyLineFieldAuthorizationAndPresentationLogic(View view, ViewModel model, 498 CollectionGroup collectionGroup, Object line, boolean readOnlyLine, List<Field> lineFields, 499 List<ActionField> actions) { 500 ViewPresentationController presentationController = view.getPresentationController(); 501 ViewAuthorizer authorizer = view.getAuthorizer(); 502 503 Person user = GlobalVariables.getUserSession().getPerson(); 504 505 for (Field lineField : lineFields) { 506 String propertyName = null; 507 if (lineField instanceof DataBinding) { 508 propertyName = ((DataBinding) lineField).getPropertyName(); 509 } 510 511 // evaluate expression on fields component security (since apply model phase has not been invoked on 512 // them yet 513 ComponentSecurity componentSecurity = lineField.getComponentSecurity(); 514 ExpressionUtils.adjustPropertyExpressions(view, componentSecurity); 515 516 Map<String, Object> context = getContextForField(view, collectionGroup, lineField); 517 getExpressionEvaluatorService().evaluateObjectExpressions(componentSecurity, model, context); 518 519 // check view field auth 520 if (lineField.isRender() && !lineField.isHidden()) { 521 boolean canViewField = authorizer.canViewLineField(view, model, collectionGroup, 522 collectionGroup.getPropertyName(), line, lineField, propertyName, user); 523 if (canViewField) { 524 canViewField = presentationController.canViewLineField(view, model, collectionGroup, 525 collectionGroup.getPropertyName(), line, lineField, propertyName); 526 } 527 528 if (!canViewField) { 529 // since removing can impact layout, set to hidden 530 // TODO: check into encryption setting 531 lineField.setHidden(true); 532 533 if (lineField.getPropertyExpressions().containsKey("hidden")) { 534 lineField.getPropertyExpressions().remove("hidden"); 535 } 536 537 continue; 538 } 539 540 // check edit field auth 541 boolean canEditField = !readOnlyLine; 542 if (!readOnlyLine) { 543 canEditField = authorizer.canEditLineField(view, model, collectionGroup, 544 collectionGroup.getPropertyName(), line, lineField, propertyName, user); 545 if (canEditField) { 546 canEditField = presentationController.canEditLineField(view, model, collectionGroup, 547 collectionGroup.getPropertyName(), line, lineField, propertyName); 548 } 549 } 550 551 if (readOnlyLine || !canEditField) { 552 lineField.setReadOnly(true); 553 554 if (lineField.getPropertyExpressions().containsKey("readOnly")) { 555 lineField.getPropertyExpressions().remove("readOnly"); 556 } 557 } 558 } 559 } 560 561 // check auth on line actions 562 for (ActionField actionField : actions) { 563 if (actionField.isRender()) { 564 boolean canPerformAction = authorizer.canPerformLineAction(view, model, collectionGroup, 565 collectionGroup.getPropertyName(), line, actionField, actionField.getActionEvent(), 566 actionField.getId(), user); 567 if (canPerformAction) { 568 canPerformAction = presentationController.canPerformLineAction(view, model, collectionGroup, 569 collectionGroup.getPropertyName(), line, actionField, actionField.getActionEvent(), 570 actionField.getId()); 571 } 572 573 if (!canPerformAction) { 574 actionField.setRender(false); 575 576 if (actionField.getPropertyExpressions().containsKey("render")) { 577 actionField.getPropertyExpressions().remove("render"); 578 } 579 } 580 } 581 } 582 } 583 584 /** 585 * Checks whether the given sub-collection should be rendered, any 586 * conditional render string is evaluated 587 * 588 * @param view 589 * - view instance the sub collection belongs to 590 * @param model 591 * - object containing the view data 592 * @param collectionGroup 593 * - collection group the sub collection belongs to 594 * @param subCollectionGroup 595 * - sub collection group to check render status for 596 * @return boolean true if sub collection should be rendered, false if it 597 * should not be rendered 598 */ 599 protected boolean checkSubCollectionRender(View view, Object model, CollectionGroup collectionGroup, 600 CollectionGroup subCollectionGroup) { 601 String conditionalRender = subCollectionGroup.getPropertyExpression("render"); 602 603 // TODO: check authorizer 604 605 // evaluate conditional render string if set 606 if (StringUtils.isNotBlank(conditionalRender)) { 607 Map<String, Object> context = new HashMap<String, Object>(); 608 context.putAll(view.getContext()); 609 context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup); 610 context.put(UifConstants.ContextVariableNames.COMPONENT, subCollectionGroup); 611 612 Boolean render = (Boolean) getExpressionEvaluatorService().evaluateExpression(model, context, 613 conditionalRender); 614 subCollectionGroup.setRender(render); 615 } 616 617 return subCollectionGroup.isRender(); 618 } 619 620 /** 621 * Creates new <code>ActionField</code> instances for the line 622 * 623 * <p> 624 * Adds context to the action fields for the given line so that the line the 625 * action was performed on can be determined when that action is selected 626 * </p> 627 * 628 * @param view 629 * - view instance the collection belongs to 630 * @param model 631 * - top level object containing the data 632 * @param collectionGroup 633 * - collection group component for the collection 634 * @param collectionLine 635 * - object instance for the current line 636 * @param lineIndex 637 * - index of the line the actions should apply to 638 */ 639 protected List<ActionField> getLineActions(View view, Object model, CollectionGroup collectionGroup, 640 Object collectionLine, int lineIndex) { 641 String lineSuffix = UifConstants.IdSuffixes.LINE + Integer.toString(lineIndex); 642 if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) { 643 lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix; 644 } 645 List<ActionField> lineActions = ComponentUtils.copyFieldList(collectionGroup.getActionFields(), lineSuffix); 646 647 for (ActionField actionField : lineActions) { 648 actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH, collectionGroup.getBindingInfo() 649 .getBindingPath()); 650 actionField.addActionParameter(UifParameters.SELECTED_LINE_INDEX, Integer.toString(lineIndex)); 651 actionField.setJumpToIdAfterSubmit(collectionGroup.getId() + "_div"); 652 653 actionField.setClientSideJs("performCollectionAction('"+collectionGroup.getId()+"');"); 654 } 655 656 ComponentUtils.updateContextsForLine(lineActions, collectionLine, lineIndex); 657 658 return lineActions; 659 } 660 661 /** 662 * Creates new <code>ActionField</code> instances for the add line 663 * 664 * <p> 665 * Adds context to the action fields for the add line so that the collection 666 * the action was performed on can be determined 667 * </p> 668 * 669 * @param view 670 * - view instance the collection belongs to 671 * @param model 672 * - top level object containing the data 673 * @param collectionGroup 674 * - collection group component for the collection 675 */ 676 protected List<ActionField> getAddLineActions(View view, Object model, CollectionGroup collectionGroup) { 677 String lineSuffix = UifConstants.IdSuffixes.ADD_LINE; 678 if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) { 679 lineSuffix = collectionGroup.getSubCollectionSuffix() + lineSuffix; 680 } 681 List<ActionField> lineActions = ComponentUtils.copyFieldList(collectionGroup.getAddLineActionFields(), 682 lineSuffix); 683 684 for (ActionField actionField : lineActions) { 685 actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH, collectionGroup.getBindingInfo() 686 .getBindingPath()); 687 actionField.setJumpToIdAfterSubmit(collectionGroup.getId() + "_div"); 688 actionField.addActionParameter(UifParameters.ACTION_TYPE, UifParameters.ADD_LINE); 689 690 String baseId = collectionGroup.getFactoryId(); 691 if (StringUtils.isNotBlank(collectionGroup.getSubCollectionSuffix())) { 692 baseId += collectionGroup.getSubCollectionSuffix(); 693 } 694 695 actionField.setClientSideJs("addLineToCollection('"+collectionGroup.getId()+"', '"+ baseId +"');"); 696 } 697 698 // get add line for context 699 String addLinePath = collectionGroup.getAddLineBindingInfo().getBindingPath(); 700 Object addLine = ObjectPropertyUtils.getPropertyValue(model, addLinePath); 701 702 ComponentUtils.updateContextsForLine(lineActions, addLine, -1); 703 704 return lineActions; 705 } 706 707 /** 708 * Helper method to build the context for a field (needed because the apply model phase for line fields has 709 * not been applied yet and their full context not set) 710 * 711 * @param view - view instance the field belongs to 712 * @param collectionGroup - collection group instance the field belongs to 713 * @param field - field instance to build context for 714 * @return Map<String, Object> context for field 715 */ 716 protected Map<String, Object> getContextForField(View view, CollectionGroup collectionGroup, Field field) { 717 Map<String, Object> context = new HashMap<String, Object>(); 718 719 context.putAll(view.getContext()); 720 context.putAll(field.getContext()); 721 context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup); 722 context.put(UifConstants.ContextVariableNames.COMPONENT, field); 723 724 return context; 725 } 726 727 /** 728 * Initializes a new instance of the collection class 729 * 730 * <p> 731 * If the add line property was not specified for the collection group the 732 * new lines will be added to the generic map on the 733 * <code>UifFormBase</code>, else it will be added to the property given by 734 * the addLineBindingInfo 735 * </p> 736 * 737 * <p> 738 * New line will only be created if the current line property is null or 739 * clearExistingLine is true. In the case of a new line default values are 740 * also applied 741 * </p> 742 * 743 * @see org.kuali.rice.krad.uif.container.CollectionGroup# 744 * initializeNewCollectionLine(View, Object, CollectionGroup, boolean) 745 */ 746 public void initializeNewCollectionLine(View view, Object model, CollectionGroup collectionGroup, 747 boolean clearExistingLine) { 748 Object newLine = null; 749 750 // determine if we are binding to generic form map or a custom property 751 if (StringUtils.isBlank(collectionGroup.getAddLinePropertyName())) { 752 // bind to form map 753 if (!(model instanceof UifFormBase)) { 754 throw new RuntimeException("Cannot create new collection line for group: " 755 + collectionGroup.getPropertyName() + ". Model does not extend " + UifFormBase.class.getName()); 756 } 757 758 // get new collection line map from form 759 Map<String, Object> newCollectionLines = ObjectPropertyUtils.getPropertyValue(model, 760 UifPropertyPaths.NEW_COLLECTION_LINES); 761 if (newCollectionLines == null) { 762 newCollectionLines = new HashMap<String, Object>(); 763 ObjectPropertyUtils.setPropertyValue(model, UifPropertyPaths.NEW_COLLECTION_LINES, newCollectionLines); 764 } 765 766 // set binding path for add line 767 String newCollectionLineKey = KRADUtils 768 .translateToMapSafeKey(collectionGroup.getBindingInfo().getBindingPath()); 769 String addLineBindingPath = UifPropertyPaths.NEW_COLLECTION_LINES + "['" + newCollectionLineKey + "']"; 770 collectionGroup.getAddLineBindingInfo().setBindingPath(addLineBindingPath); 771 772 // if there is not an instance available or we need to clear create 773 // a new instance 774 if (!newCollectionLines.containsKey(newCollectionLineKey) 775 || (newCollectionLines.get(newCollectionLineKey) == null) || clearExistingLine) { 776 // create new instance of the collection type for the add line 777 newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass()); 778 newCollectionLines.put(newCollectionLineKey, newLine); 779 } 780 } else { 781 // bind to custom property 782 Object addLine = ObjectPropertyUtils.getPropertyValue(model, collectionGroup.getAddLineBindingInfo() 783 .getBindingPath()); 784 if ((addLine == null) || clearExistingLine) { 785 newLine = ObjectUtils.newInstance(collectionGroup.getCollectionObjectClass()); 786 ObjectPropertyUtils.setPropertyValue(model, collectionGroup.getAddLineBindingInfo().getBindingPath(), 787 newLine); 788 } 789 } 790 791 // apply default values if a new line was created 792 if (newLine != null) { 793 view.getViewHelperService().applyDefaultValuesForCollectionLine(view, model, collectionGroup, newLine); 794 } 795 } 796 797 protected ExpressionEvaluatorService getExpressionEvaluatorService() { 798 return KRADServiceLocatorWeb.getExpressionEvaluatorService(); 799 } 800 801 }