You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by Svein-Erik Løken <SE...@brav.com> on 2019/06/07 15:10:22 UTC

How to let the last ajaxResponseRenderer win?

When typing "test" pretty fast into textfield id=nameFilter the result is wrong. I am typical getting "DEBUG nameFilter: te" (sometimes just "t"). The grid data corresponds to the nameFilter output.

The output from  getNamefilterDebug() is always:
getNamefilterDebug: t
getNamefilterDebug: te
getNamefilterDebug: tes
getNamefilterDebug: test



<t:form t:id="nameFilterForm" id="nameFilterForm" async="true" autofocus="true" style="display:flex;align-items: center;">
    <t:label for="nameFilter">Name:</t:label>
    <t:textfield t:id="nameFilter" oninput="$(this).closest('form').submit()" style="vertical-align: top" value="nameFilter" autocomplete="off"/>
    <t:zone t:id="destinationCountZone">${destinationBeans.size()}</t:zone>
</t:form>

<t:zone t:id="destinationTableZone">
    DEBUG nameFilter: ${namefilterDebug}

    <t:grid renderTableIfEmpty="true" style="width:auto" source="destinationBeans" rowClass="prop:rowClass" class="table table-hover table-bordered" t:row="destinationBean" model="model" rowsPerPage="1000" t:pagerPosition="top">


void onSubmitFromNameFilterForm() {
    applyFilter();
    ajaxResponseRenderer.addRender(destinationTableZone);
}


public String getNamefilterDebug() {
    System.err.format("getNamefilterDebug: %s%n",nameFilter);
    return nameFilter;
}


When using JavaScript/React I just call XMLHttpRequest.abort() for all ongoing XHR's.


I cannot figure out how to do this in Tapestry 5.4.4. I was looking for AjaxResponseRenderer addRender(ClientBodyElement zone, abortPrevious);

S-E




RE: How to let the last ajaxResponseRenderer win?

Posted by Svein-Erik Løken <SE...@brav.com>.
I have tried several methods without luck, but found a solution now. I don't want to rewrite the Tapestry javascript code if I don't have to, but found a way by stopping propagation of the events.zone.update. I have a filter on a grid. The grid is big and when Tapestry calls $.append(content) to insert zone data the browser gets very laggy. By including the input value in the zone, then I can check on match with the current user input when zone is ready to update. The mixin accept multiple zones.

I tried http://jumpstart.doublenegative.com.au/jumpstart/examples/ajax/onevent, but I does not sync data and will display wrong result (om large zone updates).

Debugging:

STOP: stopPropagation
UPDT: Let zone update be handled as default
Test on currentInputVal vs inputValInZone

STOP: tes !== t zone:destinationTableZone
STOP: tes !== t zone:destinationCountZone
STOP: test !== te zone:destinationTableZone
STOP: test !== te zone:destinationCountZone
STOP: test !== tes zone:destinationTableZone
STOP: test !== tes zone:destinationCountZone
UPDT: test === test zone:destinationTableZone ZONEUPDATE 
UPDT: test === test zone:destinationCountZone ZONEUPDATE 
STOP: testla !== testl zone:destinationTableZone
STOP: testla !== testl zone:destinationCountZone
STOP: testlab !== testla zone:destinationTableZone
STOP: testlab !== testla zone:destinationCountZone
STOP: testlab2 !== testlab zone:destinationTableZone
STOP: testlab2 !== testlab zone:destinationCountZone
UPDT: testlab2 === testlab2 zone:destinationTableZone ZONEUPDATE 
UPDT: testlab2 === testlab2 zone:destinationCountZone ZONEUPDATE

The code:
mixins\ZoneSync.java: 
public class ZoneSync {
    @Parameter(required = true, allowNull = false)
    Object[] zones;
    @Inject
    JavaScriptSupport javaScriptSupport;
    @InjectContainer
    ClientElement clientElement;

    @AfterRender
    void afterRender() {
        String zonesSelector = Arrays.stream(this.zones).map(zone -> "#" + ((Zone) zone).getClientId()).collect(Collectors.joining(","));
        javaScriptSupport.require("mixins/zone-sync").invoke("syncWithInput").with("#" + clientElement.getClientId(), zonesSelector);
    }
}

Zone-sync.ts:
export = class ZoneSync {
    /*
    <t:zone t:id="<zoneId>">
        <!-- Add div.sync -->
        <div class="sync" hidden="">${<TextField.value>}</div>
    */
    static syncWithInput(textFieldSelector: string, zonesSelector: string) {
        let textField = $(textFieldSelector);
        Dom.on(zonesSelector, Events.zone.update, function (this: ElementWrapper, event: EventWrapper) {
            let currentInputVal = textField.val();
            let inputValInZone = $(`<div>${(event.memo.content)}</div>`).children(".sync").first().text();
            if (currentInputVal !== inputValInZone) {
                event.nativeEvent.stopPropagation();
                console.log(`STOP: ${currentInputVal} !== ${inputValInZone} zone:${this.element.id}`);
            } else {
                console.log(`UPDT: ${currentInputVal} === ${inputValInZone} zone:${this.element.id}`);
            }
        });
    }
}

TML:
        <t:form t:id="nameFilterForm" id="nameFilterForm" async="true" autofocus="true" style="display:flex;align-items: center;">
            <t:label for="nameFilterField">Name:</t:label>
            <t:textfield  t:mixins="jacillacore/ZoneSync" zoneSync.zones="[destinationTableZone, destinationCountZone]" t:id="nameFilterField" oninput="$(this).closest('form').submit()" style="vertical-align: top" value="nameFilter" autocomplete="off" />
            <t:zone t:id="destinationCountZone">
                <div class="sync" hidden="">${nameFilter}</div>
                ${destinationBeans.size()}</t:zone>
        </t:form>

        <t:zone t:id="destinationTableZone">
            <div class="sync" hidden="">${nameFilter}</div>
            <t:grid renderTableIfEmpty="true" style="width:auto" source="destinationBeans" rowClass="prop:rowClass" class="table table-hover table-bordered" t:row="destinationBean" model="model" rowsPerPage="1000" t:pagerPosition="top">



S-E

-----Original Message-----
From: Cezary Biernacki <ce...@gmail.com> 
Sent: lørdag 8. juni 2019 00:09
To: Tapestry users <us...@tapestry.apache.org>
Subject: Re: How to let the last ajaxResponseRenderer win?

Hi,

A server-side solution will not solve the problem, as responses from the server can be re-ordered because of network transmission hick-ups. With Tapestry, you can the abort Ajax requests on client-side as well, it uses Jquery (or Prototype.js) under the hood.  I would monkey-patch dom.ajaxRequest function (see t5-core-dom-jquery.js or t5-core-dom-prototype.js depending if your are using Jquery or Prototype), to track currently running AJAX requests and abort it if a new one is issued using Jquery's or XMLHttpRequest's abort methods. A more advanced solution would allow multiple requests and only abort ones that are conflicting with each other.

Cezary


On Fri, Jun 7, 2019 at 8:44 PM Dmitry Gusev <dm...@gmail.com> wrote:

> Hi,
>
> There’s no reliable way to do so as all requests arrive simultaneously 
> on the server side, so unless you do a distributed lock — which is not 
> a good idea usually — there’s no way to do so.
>
> One of ideas is: you could pass sequential number with every request, 
> put that number in a storage (like Redis) using compare and set, and 
> if request is older than the one already in storage — do nothing.
>
> Usually such requests are throttled on the client side, I.e.:
> https://github.com/cowboy/jquery-throttle-debounce
>
> On Friday, June 7, 2019, Svein-Erik Løken <SE...@brav.com> wrote:
>
> > When typing "test" pretty fast into textfield id=nameFilter the 
> > result is wrong. I am typical getting "DEBUG nameFilter: te" (sometimes just "t").
> > The grid data corresponds to the nameFilter output.
> >
> > The output from  getNamefilterDebug() is always:
> > getNamefilterDebug: t
> > getNamefilterDebug: te
> > getNamefilterDebug: tes
> > getNamefilterDebug: test
> >
> >
> >
> > <t:form t:id="nameFilterForm" id="nameFilterForm" async="true"
> > autofocus="true" style="display:flex;align-items: center;">
> >     <t:label for="nameFilter">Name:</t:label>
> >     <t:textfield t:id="nameFilter"
> oninput="$(this).closest('form').submit()"
> > style="vertical-align: top" value="nameFilter" autocomplete="off"/>
> >     <t:zone t:id="destinationCountZone">${destinationBeans.size()}</t:
> > zone>
> > </t:form>
> >
> > <t:zone t:id="destinationTableZone">
> >     DEBUG nameFilter: ${namefilterDebug}
> >
> >     <t:grid renderTableIfEmpty="true" style="width:auto"
> > source="destinationBeans" rowClass="prop:rowClass" class="table
> table-hover
> > table-bordered" t:row="destinationBean" model="model" rowsPerPage="1000"
> > t:pagerPosition="top">
> >
> >
> > void onSubmitFromNameFilterForm() {
> >     applyFilter();
> >     ajaxResponseRenderer.addRender(destinationTableZone);
> > }
> >
> >
> > public String getNamefilterDebug() {
> >     System.err.format("getNamefilterDebug: %s%n",nameFilter);
> >     return nameFilter;
> > }
> >
> >
> > When using JavaScript/React I just call XMLHttpRequest.abort() for 
> > all ongoing XHR's.
> >
> >
> > I cannot figure out how to do this in Tapestry 5.4.4. I was looking 
> > for AjaxResponseRenderer addRender(ClientBodyElement zone, 
> > abortPrevious);
> >
> > S-E
> >
> >
> >
> >
>
> --
> Dmitry Gusev
>
> AnjLab Team
> http://anjlab.com
>

Re: How to let the last ajaxResponseRenderer win?

Posted by Cezary Biernacki <ce...@gmail.com>.
Hi,

A server-side solution will not solve the problem, as responses from the
server can be re-ordered because of network transmission hick-ups. With
Tapestry, you can the abort Ajax requests on client-side as well, it uses
Jquery (or Prototype.js) under the hood.  I would monkey-patch
dom.ajaxRequest function (see t5-core-dom-jquery.js
or t5-core-dom-prototype.js depending if your are using Jquery or
Prototype), to track currently running AJAX requests and abort it if a new
one is issued using Jquery's or XMLHttpRequest's abort methods. A more
advanced solution would allow multiple requests and only abort ones that
are conflicting with each other.

Cezary


On Fri, Jun 7, 2019 at 8:44 PM Dmitry Gusev <dm...@gmail.com> wrote:

> Hi,
>
> There’s no reliable way to do so as all requests arrive simultaneously on
> the server side, so unless you do a distributed lock — which is not a good
> idea usually — there’s no way to do so.
>
> One of ideas is: you could pass sequential number with every request, put
> that number in a storage (like Redis) using compare and set, and if request
> is older than the one already in storage — do nothing.
>
> Usually such requests are throttled on the client side, I.e.:
> https://github.com/cowboy/jquery-throttle-debounce
>
> On Friday, June 7, 2019, Svein-Erik Løken <SE...@brav.com> wrote:
>
> > When typing "test" pretty fast into textfield id=nameFilter the result is
> > wrong. I am typical getting "DEBUG nameFilter: te" (sometimes just "t").
> > The grid data corresponds to the nameFilter output.
> >
> > The output from  getNamefilterDebug() is always:
> > getNamefilterDebug: t
> > getNamefilterDebug: te
> > getNamefilterDebug: tes
> > getNamefilterDebug: test
> >
> >
> >
> > <t:form t:id="nameFilterForm" id="nameFilterForm" async="true"
> > autofocus="true" style="display:flex;align-items: center;">
> >     <t:label for="nameFilter">Name:</t:label>
> >     <t:textfield t:id="nameFilter"
> oninput="$(this).closest('form').submit()"
> > style="vertical-align: top" value="nameFilter" autocomplete="off"/>
> >     <t:zone t:id="destinationCountZone">${destinationBeans.size()}</t:
> > zone>
> > </t:form>
> >
> > <t:zone t:id="destinationTableZone">
> >     DEBUG nameFilter: ${namefilterDebug}
> >
> >     <t:grid renderTableIfEmpty="true" style="width:auto"
> > source="destinationBeans" rowClass="prop:rowClass" class="table
> table-hover
> > table-bordered" t:row="destinationBean" model="model" rowsPerPage="1000"
> > t:pagerPosition="top">
> >
> >
> > void onSubmitFromNameFilterForm() {
> >     applyFilter();
> >     ajaxResponseRenderer.addRender(destinationTableZone);
> > }
> >
> >
> > public String getNamefilterDebug() {
> >     System.err.format("getNamefilterDebug: %s%n",nameFilter);
> >     return nameFilter;
> > }
> >
> >
> > When using JavaScript/React I just call XMLHttpRequest.abort() for all
> > ongoing XHR's.
> >
> >
> > I cannot figure out how to do this in Tapestry 5.4.4. I was looking for
> > AjaxResponseRenderer addRender(ClientBodyElement zone, abortPrevious);
> >
> > S-E
> >
> >
> >
> >
>
> --
> Dmitry Gusev
>
> AnjLab Team
> http://anjlab.com
>

Re: How to let the last ajaxResponseRenderer win?

Posted by Dmitry Gusev <dm...@gmail.com>.
Hi,

There’s no reliable way to do so as all requests arrive simultaneously on
the server side, so unless you do a distributed lock — which is not a good
idea usually — there’s no way to do so.

One of ideas is: you could pass sequential number with every request, put
that number in a storage (like Redis) using compare and set, and if request
is older than the one already in storage — do nothing.

Usually such requests are throttled on the client side, I.e.:
https://github.com/cowboy/jquery-throttle-debounce

On Friday, June 7, 2019, Svein-Erik Løken <SE...@brav.com> wrote:

> When typing "test" pretty fast into textfield id=nameFilter the result is
> wrong. I am typical getting "DEBUG nameFilter: te" (sometimes just "t").
> The grid data corresponds to the nameFilter output.
>
> The output from  getNamefilterDebug() is always:
> getNamefilterDebug: t
> getNamefilterDebug: te
> getNamefilterDebug: tes
> getNamefilterDebug: test
>
>
>
> <t:form t:id="nameFilterForm" id="nameFilterForm" async="true"
> autofocus="true" style="display:flex;align-items: center;">
>     <t:label for="nameFilter">Name:</t:label>
>     <t:textfield t:id="nameFilter" oninput="$(this).closest('form').submit()"
> style="vertical-align: top" value="nameFilter" autocomplete="off"/>
>     <t:zone t:id="destinationCountZone">${destinationBeans.size()}</t:
> zone>
> </t:form>
>
> <t:zone t:id="destinationTableZone">
>     DEBUG nameFilter: ${namefilterDebug}
>
>     <t:grid renderTableIfEmpty="true" style="width:auto"
> source="destinationBeans" rowClass="prop:rowClass" class="table table-hover
> table-bordered" t:row="destinationBean" model="model" rowsPerPage="1000"
> t:pagerPosition="top">
>
>
> void onSubmitFromNameFilterForm() {
>     applyFilter();
>     ajaxResponseRenderer.addRender(destinationTableZone);
> }
>
>
> public String getNamefilterDebug() {
>     System.err.format("getNamefilterDebug: %s%n",nameFilter);
>     return nameFilter;
> }
>
>
> When using JavaScript/React I just call XMLHttpRequest.abort() for all
> ongoing XHR's.
>
>
> I cannot figure out how to do this in Tapestry 5.4.4. I was looking for
> AjaxResponseRenderer addRender(ClientBodyElement zone, abortPrevious);
>
> S-E
>
>
>
>

-- 
Dmitry Gusev

AnjLab Team
http://anjlab.com