View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.container;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.kim.api.identity.Person;
20  import org.kuali.rice.krad.uif.CssConstants;
21  import org.kuali.rice.krad.uif.UifConstants;
22  import org.kuali.rice.krad.uif.UifParameters;
23  import org.kuali.rice.krad.uif.UifPropertyPaths;
24  import org.kuali.rice.krad.uif.component.BindingInfo;
25  import org.kuali.rice.krad.uif.component.Component;
26  import org.kuali.rice.krad.uif.component.ComponentSecurity;
27  import org.kuali.rice.krad.uif.component.DataBinding;
28  import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
29  import org.kuali.rice.krad.uif.control.Control;
30  import org.kuali.rice.krad.uif.control.ControlBase;
31  import org.kuali.rice.krad.uif.element.Action;
32  import org.kuali.rice.krad.uif.field.DataField;
33  import org.kuali.rice.krad.uif.field.Field;
34  import org.kuali.rice.krad.uif.field.FieldGroup;
35  import org.kuali.rice.krad.uif.field.InputField;
36  import org.kuali.rice.krad.uif.field.RemoteFieldsHolder;
37  import org.kuali.rice.krad.uif.layout.CollectionLayoutManager;
38  import org.kuali.rice.krad.uif.layout.TableLayoutManagerBase;
39  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
40  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
41  import org.kuali.rice.krad.uif.util.ComponentFactory;
42  import org.kuali.rice.krad.uif.util.ComponentUtils;
43  import org.kuali.rice.krad.uif.util.ContextUtils;
44  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
45  import org.kuali.rice.krad.uif.util.ScriptUtils;
46  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
47  import org.kuali.rice.krad.uif.view.View;
48  import org.kuali.rice.krad.uif.view.ViewAuthorizer;
49  import org.kuali.rice.krad.uif.view.ViewModel;
50  import org.kuali.rice.krad.uif.view.ViewPresentationController;
51  import org.kuali.rice.krad.util.GlobalVariables;
52  import org.kuali.rice.krad.web.form.UifFormBase;
53  
54  import java.io.Serializable;
55  import java.util.ArrayList;
56  import java.util.HashMap;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Map;
60  
61  /**
62   * Process configuration from the collection group to prepare components for a new line and invokes the associated
63   * layout manager to add the line.
64   *
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   */
67  public class CollectionGroupLineBuilder implements Serializable {
68  
69      private static final long serialVersionUID = 981187437246864378L;
70  
71      private LineBuilderContext lineBuilderContext;
72      private List<Field> unauthorizedFields = new ArrayList<Field>();
73  
74      public CollectionGroupLineBuilder(LineBuilderContext lineBuilderContext) {
75          this.lineBuilderContext = lineBuilderContext;
76      }
77  
78      /**
79       * Invoked to build a line in the collection.
80       *
81       * <p>First the context for the line is preprocessed in {@link CollectionGroupLineBuilder#preprocessLine()}.
82       * After preprocessing the configured layout manager is invoked to place the line into the layout.</p>
83       */
84      public void buildLine() {
85          preprocessLine();
86  
87          boolean hasLineFields =
88                  (lineBuilderContext.getLineFields() != null) && (!lineBuilderContext.getLineFields().isEmpty());
89          boolean hasSubCollections =
90                  (lineBuilderContext.getSubCollectionFields() != null) && (!lineBuilderContext.getSubCollectionFields()
91                          .isEmpty());
92  
93          // invoke layout manager and setup edit line dialog to build the complete line
94          if (hasLineFields || hasSubCollections) {
95              // setup the edit dialog if its not an add line
96              if (!lineBuilderContext.isAddLine()) {
97                  setupEditLineDetails();
98              }
99  
100             // add the lineDialogs to the lineActions
101             List<Component> actions = new ArrayList<Component>();
102             actions.addAll(lineBuilderContext.getLineActions());
103             List<DialogGroup> dialogGroups = lineBuilderContext.getCollectionGroup().getLineDialogs();
104             if (Boolean.TRUE.equals(lineBuilderContext.getCollectionGroup().getReadOnly())) {
105                 for (DialogGroup group : dialogGroups) {
106                     group.setReadOnly(true);
107                 }
108             }
109             actions.addAll(dialogGroups);
110             lineBuilderContext.setLineActions(actions);
111             lineBuilderContext.getCollectionGroup().getLineDialogs().clear();
112 
113             // invoke the layout manager
114             lineBuilderContext.getLayoutManager().buildLine(lineBuilderContext);
115         }
116 
117         // After the lines have been processed and correct value set for readOnly and other properties
118         // set the script for enabling/disabling save button
119         applyOnChangeForSave(lineBuilderContext.getLineFields());
120     }
121 
122     /**
123      * Performs various preprocessing of the line components and configuration.
124      *
125      * <p>Preprocessing includes:
126      * <ul>
127      * <li>Make a copy of the collection groups items and adjust binding and id</li>
128      * <li>Process any remotable fields within the line</li>
129      * <li>Check line and field authorization</li>
130      * <li>Remove fields that should not be rendered</li>
131      * <li>Configure client side functionality such as save enable and add line validation</li>
132      * <li>Build field groups to hold any configured sub-collections</li>
133      * </ul></p>
134      */
135     public void preprocessLine() {
136         List<? extends Component> lineItems = initializeLineItems();
137 
138         List<Field> lineFields = processAnyRemoteFieldsHolder(lineBuilderContext.getCollectionGroup(), lineItems);
139 
140         adjustFieldBindingAndId(lineFields, lineBuilderContext.getBindingPath());
141 
142         ContextUtils.updateContextsForLine(lineFields, lineBuilderContext.getCollectionGroup(),
143                 lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
144                 lineBuilderContext.getIdSuffix());
145 
146         boolean canViewLine = checkViewLineAuthorization();
147         if (!canViewLine) {
148             return;
149         }
150 
151         List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(lineBuilderContext.getLineActions(),
152                 Action.class);
153         setFocusOnIdForActions(actions, lineFields);
154 
155         boolean canEditLine = checkEditLineAuthorization(lineFields);
156         ContextUtils.pushObjectToContextDeep(lineFields, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
157         ContextUtils.pushObjectToContextDeep(actions, UifConstants.ContextVariableNames.READONLY_LINE, !canEditLine);
158 
159         if (!canEditLine) {
160             Iterator<Action> actionsIterator = actions.iterator();
161             while (actionsIterator.hasNext()) {
162                 Action action = actionsIterator.next();
163                 if (action.getId().startsWith(ComponentFactory.EDIT_LINE_IN_DIALOG_ACTION + "_" +
164                         lineBuilderContext.getCollectionGroup().getId())) {
165                     actionsIterator.remove();
166                     break;
167                 }
168             }
169             lineBuilderContext.setLineActions(actions);
170         }
171 
172         // check authorization for line fields
173         applyLineFieldAuthorizationAndPresentationLogic(!canEditLine, lineFields, actions);
174 
175         // remove fields from the line that have render false
176         lineFields = removeNonRenderLineFields(lineFields);
177 
178         buildSubCollectionFieldGroups();
179 
180         // update action parameters for any actions that were added in the line items (as opposed to the line actions)
181         List<Action> lineFieldActions = ViewLifecycleUtils.getElementsOfTypeDeep(lineFields, Action.class);
182         if (lineFieldActions != null) {
183             lineBuilderContext.getCollectionGroup().getCollectionGroupBuilder().initializeActions(lineFieldActions,
184                     lineBuilderContext.getCollectionGroup(), lineBuilderContext.getLineIndex());
185         }
186 
187         setupAddLineControlValidation(lineFields);
188 
189         lineBuilderContext.setLineFields(lineFields);
190     }
191 
192     /**
193      * Copies either the collections groups items or add line items to a list of components that will be used
194      * for the collection line.
195      *
196      * @return list of component instance for the collection line
197      */
198     protected List<? extends Component> initializeLineItems() {
199         List<? extends Component> lineItems;
200 
201         if (lineBuilderContext.isAddLine()) {
202             lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getAddLineItems(),
203                     null);
204         } else {
205             lineItems = ComponentUtils.copyComponentList(lineBuilderContext.getCollectionGroup().getItems(), null);
206         }
207 
208         return lineItems;
209     }
210 
211     /**
212      * Iterates through the given items checking for {@code RemotableFieldsHolder}, if found
213      * the holder is invoked to retrieved the remotable fields and translate to attribute fields.
214      *
215      * <p>The translated list is then inserted into the returned list at the position of the holder</p>
216      *
217      * @param group collection group instance to check for any remotable fields holder
218      * @param items list of items to process
219      */
220     public List<Field> processAnyRemoteFieldsHolder(CollectionGroup group, List<? extends Component> items) {
221         List<Field> processedItems = new ArrayList<Field>();
222 
223         // check for holders and invoke to retrieve the remotable fields and translate
224         // translated fields are placed into the processed items list at the position of the holder
225         for (Component item : items) {
226             if (item instanceof RemoteFieldsHolder) {
227                 List<InputField> translatedFields = ((RemoteFieldsHolder) item).fetchAndTranslateRemoteFields(group);
228                 processedItems.addAll(translatedFields);
229             } else if (item instanceof Field) {
230                 processedItems.add((Field) item);
231             }
232         }
233 
234         return processedItems;
235     }
236 
237     /**
238      * Adjusts the binding path for the given fields to match the collections path, and sets the container id
239      * suffix for the fields so all nested components will get their ids adjusted.
240      *
241      * @param lineFields list of fields to update
242      * @param bindingPath binding path to add
243      */
244     protected void adjustFieldBindingAndId(List<Field> lineFields, String bindingPath) {
245         for (Field lineField : lineFields) {
246             adjustFieldBinding(lineField, bindingPath);
247             adjustFieldId(lineField);
248         }
249 
250         if (lineBuilderContext.isBindToForm()) {
251             ComponentUtils.setComponentsPropertyDeep(lineFields, UifPropertyPaths.BIND_TO_FORM, Boolean.valueOf(true));
252         }
253     }
254 
255     /**
256      * Adjusts the binding path for the given field to match the collections path.
257      *
258      * @param lineField field to update
259      * @param bindingPath binding path to add
260      */
261     protected void adjustFieldBinding(Field lineField, String bindingPath) {
262         if (lineField instanceof DataBinding && ((DataBinding) lineField).getBindingInfo().isBindToForm()) {
263             BindingInfo bindingInfo = ((DataBinding) lineField).getBindingInfo();
264             bindingInfo.setCollectionPath(null);
265             bindingInfo.setBindingName(bindingInfo.getBindingName() + "[" + lineBuilderContext.getLineIndex() + "]");
266         } else {
267             ComponentUtils.prefixBindingPath(lineField, bindingPath);
268         }
269     }
270 
271     /**
272      * Adjusts the id suffix for the given field.
273      *
274      * @param lineField field to update
275      */
276     protected void adjustFieldId(Field lineField) {
277         ComponentUtils.updateIdWithSuffix(lineField, lineBuilderContext.getIdSuffix());
278 
279         lineField.setContainerIdSuffix(lineBuilderContext.getIdSuffix());
280     }
281 
282     /**
283      * For any actions with focus id {@link org.kuali.rice.krad.uif.UifConstants.Order#LINE_FIRST}, the focus id
284      * is replaced to match to id of the first control in the line.
285      *
286      * @param actions list of actions to potientially update
287      * @param lineFields list of line fields, the control for the first field in the list
288      * will be used for the focus id
289      */
290     protected void setFocusOnIdForActions(List<Action> actions, List<Field> lineFields) {
291         for (Action action : actions) {
292             if (action == null) {
293                 continue;
294             }
295 
296             boolean focusLineFirst = StringUtils.isNotBlank(action.getFocusOnIdAfterSubmit()) && action
297                     .getFocusOnIdAfterSubmit().equalsIgnoreCase(UifConstants.Order.LINE_FIRST.toString());
298             boolean lineHasFields = !lineFields.isEmpty();
299             if (focusLineFirst && lineHasFields) {
300                 action.setFocusOnIdAfterSubmit(lineFields.get(0).getId() + UifConstants.IdSuffixes.CONTROL);
301             }
302         }
303     }
304 
305     /**
306      * If {@link CollectionGroup#isRenderSaveLineActions()} is true and the line has been added by the user, on change
307      * script is added to any controls in the line to enable the save action.
308      *
309      * @param lineFields list of line fields
310      */
311     protected void applyOnChangeForSave(List<Field> lineFields) {
312         boolean isLineNewlyAdded = ((UifFormBase) lineBuilderContext.getModel()).isAddedCollectionItem(
313                 lineBuilderContext.getCurrentLine());
314         boolean saveLineEnabled = lineBuilderContext.getCollectionGroup().isRenderSaveLineActions();
315 
316         if (!isLineNewlyAdded && !saveLineEnabled) {
317             return;
318         }
319 
320         for (Field field : lineFields) {
321             boolean isInputField = (field instanceof InputField);
322             if (field.isHidden() || Boolean.TRUE.equals(field.getReadOnly()) || !isInputField) {
323                 continue;
324             }
325 
326             // if control null, assign default
327             InputField inputField = (InputField) field;
328             if (inputField.getControl() == null) {
329                 inputField.setControl(ComponentFactory.getTextControl());
330             }
331 
332             ControlBase control = (ControlBase) ((InputField) field).getControl();
333 
334             String onBlurScript = UifConstants.JsFunctions.COLLECTION_LINE_CHANGED + "(this, '" +
335                     CssConstants.Classes.NEW_COLLECTION_ITEM + "');";
336             onBlurScript = ScriptUtils.appendScript(control.getOnBlurScript(), onBlurScript);
337 
338             control.setOnBlurScript(onBlurScript);
339         }
340     }
341 
342     /**
343      * Evaluates the render property for the given list of field instances for the line and removes any fields
344      * from the returned list that have render false.
345      *
346      * <p>The conditional render string is also taken into account. This needs to be done here as opposed
347      * to during the normal condition evaluation so the the fields are not used while building the
348      * collection lines</p>
349      *
350      * @param lineFields list of fields configured for the line
351      * @return list of field instances that should be rendered
352      */
353     protected List<Field> removeNonRenderLineFields(List<Field> lineFields) {
354         List<Field> fields = new ArrayList<Field>();
355 
356         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
357 
358         for (Field lineField : lineFields) {
359             String conditionalRender = lineField.getPropertyExpression(UifPropertyPaths.RENDER);
360 
361             // evaluate conditional render string if set
362             if (StringUtils.isNotBlank(conditionalRender)) {
363                 Map<String, Object> context = getContextForField(ViewLifecycle.getView(),
364                         lineBuilderContext.getCollectionGroup(), lineField);
365 
366                 // Adjust the condition as ExpressionUtils.adjustPropertyExpressions will only be
367                 // executed after the collection is built.
368                 conditionalRender = expressionEvaluator.replaceBindingPrefixes(ViewLifecycle.getView(), lineField,
369                         conditionalRender);
370 
371                 Boolean render = (Boolean) expressionEvaluator.evaluateExpression(context, conditionalRender);
372                 lineField.setRender(render);
373             }
374 
375             // only add line field if set to render or if it is hidden by progressive render
376             if (lineField.isRender() || StringUtils.isNotBlank(lineField.getProgressiveRender())) {
377                 fields.add(lineField);
378             }
379         }
380 
381         return fields;
382     }
383 
384     /**
385      * Determines whether the user is authorized to the view the line.
386      *
387      * @return boolean true if the user can view the line, false if not
388      */
389     protected boolean checkViewLineAuthorization() {
390         boolean canViewLine = true;
391 
392         // check view line authorization if collection is not hidden
393         if (!lineBuilderContext.isAddLine()) {
394             canViewLine = checkViewLineAuthorizationAndPresentationLogic();
395         }
396 
397         if (!canViewLine) {
398             addUnauthorizedBindingInfo();
399         }
400 
401         return canViewLine;
402     }
403 
404     /**
405      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
406      * to view the line (if a permission has been established).
407      *
408      * @return true if the user can view the line, false if not
409      */
410     protected boolean checkViewLineAuthorizationAndPresentationLogic() {
411         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
412         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
413 
414         Person user = GlobalVariables.getUserSession().getPerson();
415 
416         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
417 
418         boolean canViewLine = authorizer.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
419                 collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine(), user);
420         if (canViewLine) {
421             canViewLine = presentationController.canViewLine(ViewLifecycle.getView(), lineBuilderContext.getModel(),
422                     collectionGroup, collectionGroup.getPropertyName(), lineBuilderContext.getCurrentLine());
423         }
424 
425         return canViewLine;
426     }
427 
428     /**
429      * Determines whether the user is authorized to the edit the line.
430      *
431      * @param lineFields list of fields configured for the line
432      * @return boolean true if the user can edit the line, false if not
433      */
434     protected boolean checkEditLineAuthorization(List<Field> lineFields) {
435         boolean canEditLine = !Boolean.TRUE.equals(lineBuilderContext.getCollectionGroup().getReadOnly());
436 
437         if (!canEditLine) {
438             ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
439             View view = ViewLifecycle.getView();
440 
441             for (Field field : lineFields) {
442                 field.pushObjectToContext(UifConstants.ContextVariableNames.PARENT,
443                         lineBuilderContext.getCollectionGroup());
444                 field.pushAllToContext(view.getContext());
445                 field.pushObjectToContext(UifConstants.ContextVariableNames.COMPONENT, field);
446 
447                 expressionEvaluator.evaluatePropertyExpression(view, field.getContext(), field,
448                         UifPropertyPaths.READ_ONLY, true);
449 
450                 if (!Boolean.TRUE.equals(field.getReadOnly())) {
451                     canEditLine = true;
452                     break;
453                 }
454             }
455         }
456 
457         if (canEditLine && !lineBuilderContext.isAddLine()) {
458             canEditLine = checkEditLineAuthorizationAndPresentationLogic(lineBuilderContext.getCollectionGroup(),
459                     lineBuilderContext.getModel(), lineBuilderContext.getCurrentLine());
460         }
461 
462         if (!canEditLine) {
463             addUnauthorizedBindingInfo();
464         }
465 
466         return canEditLine;
467     }
468 
469     /**
470      * Invokes the view's configured authorizer and presentation controller to determine if the user has permission
471      * to edit the line (if a permission has been established).
472      *
473      * @return true if the user can edit the line, false if not
474      */
475     protected boolean checkEditLineAuthorizationAndPresentationLogic(CollectionGroup collectionGroup, ViewModel model,
476             Object currentLine) {
477         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
478         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
479 
480         Person user = GlobalVariables.getUserSession().getPerson();
481 
482         boolean canEditLine = authorizer.canEditLine(ViewLifecycle.getView(), model, collectionGroup,
483                 collectionGroup.getPropertyName(), currentLine, user);
484         if (canEditLine) {
485             canEditLine = presentationController.canEditLine(ViewLifecycle.getView(), model, collectionGroup,
486                     collectionGroup.getPropertyName(), currentLine);
487         }
488 
489         return canEditLine;
490     }
491 
492     /**
493      * Adds a {@link org.kuali.rice.krad.uif.component.BindingInfo} instance for the given binding
494      * path to the collection groups unauthorized list.
495      */
496     protected void addUnauthorizedBindingInfo() {
497         if (lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos() == null) {
498             lineBuilderContext.getCollectionGroup().setUnauthorizedLineBindingInfos(new ArrayList<BindingInfo>());
499         }
500 
501         BindingInfo bindingInfo = new BindingInfo();
502         bindingInfo.setDefaults(ViewLifecycle.getView(), lineBuilderContext.getBindingPath());
503         lineBuilderContext.getCollectionGroup().getUnauthorizedLineBindingInfos().add(bindingInfo);
504     }
505 
506     /**
507      * Iterates through the line fields and checks the view field authorization using the view's configured authorizer
508      * and presentation controller.
509      *
510      * <p>If the field is viewable, then sets the edit field authorization. Finally iterates
511      * through the line actions invoking the authorizer and presentation controller to authorizer the action</p>
512      *
513      * @param readOnlyLine flag indicating whether the line has been marked as read only (which will force the fields
514      * to be read only)
515      * @param lineFields list of fields instances for the line
516      * @param actionList list of action field instances for the line
517      */
518     protected void applyLineFieldAuthorizationAndPresentationLogic(boolean readOnlyLine, List<Field> lineFields,
519             List<? extends Component> actionList) {
520         ViewPresentationController presentationController = ViewLifecycle.getView().getPresentationController();
521         ViewAuthorizer authorizer = ViewLifecycle.getView().getAuthorizer();
522 
523         Person user = GlobalVariables.getUserSession().getPerson();
524         ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
525 
526         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
527         View view = ViewLifecycle.getView();
528         ViewModel model = lineBuilderContext.getModel();
529         Object currentLine = lineBuilderContext.getCurrentLine();
530 
531         for (Field lineField : lineFields) {
532             String propertyName = null;
533             if (lineField instanceof DataBinding) {
534                 propertyName = ((DataBinding) lineField).getPropertyName();
535             }
536 
537             // evaluate expression on fields component security (since apply model phase has not been invoked on
538             // them yet
539             ComponentSecurity componentSecurity = lineField.getComponentSecurity();
540 
541             Map<String, Object> context = getContextForField(ViewLifecycle.getView(), collectionGroup, lineField);
542             expressionEvaluator.evaluateExpressionsOnConfigurable(ViewLifecycle.getView(), componentSecurity, context);
543 
544             // check view field auth
545             if (!lineField.isRender() || lineField.isHidden()) {
546                 continue;
547             }
548 
549             boolean canViewField = authorizer.canViewLineField(view, model, collectionGroup,
550                     collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
551             if (canViewField) {
552                 canViewField = presentationController.canViewLineField(view, model, collectionGroup,
553                         collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
554             }
555 
556             if (!canViewField) {
557                 // since removing can impact layout, set to hidden
558                 // TODO: check into encryption setting
559                 lineField.setHidden(true);
560 
561                 if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.HIDDEN)) {
562                     lineField.getPropertyExpressions().remove(UifPropertyPaths.HIDDEN);
563                 }
564 
565                 continue;
566             }
567 
568             // check edit field auth
569             boolean canEditField = !readOnlyLine;
570             if (!readOnlyLine) {
571                 canEditField = authorizer.canEditLineField(view, model, collectionGroup,
572                         collectionGroup.getPropertyName(), currentLine, lineField, propertyName, user);
573                 if (canEditField) {
574                     canEditField = presentationController.canEditLineField(view, model, collectionGroup,
575                             collectionGroup.getPropertyName(), currentLine, lineField, propertyName);
576                 }
577             }
578 
579             if (readOnlyLine || !canEditField) {
580                 lineField.setReadOnly(true);
581 
582                 if (lineField.getPropertyExpressions().containsKey(UifPropertyPaths.READ_ONLY)) {
583                     lineField.getPropertyExpressions().remove(UifPropertyPaths.READ_ONLY);
584                 }
585             }
586         }
587 
588         // check auth on line actions
589         List<Action> actions = ViewLifecycleUtils.getElementsOfTypeDeep(actionList, Action.class);
590         for (Action action : actions) {
591             if (!action.isRender()) {
592                 continue;
593             }
594 
595             boolean canPerformAction = authorizer.canPerformLineAction(view, model, collectionGroup,
596                     collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(), action.getId(),
597                     user);
598             if (canPerformAction) {
599                 canPerformAction = presentationController.canPerformLineAction(view, model, collectionGroup,
600                         collectionGroup.getPropertyName(), currentLine, action, action.getActionEvent(),
601                         action.getId());
602             }
603 
604             if (!canPerformAction) {
605                 action.setRender(false);
606 
607                 if (action.getPropertyExpressions().containsKey(UifPropertyPaths.RENDER)) {
608                     action.getPropertyExpressions().remove(UifPropertyPaths.RENDER);
609                 }
610             }
611         }
612     }
613 
614     /**
615      * For each configured sub collection of the collection group, creates a field group by copying
616      * {@link org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()} and adds
617      * to a list which is stored in the line context.
618      */
619     protected void buildSubCollectionFieldGroups() {
620         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
621 
622         String idSuffix = lineBuilderContext.getIdSuffix();
623 
624         // sub collections are not created for the add line
625         if (lineBuilderContext.isAddLine() || (collectionGroup.getSubCollections() == null)) {
626             return;
627         }
628 
629         List<FieldGroup> subCollectionFields = new ArrayList<FieldGroup>();
630         for (int subLineIndex = 0; subLineIndex < collectionGroup.getSubCollections().size(); subLineIndex++) {
631             CollectionGroup subCollectionPrototype = collectionGroup.getSubCollections().get(subLineIndex);
632             CollectionGroup subCollectionGroup = ComponentUtils.copy(subCollectionPrototype);
633 
634             // verify the sub-collection should be rendered
635             boolean renderSubCollection = checkSubCollectionRender(subCollectionGroup);
636             if (!renderSubCollection) {
637                 continue;
638             }
639 
640             subCollectionGroup.getBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
641             if (subCollectionGroup.isRenderAddLine()) {
642                 subCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath());
643             }
644 
645             FieldGroup fieldGroupPrototype =
646                     lineBuilderContext.getLayoutManager().getSubCollectionFieldGroupPrototype();
647 
648             FieldGroup subCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype,
649                     idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
650             subCollectionFieldGroup.setGroup(subCollectionGroup);
651 
652             subCollectionFieldGroup.setContainerIdSuffix(idSuffix);
653 
654             ContextUtils.updateContextForLine(subCollectionFieldGroup, collectionGroup,
655                     lineBuilderContext.getCurrentLine(), lineBuilderContext.getLineIndex(),
656                     idSuffix + UifConstants.IdSuffixes.SUB + subLineIndex);
657             ContextUtils.pushObjectToContextDeep(subCollectionGroup, UifConstants.ContextVariableNames.PARENT_LINE,
658                     lineBuilderContext.getCurrentLine());
659 
660             subCollectionFields.add(subCollectionFieldGroup);
661         }
662 
663         ContextUtils.pushObjectToContextDeep(subCollectionFields, UifConstants.ContextVariableNames.PARENT_LINE,
664                 lineBuilderContext.getCurrentLine());
665 
666         // set the parent line on the context of every sub-collection field
667         for (FieldGroup subCollectionField : subCollectionFields) {
668             Group group = subCollectionField.getGroup();
669             if (group != null && group instanceof CollectionGroup) {
670                 CollectionGroup collectionGroup1 = (CollectionGroup) group;
671                 ContextUtils.pushObjectToContextDeep(collectionGroup1.getItems(),
672                         UifConstants.ContextVariableNames.PARENT_LINE, lineBuilderContext.getCurrentLine());
673             }
674         }
675 
676         lineBuilderContext.setSubCollectionFields(subCollectionFields);
677     }
678 
679     /**
680      * Checks whether the given sub-collection should be rendered, any conditional render string is evaluated.
681      *
682      * @param subCollectionGroup sub collection group to check render status for
683      * @return true if sub collection should be rendered, false if it
684      * should not be rendered
685      */
686     protected boolean checkSubCollectionRender(CollectionGroup subCollectionGroup) {
687         String conditionalRender = subCollectionGroup.getPropertyExpression(UifPropertyPaths.RENDER);
688 
689         // TODO: check authorizer
690 
691         // evaluate conditional render string if set
692         if (StringUtils.isNotBlank(conditionalRender)) {
693             Map<String, Object> context = new HashMap<String, Object>();
694             Map<String, Object> viewContext = ViewLifecycle.getView().getContext();
695 
696             if (viewContext != null) {
697                 context.putAll(viewContext);
698             }
699 
700             context.put(UifConstants.ContextVariableNames.PARENT, lineBuilderContext.getCollectionGroup());
701             context.put(UifConstants.ContextVariableNames.COMPONENT, subCollectionGroup);
702 
703             Boolean render = (Boolean) ViewLifecycle.getExpressionEvaluator().evaluateExpression(context,
704                     conditionalRender);
705             subCollectionGroup.setRender(render);
706         }
707 
708         return subCollectionGroup.isRender();
709     }
710 
711     /**
712      * Add additional information to the fields in the add line to allow for correct add control selection.
713      *
714      * @param lineFields list of fields instances for the line
715      */
716     protected void setupAddLineControlValidation(List<Field> lineFields) {
717         // don't process for anything but an add line
718         if (!lineBuilderContext.isAddLine()) {
719             return;
720         }
721 
722         // set up skipping fields with the given selectors in add area during standard form validation calls
723         // custom addLineToCollection js call will validate these fields manually on an add
724         List<String> selectors = new ArrayList<String>();
725         String lineFieldSelector = UifConstants.IdSuffixes.CONTROL;
726         String nestedLineFieldSelector = UifConstants.IdSuffixes.ADD_LINE + UifConstants.IdSuffixes.CONTROL;
727 
728         // apply changes to and collect selectors from all fields and field groups
729         for (Field lineField : lineFields) {
730             if (lineField instanceof InputField) {
731                 setupAddLineControlValidation((InputField) lineField, selectors, lineFieldSelector);
732             } else if (lineField instanceof FieldGroup) {
733                 Group group = ((FieldGroup) lineField).getGroup();
734                 List<InputField> nestedLineFields = ViewLifecycleUtils.getElementsOfTypeDeep(group, InputField.class);
735 
736                 for (InputField nestedLineField : nestedLineFields) {
737                     setupAddLineControlValidation(nestedLineField, selectors, nestedLineFieldSelector);
738                 }
739             }
740         }
741 
742         // add collected selectors to data attributes
743         lineBuilderContext.getCollectionGroup().addDataAttribute(
744                 UifConstants.DataAttributes.ADD_CONTROLS, StringUtils.join(selectors, ","));
745     }
746 
747     /**
748      * Add additional information to a field in the add line to allow for correct add control selection.
749      *
750      * @param lineField field instance for the line
751      * @param selectors list of selectors
752      * @param suffix id suffix to add
753      */
754     protected void setupAddLineControlValidation(InputField lineField, List<String> selectors, String suffix) {
755         Control control = lineField.getControl();
756 
757         // ignore automatic validation and grab the selector for manual validation
758         if (control != null) {
759             control.addStyleClass(CssConstants.Classes.IGNORE_VALID);
760             selectors.add("#" + lineField.getId() + suffix);
761         }
762     }
763 
764     /**
765      * Setup edit line dialog group with the line fields
766      *
767      * <p>The items for a dialog are created from line fields and added if not provided by the user, but
768      * if they are then each item is processed.</p>
769      */
770     protected void setupEditLineDetails() {
771         CollectionGroup group = lineBuilderContext.getCollectionGroup();
772 
773         if (!group.isEditWithDialog()) {
774             return;
775         }
776 
777         for (DialogGroup lineDialog : group.getLineDialogs()) {
778             String dialogId = lineDialog.getId();
779 
780             UifFormBase form = (UifFormBase) lineBuilderContext.getModel();
781             if (group.getCollectionGroupBuilder().refreshEditLineDialogContents(lineDialog, form, group,
782                     lineBuilderContext.getLineIndex()) && lineDialog.getId().contains(
783                     ComponentFactory.EDIT_LINE_DIALOG)) {
784                 form.setUpdateComponent(lineDialog);
785                 // if there are no custom user items or if there are no items, then use the line fields as items
786                 if (lineDialog.getItems() == null || lineDialog.getItems().isEmpty() || !group
787                         .isCustomEditLineDialog()) {
788                     List<Field> lineFields = lineBuilderContext.getLineFields();
789 
790                     // process and set items
791                     lineDialog.setItems(processDialogFieldsFromLineFields(lineFields, dialogId));
792 
793                     // process the sub-collection items
794                     List<Component> items = new ArrayList<Component>(lineDialog.getItems());
795                     items.addAll(processDialogSubFieldsFromLineSubFields(lineDialog));
796                     items.addAll(processDialogSubFieldsFromRowDetails(lineDialog));
797                     lineDialog.setItems(items);
798                 } else { // user provided dialog items
799                     List<Component> dialogFields = new ArrayList<>(lineDialog.getItems());
800                     List<Component> dialogComponents = new ArrayList<>();
801                     int fieldIndex = 0;
802                     int subIndex = 0;
803 
804                     // for every user provided dialog item, find its corresponding line field and set the binding info
805                     for (Component dialogField : dialogFields) {
806                         if (dialogField instanceof DataField) {
807                             DataField dataField = (DataField) dialogField;
808                             DataField lineField = findItemInLineFields(dataField);
809 
810                             if (lineField != null) {
811                                 dataField.getBindingInfo().setCollectionPath(
812                                         lineField.getBindingInfo().getCollectionPath());
813                                 // set the line field to read-only
814                                 lineField.setReadOnly(true);
815                             } else {
816                                 // update the binding info on the custom field
817                                 dataField.getBindingInfo().setCollectionPath(group.getBindingInfo().getBindingName());
818                             }
819 
820                             dataField.getBindingInfo().setBindByNamePrefix(UifPropertyPaths.DIALOG_DATA_OBJECT);
821                             dialogComponents.add(dataField);
822                         } else if (dialogField instanceof FieldGroup) {
823                             FieldGroup fieldGroup = (FieldGroup) dialogField;
824 
825                             if (fieldGroup.getGroup() instanceof CollectionGroup) {
826                                 dialogComponents.add(getNewFieldGroup(fieldGroup, (CollectionGroup) fieldGroup.
827                                         getGroup(), lineDialog, fieldIndex, subIndex, null));
828                                 subIndex++;
829                             }
830                         } else if (dialogField instanceof CollectionGroup) {
831                             CollectionGroup collectionGroup = (CollectionGroup) dialogField;
832                             FieldGroup fieldGroupPrototype =
833                                     lineBuilderContext.getLayoutManager().getSubCollectionFieldGroupPrototype();
834 
835                             dialogComponents.add(getNewFieldGroup(fieldGroupPrototype, collectionGroup, lineDialog,
836                                     fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
837                             subIndex++;
838                         } else {
839                             ComponentUtils.prefixBindingPath(dialogField, UifPropertyPaths.DIALOG_DATA_OBJECT);
840                             dialogComponents.add(dialogField);
841                         }
842                         fieldIndex++;
843                     }
844                     lineDialog.setItems(dialogComponents);
845                 }
846             }
847 
848         }
849 
850         // set all collection fields and sub-collection fields to readOnly
851         if (lineBuilderContext.getCollectionGroup().isEditWithDialog()) {
852             for (Field lineField : lineBuilderContext.getLineFields()) {
853                 if (lineField instanceof InputField) {
854                     lineField.setReadOnly(Boolean.TRUE);
855                 }
856             }
857             List<FieldGroup> subLineFields = lineBuilderContext.getSubCollectionFields();
858             if (subLineFields != null) {
859                 for (FieldGroup subLineField : subLineFields) {
860                     subLineField.setReadOnly(Boolean.TRUE);
861                 }
862             }
863         }
864     }
865 
866     /**
867      * Helper method to build sub-collection fields for the given dialog using the line's sub-collection fields
868      *
869      * @param lineDialog the line dialog to build the sub-collection for
870      * @return the list of created sub-collection fields
871      */
872     private List<FieldGroup> processDialogSubFieldsFromLineSubFields(DialogGroup lineDialog) {
873         // process the subcollections
874         List<FieldGroup> subCollectionFields = lineBuilderContext.getSubCollectionFields();
875         List<FieldGroup> newSubCollectionFields = new ArrayList<FieldGroup>();
876         int fieldIndex = lineDialog.getItems().size();
877         int subIndex = 0;
878 
879         for (FieldGroup subCollectionFieldGroup : subCollectionFields) {
880             CollectionGroup subCollectionGroup = (CollectionGroup) subCollectionFieldGroup.getGroup();
881 
882             // make a copy of the sub-group for the dialog
883             newSubCollectionFields.add(getNewFieldGroup(subCollectionFieldGroup, subCollectionGroup, lineDialog,
884                     fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
885             fieldIndex++;
886             subIndex++;
887 
888             List<Component> components = ViewLifecycleUtils.getElementsOfTypeDeep(subCollectionGroup.getItems(),
889                     Component.class);
890             for (Component component : components) {
891                 component.setReadOnly(Boolean.TRUE);
892             }
893         }
894 
895         return newSubCollectionFields;
896     }
897 
898     /**
899      * Helper method to build sub-collection fields for the given dialog using the row details group
900      *
901      * @param lineDialog the line dialog to build the sub-collection for
902      * @return the list of created sub-collection fields
903      */
904     private List<Field> processDialogSubFieldsFromRowDetails(DialogGroup lineDialog) {
905         List<Field> newSubCollectionFields = new ArrayList<Field>();
906 
907         // process the row details group
908         CollectionLayoutManager layoutManager = (CollectionLayoutManager) lineBuilderContext.
909                 getCollectionGroup().getLayoutManager();
910 
911         // only the table layout manager has row details
912         if (layoutManager instanceof TableLayoutManagerBase) {
913             TableLayoutManagerBase tableLayoutManagerBase = (TableLayoutManagerBase) layoutManager;
914             Group rowDetailsGroup = tableLayoutManagerBase.getRowDetailsGroup();
915 
916             if (rowDetailsGroup != null) {
917                 List<Component> subCollectionComponents = new ArrayList<Component>(rowDetailsGroup.getItems());
918 
919                 int fieldIndex = lineDialog.getItems().size();
920                 int subIndex = 0;
921 
922                 // for each item in the row details group create a field group to add the the line dialog's items
923                 for (Component component : subCollectionComponents) {
924                     if (component instanceof CollectionGroup) {
925                         CollectionGroup subCollectionGroup = (CollectionGroup) component;
926 
927                         boolean renderSubCollection = checkSubCollectionRender(subCollectionGroup);
928                         if (!renderSubCollection) {
929                             continue;
930                         }
931 
932                         FieldGroup fieldGroupPrototype = lineBuilderContext.
933                                 getLayoutManager().getSubCollectionFieldGroupPrototype();
934                         newSubCollectionFields.add(getNewFieldGroup(fieldGroupPrototype, subCollectionGroup, lineDialog,
935                                 fieldIndex, subIndex, UifPropertyPaths.DIALOG_DATA_OBJECT));
936                         subIndex++;
937                     } else if (component instanceof Field) {
938                         Field subCollectionField = (Field) component;
939                         Field newSubCollectionField = getNewFieldForEditLineDialog(subCollectionField,
940                                 lineDialog.getId() + UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex++));
941 
942                         newSubCollectionFields.add(newSubCollectionField);
943                     }
944 
945                     ContextUtils.pushObjectToContextDeep(newSubCollectionFields,
946                             UifConstants.ContextVariableNames.PARENT_LINE,
947                             ((UifFormBase) lineBuilderContext.getModel()).getDialogDataObject());
948                 }
949             }
950         }
951 
952         return newSubCollectionFields;
953     }
954 
955     /**
956      * Helper method that creates a new field group for a given sub-collection.
957      *
958      * @param fieldGroupPrototype the field group prototype to use
959      * @param subCollectionGroup the sub-collection to create field group for
960      * @param lineDialog the line dialog that the field group should be in
961      * @param fieldIndex the index to apply for the field group
962      * @param subIndex the index to apply for the sub-collection
963      * @return the created field group
964      */
965     private FieldGroup getNewFieldGroup(FieldGroup fieldGroupPrototype, CollectionGroup subCollectionGroup,
966             DialogGroup lineDialog, int fieldIndex, int subIndex, String bindingPrefix) {
967         FieldGroup newSubCollectionFieldGroup = ComponentUtils.copy(fieldGroupPrototype);
968         newSubCollectionFieldGroup.setId(lineDialog.getId() +
969                 UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex));
970         newSubCollectionFieldGroup.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, lineDialog);
971 
972         CollectionGroup newSubCollectionGroup = ComponentUtils.copy(subCollectionGroup);
973         newSubCollectionGroup.setId(newSubCollectionFieldGroup.getId() + UifConstants.IdSuffixes.SUB +
974                 Integer.toString(subIndex));
975         if (bindingPrefix != null) {
976             newSubCollectionGroup.getBindingInfo().setBindByNamePrefix(bindingPrefix);
977         }
978 
979         if (newSubCollectionGroup.getBindingInfo().getBindingName() == null) {
980             newSubCollectionGroup.getBindingInfo().setBindingName(newSubCollectionGroup.getPropertyName());
981         }
982 
983         newSubCollectionGroup.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, lineDialog);
984         newSubCollectionGroup.addDataAttribute(UifConstants.
985                 ContextVariableNames.PARENT, lineDialog.getId());
986 
987         if (newSubCollectionGroup.isRenderAddLine()) {
988             newSubCollectionGroup.getAddLineBindingInfo().setBindByNamePrefix(newSubCollectionGroup.
989                     getBindingInfo().getBindByNamePrefix());
990             newSubCollectionGroup.getAddLineBindingInfo().setBindingName(newSubCollectionGroup.
991                     getBindingInfo().getBindingName());
992             String addBindingPath = UifPropertyPaths.NEW_COLLECTION_LINES + "['" +
993                     newSubCollectionGroup.getBindingInfo().getBindByNamePrefix() + "." +
994                     newSubCollectionGroup.getBindingInfo().getBindingName() + "']";
995             Object addLine = ObjectPropertyUtils.getPropertyValue(lineBuilderContext.getModel(), addBindingPath);
996             if (addLine != null) {
997                 ObjectPropertyUtils.setPropertyValue(lineBuilderContext.getModel(), addBindingPath, null);
998             }
999         }
1000 
1001         // set the new collection group's line actions to refresh the entire
1002         // dialog not just the sub-collection
1003         List<Action> subLineActions = ViewLifecycleUtils.getElementsOfTypeDeep(newSubCollectionGroup.getLineActions(),
1004                 Action.class);
1005         setupSubCollectionActions(subLineActions, lineDialog.getId(),
1006                 lineBuilderContext.getCollectionGroup().getBindingInfo().getBindingName(),
1007                 lineBuilderContext.getLineIndex());
1008 
1009         // initialize the new sub-collections line actions
1010         lineBuilderContext.getCollectionGroup().getCollectionGroupBuilder().initializeLineActions(subLineActions,
1011                 ViewLifecycle.getView(), newSubCollectionGroup, lineBuilderContext.getCurrentLine(),
1012                 lineBuilderContext.getLineIndex());
1013 
1014         // get the add line actions for this group
1015             List<Component> subAddLineComponents = new ArrayList<Component>(newSubCollectionGroup.getAddLineActions());
1016             if (newSubCollectionGroup.getAddBlankLineAction() != null) {
1017                 subAddLineComponents.add(newSubCollectionGroup.getAddBlankLineAction());
1018             }
1019             List<Action> subAddLineActions = ViewLifecycleUtils.getElementsOfTypeDeep(subAddLineComponents,
1020                     Action.class);
1021 
1022             // initialize the new sub-collections add line actions
1023             setupSubCollectionActions(subAddLineActions, lineDialog.getId(), lineBuilderContext.
1024                             getCollectionGroup().getBindingInfo().getBindingName(), lineBuilderContext.getLineIndex()
1025             );
1026 
1027         newSubCollectionFieldGroup.setGroup(newSubCollectionGroup);
1028 
1029         ContextUtils.updateContextForLine(newSubCollectionFieldGroup, lineBuilderContext.
1030                         getCollectionGroup(), ((UifFormBase) lineBuilderContext.getModel()).
1031                         getDialogDataObject(), lineBuilderContext.getLineIndex(),
1032                 lineBuilderContext.getIdSuffix() + UifConstants.IdSuffixes.SUB + subIndex
1033         );
1034         ContextUtils.pushObjectToContextDeep(newSubCollectionGroup, UifConstants.ContextVariableNames.PARENT_LINE,
1035                 ((UifFormBase) lineBuilderContext.getModel()).getDialogDataObject());
1036         return newSubCollectionFieldGroup;
1037     }
1038 
1039     /**
1040      * Helper method that builds line dialog fields from the line fields
1041      *
1042      * @param lineFields the fields in the component
1043      * @param prefix the prefix to use in the id for the new fields
1044      * @return the list of created fields
1045      */
1046     private List<Field> processDialogFieldsFromLineFields(List<Field> lineFields, String prefix) {
1047         List<Field> newLineFields = new ArrayList<Field>();
1048 
1049         // for each line field create and add a corresponding dialog field
1050         int fieldIndex = 0;
1051         for (Field lineField : lineFields) {
1052             if (!(lineField instanceof FieldGroup)) {
1053                 Field newLineField = getNewFieldForEditLineDialog(lineField, prefix +
1054                         UifConstants.IdSuffixes.FIELDSET + Integer.toString(fieldIndex));
1055                 newLineFields.add(newLineField);
1056                 fieldIndex++;
1057             }
1058         }
1059         return newLineFields;
1060     }
1061 
1062     /**
1063      * Helper method to create a new field that is a copy of a given field for the edit line dialog.
1064      *
1065      * @param field the field to copy
1066      * @param id the id of the new field
1067      * @return the new field
1068      */
1069     private Field getNewFieldForEditLineDialog(Field field, String id) {
1070         Field newLineField = ComponentUtils.copy(field, id);
1071 
1072         // set the line field to point to the dialog's data object
1073         if (newLineField instanceof DataField) {
1074             ((DataField) newLineField).getBindingInfo().setBindByNamePrefix(UifPropertyPaths.DIALOG_DATA_OBJECT);
1075         }
1076         return newLineField;
1077     }
1078 
1079     /**
1080      * Helper method to setup edit line dialog's sub-collection's line actions.
1081      *
1082      * @param actions the actions to setup
1083      * @param dialogId the id of the dialog the sub-collection is in
1084      * @param bindingName the binding name of the dialog's sub-collection
1085      * @param lineIndex the index of the line being edited in the dialog
1086      */
1087     private void setupSubCollectionActions(List<Action> actions, String dialogId, String bindingName, int lineIndex) {
1088         for (Action action : actions) {
1089             action.setDialogDismissOption("REQUEST");
1090             action.setRefreshId(StringUtils.substring(dialogId, dialogId.indexOf("_") + 1, dialogId.lastIndexOf("_")));
1091             String actionScript = UifConstants.JsFunctions.SHOW_EDIT_LINE_DIALOG + "('" +
1092                     dialogId + "', '" + bindingName + "', " + lineIndex + ");";
1093             action.setRefreshedByAction(false);
1094             action.setSuccessCallback("jQuery.unblockUI();" + actionScript);
1095             action.setOnClickScript("jQuery('#" + dialogId +
1096                 "').one('hide.bs.modal', function(e) { jQuery.blockUI({ message: '<h1>Editing line ...</h1>' }); });");
1097             action.addActionParameter(UifParameters.DIALOG_ID, dialogId);
1098         }
1099     }
1100 
1101     /**
1102      * Helper method that gets a line item that corresponds to the given field
1103      *
1104      * <p>In this case, the corresponding line item for a field is one where the field's property names
1105      * are the same.</p>
1106      *
1107      * @param dataItem the data field to get the line item for
1108      */
1109     private DataField findItemInLineFields(DataField dataItem) {
1110         for (Field field : lineBuilderContext.getLineFields()) {
1111             if (field instanceof DataField) {
1112                 if (dataItem.getPropertyName().equals(((DataField) field).getPropertyName())) {
1113                     return (DataField) field;
1114                 }
1115             }
1116         }
1117         return null;
1118     }
1119 
1120     /**
1121      * Helper method to build the context for a field (needed because the apply model phase for line fields has
1122      * not been applied yet and their full context not set)
1123      *
1124      * @param view view instance the field belongs to
1125      * @param collectionGroup collection group instance the field belongs to
1126      * @param field field instance to build context for
1127      * @return Map<String, Object> context for field
1128      */
1129     protected Map<String, Object> getContextForField(View view, CollectionGroup collectionGroup, Field field) {
1130         Map<String, Object> context = new HashMap<String, Object>();
1131 
1132         Map<String, Object> viewContext = view.getContext();
1133         if (viewContext != null) {
1134             context.putAll(viewContext);
1135         }
1136 
1137         Map<String, Object> fieldContext = field.getContext();
1138         if (fieldContext != null) {
1139             context.putAll(fieldContext);
1140         }
1141 
1142         context.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
1143         context.put(UifConstants.ContextVariableNames.COMPONENT, field);
1144 
1145         return context;
1146     }
1147 }