001/** 002 * Copyright 2005-2018 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 */ 016package org.kuali.rice.krad.uif.layout; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.commons.lang.StringUtils; 026import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 027import org.kuali.rice.krad.datadictionary.uif.UifDictionaryBeanBase; 028import org.kuali.rice.krad.uif.UifConstants; 029import org.kuali.rice.krad.uif.UifConstants.ViewStatus; 030import org.kuali.rice.krad.uif.component.PropertyReplacer; 031import org.kuali.rice.krad.uif.component.ReferenceCopy; 032import org.kuali.rice.krad.uif.container.Container; 033import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 034import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase; 035import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTask; 036import org.kuali.rice.krad.uif.util.LifecycleAwareList; 037import org.kuali.rice.krad.uif.util.LifecycleAwareMap; 038import org.kuali.rice.krad.uif.util.LifecycleElement; 039import org.kuali.rice.krad.uif.view.View; 040 041/** 042 * Base class for all layout managers 043 * 044 * <p> 045 * Provides general properties of all layout managers, such as the unique id, 046 * rendering template, and style settings 047 * </p> 048 * 049 * @author Kuali Rice Team (rice.collab@kuali.org) 050 */ 051public abstract class LayoutManagerBase extends UifDictionaryBeanBase implements LayoutManager { 052 private static final long serialVersionUID = -2657663560459456814L; 053 054 private String id; 055 private String containerIdSuffix; 056 private String viewPath; 057 private Map<String, String> phasePathMapping; 058 059 private String template; 060 private String templateName; 061 062 private String style; 063 064 private List<String> libraryCssClasses; 065 private List<String> cssClasses; 066 private List<String> additionalCssClasses; 067 068 @ReferenceCopy(newCollectionInstance = true) 069 private Map<String, Object> context; 070 071 private List<PropertyReplacer> propertyReplacers; 072 073 private boolean render = true; 074 075 private String viewStatus = UifConstants.ViewStatus.CREATED; 076 077 public LayoutManagerBase() { 078 super(); 079 080 phasePathMapping = new HashMap<String, String>(); 081 context = Collections.emptyMap(); 082 cssClasses = Collections.emptyList(); 083 libraryCssClasses = Collections.emptyList(); 084 additionalCssClasses = Collections.emptyList(); 085 } 086 087 /** 088 * @see LifecycleElement#checkMutable(boolean) 089 */ 090 public void checkMutable(boolean legalDuringInitialization) { 091 if (UifConstants.ViewStatus.CACHED.equals(viewStatus)) { 092 ViewLifecycle.reportIllegalState("Cached layout manager " + getClass() + " " + getId() 093 + " is immutable, use copy() to get a mutable instance"); 094 return; 095 } 096 097 if (ViewLifecycle.isActive()) { 098 return; 099 } 100 101 if (UifConstants.ViewStatus.CREATED.equals(viewStatus)) { 102 if (!legalDuringInitialization) { 103 ViewLifecycle.reportIllegalState( 104 "View has not been fully initialized, attempting to change layout manager " 105 + getClass() + " " + getId()); 106 return; 107 } 108 } else { 109 ViewLifecycle.reportIllegalState("Layout manager " + getClass() + " " + getId() 110 + " has been initialized, but the lifecycle is not active."); 111 return; 112 } 113 } 114 115 /** 116 * @see LifecycleElement#isMutable(boolean) 117 */ 118 public boolean isMutable(boolean legalDuringInitialization) { 119 return (UifConstants.ViewStatus.CREATED.equals(viewStatus) && legalDuringInitialization) 120 || ViewLifecycle.isActive(); 121 } 122 123 /** 124 * Indicates what lifecycle phase the layout manager instance is in 125 * 126 * <p> 127 * The view lifecycle begins with the CREATED status. In this status a new instance of the view 128 * has been retrieved from the dictionary, but no further processing has been done. After the 129 * initialize phase has been run the status changes to INITIALIZED. After the model has been 130 * applied and the view is ready for render the status changes to FINAL 131 * </p> 132 * 133 * @return view status 134 * @see org.kuali.rice.krad.uif.UifConstants.ViewStatus 135 */ 136 public String getViewStatus() { 137 return this.viewStatus; 138 } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 144 public void setViewStatus(String status) { 145 this.viewStatus = status; 146 } 147 148 /** 149 * {@inheritDoc} 150 */ 151 @Override 152 public void notifyCompleted(ViewLifecyclePhase phase) { 153 } 154 155 /** 156 * {@inheritDoc} 157 */ 158 @Override 159 public void performInitialization(Object model) { 160 checkMutable(false); 161 162 // set id of layout manager from container 163 if (StringUtils.isBlank(id)) { 164 Container container = (Container) ViewLifecycle.getPhase().getElement(); 165 id = container.getId() + "_layout"; 166 } 167 } 168 169 /** 170 * {@inheritDoc} 171 */ 172 @Override 173 public void performApplyModel(Object model, LifecycleElement component) { 174 checkMutable(false); 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 public void performFinalize(Object model, LifecycleElement component) { 182 checkMutable(false); 183 184 // put together all css class names for this component, in order 185 List<String> finalCssClasses = new ArrayList<String>(); 186 187 View view = ViewLifecycle.getView(); 188 189 if (this.libraryCssClasses != null && view.isUseLibraryCssClasses()) { 190 finalCssClasses.addAll(libraryCssClasses); 191 } 192 193 if (this.cssClasses != null) { 194 finalCssClasses.addAll(cssClasses); 195 } 196 197 if (this.additionalCssClasses != null) { 198 finalCssClasses.addAll(additionalCssClasses); 199 } 200 201 cssClasses = finalCssClasses; 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 @Override 208 public boolean skipLifecycle() { 209 return false; 210 } 211 212 /** 213 * Default Impl 214 * 215 * {@inheritDoc} 216 */ 217 @Override 218 public Class<? extends Container> getSupportedContainer() { 219 return Container.class; 220 } 221 222 /** 223 * {@inheritDoc} 224 */ 225 @Override 226 @BeanTagAttribute 227 public String getId() { 228 return this.id; 229 } 230 231 /** 232 * {@inheritDoc} 233 */ 234 @Override 235 public void setId(String id) { 236 checkMutable(true); 237 this.id = id; 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 @Override 244 public String getContainerIdSuffix() { 245 return containerIdSuffix; 246 } 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override 252 public void setContainerIdSuffix(String containerIdSuffix) { 253 this.containerIdSuffix = containerIdSuffix; 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public String getViewPath() { 261 return this.viewPath; 262 } 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override 268 public void setViewPath(String viewPath) { 269 checkMutable(true); 270 this.viewPath = viewPath; 271 } 272 273 /** 274 * {@inheritDoc} 275 */ 276 @Override 277 public Map<String, String> getPhasePathMapping() { 278 return phasePathMapping; 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override 285 public void setPhasePathMapping(Map<String, String> phasePathMapping) { 286 this.phasePathMapping = phasePathMapping; 287 } 288 289 /** 290 * {@inheritDoc} 291 */ 292 @Override 293 @BeanTagAttribute 294 public String getTemplate() { 295 return this.template; 296 } 297 298 /** 299 * {@inheritDoc} 300 */ 301 @Override 302 public void setTemplate(String template) { 303 checkMutable(true); 304 this.template = template; 305 } 306 307 /** 308 * {@inheritDoc} 309 */ 310 @BeanTagAttribute 311 public String getTemplateName() { 312 return templateName; 313 } 314 315 /** 316 * {@inheritDoc} 317 */ 318 public void setTemplateName(String templateName) { 319 checkMutable(true); 320 this.templateName = templateName; 321 } 322 323 /** 324 * {@inheritDoc} 325 */ 326 @Override 327 @BeanTagAttribute 328 public String getStyle() { 329 return this.style; 330 } 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override 336 public void setStyle(String style) { 337 checkMutable(true); 338 this.style = style; 339 } 340 341 /** 342 * Additional css classes that come before css classes listed in the cssClasses property 343 * 344 * <p> 345 * These are used by the framework for styling with a library (for example, bootstrap), and 346 * should normally not be overridden. 347 * </p> 348 * 349 * @return the library cssClasses 350 */ 351 public List<String> getLibraryCssClasses() { 352 if (libraryCssClasses == Collections.EMPTY_LIST && isMutable(true)) { 353 libraryCssClasses = new LifecycleAwareList<String>(this); 354 } 355 356 return libraryCssClasses; 357 } 358 359 /** 360 * Set the libraryCssClasses 361 * 362 * @param libraryCssClasses 363 */ 364 public void setLibraryCssClasses(List<String> libraryCssClasses) { 365 checkMutable(true); 366 367 if (libraryCssClasses == null) { 368 this.libraryCssClasses = Collections.emptyList(); 369 } else { 370 this.libraryCssClasses = new LifecycleAwareList<String>(this, libraryCssClasses); 371 } 372 } 373 374 /** 375 * @see org.kuali.rice.krad.uif.layout.LayoutManager#getCssClasses() 376 */ 377 @BeanTagAttribute 378 public List<String> getCssClasses() { 379 if (cssClasses == Collections.EMPTY_LIST && isMutable(true)) { 380 cssClasses = new LifecycleAwareList<String>(this); 381 } 382 383 return cssClasses; 384 } 385 386 /** 387 * @see org.kuali.rice.krad.uif.layout.LayoutManager#setCssClasses(java.util.List) 388 */ 389 public void setCssClasses(List<String> cssClasses) { 390 checkMutable(true); 391 if (cssClasses == null) { 392 this.cssClasses = Collections.emptyList(); 393 } else { 394 this.cssClasses = new LifecycleAwareList<String>(this, cssClasses); 395 } 396 } 397 398 /** 399 * @see org.kuali.rice.krad.uif.layout.LayoutManager#getAdditionalCssClasses() 400 */ 401 @BeanTagAttribute 402 public List<String> getAdditionalCssClasses() { 403 if (additionalCssClasses == Collections.EMPTY_LIST && isMutable(true)) { 404 additionalCssClasses = new LifecycleAwareList<String>(this); 405 } 406 407 return additionalCssClasses; 408 } 409 410 /** 411 * @see org.kuali.rice.krad.uif.layout.LayoutManager#setAdditionalCssClasses(java.util.List) 412 */ 413 public void setAdditionalCssClasses(List<String> additionalCssClasses) { 414 checkMutable(true); 415 if (additionalCssClasses == null) { 416 this.additionalCssClasses = Collections.emptyList(); 417 } else { 418 this.additionalCssClasses = new LifecycleAwareList<String>(this, additionalCssClasses); 419 } 420 } 421 422 /** 423 * Builds the HTML class attribute string by combining the styleClasses list 424 * with a space delimiter 425 * 426 * @return class attribute string 427 */ 428 public String getStyleClassesAsString() { 429 if (cssClasses != null) { 430 return StringUtils.join(cssClasses, " "); 431 } 432 433 return ""; 434 } 435 436 /** 437 * Sets the styleClasses list from the given string that has the classes 438 * delimited by space. This is a convenience for configuration. If a child 439 * bean needs to inherit the classes from the parent, it should configure as 440 * a list and use merge="true" 441 * 442 * @param styleClasses 443 */ 444 public void setStyleClasses(String styleClasses) { 445 checkMutable(true); 446 String[] classes = StringUtils.split(styleClasses); 447 this.cssClasses = Arrays.asList(classes); 448 } 449 450 /** 451 * {@inheritDoc} 452 */ 453 @Override 454 public void addStyleClass(String styleClass) { 455 checkMutable(false); 456 if (cssClasses == null || cssClasses.isEmpty()) { 457 cssClasses = new ArrayList<String>(); 458 } 459 460 if (!cssClasses.contains(styleClass)) { 461 cssClasses.add(styleClass); 462 } 463 } 464 465 /** 466 * {@inheritDoc} 467 */ 468 @Override 469 public void appendToStyle(String styleRules) { 470 checkMutable(false); 471 if (style == null) { 472 style = ""; 473 } 474 style = style + styleRules; 475 } 476 477 /** 478 * {@inheritDoc} 479 */ 480 @Override 481 @BeanTagAttribute 482 public Map<String, Object> getContext() { 483 if (context == Collections.EMPTY_MAP && isMutable(true)) { 484 context = new LifecycleAwareMap<String, Object>(this); 485 } 486 487 return context; 488 } 489 490 /** 491 * {@inheritDoc} 492 */ 493 @Override 494 public void setContext(Map<String, Object> context) { 495 checkMutable(true); 496 497 if (context == null) { 498 this.context = Collections.emptyMap(); 499 } else { 500 this.context = new LifecycleAwareMap<String, Object>(this, context); 501 } 502 } 503 504 /** 505 * {@inheritDoc} 506 */ 507 @Override 508 public void pushObjectToContext(String objectName, Object object) { 509 checkMutable(false); 510 if (context == Collections.EMPTY_MAP && isMutable(true)) { 511 context = new LifecycleAwareMap<String, Object>(this); 512 } 513 514 context.put(objectName, object); 515 } 516 517 /** 518 * {@inheritDoc} 519 */ 520 @Override 521 public void pushAllToContext(Map<String, Object> sourceContext) { 522 checkMutable(false); 523 if (sourceContext == null || sourceContext.isEmpty()) { 524 return; 525 } 526 527 if (context == Collections.EMPTY_MAP && isMutable(true)) { 528 context = new LifecycleAwareMap<String, Object>(this); 529 } 530 531 this.context.putAll(sourceContext); 532 } 533 534 /** 535 * {@inheritDoc} 536 */ 537 @Override 538 @BeanTagAttribute 539 public List<PropertyReplacer> getPropertyReplacers() { 540 return this.propertyReplacers; 541 } 542 543 /** 544 * {@inheritDoc} 545 */ 546 @Override 547 public void setPropertyReplacers(List<PropertyReplacer> propertyReplacers) { 548 checkMutable(true); 549 this.propertyReplacers = propertyReplacers; 550 } 551 552 @Override 553 public LayoutManagerBase clone() throws CloneNotSupportedException { 554 LayoutManagerBase copy = (LayoutManagerBase) super.clone(); 555 556 // Copy initialized status, but reset to created for others. 557 // This allows prototypes to bypass repeating the initialized phase. 558 if (UifConstants.ViewStatus.INITIALIZED.equals(viewStatus)) { 559 copy.viewStatus = UifConstants.ViewStatus.INITIALIZED; 560 } else { 561 copy.viewStatus = UifConstants.ViewStatus.CREATED; 562 } 563 564 return copy; 565 } 566 567 /** 568 * Indicates whether the component has been initialized. 569 * 570 * @return True if the component has been initialized, false if not. 571 */ 572 public boolean isInitialized() { 573 return StringUtils.equals(viewStatus, ViewStatus.INITIALIZED) || isModelApplied(); 574 } 575 576 /** 577 * Indicates whether the component has been updated from the model. 578 * 579 * @return True if the component has been updated, false if not. 580 */ 581 public boolean isModelApplied() { 582 return StringUtils.equals(viewStatus, ViewStatus.MODEL_APPLIED) || isFinal(); 583 } 584 585 /** 586 * Indicates whether the component has been updated from the model and final updates made. 587 * 588 * @return True if the component has been updated, false if not. 589 */ 590 public boolean isFinal() { 591 return StringUtils.equals(viewStatus, ViewStatus.FINAL); 592 } 593 594 /** 595 * {@inheritDoc} 596 */ 597 @Override 598 public boolean isRender() { 599 return this.render; 600 } 601 602 /** 603 * {@inheritDoc} 604 */ 605 @Override 606 public void setRender(boolean render) { 607 this.render = render; 608 } 609 610}