AJAX Select Dependency (1)

In this example, Source and Make are independent of each other, but Model depends on Make AND Source combined.
Note that there are no models in Local Honda or Imported Holden combinations.

(optional)

The tricky part of this is that AJAX Select requests pass only one context value, ie. the value of the Select;
so when you change Source how can you also send the chosen Make, and when you change Make how can you also send the chosen Source?
We solve it by persisting chosenCarSource and chosenCarMake in the generated URLs by using @ActivationRequestParameter.

References: @ActivationRequestParameter, Ajax and Zones, Zone, Select, TextField, Request, AjaxResponseRenderer, @Inject, @InjectComponent.

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 Select Dependency (1)</h3>
    
    <noscript class="js-required">
        ${message:javascript_required}
    </noscript>
    
    In this example, Source and Make are independent of each other, but Model depends on Make AND Source combined.<br/>
    Note that there are no models in Local Honda or Imported Holden combinations.
    
    <div class="eg">
        <t:form t:id="searchCriteria">
    
            <div class="form-group form-inline">
            
                <div class="form-group">
                    <t:zone t:id="carSourceZone" id="carSourceZone" style="display: inline;">
                        <t:label for="carSource"/>
                        <t:select t:id="carSource" model="carSources" blankOption="ALWAYS" blankLabel="Choose..." validate="required" secure="never"
                            zone="carModelZone"/>
                    </t:zone>
                </div>

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

                <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"/>
                    </t:zone>
                </div>
    
                <div class="form-group">
                    <t:label for="keywords"/>
                    <t:textfield t:id="keywords" class="keywords"/>
                </div>

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

            </div>

            <div class="form-group">
                <t:submit value="Save"/>
            </div>
    
            <t:errors globalOnly="true"/>
        </t:form>
    </div>

    The tricky part of this is that AJAX Select requests pass only one context value, ie. the value of the Select; <br/>
    so when you change Source how can you also send the chosen Make, and when you change Make how can you also send the chosen Source?<br/>
    We solve it by persisting <em>chosenCarSource</em> and <em>chosenCarMake</em> in the generated URLs by using <em>@ActivationRequestParameter</em>.<br/><br/>

    References:
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/annotations/ActivationRequestParameter.html">@ActivationRequestParameter</a>, 
    <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>.<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/AjaxSelectDependency1.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxSelectDependency1.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/ajaxselect1.css"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.ajax;

import org.apache.tapestry5.annotations.ActivationRequestParameter;
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 AjaxSelectDependency1 {
    static final private String SOURCE_LOCAL = "Local";
    static final private String SOURCE_IMPORTED = "Imported";
    static final private String[] ALL_SOURCES = new String[] { SOURCE_LOCAL, SOURCE_IMPORTED };

    static final private String MAKE_HOLDEN = "Holden";
    static final private String MAKE_HONDA = "Honda";
    static final private String MAKE_TOYOTA = "Toyota";
    static final private String[] ALL_MAKES = new String[] { MAKE_HOLDEN, MAKE_HONDA, MAKE_TOYOTA };

    static final private String MODEL_COMMODORE = "Commodore";
    static final private String MODEL_CAMRY = "Camry";
    static final private String MODEL_COROLLA = "Corolla";
    static final private String MODEL_PRIUS = "Prius";
    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[] LOCAL_HOLDEN_MODELS = new String[] { MODEL_COMMODORE };
    static final private String[] IMPORTED_HOLDEN_MODELS = new String[] {};
    static final private String[] LOCAL_TOYOTA_MODELS = new String[] { MODEL_CAMRY };
    static final private String[] IMPORTED_TOYOTA_MODELS = new String[] { MODEL_CAMRY, MODEL_COROLLA, MODEL_PRIUS };
    static final private String[] LOCAL_HONDA_MODELS = new String[] {};
    static final private String[] IMPORTED_HONDA_MODELS = new String[] { MODEL_ACCORD, MODEL_CIVIC, MODEL_JAZZ };
    static final private String[] NO_MODELS = new String[] {};

    // Activation request parameters (AKA query parameters)

    @ActivationRequestParameter("source")
    private String chosenCarSource;

    @ActivationRequestParameter("make")
    private String chosenCarMake;

    // Screen fields

    @Property
    private String[] carSources;

    @Property
    private String carSource;

    @Property
    private String[] carMakes;

    @Property
    private String carMake;

    @Property
    private String[] carModels;

    @Property
    private String carModel;

    @Property
    private String keywords;

    // Other pages

    @InjectPage
    private AjaxSelectDependency2 page2;

    // Generally useful bits and pieces

    @InjectComponent
    private Zone carSourceZone;

    @InjectComponent
    private Zone carMakeZone;

    @InjectComponent
    private Zone carModelZone;

    @Inject
    private Request request;

    @Inject
    private AjaxResponseRenderer ajaxResponseRenderer;

    // The code

    void setupRender() {
        carSources = ALL_SOURCES;
        carMakes = ALL_MAKES;
        carModels = NO_MODELS;
    }

    void onValueChangedFromCarSource(String carSource) {

        // Record the source in the activation parameters (AKA query parameters) so it is available in requests from the
        // other zones.

        chosenCarSource = carSource;

        // Refresh the makes and models.

        carMake = chosenCarMake;
        carMakes = ALL_MAKES;

        refreshCarModels();

        if (request.isXHR()) {
            ajaxResponseRenderer.addRender(carMakeZone).addRender(carModelZone);
        }
    }

    void onValueChangedFromCarMake(String carMake) {

        // Record the make in the activation parameters (AKA query parameters) so it is available in requests from the
        // other zones.

        chosenCarMake = carMake;

        // Refresh the sources and models.

        carSource = chosenCarSource;
        carSources = ALL_SOURCES;

        refreshCarModels();

        if (request.isXHR()) {
            ajaxResponseRenderer.addRender(carSourceZone).addRender(carModelZone);
        }
    }

    private void refreshCarModels() {

        // Show the models of the chosen source AND make.

        carModel = null;
        carModels = NO_MODELS;

        if (chosenCarSource != null && chosenCarMake != null) {

            if (chosenCarSource.equals(SOURCE_LOCAL)) {

                if (chosenCarMake.equals(MAKE_HOLDEN)) {
                    carModels = LOCAL_HOLDEN_MODELS;
                }
                else if (chosenCarMake.equals(MAKE_HONDA)) {
                    carModels = LOCAL_HONDA_MODELS;
                }
                else if (chosenCarMake.equals(MAKE_TOYOTA)) {
                    carModels = LOCAL_TOYOTA_MODELS;
                }

            }
            else if (chosenCarSource.equals(SOURCE_IMPORTED)) {

                if (chosenCarMake.equals(MAKE_HOLDEN)) {
                    carModels = IMPORTED_HOLDEN_MODELS;
                }
                else if (chosenCarMake.equals(MAKE_HONDA)) {
                    carModels = IMPORTED_HONDA_MODELS;
                }
                else if (chosenCarMake.equals(MAKE_TOYOTA)) {
                    carModels = IMPORTED_TOYOTA_MODELS;
                }

            }

        }

    }

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