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 }