To build a SPA application using Tapestry we can use the Zone's system. You have to keep in mind:
public class MyLinkTransformer implements PageRenderLinkTransformer { ... @Override public PageRenderRequestParameters decodePageRenderRequest(final Request request) { ... final int spaIndex = requestPath.indexOf("/spa/"); if (spaIndex != -1) { final String page = requestPath.substring(spaIndex + 5) + "page"; String enumPage = null; for (final PAGES p : PAGES.values()) { if (p.getPageClass().getSimpleName().toLowerCase().equals(page)) { enumPage = p.toString(); break; } } if (enumPage != null) { return new PageRenderRequestParameters("specialPages/spa/Index", new URLEventContext(contextValueEncoder, new String[] { enumPage }), false); } } ... } }
package es.carlosmontero.webapp.t5devutil.pages.specialpages.spa; import org.apache.tapestry5.Block; import org.apache.tapestry5.ComponentResources; import org.apache.tapestry5.annotations.Environmental; import org.apache.tapestry5.annotations.InjectComponent; import org.apache.tapestry5.annotations.InjectPage; import org.apache.tapestry5.corelib.components.Zone; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.PageRenderLinkSource; import org.apache.tapestry5.services.Request; import org.apache.tapestry5.services.ajax.AjaxResponseRenderer; import org.apache.tapestry5.services.ajax.JavaScriptCallback; import org.apache.tapestry5.services.javascript.JavaScriptSupport; public class IndexPage { public enum PAGES { PAGE1(Block1Page.class), PAGE2(Block2Page.class), PAGE3(Block3Page.class); private final Class<?> clazz; private PAGES(final Class<?> clazz) { this.clazz = clazz; } public Class<?> getPageClass() { return clazz; } } @Environmental private JavaScriptSupport javaScriptSupport; @Inject private PageRenderLinkSource pageRenderLinkSource; @Inject private AjaxResponseRenderer ajaxResponseRenderer; @Inject private ComponentResources componentResources; @Inject private Request request; @InjectPage private Block1Page block1; @InjectPage private Block2Page block2; @InjectPage private Block3Page block3; @InjectComponent private Zone contentZone; private PAGES activePage; public void onActivate(final PAGES page) { activePage = page; } public Block getContentBlock() { if (activePage == null) { activePage = PAGES.PAGE1; } switch (activePage) { case PAGE1: return block1.getMainBlock(); case PAGE2: return block2.getMainBlock(); case PAGE3: return block3.getMainBlock(); } return null; } public Object onBack(final PAGES page) { if (request.isXHR()) { activePage = page; ajaxResponseRenderer.addRender(contentZone); return null; } else { return page.getPageClass(); } } public void onChangePage(final PAGES page) { activePage = page; ajaxResponseRenderer.addRender(contentZone); ajaxResponseRenderer.addCallback(new JavaScriptCallback() { @Override public void run(final JavaScriptSupport javascriptSupport) { final String restoreUrl = componentResources.createEventLink("back", page).toAbsoluteURI(); final String newUrl = pageRenderLinkSource.createPageRenderLink(page.getPageClass()).toAbsoluteURI(); javascriptSupport.require("spa").invoke("selectpage") .with( restoreUrl, page + " title", newUrl ); } }); } public void setupRender() { javaScriptSupport.require("spa").invoke("init"); } }