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 &lt;wicket:border&gt; tag. The children of the border component instance are then
047 * inserted into this markup, replacing the first &lt;wicket:body&gt; 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 *   &lt;html&gt;
054 *   &lt;body&gt;
055 *     &lt;wicket:border&gt;
056 *       First &lt;wicket:body/&gt; Last
057 *     &lt;/wicket:border&gt;
058 *   &lt;/body&gt;
059 *   &lt;/html&gt;
060 * </pre>
061 * 
062 * And the border was used on a page like this:
063 * 
064 * <pre>
065 *   &lt;html&gt;
066 *   &lt;body&gt;
067 *     &lt;span wicket:id = &quot;myBorder&quot;&gt;
068 *       Middle
069 *     &lt;/span&gt;
070 *   &lt;/body&gt;
071 *   &lt;/html&gt;
072 * </pre>
073 * 
074 * Then the resulting HTML would look like this:
075 * 
076 * <pre>
077 *   &lt;html&gt;
078 *   &lt;body&gt;
079 *     First Middle Last
080 *   &lt;/body&gt;
081 *   &lt;/html&gt;
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 &lt;wicket:body&gt; tag.
086 * <p>
087 * Regarding &lt;wicket:body/&gt; you have two options. Either use &lt;wicket:body/&gt; (open-close
088 * tag) which will automatically be expanded to &lt;wicket:body&gt;body content&lt;/wicket:body&gt;
089 * or use &lt;wicket:body&gt;preview region&lt;/wicket:body&gt; 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 *   &lt;html&gt;
100 *   &lt;body&gt;
101 *     &lt;wicket:border&gt;
102 *       &lt;span wicket:id=&quot;someContainer&quot;&gt;
103 *         &lt;wicket:body/&gt;
104 *       &lt;/span&gt;
105 *     &lt;/wicket:border&gt;
106 *   &lt;/body&gt;
107 *   &lt;/html&gt;
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 *   &lt;html&gt;
118 *   &lt;body&gt;
119 *     &lt;span wicket:id = &quot;myBorder&quot;&gt;
120 *       &lt;input wicket:id=&quot;name&quot;/&gt;
121 *     &lt;/span&gt;
122 *   &lt;/body&gt;
123 *   &lt;/html&gt;
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 &lt;wicket:body&gt; */
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         *      &lt;span wicket:id="myBorder"&gt;
190         *              &lt;input wicket:id="text1" .. /&gt;
191         *              ...
192         *      &lt;/span&gt;
193         * 
194         * </pre>
195         * 
196         * Whereas {@link #addToBorder(Component...)} will add a component associated with the following
197         * markup:
198         * 
199         * <pre>
200         *      &lt;wicket:border&gt;
201         *              &lt;form wicket:id="myForm" .. &gt;
202         *                      &lt;wicket:body/&gt;
203         *              &lt;/form&gt;
204         *      &lt;/wicket:border&gt;
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 &lt;wicket:border&gt; 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 &lt;wicket:body&gt; 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 &lt;wicket:body&gt; 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 &lt;wicket:'name' ...&gt; on the same level, but ignoring other "transparent"
541                 * tags such as &lt;wicket:enclosure&gt; 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 &lt;span wicktet:id="myBorder"&gt; 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 &lt;wicket:border&gt; 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}