001 /**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.uif.layout;
017
018 import java.util.ArrayList;
019 import java.util.List;
020
021 import org.apache.commons.lang.StringUtils;
022 import org.kuali.rice.krad.datadictionary.parse.BeanTag;
023 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
024 import org.kuali.rice.krad.datadictionary.parse.BeanTags;
025 import org.kuali.rice.krad.uif.UifConstants;
026 import org.kuali.rice.krad.uif.UifPropertyPaths;
027 import org.kuali.rice.krad.uif.component.Component;
028 import org.kuali.rice.krad.uif.component.DataBinding;
029 import org.kuali.rice.krad.uif.component.KeepExpression;
030 import org.kuali.rice.krad.uif.container.CollectionGroup;
031 import org.kuali.rice.krad.uif.container.Container;
032 import org.kuali.rice.krad.uif.container.Group;
033 import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
034 import org.kuali.rice.krad.uif.element.Action;
035 import org.kuali.rice.krad.uif.element.Message;
036 import org.kuali.rice.krad.uif.field.Field;
037 import org.kuali.rice.krad.uif.field.FieldGroup;
038 import org.kuali.rice.krad.uif.layout.collections.CollectionPagingHelper;
039 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
040 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
041 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
042 import org.kuali.rice.krad.uif.util.ComponentUtils;
043 import org.kuali.rice.krad.uif.util.LifecycleElement;
044 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
045 import org.kuali.rice.krad.uif.view.ViewModel;
046 import org.kuali.rice.krad.uif.widget.Pager;
047 import org.kuali.rice.krad.util.KRADUtils;
048 import org.kuali.rice.krad.web.form.UifFormBase;
049
050 /**
051 * Layout manager that works with {@code CollectionGroup} containers and
052 * renders the collection lines in a vertical row
053 *
054 * <p>
055 * For each line of the collection, a {@code Group} instance is created.
056 * The group header contains a label for the line (summary information), the
057 * group fields are the collection line fields, and the group footer contains
058 * the line actions. All the groups are rendered using the
059 * {@code BoxLayoutManager} with vertical orientation.
060 * </p>
061 *
062 * <p>
063 * Modify the lineGroupPrototype to change header/footer styles or any other
064 * customization for the line groups
065 * </p>
066 *
067 * @author Kuali Rice Team (rice.collab@kuali.org)
068 */
069 @BeanTags({@BeanTag(name = "stackedCollectionLayout-bean", parent = "Uif-StackedCollectionLayoutBase"),
070 @BeanTag(name = "stackedCollectionLayout-withGridItems-bean",
071 parent = "Uif-StackedCollectionLayout-WithGridItems"),
072 @BeanTag(name = "stackedCollectionLayout-withBoxItems-bean",
073 parent = "Uif-StackedCollectionLayout-WithBoxItems"),
074 @BeanTag(name = "stackedCollectionLayout-list-bean", parent = "Uif-StackedCollectionLayout-List")})
075 public class StackedLayoutManagerBase extends LayoutManagerBase implements StackedLayoutManager {
076 private static final long serialVersionUID = 4602368505430238846L;
077
078 @KeepExpression
079 private String summaryTitle;
080 private List<String> summaryFields;
081
082 private Group addLineGroup;
083 private Group lineGroupPrototype;
084 private FieldGroup subCollectionFieldGroupPrototype;
085 private Field selectFieldPrototype;
086 private Group wrapperGroup;
087 private Pager pagerWidget;
088
089 private List<Group> stackedGroups;
090
091 private boolean actionsInLineGroup;
092
093 public StackedLayoutManagerBase() {
094 super();
095
096 summaryFields = new ArrayList<String>();
097 stackedGroups = new ArrayList<Group>();
098 }
099
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 addLineGroupId = lineGroup.getId();
165 if (StringUtils.isNotBlank(collectionGroup.getContainerIdSuffix())) {
166 addLineGroupId = addLineGroupId + collectionGroup.getContainerIdSuffix();
167 }
168
169 String actionScript = "showLightboxComponent('" + addLineGroupId + "');";
170 if (StringUtils.isNotBlank(collectionGroup.getAddViaLightBoxAction().getActionScript())) {
171 actionScript = collectionGroup.getAddViaLightBoxAction().getActionScript() + actionScript;
172 }
173 collectionGroup.getAddViaLightBoxAction().setActionScript(actionScript);
174
175 lineGroup.setStyle("display: none");
176 }
177 } else {
178 lineGroup = ComponentUtils.copy(lineGroupPrototype, idSuffix);
179 }
180
181 if (((UifFormBase) lineBuilderContext.getModel()).isAddedCollectionItem(currentLine)) {
182 lineGroup.addStyleClass(collectionGroup.getNewItemsCssClass());
183 }
184
185 // any actions that are attached to the group prototype (like the header) need to get action parameters
186 // and context set for the collection line
187 List<Action> lineGroupActions = ViewLifecycleUtils.getElementsOfTypeDeep(lineGroup, Action.class);
188 if (lineGroupActions != null) {
189 collectionGroup.getCollectionGroupBuilder().initializeActions(lineGroupActions, collectionGroup, lineIndex);
190 ComponentUtils.updateContextsForLine(lineGroupActions, collectionGroup, currentLine, lineIndex, idSuffix);
191 }
192
193 ComponentUtils.updateContextForLine(lineGroup, collectionGroup, currentLine, lineIndex, idSuffix);
194
195 // build header for the group
196 if (lineBuilderContext.isAddLine()) {
197 if (lineGroup.getHeader() != null) {
198 Message headerMessage = ComponentUtils.copy(collectionGroup.getAddLineLabel());
199 lineGroup.getHeader().setRichHeaderMessage(headerMessage);
200 }
201 } else {
202 // get the collection for this group from the model
203 List<Object> modelCollection = ObjectPropertyUtils.getPropertyValue(lineBuilderContext.getModel(),
204 ((DataBinding) collectionGroup).getBindingInfo().getBindingPath());
205
206 String headerText = buildLineHeaderText(modelCollection.get(lineIndex), lineGroup);
207
208 // don't set header if text is blank (could already be set by other means)
209 if (StringUtils.isNotBlank(headerText) && lineGroup.getHeader() != null) {
210 lineGroup.getHeader().setHeaderText(headerText);
211 }
212 }
213
214 // stack all fields (including sub-collections) for the group
215 List<Component> groupFields = new ArrayList<Component>();
216 groupFields.addAll(lineFields);
217
218 if (lineBuilderContext.getSubCollectionFields() != null) {
219 groupFields.addAll(lineBuilderContext.getSubCollectionFields());
220 }
221
222 // set line actions on group footer
223 if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly() && (lineGroup.getFooter() != null)) {
224 // add the actions to the line group if isActionsInLineGroup flag is true
225 if (isActionsInLineGroup()) {
226 groupFields.addAll(actions);
227 lineGroup.setRenderFooter(false);
228 } else {
229 lineGroup.getFooter().setItems(actions);
230 }
231 }
232
233 lineGroup.setItems(groupFields);
234
235 // Must evaluate the client-side state on the lineGroup's disclosure for PlaceholderDisclosureGroup processing
236 if (lineBuilderContext.getModel() instanceof ViewModel){
237 KRADUtils.syncClientSideStateForComponent(lineGroup.getDisclosure(),
238 ((ViewModel) lineBuilderContext.getModel()).getClientStateForSyncing());
239 }
240
241 stackedGroups.add(lineGroup);
242 }
243
244 /**
245 * Builds the header text for the collection line
246 *
247 * <p>
248 * Header text is built up by first the collection label, either specified
249 * on the collection definition or retrieved from the dictionary. Then for
250 * each summary field defined, the value from the model is retrieved and
251 * added to the header.
252 * </p>
253 *
254 * <p>
255 * Note the {@link #getSummaryTitle()} field may have expressions defined, in which cause it will be copied to the
256 * property expressions map to set the title for the line group (which will have the item context variable set)
257 * </p>
258 *
259 * @param line Collection line containing data
260 * @param lineGroup Group instance for rendering the line and whose title should be built
261 * @return header text for line
262 */
263 protected String buildLineHeaderText(Object line, Group lineGroup) {
264 // check for expression on summary title
265 if (ViewLifecycle.getExpressionEvaluator().containsElPlaceholder(summaryTitle)) {
266 lineGroup.getPropertyExpressions().put(UifPropertyPaths.HEADER_TEXT, summaryTitle);
267 return null;
268 }
269
270 // build up line summary from declared field values and fixed title
271 String summaryFieldString = "";
272 for (String summaryField : summaryFields) {
273 Object summaryFieldValue = ObjectPropertyUtils.getPropertyValue(line, summaryField);
274 if (StringUtils.isNotBlank(summaryFieldString)) {
275 summaryFieldString += " - ";
276 }
277
278 if (summaryFieldValue != null) {
279 summaryFieldString += summaryFieldValue;
280 } else {
281 summaryFieldString += "Null";
282 }
283 }
284
285 String headerText = summaryTitle;
286 if (StringUtils.isNotBlank(summaryFieldString)) {
287 headerText += " ( " + summaryFieldString + " )";
288 }
289
290 return headerText;
291 }
292
293 /**
294 * Invokes {@link org.kuali.rice.krad.uif.layout.collections.CollectionPagingHelper} to carry out the
295 * paging request.
296 *
297 * {@inheritDoc}
298 */
299 @Override
300 public void processPagingRequest(Object model, CollectionGroup collectionGroup) {
301 String pageNumber = ViewLifecycle.getRequest().getParameter(UifConstants.PageRequest.PAGE_NUMBER);
302
303 CollectionPagingHelper pagingHelper = new CollectionPagingHelper();
304 pagingHelper.processPagingRequest(ViewLifecycle.getView(), collectionGroup, (UifFormBase) model, pageNumber);
305 }
306
307 /**
308 * {@inheritDoc}
309 */
310 @Override
311 public Class<? extends Container> getSupportedContainer() {
312 return CollectionGroup.class;
313 }
314
315 /**
316 * {@inheritDoc}
317 */
318 @Override
319 @BeanTagAttribute(name = "summaryTitle")
320 public String getSummaryTitle() {
321 return this.summaryTitle;
322 }
323
324 /**
325 * {@inheritDoc}
326 */
327 @Override
328 public void setSummaryTitle(String summaryTitle) {
329 this.summaryTitle = summaryTitle;
330 }
331
332 /**
333 * {@inheritDoc}
334 */
335 @Override
336 @BeanTagAttribute(name = "summaryFields", type = BeanTagAttribute.AttributeType.LISTVALUE)
337 public List<String> getSummaryFields() {
338 return this.summaryFields;
339 }
340
341 /**
342 * {@inheritDoc}
343 */
344 @Override
345 public void setSummaryFields(List<String> summaryFields) {
346 this.summaryFields = summaryFields;
347 }
348
349 /**
350 * {@inheritDoc}
351 */
352 @Override
353 @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
354 @BeanTagAttribute(name = "addLineGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
355 public Group getAddLineGroup() {
356 return this.addLineGroup;
357 }
358
359 /**
360 * {@inheritDoc}
361 */
362 @Override
363 public void setAddLineGroup(Group addLineGroup) {
364 this.addLineGroup = addLineGroup;
365 }
366
367 /**
368 * {@inheritDoc}
369 */
370 @Override
371 @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
372 @BeanTagAttribute(name = "lineGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
373 public Group getLineGroupPrototype() {
374 return this.lineGroupPrototype;
375 }
376
377 /**
378 * {@inheritDoc}
379 */
380 @Override
381 public void setLineGroupPrototype(Group lineGroupPrototype) {
382 this.lineGroupPrototype = lineGroupPrototype;
383 }
384
385 /**
386 * {@inheritDoc}
387 */
388 @Override
389 @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
390 @BeanTagAttribute(name = "subCollectionFieldGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
391 public FieldGroup getSubCollectionFieldGroupPrototype() {
392 return this.subCollectionFieldGroupPrototype;
393 }
394
395 /**
396 * {@inheritDoc}
397 */
398 @Override
399 public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) {
400 this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype;
401 }
402
403 /**
404 * {@inheritDoc}
405 */
406 @Override
407 @ViewLifecycleRestriction(UifConstants.ViewPhases.INITIALIZE)
408 @BeanTagAttribute(name = "selectFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
409 public Field getSelectFieldPrototype() {
410 return selectFieldPrototype;
411 }
412
413 /**
414 * {@inheritDoc}
415 */
416 @Override
417 public void setSelectFieldPrototype(Field selectFieldPrototype) {
418 this.selectFieldPrototype = selectFieldPrototype;
419 }
420
421 /**
422 * {@inheritDoc}
423 */
424 @Override
425 @BeanTagAttribute(name = "wrapperGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
426 public Group getWrapperGroup() {
427 return wrapperGroup;
428 }
429
430 /**
431 * {@inheritDoc}
432 */
433 @Override
434 public void setWrapperGroup(Group wrapperGroup) {
435 this.wrapperGroup = wrapperGroup;
436 }
437
438 /**
439 * {@inheritDoc}
440 */
441 @Override
442 public Pager getPagerWidget() {
443 return pagerWidget;
444 }
445
446 /**
447 * {@inheritDoc}
448 */
449 @Override
450 public void setPagerWidget(Pager pagerWidget) {
451 this.pagerWidget = pagerWidget;
452 }
453
454 /**
455 * {@inheritDoc}
456 */
457 @Override
458 @ViewLifecycleRestriction
459 @BeanTagAttribute(name = "stackedGroups", type = BeanTagAttribute.AttributeType.LISTBEAN)
460 public List<Group> getStackedGroups() {
461 return this.stackedGroups;
462 }
463
464 /**
465 * {@inheritDoc}
466 */
467 @Override
468 public List<Group> getStackedGroupsNoWrapper() {
469 return wrapperGroup != null ? null : this.stackedGroups;
470 }
471
472 /**
473 * {@inheritDoc}
474 */
475 @Override
476 public void setStackedGroups(List<Group> stackedGroups) {
477 this.stackedGroups = stackedGroups;
478 }
479
480 /**
481 * {@inheritDoc}
482 */
483 @Override
484 public boolean isActionsInLineGroup() {
485 return actionsInLineGroup;
486 }
487
488 /**
489 * {@inheritDoc}
490 */
491 @Override
492 public void setActionsInLineGroup(boolean actionsInLineGroup) {
493 this.actionsInLineGroup = actionsInLineGroup;
494 }
495 }