AJAX Multiple Zone Update

This page demonstrates how multiple zones can be updated simultaneously by using the AjaxResponseRenderer service.
Welcome Humpty Dumpty.

First Name:
Last Name:

Welcome HUMPTY DUMPTY.

Note that the following time field does not update because it's not in the zone: Mon Sep 22 18:09:26 EST 2014
References: Ajax and Zones, Zone, AjaxResponseRenderer, Inge's Zone Updater, Request, ComponentResources, @InjectComponent, @Inject.

Home

AjaxMultipleZoneUpdate.tml


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like &nbsp; 
     We use a "strict" DTD to make IE follow the alignment rules. -->
     
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<head>
    <link rel="stylesheet" type="text/css" href="${context:css/examples/js.css}"/>
</head>
<body>
    <h1>AJAX Multiple Zone Update</h1>
    
    <noscript class="js-required">
        ${message:javascript_required}
    </noscript>     

    This page demonstrates how multiple zones can be updated simultaneously by using the AjaxResponseRenderer service.
    
    <div class="eg">
        <form t:type="form">
    
            <t:zone t:id="nameZone1" id="nameZone1">
                Welcome ${name}.
            </t:zone><br/>
    
            First Name: 
            <input t:type="TextField" t:id="firstName" t:mixins="zoneUpdater" t:clientEvent="keyup" t:event="firstNameChanged" t:zone="nameZone1"/><br/>
            Last Name: 
            <input t:type="TextField" t:id="lastName" t:mixins="zoneUpdater" t:clientEvent="keyup" t:event="lastNameChanged" t:zone="nameZone1"/><br/><br/>
    
            <t:zone t:id="nameZone2" id="nameZone2">
                Welcome ${upperCaseName}.
            </t:zone><br/>
    
            Note that the following time field does not update because it's not in the zone:  ${time}<br/>
            
        </form>
    </div>
    
    References: 
    <a href="http://tapestry.apache.org/ajax-and-zones.html">Ajax and Zones</a>, 
    <a href="http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/corelib/components/Zone.html">Zone</a>,
    <a href="http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/services/ajax/AjaxResponseRenderer.html">AjaxResponseRenderer</a>, 
    <a href="http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html">Inge's Zone Updater</a>, 
    <a href="http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/services/Request.html">Request</a>, 
    <a href="http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/ComponentResources.html">ComponentResources</a>, 
    <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/InjectComponent.html">@InjectComponent</a>, 
    <a href="http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/ioc/annotations/Inject.html">@Inject</a>.<br/><br/> 

    <a t:type="eventlink" t:event="gohome" href="#">Home</a><br/><br/>

    <t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxMultipleZoneUpdate.tml"/>
    <t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxMultipleZoneUpdate.java"/>
    <t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/css/examples/js.css"/>
    <t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/mixins/ZoneUpdater.java"/>
    <t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/mixins/ZoneUpdater.js"/>
</body>
</html>

AjaxMultipleZoneUpdate.java


package jumpstart.web.pages.examples.ajax;

import java.util.Date;

import jumpstart.web.pages.Index;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.ajax.AjaxResponseRenderer;

public class AjaxMultipleZoneUpdate {

    // Screen fields

    @Property
    @Persist
    private String firstName;

    @Property
    @Persist
    private String lastName;

    // Generally useful bits and pieces

    @InjectComponent
    private Zone nameZone1;

    @InjectComponent
    private Zone nameZone2;

    @Inject
    private Request request;

    @Inject
    private AjaxResponseRenderer ajaxResponseRenderer;

    @Inject
    private ComponentResources componentResources;

    // The code

    // Life-cycle stuff. Fields that are marked @Persist MUST be initialized here rather than where they are declared.

    void setupRender() {
        if (firstName == null && lastName == null) {
            firstName = "Humpty";
            lastName = "Dumpty";
        }
    }

    void onFirstNameChanged() {
        firstName = request.getParameter("param");
        if (firstName == null) {
            firstName = "";
        }
        if (request.isXHR()) {
            ajaxResponseRenderer.addRender(nameZone1).addRender(nameZone2);
        }
    }

    void onLastNameChanged() {
        lastName = request.getParameter("param");
        if (lastName == null) {
            lastName = "";
        }
        if (request.isXHR()) {
            ajaxResponseRenderer.addRender(nameZone1).addRender(nameZone2);
        }
    }

    public String getName() {
        return firstName + " " + lastName;
    }

    public String getUpperCaseName() {
        return getName().toUpperCase();
    }

    public Date getTime() {
        return new Date();
    }

    Object onGoHome() {
        componentResources.discardPersistentFieldChanges();
        return Index.class;
    }
}

js.css


body            { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; color: #333;
                    line-height: 17px; }
h1              { font-size: 26px; line-height: 20px; } /* For IE 7 */
form            { margin: 0; }

.eg             { margin: 20px 0; padding: 20px; color: #888; 
                    border: 1px solid #ddd; border-radius: 4px; -webkit-border-radius: 4px; -mox-border-radius: 4px; }

a               { text-decoration: none; color: #3D69B6; }
a:hover         { text-decoration: underline; }

/* For javascript examples. */
.js-required    { color: red; display: block; margin-bottom: 14px; }
.js-recommended { color: red; display: block; margin-bottom: 14px; }

.grid           { border-collapse: collapse; border-spacing: 0; border: 1px solid #dddddd; font-size: 13px; }
.grid tr.odd        { background-color: #f8f8f8; }
.grid tr:hover      { background-color: #eeeeee; }
.grid th        { padding: 3px 5px; text-align: left; width: 130px; border: 1px solid #dddddd; 
                    font-weight: normal; background-color: #eeeeee; 
                    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbfbfb', endColorstr='#e4e4e4'); /* for IE */
                    background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#e4e4e4)); /* for webkit browsers */
                    background: -moz-linear-gradient(top, #fbfbfb, #e4e4e4); /* for firefox 3.6+ */ }
.grid td        { padding: 3px 5px; text-align:left; }

ZoneUpdater.java


/**
 * A simple mixin for attaching javascript that updates a zone on any client-side event.
 * Based on http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html
 */
package jumpstart.web.mixins;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

// The @Import tells Tapestry to put a link to the file in the head of the page so that the browser will pull it in. 
@Import(library = "ZoneUpdater.js")
public class ZoneUpdater {

    // Parameters

    /**
     * The event to listen for on the client. If not specified, zone update can only be triggered manually through
     * calling updateZone on the JS object.
     */
    @Parameter(name = "clientEvent", defaultPrefix = BindingConstants.LITERAL)
    private String clientEvent;

    /**
     * The event to listen for in your component class
     */
    @Parameter(name = "event", defaultPrefix = BindingConstants.LITERAL, required = true)
    private String event;

    @Parameter(name = "prefix", defaultPrefix = BindingConstants.LITERAL, value = "default")
    private String prefix;

    @Parameter(name = "context")
    private Object[] context;

    /**
     * The zone to be updated by us.
     */
    @Parameter(name = "zone", defaultPrefix = BindingConstants.LITERAL, required = true)
    private String zone;

    /**
     * Set secure to true if https is being used, else set to false.
     */
    @Parameter(name = "secure", defaultPrefix = BindingConstants.LITERAL, value = "false")
    private boolean secure;

    // Useful bits and pieces

    @Inject
    private ComponentResources componentResources;

    @Environmental
    private JavaScriptSupport javaScriptSupport;

    /**
     * The element we attach ourselves to
     */
    @InjectContainer
    private ClientElement clientElement;

    // The code

    void afterRender() {
        String listenerURI = componentResources.createEventLink(event, context).toAbsoluteURI(secure);

        // Add some JavaScript to the page to instantiate a ZoneUpdater. It will run when the DOM has been fully loaded.

        JSONObject spec = new JSONObject();
        spec.put("elementId", clientElement.getClientId());
        spec.put("clientEvent", clientEvent);
        spec.put("listenerURI", listenerURI);
        spec.put("zoneId", zone);
        javaScriptSupport.addScript("%sZoneUpdater = new ZoneUpdater(%s)", prefix, spec.toString());
    }
}

ZoneUpdater.js


// A class that updates a zone on any client-side event.
// Based on http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html
// and some help from Inge Solvoll.

ZoneUpdater = Class.create( {

    initialize : function(spec) {
        this.element = $(spec.elementId);
        this.listenerURI = spec.listenerURI;
        $(this.element).getStorage().zoneId = spec.zoneId;
        
        if (spec.clientEvent) {
            this.clientEvent = spec.clientEvent;
            this.element.observe(this.clientEvent, this.updateZone.bindAsEventListener(this));
        }
    },
    
    updateZone : function() {
        var zoneManager = Tapestry.findZoneManager(this.element);
        
        if (!zoneManager) {
            return;
        }

        var listenerURIWithValue = this.listenerURI;
        
        if (this.element.value) {
            var param = this.element.value;
            if (param) {
                listenerURIWithValue = addQueryStringParameter(listenerURIWithValue, 'param', param);
            }
        }
        
        zoneManager.updateFromURL(listenerURIWithValue);
    }
    
} )

function addQueryStringParameter(url, name, value) {
    if (url.indexOf('?') < 0) {
        url += '?'
    } else {
        url += '&';
    }
    value = escape(value);
    url += name + '=' + value;
    return url;
}