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 }