View Javadoc
1   /**
2    * Copyright 2005-2014 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.layout;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.UifPropertyPaths;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.component.DataBinding;
29  import org.kuali.rice.krad.uif.component.KeepExpression;
30  import org.kuali.rice.krad.uif.container.CollectionGroup;
31  import org.kuali.rice.krad.uif.container.Container;
32  import org.kuali.rice.krad.uif.container.Group;
33  import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
34  import org.kuali.rice.krad.uif.element.Action;
35  import org.kuali.rice.krad.uif.element.Message;
36  import org.kuali.rice.krad.uif.field.Field;
37  import org.kuali.rice.krad.uif.field.FieldGroup;
38  import org.kuali.rice.krad.uif.layout.collections.CollectionPagingHelper;
39  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
40  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
41  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
42  import org.kuali.rice.krad.uif.util.ComponentUtils;
43  import org.kuali.rice.krad.uif.util.LifecycleElement;
44  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
45  import org.kuali.rice.krad.uif.view.ViewModel;
46  import org.kuali.rice.krad.uif.widget.Pager;
47  import org.kuali.rice.krad.util.KRADUtils;
48  import org.kuali.rice.krad.web.form.UifFormBase;
49  
50  /**
51   * Layout manager that works with {@code CollectionGroup} containers and
52   * renders the collection lines in a vertical row
53   *
54   * <p>
55   * For each line of the collection, a {@code Group} instance is created.
56   * The group header contains a label for the line (summary information), the
57   * group fields are the collection line fields, and the group footer contains
58   * the line actions. All the groups are rendered using the
59   * {@code BoxLayoutManager} with vertical orientation.
60   * </p>
61   *
62   * <p>
63   * Modify the lineGroupPrototype to change header/footer styles or any other
64   * customization for the line groups
65   * </p>
66   *
67   * @author Kuali Rice Team (rice.collab@kuali.org)
68   */
69  @BeanTags({@BeanTag(name = "stackedCollectionLayout-bean", parent = "Uif-StackedCollectionLayoutBase"),
70          @BeanTag(name = "stackedCollectionLayout-withGridItems-bean",
71                  parent = "Uif-StackedCollectionLayout-WithGridItems"),
72          @BeanTag(name = "stackedCollectionLayout-withBoxItems-bean",
73                  parent = "Uif-StackedCollectionLayout-WithBoxItems"),
74          @BeanTag(name = "stackedCollectionLayout-list-bean", parent = "Uif-StackedCollectionLayout-List")})
75  public class StackedLayoutManagerBase extends LayoutManagerBase implements StackedLayoutManager {
76      private static final long serialVersionUID = 4602368505430238846L;
77  
78      @KeepExpression
79      private String summaryTitle;
80      private List<String> summaryFields;
81  
82      private Group addLineGroup;
83      private Group lineGroupPrototype;
84      private FieldGroup subCollectionFieldGroupPrototype;
85      private Field selectFieldPrototype;
86      private Group wrapperGroup;
87      private Pager pagerWidget;
88  
89      private List<Group> stackedGroups;
90  
91      private boolean actionsInLineGroup;
92  
93      public StackedLayoutManagerBase() {
94          super();
95  
96          summaryFields = new ArrayList<String>();
97          stackedGroups = new ArrayList<Group>();
98      }
99  
100     /**
101      * {@inheritDoc}
102      */
103     @Override
104     public void performInitialization(Object model) {
105         super.performInitialization(model);
106 
107         stackedGroups = new ArrayList<Group>();
108     }
109 
110     /**
111      * {@inheritDoc}
112      */
113     @Override
114     public void performApplyModel(Object model, LifecycleElement component) {
115         super.performApplyModel(model, component);
116 
117         if (wrapperGroup != null) {
118             wrapperGroup.setItems(stackedGroups);
119         }
120     }
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
126     public void performFinalize(Object model, LifecycleElement element) {
127         super.performFinalize(model, element);
128 
129         boolean serverPagingEnabled =
130                 (element instanceof CollectionGroup) && ((CollectionGroup) element).isUseServerPaging();
131 
132         // set the appropriate page, total pages, and link script into the Pager
133         if (serverPagingEnabled && this.getPagerWidget() != null) {
134             CollectionLayoutUtils.setupPagerWidget(pagerWidget, (CollectionGroup) element, model);
135         }
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     @Override
142     public void buildLine(LineBuilderContext lineBuilderContext) {
143         List<Field> lineFields = lineBuilderContext.getLineFields();
144         CollectionGroup collectionGroup = lineBuilderContext.getCollectionGroup();
145         int lineIndex = lineBuilderContext.getLineIndex();
146         String idSuffix = lineBuilderContext.getIdSuffix();
147         Object currentLine = lineBuilderContext.getCurrentLine();
148         List<? extends Component> actions = lineBuilderContext.getLineActions();
149         String bindingPath = lineBuilderContext.getBindingPath();
150 
151         // construct new group
152         Group lineGroup = null;
153         if (lineBuilderContext.isAddLine()) {
154             stackedGroups = new ArrayList<Group>();
155 
156             if (addLineGroup == null) {
157                 lineGroup = ComponentUtils.copy(lineGroupPrototype, idSuffix);
158             } else {
159                 lineGroup = ComponentUtils.copy(getAddLineGroup(), idSuffix);
160                 lineGroup.addStyleClass(collectionGroup.getAddItemCssClass());
161             }
162 
163             if (collectionGroup.isAddViaLightBox()) {
164                 String actionScript = "showLightboxComponent('" + lineGroup.getId() + "');";
165                 if (StringUtils.isNotBlank(collectionGroup.getAddViaLightBoxAction().getActionScript())) {
166                     actionScript = collectionGroup.getAddViaLightBoxAction().getActionScript() + actionScript;
167                 }
168                 collectionGroup.getAddViaLightBoxAction().setActionScript(actionScript);
169                 lineGroup.setStyle("display: none");
170             }
171         } else {
172             lineGroup = ComponentUtils.copy(lineGroupPrototype, idSuffix);
173         }
174 
175         if (((UifFormBase) lineBuilderContext.getModel()).isAddedCollectionItem(currentLine)) {
176             lineGroup.addStyleClass(collectionGroup.getNewItemsCssClass());
177         }
178 
179         // any actions that are attached to the group prototype (like the header) need to get action parameters
180         // and context set for the collection line
181         List<Action> lineGroupActions = ViewLifecycleUtils.getElementsOfTypeDeep(lineGroup, Action.class);
182         if (lineGroupActions != null) {
183             collectionGroup.getCollectionGroupBuilder().initializeActions(lineGroupActions, collectionGroup, lineIndex);
184             ComponentUtils.updateContextsForLine(lineGroupActions, collectionGroup, currentLine, lineIndex, idSuffix);
185         }
186 
187         ComponentUtils.updateContextForLine(lineGroup, collectionGroup, currentLine, lineIndex, idSuffix);
188 
189         // build header for the group
190         if (lineBuilderContext.isAddLine()) {
191             if (lineGroup.getHeader() != null) {
192                 Message headerMessage = ComponentUtils.copy(collectionGroup.getAddLineLabel());
193                 lineGroup.getHeader().setRichHeaderMessage(headerMessage);
194             }
195         } else {
196             // get the collection for this group from the model
197             List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(lineBuilderContext.getModel(),
198                     ((DataBinding) collectionGroup).getBindingInfo().getBindingPath());
199 
200             String headerText = buildLineHeaderText(modelCollection.get(lineIndex), lineGroup);
201 
202             // don't set header if text is blank (could already be set by other means)
203             if (StringUtils.isNotBlank(headerText) && lineGroup.getHeader() != null) {
204                 lineGroup.getHeader().setHeaderText(headerText);
205             }
206         }
207 
208         // stack all fields (including sub-collections) for the group
209         List<Component> groupFields = new ArrayList<Component>();
210         groupFields.addAll(lineFields);
211 
212         if (lineBuilderContext.getSubCollectionFields() != null) {
213             groupFields.addAll(lineBuilderContext.getSubCollectionFields());
214         }
215 
216         // set line actions on group footer
217         if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly() && (lineGroup.getFooter() != null)) {
218             // add the actions to the line group if isActionsInLineGroup flag is true
219             if (isActionsInLineGroup()) {
220                 groupFields.addAll(actions);
221                 lineGroup.setRenderFooter(false);
222             } else {
223                 lineGroup.getFooter().setItems(actions);
224             }
225         }
226 
227         lineGroup.setItems(groupFields);
228         
229         // Must evaluate the client-side state on the lineGroup's disclosure for PlaceholderDisclosureGroup processing
230         if (lineBuilderContext.getModel() instanceof ViewModel){
231             KRADUtils.syncClientSideStateForComponent(lineGroup.getDisclosure(),
232                     ((ViewModel) lineBuilderContext.getModel()).getClientStateForSyncing());
233         }
234 
235         stackedGroups.add(lineGroup);
236     }
237 
238     /**
239      * Builds the header text for the collection line
240      *
241      * <p>
242      * Header text is built up by first the collection label, either specified
243      * on the collection definition or retrieved from the dictionary. Then for
244      * each summary field defined, the value from the model is retrieved and
245      * added to the header.
246      * </p>
247      *
248      * <p>
249      * Note the {@link #getSummaryTitle()} field may have expressions defined, in which cause it will be copied to the
250      * property expressions map to set the title for the line group (which will have the item context variable set)
251      * </p>
252      *
253      * @param line Collection line containing data
254      * @param lineGroup Group instance for rendering the line and whose title should be built
255      * @return header text for line
256      */
257     protected String buildLineHeaderText(Object line, Group lineGroup) {
258         // check for expression on summary title
259         if (ViewLifecycle.getExpressionEvaluator().containsElPlaceholder(summaryTitle)) {
260             lineGroup.getPropertyExpressions().put(UifPropertyPaths.HEADER_TEXT, summaryTitle);
261             return null;
262         }
263 
264         // build up line summary from declared field values and fixed title
265         String summaryFieldString = "";
266         for (String summaryField : summaryFields) {
267             Object summaryFieldValue = ObjectPropertyUtils.getPropertyValue(line, summaryField);
268             if (StringUtils.isNotBlank(summaryFieldString)) {
269                 summaryFieldString += " - ";
270             }
271 
272             if (summaryFieldValue != null) {
273                 summaryFieldString += summaryFieldValue;
274             } else {
275                 summaryFieldString += "Null";
276             }
277         }
278 
279         String headerText = summaryTitle;
280         if (StringUtils.isNotBlank(summaryFieldString)) {
281             headerText += " ( " + summaryFieldString + " )";
282         }
283 
284         return headerText;
285     }
286 
287     /**
288      * Invokes {@link org.kuali.rice.krad.uif.layout.collections.CollectionPagingHelper} to carry out the
289      * paging request.
290      *
291      * {@inheritDoc}
292      */
293     @Override
294     public void processPagingRequest(Object model, CollectionGroup collectionGroup) {
295         String pageNumber = ViewLifecycle.getRequest().getParameter(UifConstants.PageRequest.PAGE_NUMBER);
296 
297         CollectionPagingHelper pagingHelper = new CollectionPagingHelper();
298         pagingHelper.processPagingRequest(ViewLifecycle.getView(), collectionGroup, (UifFormBase) model, pageNumber);
299     }
300 
301     /**
302      * {@inheritDoc}
303      */
304     @Override
305     public Class<? extends Container> getSupportedContainer() {
306         return CollectionGroup.class;
307     }
308 
309     /**
310      * {@inheritDoc}
311      */
312     @Override
313     @BeanTagAttribute(name = "summaryTitle")
314     public String getSummaryTitle() {
315         return this.summaryTitle;
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
321     @Override
322     public void setSummaryTitle(String summaryTitle) {
323         this.summaryTitle = summaryTitle;
324     }
325 
326     /**
327      * {@inheritDoc}
328      */
329     @Override
330     @BeanTagAttribute(name = "summaryFields", type = BeanTagAttribute.AttributeType.LISTVALUE)
331     public List<String> getSummaryFields() {
332         return this.summaryFields;
333     }
334 
335     /**
336      * {@inheritDoc}
337      */
338     @Override
339     public void setSummaryFields(List<String> summaryFields) {
340         this.summaryFields = summaryFields;
341     }
342 
343     /**
344      * {@inheritDoc}
345      */
346     @Override
347     @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
348     @BeanTagAttribute(name = "addLineGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
349     public Group getAddLineGroup() {
350         return this.addLineGroup;
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     @Override
357     public void setAddLineGroup(Group addLineGroup) {
358         this.addLineGroup = addLineGroup;
359     }
360 
361     /**
362      * {@inheritDoc}
363      */
364     @Override
365     @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
366     @BeanTagAttribute(name = "lineGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
367     public Group getLineGroupPrototype() {
368         return this.lineGroupPrototype;
369     }
370 
371     /**
372      * {@inheritDoc}
373      */
374     @Override
375     public void setLineGroupPrototype(Group lineGroupPrototype) {
376         this.lineGroupPrototype = lineGroupPrototype;
377     }
378 
379     /**
380      * {@inheritDoc}
381      */
382     @Override
383     @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
384     @BeanTagAttribute(name = "subCollectionFieldGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
385     public FieldGroup getSubCollectionFieldGroupPrototype() {
386         return this.subCollectionFieldGroupPrototype;
387     }
388 
389     /**
390      * {@inheritDoc}
391      */
392     @Override
393     public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) {
394         this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype;
395     }
396 
397     /**
398      * {@inheritDoc}
399      */
400     @Override
401     @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
402     @BeanTagAttribute(name = "selectFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
403     public Field getSelectFieldPrototype() {
404         return selectFieldPrototype;
405     }
406 
407     /**
408      * {@inheritDoc}
409      */
410     @Override
411     public void setSelectFieldPrototype(Field selectFieldPrototype) {
412         this.selectFieldPrototype = selectFieldPrototype;
413     }
414 
415     /**
416      * {@inheritDoc}
417      */
418     @Override
419     @BeanTagAttribute(name = "wrapperGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
420     public Group getWrapperGroup() {
421         return wrapperGroup;
422     }
423 
424     /**
425      * {@inheritDoc}
426      */
427     @Override
428     public void setWrapperGroup(Group wrapperGroup) {
429         this.wrapperGroup = wrapperGroup;
430     }
431 
432     /**
433      * {@inheritDoc}
434      */
435     @Override
436     public Pager getPagerWidget() {
437         return pagerWidget;
438     }
439 
440     /**
441      * {@inheritDoc}
442      */
443     @Override
444     public void setPagerWidget(Pager pagerWidget) {
445         this.pagerWidget = pagerWidget;
446     }
447 
448     /**
449      * {@inheritDoc}
450      */
451     @Override
452     @ViewLifecycleRestriction
453     @BeanTagAttribute(name = "stackedGroups", type = BeanTagAttribute.AttributeType.LISTBEAN)
454     public List<Group> getStackedGroups() {
455         return this.stackedGroups;
456     }
457 
458     /**
459      * {@inheritDoc}
460      */
461     @Override
462     public List<Group> getStackedGroupsNoWrapper() {
463         return wrapperGroup != null ? null : this.stackedGroups;
464     }
465 
466     /**
467      * {@inheritDoc}
468      */
469     @Override
470     public void setStackedGroups(List<Group> stackedGroups) {
471         this.stackedGroups = stackedGroups;
472     }
473 
474     /**
475      * {@inheritDoc}
476      */
477     @Override
478     public boolean isActionsInLineGroup() {
479         return actionsInLineGroup;
480     }
481 
482     /**
483      * {@inheritDoc}
484      */
485     @Override
486     public void setActionsInLineGroup(boolean actionsInLineGroup) {
487         this.actionsInLineGroup = actionsInLineGroup;
488     }
489 }