Nice solution Ben 😊

We found that we couldn’t visual style a submit input button to give feedback 
that the request was being processed so instead created a mixin that when the 
submit button was clicked, it was converted into a link that could then be 
styled with a spinner and visually disabled. It is a very hacky solution so 
happy to see alternative solutions especially with a submit input button.


DisableAfterSubmit.java

import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.annotations.Environmental;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.annotations.MixinAfter;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;

@MixinAfter
public class DisableAfterSubmit
{

     @InjectContainer private ClientElement element;

     @Environmental private JavaScriptSupport javaScriptSupport;

     void afterRender() {
           final JSONObject spec = new JSONObject();
           spec.put("elementId", element.getClientId());
          
javaScriptSupport.require("pqc/mixins/DisableAfterSubmit").invoke("initialize").with(spec);
     }
}



DisableAfterSubmit.js

(function() {
     define(["jquery"], function($j) {

           var initialize = function(spec) {
                $j('#' + spec.elementId).on('click', spec, afterClick);
           };

           var afterClick = function(event) {
                // AJAX submit hack: to allow a spinner to be positioned by 
changing from an input[type=submit] to an anchor element
                // (as Tapestry form submissions via AJAX do not work now 
except through an input[type=submit])
                var originalElement = $j('#' + event.data.elementId);
                var attrs = {'href':'#'};
                var valueAttr;
                var idAttr = 'hack';
                $j.each(originalElement[0].attributes, function(index, attr) {
                     if (attr.nodeName === 'type') {
                           // No need to add type=submit so continue
                           return;
                     }
                     if (attr.nodeName === 'value') {
                           valueAttr = attr.nodeValue;
                           return;
                     }
                     if (attr.nodeName === 'id') {
                           attrs[attr.nodeName] = attr.nodeValue + idAttr;
                           return;
                     }
                     attrs[attr.nodeName] = attr.nodeValue;
                });
                originalElement.hide();
                originalElement.after(function () {
                    return $j('<a/>', attrs).append(valueAttr);
                });
                $j('#' + event.data.elementId + idAttr).addClass('disabled 
spinner').attr({'role':'alert', 'aria-busy':'true'}).focus();
           };

         return {
             initialize : initialize
         };
     });
}).call(this);

Cheers,
Niran

PS:
I’m sorry if you receive this email twice as I used different email domain for 
subscribing to this list and I didn’t see that initial email come through. I 
have now subscribed using this email address..

From: Ben Weidig <b...@netzgut.net>
Date: Sunday, 26 February 2023 at 14:22
To: Tapestry users <users@tapestry.apache.org>
Subject: Re: Preventing double click eventlink and form submit
Hi Nathan,

Maybe I misunderstood your goal, but I would solve it with a module
configured by CSS classes or data attributes instead of dedicated
components.

Something along the line of this (untested) CoffeeScript I improvised:


define ['jquery', 't5/core/events'], ($, events) ->

    EVENT = 'click.request-in-progress'

    register = ->
        $('.show-request-in-progress').off(EVENT).on EVENT, ->
            $el = $(this)

            $el.addClass('request-in-progress')

            relatedZone = $el.data('target-zone')
            elEvent = "#{t5events.zone.wilUpdate}.##{$el.id}"
            zone.on elEvent, ->
                zone.off(elEvent)
                trigger.removeClass('request-in-progress')
            return true

    $ ->
        register()
        $(document).on events.zone.didUpdate, register

See 
https://urldefense.com/v3/__https://gist.github.com/benweidig/2f2fdb32c74cbd57764b1f7dabbe7079__;!!NknhfzgzgQ!yowXeuZLQZ1AaayarMGyXEXL1HzNLMBuDQXODpgTaFvtxz2t3os3D2bD6FeSb24bEnUMv9tTOwG6dX3IZPu215m5$<https://urldefense.com/v3/__https:/gist.github.com/benweidig/2f2fdb32c74cbd57764b1f7dabbe7079__;!!NknhfzgzgQ!yowXeuZLQZ1AaayarMGyXEXL1HzNLMBuDQXODpgTaFvtxz2t3os3D2bD6FeSb24bEnUMv9tTOwG6dX3IZPu215m5$>
  for
a nicely formatted version.

This way, you wouldn't need an additional zone and just need to listen to
the content zone to be updated. Only the ".show-request-in-progress" and
"data-target-zone" must be set accordingly.
Actually, the CSS classes isn't needed and the relevant elements could be
targeted via the data attribute.

Cheers
Ben

On Fri, Feb 24, 2023 at 2:11 PM Nathan Quirynen <nat...@pensionarchitects.be>
wrote:

> Hi,
>
> I am looking for a solution to prevent multiple clicks on event links or
> form submits in our Tapestry application and in the meanwhile if
> possible giving the user visual feedback while the request is being
> processed.
> We use ajax (async="true") on both our eventlinks and form components,
> but there are no javascript events to hook into right before the request
> is fired and when the request response is handled. But after some
> research I found I can use the zone parameter instead of the async
> parameter and then make use of the t5:zone:refresh and
> t5:zone:did-update events to do exactly that.
> So I came up with following javascript module which can be used for both
> form submit buttons and eventlinks:
>
>              var init = function(triggerClientId, zoneClientId) {
>
>                  var trigger = $('#' + triggerClientId);
>                  var zone = $('#' + zoneClientId);
>
>                  var event_request = 't5:zone:refresh' + '.' +
> triggerClientId;
>
>                  zone.on(event_request, function(e) {
>
>                     // disable trigger + add css class
>                      trigger.css('pointer-events', 'none');
>                      trigger.addClass('request-in-progress');
>
>                      var event_response = 't5:zone:did-update' + '.' +
> triggerClientId;
>                      zone.on(event_response, function() {
>
>                          // remove response event handler
>                          zone.off(event_response);
>
>                          // reenable trigger + remove css class
>                          trigger.css('pointer-events', 'auto');
> trigger.removeClass('request-in-progress');
>
>                      });
>                  });
>
>              }
>
> I would make new components AjaxForm/AjaxEventLink to wrap the original
> ones and implement everything there.
> This seems to work as I hoped it would be, but then a zone element is
> added for every EventLink/Form which seems a bit like overkill, but I
> don't see another way at the moment.
> I was wondering if others have tried to do something like I am, or if
> there's any better way of achieving what I am trying here? Or anything
> that I am missing could be wrong with my implementation?
>
>
> Nathan
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
> For additional commands, e-mail: users-h...@tapestry.apache.org
>
>

Confidentiality note: This e-mail may contain confidential information from 
Clarivate. If you are not the intended recipient, be aware that any disclosure, 
copying, distribution or use of the contents of this e-mail is strictly 
prohibited. If you have received this e-mail in error, please delete this 
e-mail and notify the sender immediately.

Reply via email to