AJAX Select More (1)

We can chain the AJAX-enabled Selects deeper and deeper...


...but at some point the Tree component might a better choice.

References: Ajax and Zones, Zone, Select, TextField, Request, AjaxResponseRenderer, @Inject, @InjectComponent, t5/core/zone, t5/core/ajax, t5/core/forms.


<!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 Select More (1)</h3>
    <noscript class="js-required">

    We can chain the AJAX-enabled Selects deeper and deeper...

    <div class="eg">
        <t:form t:id="searchCriteria">
            <div class="form-group form-inline">
                <div class="form-group">
                    <t:label for="carMake"/>
                    <t:select t:id="carMake" model="carMakes" blankOption="ALWAYS" blankLabel="Choose..." validate="required" secure="never"

                <div class="form-group">
                    <t:zone t:id="carModelZone" id="carModelZone" style="display: inline;">
                        <t:label for="carModel"/>
                        <t:select t:id="carModel" model="carModels" blankOption="ALWAYS" blankLabel="Choose..." validate="required" secure="never"

                <div class="form-group">
                    <t:zone t:id="carStyleZone" id="carStyleZone" style="display: inline;">
                        <t:label for="carStyle"/>
                        <t:select t:id="carStyle" model="carStyles" blankOption="ALWAYS" blankLabel="Choose..." validate="required" secure="never"/>

                <div class="form-group">
                    <t:label for="keywords"/>
                    <t:textfield t:id="keywords" class="keywords"/>

                <div class="form-group">
                    <p class="form-control-static">(optional)</p>


            <div class="form-group">
                <t:submit value="Save"/>
            <t:errors globalOnly="true"/>
    ...but at some point the Tree component might a better choice.<br/><br/>

    <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://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/Select.html">Select</a>,
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/TextField.html">TextField</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/apidocs/org/apache/tapestry5/services/ajax/AjaxResponseRenderer.html">AjaxResponseRenderer</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/ioc/annotations/Inject.html">@Inject</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/annotations/InjectComponent.html">@InjectComponent</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/zone.html">t5/core/zone</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/ajax.html">t5/core/ajax</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/forms.html">t5/core/forms</a>.<br/><br/> 
    <t:pagelink page="Index">Home</t:pagelink><br/><br/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxSelectMore1.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxSelectMore1.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/ajaxselect1.css"/>

package jumpstart.web.pages.examples.ajax;

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

@Import(stylesheet = "css/examples/ajaxselect1.css")
public class AjaxSelectMore1 {
    static final private String MAKE_HONDA = "Honda";
    static final private String MAKE_TOYOTA = "Toyota";
    static final private String[] ALL_MAKES = new String[] { MAKE_HONDA, MAKE_TOYOTA };

    static final private String MODEL_ACCORD = "Accord";
    static final private String MODEL_CIVIC = "Civic";
    static final private String MODEL_JAZZ = "Jazz";
    static final private String MODEL_CAMRY = "Camry";
    static final private String MODEL_COROLLA = "Corolla";
    static final private String[] HONDA_MODELS = new String[] { MODEL_ACCORD, MODEL_CIVIC, MODEL_JAZZ };
    static final private String[] TOYOTA_MODELS = new String[] { MODEL_CAMRY, MODEL_COROLLA };
    static final private String[] NO_MODELS = new String[] {};

    static final private String[] ACCORD_STYLES = new String[] { "Sedan", "Hatchback" };
    static final private String[] CIVIC_STYLES = new String[] { "Sedan", "Wagon", "Coupe" };
    static final private String[] JAZZ_STYLES = new String[] { "1.6", "2.0" };
    static final private String[] CAMRY_STYLES = new String[] { "Sedan", "Wagon" };
    static final private String[] COROLLA_STYLES = new String[] { "Town", "Sports" };
    static final private String[] NO_STYLES = new String[] {};

    // Screen fields

    private String[] carMakes;

    private String carMake;

    private String[] carModels;

    private String carModel;

    private String[] carStyles;

    private String carStyle;

    private String keywords;

    // Other pages

    private AjaxSelectMore2 page2;

    // Generally useful bits and pieces

    private Zone carModelZone;

    private Zone carStyleZone;

    private Request request;

    private AjaxResponseRenderer ajaxResponseRenderer;

    // The code

    void setupRender() {
        if (carMakes == null) {
            carMakes = ALL_MAKES;
            carModels = NO_MODELS;
            carStyles = NO_STYLES;

    void onValueChangedFromCarMake(String carMake) {

        // A new make has been chosen - clear the model and style.

        carModel = null;
        carModels = NO_MODELS;
        carStyle = null;
        carStyles = NO_STYLES;

        // Show the models of the chosen make.

        if (carMake != null) {
            if (carMake.equals(MAKE_HONDA)) {
                carModels = HONDA_MODELS;
            else if (carMake.equals(MAKE_TOYOTA)) {
                carModels = TOYOTA_MODELS;

        if (request.isXHR()) {

    void onValueChangedFromCarModel(String carModel) {

        // A new model has been chosen - clear the style.

        carStyle = null;
        carStyles = NO_STYLES;

        // Show the styles of the chosen model.

        if (carModel != null) {
            if (carModel.equals(MODEL_ACCORD)) {
                carStyles = ACCORD_STYLES;
            else if (carModel.equals(MODEL_CIVIC)) {
                carStyles = CIVIC_STYLES;
            else if (carModel.equals(MODEL_JAZZ)) {
                carStyles = JAZZ_STYLES;
            else if (carModel.equals(MODEL_CAMRY)) {
                carStyles = CAMRY_STYLES;
            else if (carModel.equals(MODEL_COROLLA)) {
                carStyles = COROLLA_STYLES;

        if (request.isXHR()) {
            ajaxResponseRenderer.addRender("carStyleZone", carStyleZone);

    Object onSuccess() {
        page2.set(carMake, carModel, carStyle, keywords);
        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;

.eg select {
                width: auto;
                margin-right: 12px;

.eg .keywords {
                width: auto;