AJAX Components (1)

This page demonstrates creating a custom component which contains a Zone. It also demonstrates using that component more than once in the same page.

The component is called TextEchoer. It contains a TextField and a Zone that "echoes" the value of the TextField.

Humpty

Dumpty

The tricky part about using an AJAX component more than once in a page is that each occurrence of a Zone must have a unique client-side id. Otherwise the first occurrence of the Zone will receive all the updates intended for the other occurrences.

TextEchoer achieves this with id="prop:componentResources.id", which is simply getting the id given to it by its container (in this case, firstName and lastName).

An alternative technique would be to generate a random id in the component, TextEchoer.

References: Ajax and Zones, Zone, Inge's Zone Updater, Request, t5/core/zone.

Home


<!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_4.xsd">
<body class="container">
    <h3>AJAX Components (1)</h3>
    
    <noscript class="js-required">
        ${message:javascript_required}
    </noscript>     

    <p>This page demonstrates creating a custom component which contains a Zone. 
    It also demonstrates using that component more than once in the same page.</p>
    
    <p>The component is called TextEchoer. It contains a TextField and a Zone that "echoes" the value of the TextField.</p>
    
    <div class="eg">
        <t:form class="form-horizontal">
            <div class="form-group">
                <label for="firstName" class="col-sm-2 control-label">First Name</label>
                <div class="col-sm-8">
                    <t:examples.ajax.TextEchoer t:id="firstName"/>
                </div>
            </div>
            <div class="form-group">
                <label for="lastName" class="col-sm-2 control-label">Last Name</label>
                <div class="col-sm-8">
                    <t:examples.ajax.TextEchoer t:id="lastName"/>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-4 col-sm-offset-2">
                    <t:submit value="Display"/>
                </div>
            </div>
        </t:form>
    </div>
    
    <p>The tricky part about using an AJAX component more than once in a page is that each occurrence of a Zone must have a unique client-side id. 
    Otherwise the first occurrence of the Zone will receive all the updates intended for the other occurrences.</p>

    <p>TextEchoer achieves this with <code>id="prop:componentResources.id"</code>, which is simply getting the id given to it by its container 
    (in this case, <code>firstName</code> and <code>lastName</code>).</p>

    <p>An alternative technique would be to generate a random id in the component, TextEchoer.</p>
    
    References: 
    <a href="http://tapestry.apache.org/ajax-and-zones.html">Ajax and Zones</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/Zone.html">Zone</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.4/apidocs/org/apache/tapestry5/services/Request.html">Request</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/zone.html">t5/core/zone</a>.<br/><br/>

    <t:pagelink page="Index">Home</t:pagelink><br/><br/>

    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxComponents1.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxComponents1.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/js.css"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/examples/ajax/TextEchoer.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/components/examples/ajax/TextEchoer.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/mixins/ZoneUpdater.java"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/mixins/zone-updater.js"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.ajax;

import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Property;

@Import(stylesheet = "css/examples/js.css")
public class AjaxComponents1 {

    // Screen fields

    @Property
    private String firstName;

    @Property
    private String lastName;

    // Other pages

    @InjectPage
    private AjaxComponents2 page2;

    // The code

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

    Object onSuccess() {
        page2.set(firstName, lastName);
        return page2;
    }

}


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

.js-required {
                color: red;
                display: block;
                margin-bottom: 14px;
}

.js-recommended {
                color: red;
                display: block;
                margin-bottom: 14px;
}


<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd">
<t:content>

    <div class="form-group">
        <div class="col-sm-6">
            <t:textfield t:id="value" t:mixins="zoneUpdater" 
                ZoneUpdater.clientEvent="keyup" ZoneUpdater.event="valueChanged" ZoneUpdater.zone="prop:componentResources.id"/>
        </div>
        <div class="col-sm-6">
            <t:zone t:id="echoZone" id="prop:componentResources.id">
                <p class="form-control-static">${echo}</p>
            </t:zone>
        </div>
    </div>

</t:content>
</html>


package jumpstart.web.components.examples.ajax;

import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Parameter;
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;

/**
 * Contains a TextField and a Zone that "echoes" the value of the TextField.
 */
public class TextEchoer {

    @Parameter(required = true, principal = true, autoconnect = true)
    @Property
    private String value;

    @InjectComponent
    private Zone echoZone;

    @Inject
    private AjaxResponseRenderer ajaxResponseRenderer;

    @Inject
    private Request request;

    void onValueChanged() {
        value = request.getParameter("param");

        if (value == null) {
            value = "";
        }

        if (request.isXHR()) {
            ajaxResponseRenderer.addRender(echoZone);
        }
    }

    public String getEcho() {
        return value;
    }

}


/**
 * 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.InjectContainer;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

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 attachedTo;

    // The code

    void afterRender() {

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

        javaScriptSupport.require("zone-updater").with(attachedTo.getClientId(), clientEvent, listenerURI, zone);
    }
}

The file was not found. Path given was /WEB-INF/sourcecode/web/src/main/java/jumpstart/web/mixins/zone-updater.js