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: &quot;lucida console&quot;, 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]?&nbsp;</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.&nbsp;</div><div class=3D"">I have =
done some experiments with <a href=3D"https://hotwired.dev/" =
class=3D"">https://hotwired.dev/</a>&nbsp;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.&nbsp;</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 &lt;<a href=3D"mailto:[email protected]" =
class=3D"">[email protected]</a>&gt; 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&gt;&gt;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. &nbsp;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.&nbsp;</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.&nbsp;</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&nbsp;</div><div class=3D""><span class=3D"Apple-tab-span" =
style=3D"white-space:pre">		</span>value: self =
stringValue;&nbsp;</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 |&nbsp;</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 =
&nbsp;, ' value: ' , value asString]</div><div class=3D""><span =
class=3D"Apple-tab-span" style=3D"white-space:pre">				=
</span>value: (html jQuery this value);&nbsp;</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.&nbsp;</div><div class=3D""><span class=3D"Apple-tab-span" =
style=3D"white-space:pre">	</span>html button&nbsp;</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 &lt;<a href=3D"mailto:[email protected]" =
class=3D"">[email protected]</a>&gt; 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: =
&quot;lucida console&quot;, 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&nbsp;<span =
class=3D"">JQAjax&gt;&gt;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.&nbsp;</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.&nbsp;</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: &quot;Helvetica Neue&quot;, 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 &lt;<a href=3D"mailto:[email protected]" =
class=3D"">[email protected]</a>&gt; 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.&nbsp;</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 =
&lt;script&gt;-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 &lt;script&gt;-tag at the end of your #testDiv.&nbsp;</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 &lt;script&gt; also =
means replacing the &lt;button&gt; 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==--