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.extensions.breadcrumb;
018:
019: import java.util.ArrayList;
020: import java.util.Iterator;
021: import java.util.List;
022:
023: import org.apache.wicket.Component;
024: import org.apache.wicket.markup.html.basic.Label;
025: import org.apache.wicket.markup.html.list.ListItem;
026: import org.apache.wicket.markup.html.list.ListView;
027: import org.apache.wicket.markup.html.panel.Panel;
028: import org.apache.wicket.model.IDetachable;
029: import org.apache.wicket.model.LoadableDetachableModel;
030:
031: /**
032: * A component that renders bread crumbs. By default, it renders a horizontal
033: * list from left to right (oldest left) with bread crumb links and a ' / ' as a
034: * seperator, e.g.
035: *
036: * <pre>
037: * first / second / third
038: * </pre>
039: *
040: * This component also functions as a implementation of
041: * {@link IBreadCrumbModel bread crumb model}. This component holds the state
042: * as well as doing the rendering. Override and provide your own markup file if
043: * you want to work with other elements, e.g. uls instead of spans.
044: *
045: * @author Eelco Hillenius
046: */
047: public class BreadCrumbBar extends Panel implements IBreadCrumbModel {
048: /** Default crumb component. */
049: private static final class BreadCrumbComponent extends Panel {
050: private static final long serialVersionUID = 1L;
051:
052: /**
053: * Construct.
054: *
055: * @param id
056: * Component id
057: * @param index
058: * The index of the bread crumb
059: * @param breadCrumbModel
060: * The bread crumb model
061: * @param breadCrumbParticipant
062: * The bread crumb
063: * @param enableLink
064: * Whether the link should be enabled
065: */
066: public BreadCrumbComponent(String id, int index,
067: IBreadCrumbModel breadCrumbModel,
068: final IBreadCrumbParticipant breadCrumbParticipant,
069: boolean enableLink) {
070: super (id);
071: add(new Label("sep", (index > 0) ? "/" : "")
072: .setEscapeModelStrings(false).setRenderBodyOnly(
073: true));
074: BreadCrumbLink link = new BreadCrumbLink("link",
075: breadCrumbModel) {
076: private static final long serialVersionUID = 1L;
077:
078: protected IBreadCrumbParticipant getParticipant(
079: String componentId) {
080: return breadCrumbParticipant;
081: }
082: };
083: link.setEnabled(enableLink);
084: add(link);
085: link.add(new Label("label", breadCrumbParticipant
086: .getTitle()).setRenderBodyOnly(true));
087: }
088: }
089:
090: /**
091: * List view for rendering the bread crumbs.
092: */
093: protected class BreadCrumbsListView extends ListView implements
094: IBreadCrumbModelListener {
095: private static final long serialVersionUID = 1L;
096:
097: private transient boolean dirty = false;
098:
099: private transient int size;
100:
101: /**
102: * Construct.
103: *
104: * @param id
105: * Component id
106: */
107: public BreadCrumbsListView(String id) {
108: super (id);
109: setReuseItems(false);
110: setModel(new LoadableDetachableModel() {
111: private static final long serialVersionUID = 1L;
112:
113: protected Object load() {
114: // save a copy
115: List l = new ArrayList(allBreadCrumbParticipants());
116: size = l.size();
117: return l;
118: }
119: });
120: }
121:
122: /**
123: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener#breadCrumbActivated(org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant,
124: * org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant)
125: */
126: public void breadCrumbActivated(
127: IBreadCrumbParticipant previousParticipant,
128: IBreadCrumbParticipant breadCrumbParticipant) {
129: signalModelChange();
130: }
131:
132: /**
133: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener#breadCrumbAdded(org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant)
134: */
135: public void breadCrumbAdded(
136: IBreadCrumbParticipant breadCrumbParticipant) {
137: }
138:
139: /**
140: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener#breadCrumbRemoved(org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant)
141: */
142: public void breadCrumbRemoved(
143: IBreadCrumbParticipant breadCrumbParticipant) {
144: }
145:
146: /**
147: * Signal model change.
148: */
149: private void signalModelChange() {
150: // else let the listview recalculate it's childs immediately;
151: // it was attached, but it needs to go trhough that again now
152: // as the signalling component attached after this
153: getModel().detach();
154: super .internalOnAttach();
155: }
156:
157: /**
158: * @see org.apache.wicket.markup.html.list.ListView#onBeforeRender()
159: */
160: protected void onBeforeRender() {
161: super .onBeforeRender();
162: if (dirty) {
163: super .internalOnAttach();
164: this .dirty = false;
165: }
166: }
167:
168: /**
169: * @see org.apache.wicket.markup.html.list.ListView#populateItem(org.apache.wicket.markup.html.list.ListItem)
170: */
171: protected void populateItem(ListItem item) {
172: int index = item.getIndex();
173: IBreadCrumbParticipant breadCrumbParticipant = (IBreadCrumbParticipant) item
174: .getModelObject();
175: item.add(newBreadCrumbComponent("crumb", index, size,
176: breadCrumbParticipant));
177: }
178: }
179:
180: private static final long serialVersionUID = 1L;
181:
182: /** The currently active participant, if any (possibly null). */
183: private IBreadCrumbParticipant activeParticipant = null;
184:
185: /** Holds the current list of crumbs. */
186: private List crumbs = new ArrayList();
187:
188: /** listeners utility. */
189: private final BreadCrumbModelListenerSupport listenerSupport = new BreadCrumbModelListenerSupport();
190:
191: /**
192: * Construct.
193: *
194: * @param id
195: * Component id
196: */
197: public BreadCrumbBar(String id) {
198: super (id);
199: BreadCrumbsListView breadCrumbsListView = new BreadCrumbsListView(
200: "crumbs");
201: addListener(breadCrumbsListView);
202: add(breadCrumbsListView);
203: }
204:
205: /**
206: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel#addListener(org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener)
207: */
208: public final void addListener(IBreadCrumbModelListener listener) {
209: this .listenerSupport.addListener(listener);
210: }
211:
212: /**
213: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel#allBreadCrumbParticipants()
214: */
215: public final List allBreadCrumbParticipants() {
216: return crumbs;
217: }
218:
219: /**
220: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel#getActive()
221: */
222: public IBreadCrumbParticipant getActive() {
223: return activeParticipant;
224: }
225:
226: /**
227: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel#removeListener(org.apache.wicket.extensions.breadcrumb.IBreadCrumbModelListener)
228: */
229: public final void removeListener(IBreadCrumbModelListener listener) {
230: this .listenerSupport.removeListener(listener);
231: }
232:
233: /**
234: * @see org.apache.wicket.extensions.breadcrumb.IBreadCrumbModel#setActive(org.apache.wicket.extensions.breadcrumb.IBreadCrumbParticipant)
235: */
236: public final void setActive(
237: final IBreadCrumbParticipant breadCrumbParticipant) {
238: // see if the bread crumb was already added, and if so,
239: // clean up the stack after (on top of) this bred crumb
240: // and notify listeners of the removal
241: int len = crumbs.size() - 1;
242: int i = len;
243: while (i > -1) {
244: IBreadCrumbParticipant temp = (IBreadCrumbParticipant) crumbs
245: .get(i);
246:
247: // if we found the bread crumb
248: if (breadCrumbParticipant.equals(temp)) {
249: // remove the bread crumbs after this one
250: int j = len;
251: while (j > i) {
252: // remove and fire event
253: IBreadCrumbParticipant removed = (IBreadCrumbParticipant) crumbs
254: .remove(j--);
255: listenerSupport.fireBreadCrumbRemoved(removed);
256: }
257:
258: // activate the bread crumb participant
259: activate(breadCrumbParticipant);
260:
261: // we're done; the provided bread crumb is on top
262: // and the content is replaced, so just return this function
263: return;
264: }
265:
266: i--;
267: }
268:
269: // arriving here means we weren't able to find the bread crumb
270: // add the new crumb
271: crumbs.add(breadCrumbParticipant);
272:
273: // and notify listeners
274: listenerSupport.fireBreadCrumbAdded(breadCrumbParticipant);
275:
276: // activate the bread crumb participant
277: activate(breadCrumbParticipant);
278: }
279:
280: /**
281: * Activates the bread crumb participant.
282: *
283: * @param breadCrumbParticipant
284: * The participant to activate
285: */
286: protected final void activate(
287: final IBreadCrumbParticipant breadCrumbParticipant) {
288: // get old value
289: IBreadCrumbParticipant previousParticipant = this .activeParticipant;
290:
291: // and set the provided participant as the active one
292: this .activeParticipant = breadCrumbParticipant;
293:
294: // fire bread crumb activated event
295: listenerSupport.fireBreadCrumbActivated(previousParticipant,
296: breadCrumbParticipant);
297:
298: // signal the bread crumb participant that it is selected as the
299: // currently active one
300: breadCrumbParticipant.onActivate(previousParticipant);
301: }
302:
303: /**
304: * Gets whether the current bread crumb should be displayed as a link (e.g.
305: * for refreshing) or as a disabled link (effictively just a label). The
306: * latter is the default. Override if you want different behavior.
307: *
308: * @return Whether the current bread crumb should be displayed as a link;
309: * this method returns false
310: */
311: protected boolean getEnableLinkToCurrent() {
312: return false;
313: }
314:
315: /**
316: * Creates a new bread crumb component. That component will be rendered as
317: * part of the bread crumbs list (which is a <ul> <li>
318: * structure).
319: *
320: * @param id
321: * The component id
322: * @param index
323: * The index of the bread crumb
324: * @param total
325: * The total number of bread crumbs in the current model
326: * @param breadCrumbParticipant
327: * the bread crumb
328: * @return A new bread crumb component
329: */
330: protected Component newBreadCrumbComponent(String id, int index,
331: int total, IBreadCrumbParticipant breadCrumbParticipant) {
332: boolean enableLink = getEnableLinkToCurrent()
333: || (index < (total - 1));
334: return new BreadCrumbComponent(id, index, this ,
335: breadCrumbParticipant, enableLink);
336: }
337:
338: /**
339: * @see org.apache.wicket.Component#onDetach()
340: */
341: protected void onDetach() {
342: super .onDetach();
343: for (Iterator i = crumbs.iterator(); i.hasNext();) {
344: IBreadCrumbParticipant crumb = (IBreadCrumbParticipant) i
345: .next();
346: if (crumb instanceof Component) {
347: ((Component) crumb).detach();
348: } else if (crumb instanceof IDetachable) {
349: ((IDetachable) crumb).detach();
350: }
351: }
352: }
353: }
|