Re: ajax onBlur callback with load html: [...]
Johan Brichau <[email protected]> Thu, 16 Sep 2021 16:56:13 +0200
| Newsgroups | gmane.comp.lang.smalltalk.squeak.seaside |
|---|---|
| Message-ID | <[email protected]> |
--===============4262232551056322511== Content-Type: multipart/alternative; boundary="Apple-Mail=_F2625B4C-358E-4DD6-9B66-169752611012" --Apple-Mail=_F2625B4C-358E-4DD6-9B66-169752611012 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 Bob, Now that browsed through the email thread to your original question: > I have a design pattern question: what is the good way to deal with = ajax callbacks when the component is refreshed by a previous = component=E2=80=99s onBlur action, using jQuery load html: [=E2=80=A6]?=20= Using plain Seaside and jQuery-Javascript, I think the best one can do = is: - not refresh the button itself when updating =E2=80=98on blur=E2=80=99 - not use a full re-render of the entire html but rather generate = javascript that replaces only the contents of the input fields We have applied both strategies, depending on their appropriateness. Since quite a while, I have been pondering and briefly experimenting = with using Javascript libraries that offer more support for incremental = updating in combination with =E2=80=98traditional=E2=80=99 Seaside.=20 I have done some experiments with https://hotwired.dev/ = <https://hotwired.dev/> which is what the Ruby-on-Rails guys are using = to do this. In short, the actual html that gets replaced in the browser = is the diff between what is already there and what gets sent from the = server in the update. That would mean the button is not replaced, the = callback would be fired and we would not be having this conversation. Unfortunately, time is short and I have not explored it in depth. There = are also a few caveats that need to be tackled in the combination with = Seaside though, so it=E2=80=99s not just a case =E2=80=98use this = library=E2=80=99.=20 Johan > On 16 Sep 2021, at 10:30, Johan Brichau <[email protected]> wrote: >=20 > Hi Bob, >=20 > The JQAjax#callback being nil is normal if there is no =E2=80=9Cprimary=E2= =80=9D callback. This is what you see: the Ajax callback registered on = the onblur event (using JQAjax>>callback:value:, which is a secondary = callback) has no primary callback block. The Ajax callback registered on = the onClick event only has a primary callback block. This is fine and = works as intended. >=20 > The problem you are seeing is on the client side and all about browser = events: the click on the button triggers the browser=E2=80=99s onBlur = event first, which triggers the Seaside callback for the blur, which = triggers another callback to replace the html snippet including the = button you pressed and the input field that triggered the blur. The html = load replaces the button you are clicking=E2=80=A6. Now, depending on = the speed of events and callbacks, the html (including your button) will = be replaced before or after the browser can send the onClick event to = the button, which would trigger the Seaside callback for the button.=20 >=20 > In addition, there is the ordering of events in the browser that makes = this very complicated to think about: an onClick is only fired after a = mouseDown and a mouseUp and an onBlur is sent because of the mouseDown = on the button. >=20 > If you include a 100ms delay in the response of the Seaside callback, = you are effectively making it possible for the browser to trigger the = onClick event on the original button as well. > Without this delay, the browser will have sent the mouseDown event to = the button but the button will be replaced before the browser is able to = trigger a mouseUp and an onClick.=20 > Because the new button is not the one you clicked, it will not have = it=E2=80=99s onclick handler fired. >=20 > If you do not replace the button while rendering the Ajax update, it = works as well. >=20 > Doing some console logging can be helpful to debug such issues. See = code below and try with or without the delay commented out. >=20 > html textInput=20 > value: self stringValue;=20 > onBlur: ((JSStream on: 'console.log(''onblur'')'), > (html jQuery ajax callback: [:value |=20 > self stringValue: value. = (Delay forMilliseconds: 100) wait. > Transcript cr; show: = Time now displayString , ' value: ' , value asString] > value: (html jQuery this value);=20 > onSuccess: ((JSStream on: = 'console.log(''contentload-onblur'')'),(self renderContentLoadDivOn: = html) ))). > html break; break.=20 > html button=20 > onClick: ( (JSStream on: 'console.log(''onclick'')'), > (html jQuery ajax callback: [ > self stringValue: self = stringValue , '+'. > Transcript cr; show: Time now = displayString , ' button: ' , self stringValue asString]; > onSuccess: ((JSStream on: = 'console.log(''contentload-onclick'')'),(self renderContentLoadDivOn: = html) ))); > with: 'Press=E2=80=99. >=20 >=20 > Hope this helps, > Johan >=20 >> On 15 Sep 2021, at 23:40, Bob Nemec <[email protected] = <mailto:[email protected]>> wrote: >>=20 >>=20 >> Wow, this is proving to be challenge. The problem is that in = JQAjax>>processCallback #callback is sometimes nil. When it is, the = button press callback is not processed and the button has to be pressed = again. But, if the onBlur of the text input field has a 100ms delay, the = #callback value is not nil and the button callback works all the time. = Which is especially frustrating because adding diagnostic traces slows = down the first callback enough for things to work.=20 >>=20 >> This is pushing me to the edge of my Seaside knowledge. I will try = this same example with the latest Seaside code on Pharo. If it works, = I'll work on getting us updated (we develop in VW but deploy on GS). If = not, I'll have a nice packaged problem to raise on the Seaside part of = the Pharo Discord channel.=20 >>=20 >> Bob >>=20 >> On Tuesday, September 14, 2021, 08:58:31 a.m. EDT, Karsten Kusche = <[email protected] <mailto:[email protected]>> wrote: >>=20 >>=20 >> Hi Bob, >>=20 >>> We use onBlur to get the data from input fields and to refresh the = state of the view. The problem comes up if a button is included in the = refresh: the ajax callback from that button is not handled (which makes = sense due to the jQuery load), so the user has to press the button = again. >>=20 >> I=E2=80=99d say it actually doesn=E2=80=99t make any sense. Your = callbacks are registered, they should work regardless. You could use the = Web-Browser=E2=80=99s network tool/profiler to see which event-handler = is fired and what network requests are made. Maybe one of the requests = doesn=E2=80=99t come through correctly and you can investigate that = further.=20 >>=20 >> Some things you should also consider: when you use render = HTML-Snippets that contain Javascript (like new click-handlers), Seaside = will automatically add these inside a <script>-tag at the end of this = snippet and inject that into your DOM at the place where you inject the = HTML. In your case you should have a <script>-tag at the end of your = #testDiv.=20 >>=20 >> I=E2=80=99m not entirely sure about this, but iirc replacing that = script-tag will also remove all event-handlers that were registered as = part of this tag. On the other hand replacing the <script> also means = replacing the <button> so you should still have a working button in any = case=E2=80=A6 like i said, it doesn=E2=80=99t really make much sense. >>=20 >> Karsten >>=20 >> _______________________________________________ >> seaside mailing list >> [email protected] = <mailto:[email protected]> >> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside = <http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside> --Apple-Mail=_F2625B4C-358E-4DD6-9B66-169752611012 Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8 <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; = charset=3Dutf-8"></head><body style=3D"word-wrap: break-word; = -webkit-nbsp-mode: space; line-break: after-white-space;" = class=3D"">Bob,<div class=3D""><br class=3D""></div><div class=3D"">Now = that browsed through the email thread to your original = question:</div><div class=3D""><br class=3D""></div><div = class=3D""><blockquote type=3D"cite" class=3D""><span = style=3D"font-family: "lucida console", sans-serif; font-size: = 13px;" class=3D"">I have a design pattern question: what is the good way = to deal with ajax callbacks when the component is refreshed by a = previous component=E2=80=99s onBlur action, using jQuery load html: = [=E2=80=A6]? </span></blockquote><div class=3D""><br = class=3D""></div><div class=3D"">Using plain Seaside and = jQuery-Javascript, I think the best one can do is:</div><div class=3D"">- = not refresh the button itself when updating =E2=80=98on = blur=E2=80=99</div><div class=3D"">- not use a full re-render of the = entire html but rather generate javascript that replaces only the = contents of the input fields</div><div class=3D""><br = class=3D""></div><div class=3D"">We have applied both strategies, = depending on their appropriateness.</div><div class=3D""><br = class=3D""></div><div class=3D"">Since quite a while, I have been = pondering and briefly experimenting with using Javascript libraries that = offer more support for incremental updating in combination with = =E2=80=98traditional=E2=80=99 Seaside. </div><div class=3D"">I have = done some experiments with <a href=3D"https://hotwired.dev/" = class=3D"">https://hotwired.dev/</a> which is what the = Ruby-on-Rails guys are using to do this. In short, the actual html that = gets replaced in the browser is the diff between what is already there = and what gets sent from the server in the update. That would mean the = button is not replaced, the callback would be fired and we would not be = having this conversation.</div><div class=3D""><br class=3D""></div><div = class=3D"">Unfortunately, time is short and I have not explored it in = depth. There are also a few caveats that need to be tackled in the = combination with Seaside though, so it=E2=80=99s not just a case =E2=80=98= use this library=E2=80=99. </div><div class=3D""><br = class=3D""></div>Johan<br class=3D""><div><br class=3D""><blockquote = type=3D"cite" class=3D""><div class=3D"">On 16 Sep 2021, at 10:30, Johan = Brichau <<a href=3D"mailto:[email protected]" = class=3D"">[email protected]</a>> wrote:</div><br = class=3D"Apple-interchange-newline"><div class=3D""><meta = http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" = class=3D""><div style=3D"word-wrap: break-word; -webkit-nbsp-mode: = space; line-break: after-white-space;" class=3D"">Hi Bob,<div = class=3D""><br class=3D""></div><div class=3D"">The JQAjax#callback = being nil is normal if there is no =E2=80=9Cprimary=E2=80=9D callback. = This is what you see: the Ajax callback registered on the onblur event = (using JQAjax>>callback:value:, which is a secondary callback) has = no primary callback block. The Ajax callback registered on the onClick = event only has a primary callback block. This is fine and works as = intended.</div><div class=3D""><br class=3D""></div><div class=3D"">The = problem you are seeing is on the client side and all about browser = events: the click on the button triggers the browser=E2=80=99s onBlur = event first, which triggers the Seaside callback for the blur, which = triggers another callback to replace the html snippet including the = button you pressed and the input field that triggered the blur. The html = load replaces the button you are clicking=E2=80=A6. Now, depending = on the speed of events and callbacks, the html (including your button) = will be replaced before or after the browser can send the onClick event = to the button, which would trigger the Seaside callback for the = button. </div><div class=3D""><br class=3D""></div><div class=3D"">In= addition, there is the ordering of events in the browser that makes = this very complicated to think about: an onClick is only fired after a = mouseDown and a mouseUp and an onBlur is sent because of the mouseDown = on the button.</div><div class=3D""><br class=3D""></div><div = class=3D"">If you include a 100ms delay in the response of the Seaside = callback, you are effectively making it possible for the browser to = trigger the onClick event on the original button as well.</div><div = class=3D"">Without this delay, the browser will have sent the mouseDown = event to the button but the button will be replaced before the browser = is able to trigger a mouseUp and an onClick. </div><div = class=3D"">Because the new button is not the one you clicked, it will = not have it=E2=80=99s onclick handler fired.</div><div class=3D""><br = class=3D""></div><div class=3D"">If you do not replace the button while = rendering the Ajax update, it works as well.</div><div class=3D""><br = class=3D""></div><div class=3D""><div class=3D"">Doing some console = logging can be helpful to debug such issues. See code below and try with = or without the delay commented out.</div><div class=3D""><br = class=3D""></div><div class=3D""><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> </span>html = textInput </div><div class=3D""><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>value: self = stringValue; </div><div class=3D""><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>onBlur: ((JSStream on: = 'console.log(''onblur'')'),</div><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>(html jQuery ajax callback: [:value | </div><div = class=3D""><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>self stringValue: value. (Delay = forMilliseconds: 100) wait.</div><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>Transcript cr; show: Time now displayString = , ' value: ' , value asString]</div><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>value: (html jQuery this value); </div><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>onSuccess: ((JSStream on: = 'console.log(''contentload-onblur'')'),(self renderContentLoadDivOn: = html) ))).</div><div class=3D""><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>html break; = break. </div><div class=3D""><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> </span>html button </div><div = class=3D""><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>onClick: ( (JSStream on: 'console.log(''onclick'')'),</div><div = class=3D""><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>(html jQuery ajax callback: [</div><div = class=3D""><span class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>self stringValue: self stringValue , = '+'.</div><div class=3D""><span class=3D"Apple-tab-span" = style=3D"white-space:pre"> = </span>Transcript cr; show: Time now displayString , ' button: ' , self = stringValue asString];</div><div class=3D""><span class=3D"Apple-tab-span"= style=3D"white-space:pre"> </span>onSuccess: = ((JSStream on: 'console.log(''contentload-onclick'')'),(self = renderContentLoadDivOn: html) )));</div><div class=3D""><span = class=3D"Apple-tab-span" style=3D"white-space:pre"> = </span>with: 'Press=E2=80=99.</div><div class=3D""><br = class=3D""></div><div class=3D""><br class=3D""></div><div class=3D"">Hope= this helps,</div><div class=3D"">Johan</div><div class=3D""><br = class=3D""></div><blockquote type=3D"cite" class=3D""><div class=3D"">On = 15 Sep 2021, at 23:40, Bob Nemec <<a href=3D"mailto:[email protected]" = class=3D"">[email protected]</a>> wrote:</div><br = class=3D"Apple-interchange-newline"><div class=3D""><div = class=3D"ydpb0c0ebbayahoo-style-wrap" style=3D"caret-color: rgb(0, 0, = 0); font-style: normal; font-variant-caps: normal; font-weight: normal; = letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none; font-family: = "lucida console", sans-serif; font-size: 13px;"><div dir=3D"ltr"= data-setdir=3D"false" class=3D""><br = class=3D"Apple-interchange-newline">Wow, this is proving to be = challenge. The problem is that in <span = class=3D"">JQAjax>>processCallback #<span class=3D"">callback is = sometimes nil. When it is, the button press callback is not processed = and the button has to be pressed again. But, if the onBlur of the text = input field has a 100ms delay, the #callback value is not nil and the = button callback works all the time. Which is especially frustrating = because adding diagnostic traces slows down the first callback enough = for things to work. </span></span></div><div dir=3D"ltr" = data-setdir=3D"false" class=3D""><span class=3D""><span class=3D""><br = class=3D""></span></span></div><div dir=3D"ltr" data-setdir=3D"false" = class=3D""><span class=3D""><span class=3D"">This is pushing me to the = edge of my Seaside knowledge. I will try this same example with the = latest Seaside code on Pharo. If it works, I'll work on getting us = updated (we develop in VW but deploy on GS). If not, I'll have a nice = packaged problem to raise on the Seaside part of the Pharo Discord = channel. </span></span></div><div dir=3D"ltr" data-setdir=3D"false" = class=3D""><span class=3D""><span class=3D""><br = class=3D""></span></span></div><div dir=3D"ltr" data-setdir=3D"false" = class=3D""><span class=3D""><span class=3D"">Bob</span></span></div><div = dir=3D"ltr" data-setdir=3D"false" class=3D""><span class=3D""><span = class=3D""><br class=3D""></span></span></div></div><div = id=3D"yahoo_quoted_2112089454" class=3D"yahoo_quoted" = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none;"><div = style=3D"font-family: "Helvetica Neue", Helvetica, Arial, = sans-serif; font-size: 13px; color: rgb(38, 40, 42);" class=3D""><div = class=3D"">On Tuesday, September 14, 2021, 08:58:31 a.m. EDT, Karsten = Kusche <<a href=3D"mailto:[email protected]" = class=3D"">[email protected]</a>> wrote:</div><div class=3D""><br = class=3D""></div><div class=3D""><br class=3D""></div><div class=3D""><div= id=3D"yiv0857676699" class=3D""><div class=3D""><div = style=3D"font-family: Helvetica, Arial; font-size: 13px;" class=3D"">Hi = Bob,</div><div class=3D"yiv0857676699yqt6264543426" = id=3D"yiv0857676699yqtfd02179"><div style=3D"font-family: Helvetica, = Arial; font-size: 13px;" class=3D""><br clear=3D"none" = class=3D""></div></div><div class=3D""><div = class=3D"yiv0857676699yqt6264543426" = id=3D"yiv0857676699yqtfd06452"></div><blockquote = class=3D"yiv0857676699clean_bq" type=3D"cite" style=3D"font-family: = Helvetica, Arial; font-size: 13px; font-style: normal; font-weight: = normal; letter-spacing: normal; text-indent: 0px; text-transform: none; = white-space: normal; word-spacing: 0px; text-decoration: none;"><div = class=3D"yiv0857676699yqt6264543426" id=3D"yiv0857676699yqtfd95783"><span = class=3D""></span><div class=3D"">We use onBlur to get the data from = input fields and to refresh the state of the view. The problem comes up = if a button is included in the refresh: the ajax callback from that = button is not handled (which makes sense due to the jQuery load), so the = user has to press the button again.</div></div></blockquote></div><p = class=3D"">I=E2=80=99d say it actually doesn=E2=80=99t make any sense. = Your callbacks are registered, they should work regardless. You could = use the Web-Browser=E2=80=99s network tool/profiler to see which = event-handler is fired and what network requests are made. Maybe one of = the requests doesn=E2=80=99t come through correctly and you can = investigate that further. </p><p class=3D"">Some things you should = also consider: when you use render HTML-Snippets that contain Javascript = (like new click-handlers), Seaside will automatically add these inside a = <script>-tag at the end of this snippet and inject that into your = DOM at the place where you inject the HTML. In your case you should have = a <script>-tag at the end of your #testDiv. </p><p = class=3D"">I=E2=80=99m not entirely sure about this, but iirc replacing = that script-tag will also remove all event-handlers that were registered = as part of this tag. On the other hand replacing the <script> also = means replacing the <button> so you should still have a working = button in any case=E2=80=A6 like i said, it doesn=E2=80=99t really make = much sense.</p><p class=3D"">Karsten</p><div = class=3D"yiv0857676699yqt6264543426" id=3D"yiv0857676699yqtfd17937"><div = class=3D""></div></div></div></div></div></div></div><span = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none; float: none; = display: inline !important;" = class=3D"">_______________________________________________</span><br = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none;" class=3D""><span = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none; float: none; = display: inline !important;" class=3D"">seaside mailing list</span><br = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none;" class=3D""><span = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none; float: none; = display: inline !important;" class=3D""><a = href=3D"mailto:[email protected]" = class=3D"">[email protected]</a></span><br = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none;" class=3D""><span = style=3D"caret-color: rgb(0, 0, 0); font-family: Helvetica; font-size: = 12px; font-style: normal; font-variant-caps: normal; font-weight: = normal; letter-spacing: normal; text-align: start; text-indent: 0px; = text-transform: none; white-space: normal; word-spacing: 0px; = -webkit-text-stroke-width: 0px; text-decoration: none; float: none; = display: inline !important;" class=3D""><a = href=3D"http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside= " = class=3D"">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seas= ide</a></span></div></blockquote></div><br = class=3D""></div></div></div></blockquote></div><br = class=3D""></div></body></html>= --Apple-Mail=_F2625B4C-358E-4DD6-9B66-169752611012-- --===============4262232551056322511== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline X19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX19fX18Kc2Vhc2lkZSBt YWlsaW5nIGxpc3QKc2Vhc2lkZUBsaXN0cy5zcXVlYWtmb3VuZGF0aW9uLm9yZwpodHRwOi8vbGlz dHMuc3F1ZWFrZm91bmRhdGlvbi5vcmcvY2dpLWJpbi9tYWlsbWFuL2xpc3RpbmZvL3NlYXNpZGUK --===============4262232551056322511==--