001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.border; 018 019import org.apache.wicket.Component; 020import org.apache.wicket.DequeueContext; 021import org.apache.wicket.DequeueTagAction; 022import org.apache.wicket.IQueueRegion; 023import org.apache.wicket.MarkupContainer; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.IMarkupFragment; 026import org.apache.wicket.markup.MarkupElement; 027import org.apache.wicket.markup.MarkupException; 028import org.apache.wicket.markup.MarkupFragment; 029import org.apache.wicket.markup.MarkupStream; 030import org.apache.wicket.markup.TagUtils; 031import org.apache.wicket.markup.WicketTag; 032import org.apache.wicket.markup.html.MarkupUtil; 033import org.apache.wicket.markup.html.WebMarkupContainer; 034import org.apache.wicket.markup.html.panel.BorderMarkupSourcingStrategy; 035import org.apache.wicket.markup.html.panel.IMarkupSourcingStrategy; 036import org.apache.wicket.markup.parser.XmlTag.TagType; 037import org.apache.wicket.markup.resolver.IComponentResolver; 038import org.apache.wicket.model.IModel; 039import org.apache.wicket.util.lang.Args; 040 041/** 042 * A border component has associated markup which is drawn and determines placement of markup and/or 043 * components nested within the border component. 044 * <p> 045 * The portion of the border's associated markup file which is to be used in rendering the border is 046 * denoted by a <wicket:border> tag. The children of the border component instance are then 047 * inserted into this markup, replacing the first <wicket:body> tag in the border's associated 048 * markup. 049 * <p> 050 * For example, if a border's associated markup looked like this: 051 * 052 * <pre> 053 * <html> 054 * <body> 055 * <wicket:border> 056 * First <wicket:body/> Last 057 * </wicket:border> 058 * </body> 059 * </html> 060 * </pre> 061 * 062 * And the border was used on a page like this: 063 * 064 * <pre> 065 * <html> 066 * <body> 067 * <span wicket:id = "myBorder"> 068 * Middle 069 * </span> 070 * </body> 071 * </html> 072 * </pre> 073 * 074 * Then the resulting HTML would look like this: 075 * 076 * <pre> 077 * <html> 078 * <body> 079 * First Middle Last 080 * </body> 081 * </html> 082 * </pre> 083 * 084 * In other words, the body of the myBorder component is substituted into the border's associated 085 * markup at the position indicated by the <wicket:body> tag. 086 * <p> 087 * Regarding <wicket:body/> you have two options. Either use <wicket:body/> (open-close 088 * tag) which will automatically be expanded to <wicket:body>body content</wicket:body> 089 * or use <wicket:body>preview region</wicket:body> in your border's markup. The preview 090 * region (everything in between the open and close tag) will automatically be removed. 091 * <p> 092 * The border body container will automatically be created for you and added to the border 093 * container. It is accessible via {@link #getBodyContainer()}. In case the body markup is not an 094 * immediate child of border (see the example below), then you must use code such as 095 * <code>someContainer.add(getBodyContainer())</code> to add the body component to the correct 096 * container. 097 * 098 * <pre> 099 * <html> 100 * <body> 101 * <wicket:border> 102 * <span wicket:id="someContainer"> 103 * <wicket:body/> 104 * </span> 105 * </wicket:border> 106 * </body> 107 * </html> 108 * </pre> 109 * 110 * The component "someContainer" in the previous example must be added to the border, and not the 111 * body, which is achieved via {@link #addToBorder(Component...)}. 112 * <p/> 113 * {@link #add(Component...)} is an alias to {@code getBodyContainer().add(Component...)} and will 114 * add a child component to the border body as shown in the example below. 115 * 116 * <pre> 117 * <html> 118 * <body> 119 * <span wicket:id = "myBorder"> 120 * <input wicket:id="name"/> 121 * </span> 122 * </body> 123 * </html> 124 * </pre> 125 * 126 * This implementation does not apply any magic with respect to component handling. In doubt think 127 * simple. But everything you can do with a MarkupContainer or Component, you can do with a Border 128 * or its Body as well. 129 * <p/> 130 * 131 * Other methods like {@link #remove()}, {@link #get(String)}, {@link #iterator()}, etc. are not 132 * aliased to work on the border's body and attention must be paid when they need to be used. 133 * 134 * @see BorderPanel An alternative implementation based on Panel 135 * @see BorderBehavior A behavior which adds (raw) markup before and after the component 136 * 137 * @author Jonathan Locke 138 * @author Juergen Donnerstag 139 */ 140public abstract class Border extends WebMarkupContainer implements IComponentResolver, IQueueRegion 141{ 142 private static final long serialVersionUID = 1L; 143 144 /** */ 145 public static final String BODY = "body"; 146 147 /** */ 148 public static final String BORDER = "border"; 149 150 /** The body component associated with <wicket:body> */ 151 private final BorderBodyContainer body; 152 153 /** 154 * @see org.apache.wicket.Component#Component(String) 155 */ 156 public Border(final String id) 157 { 158 this(id, null); 159 } 160 161 /** 162 * @see org.apache.wicket.Component#Component(String, IModel) 163 */ 164 public Border(final String id, final IModel<?> model) 165 { 166 super(id, model); 167 168 body = new BorderBodyContainer(id + "_" + BODY); 169 queueToBorder(body); 170 } 171 172 /** 173 * Returns the border body container. 174 * 175 * NOTE: this component is NOT meant to be directly handled by users, meaning that you 176 * can not explicitly add it to an arbitrary container or remove it from its original parent container. 177 * 178 * @return The border body container 179 */ 180 public final BorderBodyContainer getBodyContainer() 181 { 182 return body; 183 } 184 185 /** 186 * This is for all components which have been added to the markup like this: 187 * 188 * <pre> 189 * <span wicket:id="myBorder"> 190 * <input wicket:id="text1" .. /> 191 * ... 192 * </span> 193 * 194 * </pre> 195 * 196 * Whereas {@link #addToBorder(Component...)} will add a component associated with the following 197 * markup: 198 * 199 * <pre> 200 * <wicket:border> 201 * <form wicket:id="myForm" .. > 202 * <wicket:body/> 203 * </form> 204 * </wicket:border> 205 * 206 * </pre> 207 * 208 * @see org.apache.wicket.MarkupContainer#add(org.apache.wicket.Component[]) 209 */ 210 @Override 211 public Border add(final Component... children) 212 { 213 for (Component component : children) 214 { 215 if (component == body || component.isAuto()) 216 { 217 addToBorder(component); 218 } 219 else 220 { 221 getBodyContainer().add(component); 222 } 223 } 224 return this; 225 } 226 227 @Override 228 public Border addOrReplace(final Component... children) 229 { 230 for (Component component : children) 231 { 232 if (component == body) 233 { 234 // in this case we do not want to redirect to body 235 // container but to border's old remove. 236 super.addOrReplace(component); 237 } 238 else 239 { 240 getBodyContainer().addOrReplace(component); 241 } 242 } 243 return this; 244 } 245 246 @Override 247 public Border remove(final Component component) 248 { 249 if (component == body) 250 { 251 // in this case we do not want to redirect to body 252 // container but to border's old remove. 253 removeFromBorder(component); 254 } 255 else 256 { 257 getBodyContainer().remove(component); 258 } 259 return this; 260 } 261 262 263 264 @Override 265 public Border remove(final String id) 266 { 267 if (body.getId().equals(id)) 268 { 269 // in this case we do not want to redirect to body 270 // container but to border's old remove. 271 super.remove(id); 272 } 273 else 274 { 275 getBodyContainer().remove(id); 276 } 277 return this; 278 } 279 280 @Override 281 public Border removeAll() 282 { 283 getBodyContainer().removeAll(); 284 return this; 285 } 286 287 @Override 288 public Border replace(final Component replacement) 289 { 290 if (body.getId().equals(replacement.getId())) 291 { 292 // in this case we do not want to redirect to body 293 // container but to border's old remove. 294 replaceInBorder(replacement); 295 } 296 else 297 { 298 getBodyContainer().replace(replacement); 299 } 300 return this; 301 } 302 303 /** 304 * Adds children components to the Border itself 305 * 306 * @param children 307 * the children components to add 308 * @return this 309 */ 310 public Border addToBorder(final Component... children) 311 { 312 super.add(children); 313 return this; 314 } 315 316 @Override 317 public Border queue(Component... components) 318 { 319 getBodyContainer().queue(components); 320 return this; 321 } 322 323 @Override 324 protected void onConfigure() 325 { 326 super.onConfigure(); 327 dequeue(); 328 } 329 330 /** 331 * Queues children components to the Border itself 332 * 333 * @param children 334 * the children components to queue 335 * @return this 336 */ 337 public Border queueToBorder(final Component... children) 338 { 339 super.queue(children); 340 return this; 341 } 342 343 /** 344 * Removes child from the Border itself 345 * 346 * @param child 347 * @return {@code this} 348 */ 349 public Border removeFromBorder(final Component child) 350 { 351 super.remove(child); 352 return this; 353 } 354 355 /** 356 * Replaces component in the Border itself 357 * 358 * @param component 359 * @return {@code this} 360 */ 361 public Border replaceInBorder(final Component component) 362 { 363 super.replace(component); 364 return this; 365 } 366 367 /** 368 * {@inheritDoc} 369 */ 370 @Override 371 public Component resolve(final MarkupContainer container, final MarkupStream markupStream, 372 final ComponentTag tag) 373 { 374 // make sure nested borders are resolved properly 375 if (body.rendering == false) 376 { 377 // We are only interested in border body tags. The tag ID actually is irrelevant since 378 // always preset with the same default 379 if (TagUtils.isWicketBodyTag(tag)) 380 { 381 return body; 382 } 383 } 384 385 return null; 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override 392 protected IMarkupSourcingStrategy newMarkupSourcingStrategy() 393 { 394 return new BorderMarkupSourcingStrategy(); 395 } 396 397 /** 398 * Search for the child markup in the file associated with the Border. The child markup must in 399 * between the <wicket:border> tags. 400 */ 401 @Override 402 public IMarkupFragment getMarkup(final Component child) 403 { 404 // Border require an associated markup resource file 405 IMarkupFragment markup = getAssociatedMarkup(); 406 if (markup == null) 407 { 408 throw new MarkupException("Unable to find associated markup file for Border: " + 409 this.toString()); 410 } 411 412 // Find <wicket:border> 413 IMarkupFragment borderMarkup = null; 414 for (int i = 0; i < markup.size(); i++) 415 { 416 MarkupElement elem = markup.get(i); 417 if (TagUtils.isWicketBorderTag(elem)) 418 { 419 borderMarkup = new MarkupFragment(markup, i); 420 break; 421 } 422 } 423 424 if (borderMarkup == null) 425 { 426 throw new MarkupException(markup.getMarkupResourceStream(), 427 "Unable to find <wicket:border> tag in associated markup file for Border: " + 428 this.toString()); 429 } 430 431 // If child == null, return the markup fragment starting with the <wicket:border> tag 432 if (child == null) 433 { 434 return borderMarkup; 435 } 436 437 // Is child == BorderBody? 438 if (child == body) 439 { 440 // Get the <wicket:body> markup 441 return body.getMarkup(); 442 } 443 444 // Find the markup for the child component 445 IMarkupFragment childMarkup = borderMarkup.find(child.getId()); 446 if (childMarkup != null) 447 { 448 return childMarkup; 449 } 450 451 return ((BorderMarkupSourcingStrategy)getMarkupSourcingStrategy()).findMarkupInAssociatedFileHeader( 452 this, child); 453 } 454 455 /** 456 * The container to be associated with the <wicket:body> tag 457 */ 458 public class BorderBodyContainer extends WebMarkupContainer implements IQueueRegion 459 { 460 private static final long serialVersionUID = 1L; 461 462 /** The markup */ 463 private transient IMarkupFragment markup; 464 465 // properly resolve borders added to borders 466 protected boolean rendering; 467 468 /** 469 * Constructor 470 * 471 * @param id 472 */ 473 public BorderBodyContainer(final String id) 474 { 475 super(id); 476 } 477 478 @Override 479 protected void onComponentTag(final ComponentTag tag) 480 { 481 // Convert open-close to open-body-close 482 if (tag.isOpenClose()) 483 { 484 tag.setType(TagType.OPEN); 485 tag.setModified(true); 486 } 487 488 super.onComponentTag(tag); 489 } 490 491 @Override 492 public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 493 { 494 // skip the <wicket:body> body 495 if (markupStream.getPreviousTag().isOpen()) 496 { 497 // Only RawMarkup is allowed within the preview region, 498 // which gets stripped from output 499 markupStream.skipRawMarkup(); 500 } 501 502 // Get the <span wicket:id="myBorder"> markup and render that instead 503 IMarkupFragment markup = Border.this.getMarkup(); 504 MarkupStream stream = new MarkupStream(markup); 505 ComponentTag tag = stream.getTag(); 506 stream.next(); 507 508 super.onComponentTagBody(stream, tag); 509 } 510 511 @Override 512 protected void onRender() 513 { 514 rendering = true; 515 516 try 517 { 518 super.onRender(); 519 } 520 finally 521 { 522 rendering = false; 523 } 524 } 525 526 /** 527 * Get the <wicket:body> markup from the body's parent container 528 */ 529 @Override 530 public IMarkupFragment getMarkup() 531 { 532 if (markup == null) 533 { 534 markup = findByName(getParent().getMarkup(null), BODY); 535 } 536 return markup; 537 } 538 539 /** 540 * Search for <wicket:'name' ...> on the same level, but ignoring other "transparent" 541 * tags such as <wicket:enclosure> etc. 542 * 543 * @param markup 544 * @param name 545 * @return null, if not found 546 */ 547 private IMarkupFragment findByName(final IMarkupFragment markup, final String name) 548 { 549 Args.notEmpty(name, "name"); 550 551 MarkupStream stream = new MarkupStream(markup); 552 553 // Skip any raw markup 554 stream.skipUntil(ComponentTag.class); 555 556 // Skip <wicket:border> 557 stream.next(); 558 559 while (stream.skipUntil(ComponentTag.class)) 560 { 561 ComponentTag tag = stream.getTag(); 562 if (tag.isOpen() || tag.isOpenClose()) 563 { 564 if (TagUtils.isWicketBodyTag(tag)) 565 { 566 return stream.getMarkupFragment(); 567 } 568 } 569 570 stream.next(); 571 } 572 573 return null; 574 } 575 576 /** 577 * Get the child markup which must be in between the <span wicktet:id="myBorder"> tags 578 */ 579 @Override 580 public IMarkupFragment getMarkup(final Component child) 581 { 582 IMarkupFragment markup = Border.this.getMarkup(); 583 if (markup == null) 584 { 585 return null; 586 } 587 588 if (child == null) 589 { 590 return markup; 591 } 592 593 return markup.find(child.getId()); 594 } 595 596 @Override 597 public DequeueContext newDequeueContext() 598 { 599 Border border = findParent(Border.class); 600 IMarkupFragment fragment = border.getMarkup(); 601 602 if (fragment == null) 603 { 604 return null; 605 } 606 607 return new DequeueContext(fragment, this, true); 608 } 609 610 @Override 611 public Component findComponentToDequeue(ComponentTag tag) 612 { 613 /* 614 * the body container is allowed to search for queued components all 615 * the way to the page even though it is an IQueueRegion so it can 616 * find components queued below the border 617 */ 618 619 Component component = super.findComponentToDequeue(tag); 620 if (component != null) 621 { 622 return component; 623 } 624 625 MarkupContainer cursor = getParent(); 626 while (cursor != null) 627 { 628 component = cursor.findComponentToDequeue(tag); 629 if (component != null) 630 { 631 return component; 632 } 633 if (cursor instanceof BorderBodyContainer) 634 { 635 // optimization - find call above would've already recursed 636 // to page 637 break; 638 } 639 cursor = cursor.getParent(); 640 } 641 return null; 642 } 643 } 644 645 @Override 646 protected DequeueTagAction canDequeueTag(ComponentTag tag) 647 { 648 if (canDequeueBody(tag)) 649 { 650 return DequeueTagAction.DEQUEUE; 651 } 652 653 return super.canDequeueTag(tag); 654 } 655 656 @Override 657 public Component findComponentToDequeue(ComponentTag tag) 658 { 659 if (canDequeueBody(tag)) 660 { 661 //synch the tag id with the one of the body component 662 tag.setId(body.getId()); 663 } 664 665 return super.findComponentToDequeue(tag); 666 } 667 668 private boolean canDequeueBody(ComponentTag tag) 669 { 670 boolean isBodyTag = (tag instanceof WicketTag) && ((WicketTag)tag).isBodyTag(); 671 672 return isBodyTag; 673 } 674 675 @Override 676 protected void addDequeuedComponent(Component component, ComponentTag tag) 677 { 678 // components queued in border get dequeued into the border not into the body container 679 super.add(component); 680 } 681 682 /** 683 * Returns the markup inside <wicket:border> tag. 684 * If such tag is not found, all the markup is returned. 685 * 686 * @see IQueueRegion#getRegionMarkup() 687 */ 688 @Override 689 public IMarkupFragment getRegionMarkup() 690 { 691 IMarkupFragment markup = super.getRegionMarkup(); 692 693 if (markup == null) 694 { 695 return markup; 696 } 697 698 IMarkupFragment borderMarkup = MarkupUtil.findStartTag(markup, BORDER); 699 700 return borderMarkup != null ? borderMarkup : markup; 701 } 702 703}