View Javadoc

1   /**
2    * Copyright 2005-2011 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
20  import org.kuali.rice.krad.uif.component.KeepExpression;
21  import org.kuali.rice.krad.uif.container.CollectionGroup;
22  import org.kuali.rice.krad.uif.container.Container;
23  import org.kuali.rice.krad.uif.container.Group;
24  import org.kuali.rice.krad.uif.field.FieldGroup;
25  import org.kuali.rice.krad.uif.view.View;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.component.DataBinding;
28  import org.kuali.rice.krad.uif.field.ActionField;
29  import org.kuali.rice.krad.uif.field.Field;
30  import org.kuali.rice.krad.uif.util.ComponentUtils;
31  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
32  
33  import java.util.ArrayList;
34  import java.util.List;
35  
36  /**
37   * Layout manager that works with <code>CollectionGroup</code> containers and
38   * renders the collection lines in a vertical row
39   *
40   * <p>
41   * For each line of the collection, a <code>Group</code> instance is created.
42   * The group header contains a label for the line (summary information), the
43   * group fields are the collection line fields, and the group footer contains
44   * the line actions. All the groups are rendered using the
45   * <code>BoxLayoutManager</code> with vertical orientation.
46   * </p>
47   *
48   * <p>
49   * Modify the lineGroupPrototype to change header/footer styles or any other
50   * customization for the line groups
51   * </p>
52   *
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   */
55  public class StackedLayoutManager extends LayoutManagerBase implements CollectionLayoutManager {
56      private static final long serialVersionUID = 4602368505430238846L;
57  
58      @KeepExpression
59      private String summaryTitle;
60      private List<String> summaryFields;
61  
62      private Group addLineGroup;
63      private Group lineGroupPrototype;
64      private FieldGroup subCollectionFieldGroupPrototype;
65      private Field selectFieldPrototype;
66      private Group wrapperGroup;
67  
68      private List<Group> stackedGroups;
69  
70      public StackedLayoutManager() {
71          super();
72  
73          summaryFields = new ArrayList<String>();
74          stackedGroups = new ArrayList<Group>();
75      }
76  
77      /**
78       * The following actions are performed:
79       *
80       * <ul>
81       * <li>Initializes the prototypes</li>
82       * </ul>
83       *
84       * @see org.kuali.rice.krad.uif.layout.BoxLayoutManager#performInitialization(org.kuali.rice.krad.uif.view.View,
85       *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
86       */
87      @Override
88      public void performInitialization(View view, Object model, Container container) {
89          super.performInitialization(view, model, container);
90  
91          stackedGroups = new ArrayList<Group>();
92  
93          if (addLineGroup != null) {
94              view.getViewHelperService().performComponentInitialization(view, model, addLineGroup);
95          }
96          view.getViewHelperService().performComponentInitialization(view, model, lineGroupPrototype);
97          view.getViewHelperService().performComponentInitialization(view, model, subCollectionFieldGroupPrototype);
98          view.getViewHelperService().performComponentInitialization(view, model, selectFieldPrototype);
99      }
100 
101     /**
102      * The following actions are performed:
103      *
104      * <ul>
105      * <li>If wrapper group is specified, places the stacked groups into the wrapper</li>
106      * </ul>
107      *
108      * @see org.kuali.rice.krad.uif.layout.BoxLayoutManager#performApplyModel(org.kuali.rice.krad.uif.view.View,
109      *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
110      */
111     @Override
112     public void performApplyModel(View view, Object model, Container container) {
113         super.performApplyModel(view, model, container);
114 
115         if (wrapperGroup != null) {
116             wrapperGroup.setItems(stackedGroups);
117         }
118     }
119 
120     /**
121      * Builds a <code>Group</code> instance for a collection line. The group is
122      * built by first creating a copy of the configured prototype. Then the
123      * header for the group is created using the configured summary fields on
124      * the <code>CollectionGroup</code>. The line fields passed in are set as
125      * the items for the group, and finally the actions are placed into the
126      * group footer
127      *
128      * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#buildLine(org.kuali.rice.krad.uif.view.View,
129      *      java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup,
130      *      java.util.List, java.util.List, java.lang.String, java.util.List,
131      *      java.lang.String, java.lang.Object, int)
132      */
133     public void buildLine(View view, Object model, CollectionGroup collectionGroup, List<Field> lineFields,
134             List<FieldGroup> subCollectionFields, String bindingPath, List<ActionField> actions, String idSuffix,
135             Object currentLine, int lineIndex) {
136         boolean isAddLine = lineIndex == -1;
137 
138         // construct new group
139         Group lineGroup = null;
140         if (isAddLine) {
141             stackedGroups = new ArrayList<Group>();
142 
143             if (addLineGroup == null) {
144                 lineGroup = ComponentUtils.copy(lineGroupPrototype, idSuffix);
145             } else {
146                 lineGroup = ComponentUtils.copy(getAddLineGroup(), idSuffix);
147             }
148         } else {
149             lineGroup = ComponentUtils.copy(lineGroupPrototype, idSuffix);
150         }
151 
152         ComponentUtils.updateContextForLine(lineGroup, currentLine, lineIndex);
153 
154         // build header text for group
155         String headerText = "";
156         if (isAddLine) {
157             headerText = collectionGroup.getAddLineLabel();
158         } else {
159             // get the collection for this group from the model
160             List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(model,
161                     ((DataBinding) collectionGroup).getBindingInfo().getBindingPath());
162 
163             headerText = buildLineHeaderText(modelCollection.get(lineIndex), lineGroup);
164         }
165 
166         // don't set header if text is blank (could already be set by other means)
167         if (StringUtils.isNotBlank(headerText)) {
168             lineGroup.getHeader().setHeaderText(headerText);
169         }
170 
171         // stack all fields (including sub-collections) for the group
172         List<Field> groupFields = new ArrayList<Field>();
173         groupFields.addAll(lineFields);
174         groupFields.addAll(subCollectionFields);
175 
176         lineGroup.setItems(groupFields);
177 
178         // set line actions on group footer
179         if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly() && (lineGroup.getFooter() != null)) {
180             lineGroup.getFooter().setItems(actions);
181         }
182 
183         stackedGroups.add(lineGroup);
184     }
185 
186     /**
187      * Builds the header text for the collection line
188      *
189      * <p>
190      * Header text is built up by first the collection label, either specified
191      * on the collection definition or retrieved from the dictionary. Then for
192      * each summary field defined, the value from the model is retrieved and
193      * added to the header.
194      * </p>
195      *
196      * <p>
197      * Note the {@link #getSummaryTitle()} field may have expressions defined, in which cause it will be copied to the
198      * property expressions map to set the title for the line group (which will have the item context variable set)
199      * </p>
200      *
201      * @param line - Collection line containing data
202      * @param lineGroup - Group instance for rendering the line and whose title should be built
203      * @return String header text for line
204      */
205     protected String buildLineHeaderText(Object line, Group lineGroup) {
206         // check for expression on summary title
207         if (KRADServiceLocatorWeb.getExpressionEvaluatorService().containsElPlaceholder(summaryTitle)) {
208             lineGroup.getPropertyExpressions().put("title", summaryTitle);
209             return null;
210         }
211 
212         // build up line summary from declared field values and fixed title
213         String summaryFieldString = "";
214         for (String summaryField : summaryFields) {
215             Object summaryFieldValue = ObjectPropertyUtils.getPropertyValue(line, summaryField);
216             if (StringUtils.isNotBlank(summaryFieldString)) {
217                 summaryFieldString += " - ";
218             }
219 
220             if (summaryFieldValue != null) {
221                 summaryFieldString += summaryFieldValue;
222             } else {
223                 summaryFieldString += "Null";
224             }
225         }
226 
227         String headerText = summaryTitle;
228         if (StringUtils.isNotBlank(summaryFieldString)) {
229             headerText += " ( " + summaryFieldString + " )";
230         }
231 
232         return headerText;
233     }
234 
235     /**
236      * @see org.kuali.rice.krad.uif.layout.ContainerAware#getSupportedContainer()
237      */
238     @Override
239     public Class<? extends Container> getSupportedContainer() {
240         return CollectionGroup.class;
241     }
242 
243     /**
244      * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#getComponentsForLifecycle()
245      */
246     @Override
247     public List<Component> getComponentsForLifecycle() {
248         List<Component> components = super.getComponentsForLifecycle();
249 
250         if (wrapperGroup != null) {
251             components.add(wrapperGroup);
252         } else {
253             components.addAll(stackedGroups);
254         }
255 
256         return components;
257     }
258 
259     /**
260      * @see org.kuali.rice.krad.uif.layout.LayoutManager#getComponentPrototypes()
261      */
262     @Override
263     public List<Component> getComponentPrototypes() {
264         List<Component> components = super.getComponentPrototypes();
265 
266         components.add(addLineGroup);
267         components.add(lineGroupPrototype);
268         components.add(subCollectionFieldGroupPrototype);
269         components.add(selectFieldPrototype);
270 
271         return components;
272     }
273 
274     /**
275      * Text to appears in the header for each collection lines Group. Used in
276      * conjunction with {@link #getSummaryFields()} to build up the final header
277      * text
278      *
279      * @return String summary title text
280      */
281     public String getSummaryTitle() {
282         return this.summaryTitle;
283     }
284 
285     /**
286      * Setter for the summary title text
287      *
288      * @param summaryTitle
289      */
290     public void setSummaryTitle(String summaryTitle) {
291         this.summaryTitle = summaryTitle;
292     }
293 
294     /**
295      * List of attribute names from the collection line class that should be
296      * used to build the line summary. To build the summary the value for each
297      * attribute is retrieved from the line instance. All the values are then
298      * placed together with a separator.
299      *
300      * @return List<String> summary field names
301      * @see #buildLineHeaderText(java.lang.Object)
302      */
303     public List<String> getSummaryFields() {
304         return this.summaryFields;
305     }
306 
307     /**
308      * Setter for the summary field name list
309      *
310      * @param summaryFields
311      */
312     public void setSummaryFields(List<String> summaryFields) {
313         this.summaryFields = summaryFields;
314     }
315 
316     /**
317      * Group instance that will be used for the add line
318      *
319      * <p>
320      * Add line fields and actions configured on the
321      * <code>CollectionGroup</code> will be set onto the add line group (if add
322      * line is enabled). If the add line group is not configured, a new instance
323      * of the line group prototype will be used for the add line.
324      * </p>
325      *
326      * @return Group add line group instance
327      * @see #getAddLineGroup()
328      */
329     public Group getAddLineGroup() {
330         return this.addLineGroup;
331     }
332 
333     /**
334      * Setter for the add line group
335      *
336      * @param addLineGroup
337      */
338     public void setAddLineGroup(Group addLineGroup) {
339         this.addLineGroup = addLineGroup;
340     }
341 
342     /**
343      * Group instance that is used as a prototype for creating the collection
344      * line groups. For each line a copy of the prototype is made and then
345      * adjusted as necessary
346      *
347      * @return Group instance to use as prototype
348      */
349     public Group getLineGroupPrototype() {
350         return this.lineGroupPrototype;
351     }
352 
353     /**
354      * Setter for the line group prototype
355      *
356      * @param lineGroupPrototype
357      */
358     public void setLineGroupPrototype(Group lineGroupPrototype) {
359         this.lineGroupPrototype = lineGroupPrototype;
360     }
361 
362     /**
363      * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()
364      */
365     public FieldGroup getSubCollectionFieldGroupPrototype() {
366         return this.subCollectionFieldGroupPrototype;
367     }
368 
369     /**
370      * Setter for the sub-collection field group prototype
371      *
372      * @param subCollectionFieldGroupPrototype
373      */
374     public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) {
375         this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype;
376     }
377 
378     /**
379      * Field instance that serves as a prototype for creating the select field on each line when
380      * {@link org.kuali.rice.krad.uif.container.CollectionGroup#isRenderSelectField()} is true
381      *
382      * <p>
383      * This prototype can be used to set the control used for the select field (generally will be a checkbox control)
384      * in addition to styling and other setting. The binding path will be formed with using the
385      * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getSelectPropertyName()} or if not set the framework
386      * will use {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedCollectionLines()}
387      * </p>
388      *
389      * @return Field select field prototype instance
390      */
391     public Field getSelectFieldPrototype() {
392         return selectFieldPrototype;
393     }
394 
395     /**
396      * Setter for the prototype instance for select fields
397      *
398      * @param selectFieldPrototype
399      */
400     public void setSelectFieldPrototype(Field selectFieldPrototype) {
401         this.selectFieldPrototype = selectFieldPrototype;
402     }
403 
404     /**
405      * Group that will 'wrap' the generated collection lines so that they have a different layout from the general
406      * stacked layout
407      *
408      * <p>
409      * By default (when the wrapper group is null), each collection line will become a group and the groups are
410      * rendered one after another. If the wrapper group is configured, the generated groups will be inserted as the
411      * items for the wrapper group, and the layout manager configured for the wrapper group will determine how they
412      * are rendered. For example, the layout manager could be a grid layout configured for three columns, which would
413      * layout the first three lines horizontally then break to a new row.
414      * </p>
415      *
416      * @return Group instance whose items list should be populated with the generated groups, or null to use the
417      *         default layout
418      */
419     public Group getWrapperGroup() {
420         return wrapperGroup;
421     }
422 
423     /**
424      * Setter for the wrapper group that will receive the generated line groups
425      *
426      * @param wrapperGroup
427      */
428     public void setWrapperGroup(Group wrapperGroup) {
429         this.wrapperGroup = wrapperGroup;
430     }
431 
432     /**
433      * Final <code>List</code> of Groups to render for the collection
434      *
435      * @return List<Group> collection groups
436      */
437     public List<Group> getStackedGroups() {
438         return this.stackedGroups;
439     }
440 
441     /**
442      * Setter for the collection groups
443      *
444      * @param stackedGroups
445      */
446     public void setStackedGroups(List<Group> stackedGroups) {
447         this.stackedGroups = stackedGroups;
448     }
449 
450 }