AJAX Loading Spinner

This example displays an "AJAX loading spinner" while a Zone request is in progress.
Show things, with a delay...
It uses JavaScript to detect any Form or link request that involves a Zone refresh, then it overlays the nominated Zone with a CSS class that darkens the area and displays an animated GIF. When the Zone refresh returns from the server it replaces the Zone completely, without the overlay.

This solution will work with a Form or link that specifies the zone parameter and not async="true".

This solution has additional limitations which are described in the original article Adding 'Ajax Throbbers' to Zone updates.

For a mixin, have a look at the ZoneLoadingSpinner in Lenny's FlowLogix.

References: Adding 'Ajax Throbbers', Tapestry JavaScript, CSS3 Animations, @Import, JavaScriptSupport, t5/core/dom, t5/core/events, t5/core/zone, 5 Online Loading AJAX Spinner Generator Tools.

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 Loading Spinner</h3>

    <noscript class="js-required">
        ${message:javascript_required}
    </noscript>     

    This example displays an "AJAX loading spinner" while a Zone request is in progress.

    <div class="eg">
        <t:form zone="thingsZone" class="form-inline">
            <t:eventlink event="showThings" zone="thingsZone">Show things, with a delay...</t:eventlink>
            <t:submit t:id="WithAZone" value="Show things, with a delay..." class="btn btn-default"/>
        </t:form>
        
        <t:zone t:id="thingsZone" id="thingsZone" style="width: 140px; min-height: 80px;">
            <div t:type="Loop" t:source="things" t:value="thing">
                ${thing}
            </div>
        </t:zone>
        
    </div>  
    
    It uses JavaScript to detect any Form or link request that involves a Zone refresh, 
    then it overlays the nominated Zone with a CSS class that darkens the area and displays an animated GIF. 
    When the Zone refresh returns from the server it replaces the Zone completely, without the overlay.<br/><br/> 

    This solution will work with a Form or link that specifies the <code>zone</code> parameter and not <code>async="true"</code>.<br/><br/>
    
    This solution has additional limitations which are described in the original article 
    <a href="http://java.dzone.com/articles/adding-ajax-throbbers-zone">Adding 'Ajax Throbbers' to Zone updates</a>.<br/><br/>
    
    For a mixin, have a look at the ZoneLoadingSpinner in Lenny's <a href="http://code.google.com/p/flowlogix/">FlowLogix</a>.<br/><br/>
    
    References: 
    <a href="http://java.dzone.com/articles/adding-ajax-throbbers-zone">Adding 'Ajax Throbbers'</a>, 
    <a href="http://tapestry.apache.org/javascript.html">Tapestry JavaScript</a>, 
    <a href="http://www.w3schools.com/css/css3_animations.asp">CSS3 Animations</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/annotations/Import.html">@Import</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/services/javascript/JavaScriptSupport.html">JavaScriptSupport</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/dom.html">t5/core/dom</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/events.html">t5/core/events</a>, 
    <a href="http://tapestry.apache.org/5.4/coffeescript/zone.html">t5/core/zone</a>,
    <a href="http://www.jquery4u.com/tools/online-loading-ajax-spinner-generators/">5 Online Loading AJAX Spinner Generator Tools</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/AjaxLoadingSpinner.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxLoadingSpinner.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/modules/zone-overlay.js"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/js.css"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/zone-overlay.css"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/zone-overlay-ie.css"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.ajax;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.Path;
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 org.apache.tapestry5.services.javascript.JavaScriptSupport;
import org.apache.tapestry5.services.javascript.StylesheetLink;
import org.apache.tapestry5.services.javascript.StylesheetOptions;

@Import(stylesheet = { "css/examples/js.css", "css/examples/zone-overlay.css" })
public class AjaxLoadingSpinner {
    static final private String[] ALL_THINGS = { "Sugar", "Spice", "All Things Nice" };

    // Screen fields

    @Property
    private String[] things;

    @Property
    private String thing;

    // Generally useful bits and pieces

    @Inject
    private Request request;

    @Inject
    private AjaxResponseRenderer ajaxResponseRenderer;

    @InjectComponent
    private Zone thingsZone;

    @Inject
    private JavaScriptSupport javaScriptSupport;

    @Inject
    @Path("css/examples/zone-overlay-ie.css")
    private Asset ieCSS;

    // The code

    void afterRender() {

        // Add a stylesheet that is conditional on whether the browser is Internet Explorer. We would do this in the
        // @Import above except that it doesn't have the syntax to specify a condition.

        javaScriptSupport.importStylesheet(new StylesheetLink(ieCSS, new StylesheetOptions().withCondition("IE")));

        javaScriptSupport.require("zone-overlay");
    }

    void onShowThings() {
        refreshZoneSlowly();
    }

    void onSuccess() {
        refreshZoneSlowly();
    }

    private void refreshZoneSlowly() {
        // Set up the list of things to display
        things = ALL_THINGS;

        // Sleep 4 seconds to simulate a long-running operation
        sleep(4000);

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

    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        }
        catch (InterruptedException e) {
            // Ignore
        }
    }

}


// A script that detects when a zone-related refresh is requested. 
// It reacts by overlaying the zone with a div of class "zone-loading-overlay". 
// The idea is that you should define that class, in css, to display an animated GIF.
//
// Based on a solution by Howard Lewis Ship at http://tapestryjava.blogspot.co.uk/2011/12/adding-ajax-throbbers-to-zone-updates.html .

define(["t5/core/dom", "t5/core/events", "t5/core/zone"], function(dom, events, zoneManager) {

    return function(params) {

        function addZoneOverlay() {
            var $zone = this.$;
            this.prepend("<div class='zone-loading-overlay'/>");
            var $overlay = $zone.find("div:first");

            $overlay.css({
                width : $zone.width() + "px",
                height : $zone.height() + "px"
            });
        }

        // Tell t5/core/dom to call my addZoneOverlay() whenever it receives t5/core/events.zone.refresh 
        // (which bubbles up from t5/core/zone when you click a Form or link that specifies zone).

        dom.onDocument(events.zone.refresh, addZoneOverlay);
    }

});


.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;
}


/* Based on a solution by Howard Lewis Ship at http://tapestryjava.blogspot.co.uk/2011/12/adding-ajax-throbbers-to-zone-updates.html .*/

@-webkit-keyframes fade-in {
  from {
     opacity: 0;
  }
  to {
    opacity: .5
  }
}

@-moz-keyframes fade-in {
  from {
     opacity: 0;
  }
  to {
    opacity: .5
  }
}

div.zone-loading-overlay {
    position: absolute;
    background-color: #eee;
    opacity: 0;
    -webkit-animation-name: fade-in;
    -webkit-animation-duration: 250ms;
    -webkit-animation-delay: 50ms;
    -webkit-animation-fill-mode: forwards;
    -moz-animation-name: fade-in;
    -moz-animation-duration: 250ms;
    -moz-animation-delay: 50ms;
    -moz-animation-fill-mode: forwards;
    background-image: url(../../tapestry5/ajax-loader.gif);
    background-repeat: no-repeat;
    background-position: center center;
    z-index: 9999;
}


/* Based on a solution by Howard Lewis Ship at http://tapestryjava.blogspot.co.uk/2011/12/adding-ajax-throbbers-to-zone-updates.html .*/

div.zone-ajax-overlay {
    background-color: silver;
    filter: alpha(opacity = 50);
}