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: */
017: package org.apache.wicket.markup.html.panel;
018:
019: import org.apache.wicket.MarkupContainer;
020: import org.apache.wicket.markup.ComponentTag;
021: import org.apache.wicket.markup.MarkupException;
022: import org.apache.wicket.markup.MarkupNotFoundException;
023: import org.apache.wicket.markup.MarkupStream;
024: import org.apache.wicket.markup.html.WebMarkupContainerWithAssociatedMarkup;
025: import org.apache.wicket.markup.parser.XmlTag;
026: import org.apache.wicket.model.IModel;
027: import org.apache.wicket.util.lang.Objects;
028: import org.apache.wicket.version.undo.Change;
029:
030: /**
031: * Usually you either have a markup file or a xml tag with
032: * wicket:id="myComponent" to associate markup with a component. However in some
033: * rare cases, especially when working with small panels it is a bit awkward to
034: * maintain tiny pieces of markup in plenty of panel markup files. Use cases are
035: * for example list views where list items are different depending on a state.
036: * <p>
037: * Fragments provide a means to maintain the panels tiny piece of markup in the
038: * parents markup file.
039: * <p>
040: *
041: * <pre>
042: * <span wicket:id="myPanel">Example input (will be removed)</span>
043: *
044: * <wicket:fragment wicket:id="frag1">panel 1</wicket:fragment>
045: * <wicket:fragment wicket:id="frag2">panel 2</wicket:fragment>
046: * </pre>
047: * <pre>
048: * add(new Fragment("myPanel1", "frag1");
049: * </pre>
050: *
051: * @author Juergen Donnerstag
052: */
053: public class Fragment extends WebMarkupContainerWithAssociatedMarkup {
054: private static final long serialVersionUID = 1L;
055:
056: /** The wicket:id of the associated markup fragment */
057: private String markupId;
058:
059: /** The container providing the inline markup */
060: private final MarkupContainer markupProvider;
061:
062: /**
063: * Constructor.
064: *
065: * @see org.apache.wicket.Component#Component(String)
066: *
067: * @param id
068: * The component id
069: * @param markupId
070: * The associated id of the associated markup fragment
071: */
072: public Fragment(final String id, final String markupId) {
073: this (id, markupId, null, null);
074: }
075:
076: /**
077: * Constructor.
078: *
079: * @see org.apache.wicket.Component#Component(String)
080: *
081: * @param id
082: * The component id
083: * @param markupId
084: * The associated id of the associated markup fragment
085: * @param model
086: * The model for this fragment
087: */
088: public Fragment(final String id, final String markupId,
089: final IModel model) {
090: this (id, markupId, null, model);
091: }
092:
093: /**
094: * Constructor.
095: *
096: * @see org.apache.wicket.Component#Component(String)
097: *
098: * @param id
099: * The component id
100: * @param markupId
101: * The associated id of the associated markup fragment
102: * @param markupProvider
103: * The component whose markup contains the fragment's markup
104: */
105: public Fragment(final String id, final String markupId,
106: final MarkupContainer markupProvider) {
107: this (id, markupId, markupProvider, null);
108: }
109:
110: /**
111: * Constructor.
112: *
113: * @see org.apache.wicket.Component#Component(String)
114: *
115: * @param id
116: * The component id
117: * @param markupId
118: * The associated id of the associated markup fragment
119: * @param markupProvider
120: * The component whose markup contains the fragment's markup
121: * @param model
122: * The model for this fragment
123: */
124: public Fragment(final String id, final String markupId,
125: final MarkupContainer markupProvider, final IModel model) {
126: super (id, model);
127:
128: if (markupId == null) {
129: throw new IllegalArgumentException(
130: "markupId cannot be null");
131: }
132:
133: this .markupId = markupId;
134: this .markupProvider = markupProvider;
135: }
136:
137: /**
138: * The associated markup fragment can be modified
139: *
140: * @param markupId
141: */
142: public final void setMarkupTagReferenceId(final String markupId) {
143: if (markupId == null) {
144: throw new IllegalArgumentException(
145: "markupId cannot be null");
146: }
147: if (!Objects.equal(this .markupId, markupId)) {
148: addStateChange(new Change() {
149: private static final long serialVersionUID = 1L;
150: private final String oldMarkupId = Fragment.this .markupId;
151:
152: public void undo() {
153: Fragment.this .markupId = oldMarkupId;
154: }
155:
156: });
157: }
158: this .markupId = markupId;
159: }
160:
161: /**
162: * Make sure we open up open-close tags to open-body-close
163: *
164: * @see org.apache.wicket.Component#onComponentTag(org.apache.wicket.markup.ComponentTag)
165: */
166: protected void onComponentTag(final ComponentTag tag) {
167: if (tag.isOpenClose()) {
168: tag.setType(XmlTag.OPEN);
169: }
170: super .onComponentTag(tag);
171: }
172:
173: /**
174: *
175: * @see org.apache.wicket.Component#onComponentTagBody(org.apache.wicket.markup.MarkupStream,
176: * org.apache.wicket.markup.ComponentTag)
177: */
178: protected void onComponentTagBody(final MarkupStream markupStream,
179: final ComponentTag openTag) {
180: // Skip the components body. It will be replaced by the fragment
181: if (((ComponentTag) markupStream.get(markupStream
182: .getCurrentIndex() - 1)).isOpen()) {
183: markupStream.skipRawMarkup();
184: }
185:
186: final MarkupStream providerMarkupStream = chooseMarkupStream(markupStream);
187: if (providerMarkupStream == null) {
188: throw new MarkupNotFoundException(
189: "Fragment: No markup stream found for providing markup container "
190: + markupProvider.toString()
191: + ". Fragment: " + toString());
192: }
193:
194: renderFragment(providerMarkupStream, openTag);
195: }
196:
197: /**
198: * Get the markup stream which shall be used to search for the fragment
199: *
200: * @param markupStream
201: * The markup stream is associated with the component (not the
202: * fragment)
203: * @return The markup stream to be used to find the fragment markup
204: */
205: protected MarkupStream chooseMarkupStream(
206: final MarkupStream markupStream) {
207: MarkupStream stream = null;
208:
209: if (markupProvider == null) {
210: stream = markupStream;
211: } else {
212: stream = markupProvider.getAssociatedMarkupStream(false);
213: if (stream == null) {
214: // The following statement assumes that the markup provider is a
215: // parent along the line up to the Page
216: stream = markupProvider.getMarkupStream();
217: }
218: }
219: return stream;
220: }
221:
222: /**
223: * Render the markup starting at the current position of the markup strean
224: *
225: * @see #onComponentTagBody(MarkupStream, ComponentTag)
226: *
227: * @param providerMarkupStream
228: * @param openTag
229: */
230: private void renderFragment(
231: final MarkupStream providerMarkupStream,
232: final ComponentTag openTag) {
233: // remember the current position in the markup. Will have to come back
234: // to it.
235: int currentIndex = providerMarkupStream.getCurrentIndex();
236:
237: // Find the markup fragment
238: int index = providerMarkupStream.findComponentIndex(null,
239: markupId);
240: if (index == -1) {
241: throw new MarkupException("Markup of component class `"
242: + providerMarkupStream.getContainerClass()
243: .getName()
244: + "` does not contain a fragment with wicket:id `"
245: + markupId + "`. Context: " + toString());
246: }
247:
248: // Set the markup stream position to where the fragment begins
249: providerMarkupStream.setCurrentIndex(index);
250:
251: try {
252: // Get the fragments open tag
253: ComponentTag fragmentOpenTag = providerMarkupStream
254: .getTag();
255:
256: // We'll completely ignore the fragments open tag. It'll not be
257: // rendered
258: providerMarkupStream.next();
259:
260: // Render the body of the fragment
261: super .onComponentTagBody(providerMarkupStream,
262: fragmentOpenTag);
263: } finally {
264: // Make sure the markup stream is positioned where we started back
265: // at the original component
266: providerMarkupStream.setCurrentIndex(currentIndex);
267: }
268: }
269:
270: /**
271: * Position the markup stream at the child component relative to the
272: * <b>provider</b> markup
273: *
274: * @param path
275: * @return
276: */
277: public MarkupStream findComponentIndex(final String path) {
278: MarkupStream markupStream = getAssociatedMarkupStream(true);
279: int index = markupStream.findComponentIndex(markupId, path);
280: if (index == -1) {
281: throw new MarkupException("Markup of component class `"
282: + markupStream.getContainerClass().getName()
283: + "` does not contain a fragment with wicket:id `"
284: + markupId + "`. Context: " + toString());
285: }
286: markupStream.setCurrentIndex(index);
287: return markupStream;
288: }
289:
290: /**
291: * @see org.apache.wicket.MarkupContainer#hasAssociatedMarkup()
292: */
293: public boolean hasAssociatedMarkup() {
294: return true;
295: }
296:
297: /**
298: * @see org.apache.wicket.MarkupContainer#getAssociatedMarkupStream(boolean)
299: */
300: public MarkupStream getAssociatedMarkupStream(boolean throwException) {
301: MarkupStream stream = null;
302:
303: if (markupProvider != null) {
304: stream = markupProvider.getAssociatedMarkupStream(false);
305: }
306:
307: // try self's markup stream
308: if (stream == null) {
309: stream = super .getAssociatedMarkupStream(false);
310: }
311:
312: // if self doesnt have markup stream try the parent's
313: if (stream == null && getParent() != null) {
314: stream = getParent().getAssociatedMarkupStream(false);
315: }
316:
317: // if we cant find any markup stream
318: if (stream == null && throwException) {
319: // fail, but fail with an error message that will point to this
320: // component
321: super .getAssociatedMarkupStream(true);
322: }
323:
324: return stream;
325: }
326: }
|