I just finished getting my POC working. It's three classes (the two
new ones are attached) with no modifications to the original Wicket
source. It could probably be a lot more elegant otherwise, but I was
mainly concerned with weather my approach was even possible. Basicly,
you just set your IPageFactory to StatelessPageFactory and wrap any
controls that you want maintained on the client in a StatelessForm,
which handles adding JavaScript to links for form submission and the
hidden fields. It's kind of cool to take a complicated sorted, paging
list, click it a bunch of times, restart the server, click refresh,
and have it pop right back exactly how you left it. :)

-Phil

On 7/1/05, Phil Kulak <[EMAIL PROTECTED]> wrote:
> Johan, I think you're making it a bit harder then it needs to be. I've
> actually got it working (depending on how you define "working") on my
> test machine right now with just that serialization class and a
> subclass of form. Since a form has easy access to it's page and all
> it's components, it can add javascript to every link it finds for
> onClick submits, serialize its page on render on right it to a hidden
> field, unserialize the page on beginRequest, and find it's submitting
> link on validate to call onClick(). Granted, it would probably be nice
> to move some of these things out of the form, page unserialization
> probably, but it can be pretty simple.
> 
> The only problem I'm having is that I have to use an
> IFormSubmitListener, which means that while the page IS restored from
> the client, it's first taken from the session, which doesn't really
> make any sense. I haven't figured out the "right" way to create a new
> page on every request. I'll probably take a look at how bookmarkable
> pages work.
> 
> -Phil
> 
> On 7/1/05, Eelco Hillenius <[EMAIL PROTECTED]> wrote:
> > Your mailbox is full. We (Martijn and I) keep getting warnings from your
> > box.
> >
> > Eelco
> >
> > Johan Compagner wrote:
> >
> > > i didn't see the patch yet (where is that mail?? sourceforge problem?)
> >
> >
> >
> >
> > -------------------------------------------------------
> > SF.Net email is sponsored by: Discover Easy Linux Migration Strategies
> > from IBM. Find simple to follow Roadmaps, straightforward articles,
> > informative Webcasts and more! Get everything you need to get up to
> > speed, fast. http://ads.osdn.com/?ad_id=7477&alloc_id=16492&op=click
> > _______________________________________________
> > Wicket-develop mailing list
> > [email protected]
> > https://lists.sourceforge.net/lists/listinfo/wicket-develop
> >
>
package com.apropobenefits.wicket;

import wicket.AttributeModifier;
import wicket.Component;
import wicket.markup.ComponentTag;
import wicket.markup.MarkupStream;
import wicket.markup.html.form.Form;
import wicket.markup.html.link.Link;
import wicket.markup.parser.XmlTag;
import wicket.model.Model;

public class StatelessForm extends Form {
	public StatelessForm(final String id) {
		super(id);
	}
	
	/**
	 * Adds an ID and sets the action of the form to a bookmarkable link.
	 */
	@Override
	protected void onComponentTag(final ComponentTag tag) {
		super.onComponentTag(tag);
		tag.put("action", getPage().urlFor(null, getPage().getClass(), null));
		tag.put("id", "theForm");
	}
	
	/**
	 * Adds some javascript to every link within this form. As well as a
	 * couple hidden input fields.
	 */
	@Override
	protected void onComponentTagBody(MarkupStream markupStream, 
			ComponentTag openTag) {
		
		// This gives every link a JavaScript onClick() method and wipes out
		// its origonal href attribute. It would probably be better if the links
		// themselves took care of thier own rendering, but this was the quickest
		// solution.
		this.visitChildren(Link.class, new IVisitor() {
			public Object component(Component component) {
				String script = 
					"document.getElementById('submittingLink').value = '" +
					component.getPageRelativePath() + "';" +
					"document.getElementById('theForm').submit();" + 
					"return false";
				
				component.add(new AttributeModifier("onClick", true, 
					new Model(script)));
				
				component.add(new AttributeModifier("href", true, 
					new Model("#")));
				return CONTINUE_TRAVERSAL;
			}
			
		});
		
		// The pageState hidden field.
		XmlTag psInput = new XmlTag();
		psInput.setType(XmlTag.OPEN_CLOSE);
		psInput.setName("input");
		psInput.put("type", "hidden");
		psInput.put("name", PageState.REQUEST_KEY);
		psInput.put("value", new PageState(getPage()).getString());
		
		renderComponentTag(new ComponentTag(psInput));
		
		// The field that gets populated with the path to the submitting link.
		XmlTag subLink = new XmlTag();
		subLink.setType(XmlTag.OPEN_CLOSE);
		subLink.setName("input");
		subLink.put("type", "hidden");
		subLink.put("name", "submittingLink");
		subLink.put("id", "submittingLink");
		
		renderComponentTag(new ComponentTag(subLink));
		
		super.onComponentTagBody(markupStream, openTag);
	}
	
	/**
	 * Called by the page factory just after population.
	 */
	protected void onLinkSubmit() {
		Link link = findSubmittingLink();
		
		if (link != null) {
			link.onClick();
		}
	}
	
	/**
	 * Returns the link that caused this form to be submitted or null
	 * if it wasn't submitted with a link.
	 */
	private Link findSubmittingLink() {
		final String path = getRequest().getParameter("submittingLink");
		
		return (Link) visitChildren(Link.class, new IVisitor() {
			public Object component(final Component component) {
				if (component.getPageRelativePath().equals(path)) {
					return component;
				}
				return CONTINUE_TRAVERSAL;
			}
		});
	}
}
package com.apropobenefits.wicket;

import wicket.Component;
import wicket.DefaultPageFactory;
import wicket.IPageFactory;
import wicket.Page;
import wicket.PageParameters;
import wicket.Request;
import wicket.RequestCycle;
import wicket.Component.IVisitor;

/**
 * A page factory that takes care of state management.
 * 
 * @author Phil Kulak
 */
public class StatelessPageFactory implements IPageFactory {
	private IPageFactory internalFactory = new DefaultPageFactory();

	public Page newPage(Class c, PageParameters params) {
		return populatePage(internalFactory.newPage(c, params));
	}

	public Page newPage(Class c) {
		return populatePage(internalFactory.newPage(c));
	}
	
	private Page populatePage(Page page) {
		Request request = RequestCycle.get().getRequest();
		String ser = request.getParameter(PageState.REQUEST_KEY);
		
		if (ser != null) {
			// Return the page to its origonal state.
			new PageState(ser).populatePage(page);
			
			// Find the form that submitted. Actually, find the FIRST stateless
			// form. If there's two forms you're out of luck.
			page.visitChildren(StatelessForm.class, new IVisitor() {
				public Object component(Component component) {
					((StatelessForm) component).onLinkSubmit();
					return STOP_TRAVERSAL;
				}
			});
			return page;
		}
		return page;
	}
}

Reply via email to