Hi all,

thank you bob for your answer. I understand my problem was more of a general
design concern than a real click centric problem.

As I needed to go be able to track the whole history of navigation of my
users, I used the bread crumb implementation from click click.

Nevertheless, the breadcrumb was not enough for me as I wanted to be able to
record parameters, not only paths of pages, into the bread crumb. Thus I
modified the breadcrumb to provide 2 additional propreties :  currentpageurl
and currentpagename.

Both can be set at init time and are optional. The first one allows to
customize the url that is added to the breadcrumb when recording the current
page (and thus adding parameters) and the second one allows to custmize the
label of the current page in the breadcrumb.

People can also get access to the last visited page url through the
getCurrentReferrer. This is handy if you want a button/link "return to
previous page" inside your page.

Here attached, you will find a patch for those classes.
(sorry package names are not valid)

Thanks for your help, my click app rocks. :)

Stéphane

2010/11/12 Bob Schellink <[email protected]>

> Hi Stéphane,
>
> This is more of a general web flow question than Click specific. From what
> you describe it sounds as
> if you don't have a page flow in your app. You simply want to unwind their
> browser history? In other
> word there is no knowledge of where users should go next?
>
> In that case you'll need to use either the session or cookies to track
> history and unwind the stack
> when needed. If your requirements are to always navigate users back, I
> don't see another way really.
>
> In our app we generally have our main navigation through a drop-down menu
> to navigate to the various
> parts of the system. That often leads to a master page where users can
> drill into a detail page and
> back to the master.So back for us means back to the master. This is quite
> similar to how the Click
> examples work as well.
>
> Another pattern is to use a breadcrumb. However I've often seen breadcrumbs
> implemented as history
> bars[1][2] which doesn't add much since browsers have back buttons.
>
> On 13/11/2010 02:24, Stéphane NICOLAS wrote:
> >
> > I got a question that remains unanswered after many attempts to solve it
> : page flows.
> >
> > I made a pretty app with click. It handles the editing and listing of 6
> different types of data, all
> > interlinked.
> > But I don't have a very good model to deal with the page flow inside my
> application.
> >
> > --- All I want is a button, on each page to come back to where you come
> from.---
> > (and of course I got many links inside my app to go to different pages)
> >
> > *It seems to me that if I use the "referrer" technique, with hidden
> fileds on post and url params on
> > get, I can achieve only one level of depth to remember the page where I
> come from. But I can't
> > remember the pages before that.
> >
> > *If I use the history.back (javascript) technique, it 's not possible to
> deal with the post
> > mechanism inside the application, it always
>
> I've found history.back quite problematic and never use it. To get around
> POST issues, use the
> Redirect after Post pattern[3]. Example here[4].
>
> > gonna resubmit. It prevent that with a onSecurityCheck but then a user
> would get errors just by
> > coming back to a page S/he posted.
> >
> > So, I don't see clearly a clean model to ease page navigation and that
> remains a real mystery to me.
> >
> > One solution though would be to put a kind of history in the session, but
> having done all those
> > efforts not to rely on sessions, putting hidden fields every where, it
> seems a pity.
>
> In the end it boils down to your requirements and what will provide the
> best experience.
>
> Kind regards
>
> Bob
>
> [1]: http://clickclick-examples.appspot.com/home.htm
> [2]:
>
> http://code.google.com/p/clickclick/source/browse/trunk/clickclick/core/src/net/sf/clickclick/control/breadcrumb
> [3]:http://click.apache.org/docs/faq.html#multiple-posts
> [4]: http://click.avoka.com/click-examples/introduction/advanced-table.htm-> 
> Edit customer
>
package org.ecoquartier.webui.control;
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.apache.click.Context;
import org.apache.click.control.AbstractControl;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.HtmlStringBuffer;

/**
 * Provides a history bar, sometimes referred to as a breadcrumb.
 * <p/>
 * The Breadcrumb records all pages a user visits and builds up a trail so one
 * can navigate back to previous pages.
 * <p/>
 * <p/>
 * By default, the Breadcrumb will record the path of the current page it has been added,
 * and display its label through getDisplayLabel. It is also possible
 * to customize both the url associated with the current page link
 * and the current page label through the currentPageURL and currentPageName properties.
 * Both properties have to be defined during the page onInit stage.
 * The current page url can be used to send parameters to the page added 
 * to the breadcrumb.
 * <p/>
 * <b>Please note</b> there are different ways to implement a breadcrumb. The
 * following article outlines a different approach than what was taken here:
 * http://www.useit.com/alertbox/breadcrumbs.html.
 */
public class Breadcrumb extends AbstractControl {

    // -------------------------------------------------------------- Constants

    /**
     * The value under which the breadcrumb trail is stored in the HttpSession,
     * "<tt>breadcrumb</tt>".
     */
    public static final String BREADCRUMB_KEY = "breadcrumb";

    // -------------------------------------------------------------- Variables

    /** The maximum number of breadcrumbs to render. */
    protected int trailLength = 5;

    /** The separator between breadcrumbs, defaults to <tt>"/"</tt>. */
    protected String separator = " >> ";

    /** Tracks the page paths the user visits. */
    protected transient Trail<String, String> trail;

    /** The breadcrumb label. */
    protected String label;

    /** Set of URL paths that should not be tracked. */
    protected transient Set<String> excludedPaths = new HashSet<String>();

    /** Name of the current page to be registered at init time. */
    protected String currentPageName = null;
    
    /** URL of the current page to be registered at init time. 
     * Can be used to pass parameters to the page inside the trail. */
    protected String currentPageURL = null;
    // ----------------------------------------------------------- Constructors

    /**
     * Create a default Breadcrumb.
     */
    public Breadcrumb() {
    }

    /**
     * Create a Breadcrumb with the given name.
     *
     * @param name the name of the breadcrumb
     */
    public Breadcrumb(String name) {
        setName(name);
    }

    /**
     * Create a Breadcrumb with the given name and label.
     *
     * @param name the name of the breadcrumb
     * @param label the label of the breadcrumb
     */
    public Breadcrumb(String name, String label) {
        setName(name);
        setLabel(label);
    }

    /**
     * Create a Breadcrumb with the given name and trail length.
     *
     * @param name the name of the breadcrumb
     * @param trailLength the number of breadcrumbs to render
     */
    public Breadcrumb(String name, int trailLength) {
        this(name, null, trailLength, " >> ");
    }

    /**
     * Create a Breadcrumb with the given name, label and trail length.
     *
     * @param name the name of the breadcrumb
     * @param label the label of the breadcrumb
     * @param trailLength the number of breadcrumbs to render
     */
    public Breadcrumb(String name, String label, int trailLength) {
        this(name, label, trailLength, " >> ");
    }

    /**
     * Create a Breadcrumb with the given name, label, trail length and
     * separator.
     *
     * @param name the name of the breadcrumb
     * @param label the label of the breadcrumb
     * @param trailLength the number of breadcrumbs to render
     * @param separator the separator to render between breadcrumbs
     */
    public Breadcrumb(String name, String label, int trailLength, String separator) {
        setName(name);
        setLabel(label);
        setTrailLength(trailLength);
        setSeparator(separator);
    }

    // ------------------------------------------------------ Public Properties

    /**
     * Return the number of breadcrumbs to render.
     *
     * @return the number of breadcrumbs to render
     */
    public int getTrailLength() {
        return trailLength;
    }

    public String getCurrentPageURL() {
		return currentPageURL;
	}

	public void setCurrentPageURL(String currentPageURL) {
		this.currentPageURL = currentPageURL;
	}

	public String getCurrentPageName() {
		return currentPageName;
	}

	public void setCurrentPageName(String currentPageName) {
		this.currentPageName = currentPageName;
	}

	/**
     * Set the number of breadcrumbs to render.
     *
     * @param trailLength the number of breadcrumbs to render
     */
    public void setTrailLength(int trailLength) {
        this.trailLength = trailLength;
    }

    /**
     * Return the separator to render between breadcrumbs.
     *
     * @return the separator to render between breadcrumbs
     */
    public String getSeparator() {
        return separator;
    }

    /**
     * Set the separator to render between breadcrumbs.
     *
     * @param separator the separator to render between breadcrumbs
     */
    public void setSeparator(String separator) {
        this.separator = separator;
    }

    /**
     * Return the breadcrumb label.
     *
     * @return the breadcrumb label
     */
    public String getLabel() {
        return label;
    }

    /**
     * Set the breadcrumb label.
     *
     * @param label the breadcrumb label
     */
    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * Return the set of URL paths that should not be tracked.
     *
     * @return the set of URL paths that should not be tracked
     */
    public Set<String> getExcludedPaths() {
        return excludedPaths;
    }

    /**
     * Set the set of URL paths that should not be tracked.
     *
     * @param excludedPaths the set of URL paths that should not be tracked
     */
    public void setLabel(Set<String> excludedPaths) {
        this.excludedPaths = excludedPaths;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * The onInit event handler performs the following operations:
     * <ul>
     * <li>Invokes {...@link #restoreState(org.apache.click.Context)} to retrieve
     * the stored {...@link Trail} instance between requests.</li>
     * <li>Stores the {...@link org.apache.click.util.ClickUtils#getResourcePath(javax.servlet.http.HttpServletRequest) path}
     * of the parent Page in the {...@link Trail} instance.</li>
     * </ul>
     */
    public void onInit() {
        Context context = getContext();
        restoreState(context);
        String contextPath = context.getRequest().getContextPath();
        String path = ClickUtils.getResourcePath(context.getRequest());
        
        String pageURL = currentPageURL != null ? currentPageURL : contextPath + path ;
        String pageName = currentPageName != null ? currentPageName : getDisplayLabel(path);
        addTrail( pageURL , pageName  );
    }

    /**
     * The onDestroy event handler performs the following operations:
     * <ul>
     * <li>Invokes {...@link #saveState(org.apache.click.Context)} to store the
     * {...@link Trail} instance between requests.</li>
     * </ul>
     */
    public void onDestroy() {
        Context context = getContext();
        saveState(context);
    }

   /**
    * Override default implementation as no processing is necessary.
    *
    * @see org.apache.click.Control#onProcess()
    *
    * @return true to continue Page event processing or false otherwise
    */
    public boolean onProcess() {
        return true;
    }

   
    /**
     * Add the specified path to the trail.
     *
     * @param path the path to add to the trail
     */
    public void addTrail(String path, String pageName ) {
        for (String excludedPath : getExcludedPaths()) {
            if (path.indexOf(excludedPath) >= 0) {
                return;
            }
        }

        if (getTrail().containsKey(path)) {
            collapseTrail(path);
            return;
        }
        
        
        getTrail().put(path, pageName);
    }

    /**
     * Remove the specified path from the trail.
     *
     * @param path the path to remove from the trail
     */
    public void removeTrail(String path) {
        getTrail().remove(path);
    }

    /**
     * Return the size of the breadcrumb.
     *
     * @return the size of the breadcrumb
     */
    public int size() {
        return getTrail().size();
    }

    /**
     * Remove all entries from the breadcrumb.
     */
    public void clear() {
        getTrail().clear();
    }

    /**
     * Return the trail of breadcrumbs as a Map.
     *
     * @return the trail of breadcrumb
     */
    public Map<String, String> getTrail() {
        if (trail == null) {
            trail = new Trail<String, String>(this);
        }
        return (Map<String, String>) trail;
    }

    /**
     * Render the breadcrumb's output to the specified buffer.
     * <p/>
     * @see org.apache.click.Control#render(org.apache.click.util.HtmlStringBuffer)
     *
     * @param buffer the specified buffer to render the control's output to
     */
    public void render(HtmlStringBuffer buffer) {
        buffer.elementStart("div");
        buffer.appendAttribute("id", getId());
        buffer.closeTag();
        if (getLabel() != null) {
            buffer.append("<span>");
            buffer.append(getLabel());
            buffer.append(" - ");
            buffer.append("</span>");
        }

        // Only render when there are at least 1 trail entries
        if (getTrail().size() > 0) {
            Iterator it = getTrail().entrySet().iterator();
            while (it.hasNext()) {
                Entry entry = (Entry) it.next();

                String path = (String) entry.getKey();
                String label = (String) entry.getValue();
                renderPath(buffer, path, label, !it.hasNext());

                if (it.hasNext()) {
                    buffer.append("<span style=\"padding:0 2px;\">");
                    buffer.append(getSeparator());
                    buffer.append("</span>");
                }
            }
        }
        buffer.append("</div>");
    }

   /**
     * Returns the HTML representation of this control.
     * <p/>
     * This method delegates the rendering to the method
     * {...@link #render(org.apache.click.util.HtmlStringBuffer)}.
     *
     * @see Object#toString()
     *
     * @return the HTML representation of this control
     */
    public String toString() {
        HtmlStringBuffer buffer = new HtmlStringBuffer(getTrail().size() * 20);
        render(buffer);
        return buffer.toString();
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Render a breadcrumb path to the given buffer.
     *
     * @param buffer the buffer to render to
     * @param path the path to render
     * @param label the label to render for the given path
     * @param isLastEntry true if this is the last path to render, false otherwise
     */
    protected void renderPath(HtmlStringBuffer buffer, String path,
        String label, boolean isLastEntry) {

        // If its the last entry only render a string not a hyperlink.
        if (isLastEntry) {
            buffer.append(label);
            return;
        }

        buffer.elementStart("a");

        buffer.appendAttribute("href", path);

        // Append all attributes
        appendAttributes(buffer);

        buffer.closeTag();

        buffer.append(label);

        buffer.elementEnd("a");
    }

    /**
     * Return the label to display for the given path.
     *
     * @param path the path of the current page
     * @return the label to display for the given path
     */
    protected String getDisplayLabel(String path) {

        // We start off optimistic
        String pagePath = path;

        // Extract the page path
        int pageIndex = pagePath.lastIndexOf("/");
        if (pageIndex != -1) {
            pagePath = pagePath.substring(pageIndex + 1);
        }

        // Chop off the page extension eg. ".htm"
        int extensionIndex = pagePath.lastIndexOf(".");
        if (extensionIndex != -1) {
            pagePath = pagePath.substring(0, extensionIndex);
        }
        return pagePath;
    }

    /**
     * This method ensures that when a user navigates to a URL that is already
     * present in the breadcrumb trail, all entries after this path is removed.
     *
     * @param newPath the current URL path
     */
    protected void collapseTrail(String newPath) {
        // Indicates if trail entries must be removed
        boolean remove = false;
        for (Iterator it = getTrail().keySet().iterator(); it.hasNext();) {
            String path = (String) it.next();

            if (remove) {
                // Remove the current trail entry and continue iterating
                it.remove();
                continue;
            }

            // Check if current trail path equals newPath
            if (path.equals(newPath)) {
                // Activate remove indicator to remove all remaining entries
                remove = true;
            }
        }
    }

    /**
     * Retrieves the breadcrumb {...@link #getTrail() trail} from the HttpSession.
     * The trail is retrieved from the HttpSession with the key {...@link #BREADCRUMB_KEY}.
     *
     * @param context the current request context
     */
    protected void restoreState(Context context) {
        Trail existingTrail = (Trail) context.getSession().getAttribute(BREADCRUMB_KEY);
        if (existingTrail == null) {
            return;
        }
        this.trail = existingTrail;
        this.trail.setBreadcrumb(this);
        
    }

    /**
     * Stores the breadcrumb {...@link #getTrail() trail} in the HttpSession.
     * The trail is stored in the HttpSession under the key {...@link #BREADCRUMB_KEY}.
     *
     * @param context the current request context
     */
    protected void saveState(Context context) {
        HttpSession session = context.getRequest().getSession(false);
        if (session != null) {
            session.setAttribute(BREADCRUMB_KEY, trail);
        }
    }

	public String getCurrentReferrer() {
		String referrer = null;
		String current = null;
		Iterator<String> iter = this.trail.keySet().iterator();
		while( iter.hasNext() )
		{
			referrer = current;
			current = iter.next();
		}//while
		return referrer;
	}
}
package org.ecoquartier.webui.control;

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Provides a trail for the breadcrumb control.
 * <p/>
 * This class provides a FIFO (First In First Out) cache clearance. Thus the
 * first entry added will be the first entry being removed when necessary.
 */
public class Trail<K, V> extends LinkedHashMap<K, V> {

    // -------------------------------------------------------------- Variables

	private static final long serialVersionUID = 12345435L;
	/** The breadcrumb control. */
    private transient Breadcrumb breadcrumb;

    /**
     * Create a Trail for the given breadcrumb.
     *
     * @param breadcrumb the breadcrumb control
     */
    public Trail(Breadcrumb breadcrumb) {
        this.breadcrumb = breadcrumb;
    }

    // ------------------------------------------------------ Public Properties

    /**
     * Return the Trail's breadcrumb control.
     *
     * @return the Trail's breadcrumb control
     */
    public Breadcrumb getBreadcrumb() {
        return breadcrumb;
    }

    /**
     * Set the Trail's breadcrumb control.
     *
     * @param breadcrumb the Trail's breadcrumb control
     */
    public void setBreadcrumb(Breadcrumb breadcrumb) {
        this.breadcrumb = breadcrumb;
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Return true if the older entry must be removed, false otherwise.
     *
     * @param eldest the eldest entry in the Map
     * @return true if the eldest entry must be removed, false otherwise
     */
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return size() > getBreadcrumb().getTrailLength();
    }
}

Reply via email to