<div dir="ltr"><div dir="ltr"><div>Hi Paul,</div><div><br></div><div>Yes, such alternative was presented in the "Combining Seaside with React" thread some weeks ago.</div><div><br></div><div>However it seems to be a big leap from the basic updating of URL using traditional request/response XHR requests.</div><div><br></div><div>The morphdom(), however, seems trivial to implement as an alternative to jQuery's replace().</div><div><br></div><div>Regards!</div><div><br></div><div>ps: I haven't pursued the PJAX approach not because I chose something else, but because my backlog got filled with more important things :)</div><div><br></div><div>Esteban A. Maringolo</div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Apr 29, 2019 at 2:58 PM Paul DeBruicker <<a href="mailto:pdebruic@gmail.com">pdebruic@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This could be a better way for instance: <br>
<br>
<a href="https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript" rel="noreferrer" target="_blank">https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript</a><br>
<br>
<br>
<br>
<br>
<br>
Paul DeBruicker wrote<br>
> I fully believe there is a better/less hackish way to do this if its going<br>
> to be a seaside feature.  But what I have does to the partial page<br>
> update/back button/shareable links stuff well enough for me for now.<br>
> <br>
> You're right I do generally use it for navigation toreplace the content of<br>
> a<br>
> div with the id 'body' but  I also use it to <br>
> <br>
>     - update the page title<br>
>     - load on page help for the current view <br>
>     - move the person along in the guided tour if they're taking one<br>
>     - update other indicators/notifications if they've changed.   e.g.<br>
> "You<br>
> have a new message from ..."<br>
> <br>
> I think that the DOM editing code could easily be adapted to also replace<br>
> an<br>
> arbitrary collection of specific DOM components like you'd prefer without<br>
> too much trouble.  If you had an id for every DOM element you wanted to<br>
> replace in the real DOM and a matching div with the content in the Seaside<br>
> ajax response then you could itereate of the received divs putting their<br>
> contents into the target ids in the real DOM.  The duplicated ids would<br>
> only<br>
> exist in the response handling code.  And you could update multiple spots<br>
> on<br>
> the page at once for a given round trip to the server. <br>
> <br>
> <br>
> The setBodyJs code just searches the received div's putting them in the<br>
> correct spots and then runs the scripts that were included in the<br>
> response.  <br>
> Anyway that code is<br>
> <br>
> MyComponent>>#setBodyJs<br>
>       ^ (JSStream on: '$("#body").setBody(resp, status, xhr);') asFunction:<br>
> #('resp' 'status' 'xhr')<br>
> <br>
> <br>
>  $.fn.setBody = function (response, txtStatus, jqXhr) {<br>
>          var content, body = $('#body'), newElement, target;<br>
>          newElement=document.createElement('div');<br>
>          newElement.innerHTML = response;<br>
>          <br>
>          content = newElement.querySelector('#b');<br>
>          if(content){<br>
>                  body.empty().append(content.innerHTML);<br>
>          } else {<br>
>              window.location.href = window.location.href.split("?")[0];<br>
>          };<br>
> <br>
>          content = newElement.querySelector('#h');<br>
>          if(content){<br>
>              $('#helpBarContent').empty().append(content.innerHTML);<br>
>          };<br>
> <br>
>          content = newElement.querySelector('#t');<br>
>          if(content){<br>
>              $('#tour').empty().append(content.innerHTML);<br>
>          };<br>
> <br>
>          content = newElement.querySelector('#tp');<br>
>          if(content){<br>
>              $('#tourProgress').empty().append(content.innerHTML);<br>
>          };<br>
> <br>
>          $(response).filter('script').each(function () {<br>
>              $.globalEval(this.text || this.textContent || this.innerHTML<br>
> ||<br>
> '');<br>
>          });<br>
>          document.title = newElement.querySelector('#ti').innerHTML;<br>
>          $viewport.scrollToTop();<br>
>  };<br>
> <br>
> So you can see that its just finding elements by id ('#b' '#h' '#t' '#tp'<br>
> '#ti') in the Seaside response and replacing others in the DOM and could<br>
> be<br>
> generalized into a loop if you wanted to spend the time to do it (if you<br>
> were making it part of an open source package others could use for example<br>
> ;) ) .  <br>
> <br>
> <br>
> For keeping the URL in sync I do not use updateUrl: because it only (I<br>
> think?) edits the response URL & instead I just edit the url for the<br>
> anchor<br>
> before the pjaxCallback: e.g.<br>
> <br>
> html anchor<br>
>      backEndUrl:'mypage';<br>
>      pjaxCallback:[self showMyPage] on: html;<br>
>      with: 'click me to see my page'.<br>
> <br>
> <br>
> and WAAnchorTag>>#backEndUrl: is <br>
> <br>
> backEndUrl: anExtraPath<br>
>       self<br>
>               useBaseUrl;<br>
>               extraPath: MyApp appName;<br>
>               extraPath: anExtraPath<br>
> <br>
> By editing the url for the anchor it can be copy and pasted among users<br>
> both<br>
> from the anchor on the page and the url bar in the browser once they've<br>
> arrived on the page of interest.  It is only dissected and used in<br>
> #initialRequest: when people share links  and when the back button is<br>
> clicked as described below. I use cookies for sessions so there isn't a <br>
> _s<br>
> parameter in the urls that are created.  <br>
> <br>
> <br>
> To handle the back button I'm overriding the browser's native back button<br>
> handling.  There is some js where I add a header when the back button is<br>
> clicked, and then look for that in my session class and handle it.<br>
> <br>
> The JS that adds the header is is<br>
> <br>
> window.onpopstate = function (event) {<br>
>    // console.log(event.state);<br>
>     if ($('body.back').length && event.state) {<br>
>       $.ajax({url: event.state, <br>
>                 success: function(data, textStatus, jqXhr)<br>
> {$('body').setBodyAfterBackButton(data, textStatus, jqXhr);}, <br>
>                 beforeSend:<br>
> function(xhr){xhr.setRequestHeader("X-Requested-With", "pjaxBB");}});     <br>
>     }<br>
> }; <br>
> <br>
> and the setBodyAfterBackButton replaces the existing page with the Seaside<br>
> ajax response.<br>
> <br>
> $.fn.setBodyAfterBackButton = function (data, status, jqXhr) {<br>
>          var newDoc = document.open("text/html", "replace");<br>
>          newDoc.write(data);<br>
>          newDoc.close();<br>
> };<br>
> <br>
> <br>
> Then in the session I override #handleFiltered: like this<br>
> <br>
> <br>
> MySession>>#handleFiltered: aRequestContext<br>
>       (self isBackButtonPjaxRequest: aRequestContext)<br>
>               ifTrue: [ self handleBackButtonRequest: aRequestContext ]<br>
>               ifFalse: [ super handleFiltered: aRequestContext ]<br>
> <br>
> <br>
> MySession>>#isBackButtonPjaxRequest: aRequestContext<br>
>       ^ (aRequestContext request headers at: 'x-requested-with' ifAbsent: [ nil<br>
> ]) = 'pjaxBB'<br>
> <br>
> <br>
> MySession>>#handleBackButtonRequest: aRequestContext<br>
>               | bdy path |<br>
>               path := aRequestContext request uri path allButFirst.<br>
>               bdy := self bodyFromBackButtonPath: path.<br>
>               self  body: bdy.<br>
>               aRequestContext request uri addField: '_n'.<br>
>               super handleFiltered: aRequestContext<br>
> <br>
> <br>
> MySession>>#bodyFromBackButtonPath: path<br>
>    "App specific code to find which page of the app should be shown and<br>
> what<br>
> content should be in it just like in #initialRequest: "<br>
> <br>
> <br>
> <br>
> <br>
> Thats it I think.  It works. Could be clearer/cleaner/more general.   I'd<br>
> prefer it to be more automatic (evidenlty like Iliad I guess) but I'm not<br>
> creating so many new things that setting the urls & adding the lookup code<br>
> for the #bodyFromBackButtonPath:/#initialRequest: is a burden.  <br>
> <br>
> <br>
> And like you remember I really only use this for navigation among areas of<br>
> the app.  It doesn't remember if you had a modal open or a few accordions<br>
> expanded and others collapsed or were editing a thing on one page and<br>
> another thing on another page.  And I don't know how to go back to prior<br>
> server states with this. It will take you back ten screens in order but<br>
> that<br>
> screen will have the most recently edited data.  Not be in the same state<br>
> it<br>
> was when you first looked at it.   Unlike the counter demo I'm not sure it<br>
> should in a multi user app where others might've seen it or been affected<br>
> by<br>
> the changes. <br>
> <br>
> Hope this helps<br>
> <br>
> Paul<br>
> <br>
> <br>
> <br>
> <br>
> <br>
> Esteban A. Maringolo wrote<br>
>> Hi Paul,<br>
>> <br>
>> I've seen that code before ;-) and maybe setting the URL **before** is<br>
>> the way to go.<br>
>> <br>
>> In your case you always replace the "body" component of your page, I<br>
>> might need to adapt it in a way that could replace any DOM element.<br>
>> <br>
>> Regarding this:<br>
>> "Not sure its what you want to do but it definitely sets the window<br>
>> location<br>
>> (url) to something that when you hit refresh gives you the same page you<br>
>> see<br>
>> before you hit refresh (with updated server content, if anything there<br>
>> has<br>
>> changed in the interim)."<br>
>> <br>
>> I don't remember how you consumed that in #initialRequest and whether<br>
>> you specified #updateUrl: (and kept it in sync on each<br>
>> "pjaxCallback:..." call).<br>
>> <br>
>> Regards,<br>
>> <br>
>> Esteban A. Maringolo<br>
>> <br>
>> On Fri, Apr 12, 2019 at 1:47 PM Paul DeBruicker &lt;<br>
> <br>
>> pdebruic@<br>
> <br>
>> &gt; wrote:<br>
>>><br>
>>> Oh and I forgot that the onClick: is housed in another function that<br>
>>> allows<br>
>>> the ajax pushState anchors to work even if JS is disabled or there is no<br>
>>> session and looks like this:<br>
>>><br>
>>><br>
>>> WAAnchorTag>>#pjaxCallback: aBlockClosure on: html withSuccessScript:<br>
>>> aScript<br>
>>>   self<br>
>>>     onClick:<br>
>>>         (self<br>
>>>             processCallback: aBlockClosure<br>
>>>             onSuccess: self setBodyJs , aScript<br>
>>>             return: false<br>
>>>             thenUpdateBodyOn: html);<br>
>>>     callback: aBlockClosure<br>
>>><br>
>>><br>
>>> &<br>
>>><br>
>>> processCallback: aCallback onSuccess: aJSFunction return: aBoolean<br>
>>> thenUpdateBodyOn: html<br>
>>>   self hasSession<br>
>>>     ifTrue: [<br>
>>>       | scr |<br>
>>>       scr := html jQuery ajax<br>
>>>         html: [ :h |<br>
>>>               aCallback value.<br>
>>>               self renderUpdatedBodyContentFor: self session pjaxBody<br>
>>> on:<br>
>>> h<br>
>>> ];<br>
>>>         onSuccess: aJSFunction.<br>
>>>       aBoolean notNil<br>
>>>         ifTrue: [ scr return: aBoolean ].<br>
>>>       ^ html jQuery this updateUrl<br>
>>>         , ((html jQuery class: 'active') removeClass: 'active') , scr ]<br>
>>>     ifFalse: [ ^ nil ]<br>
>>><br>
>>><br>
>>> So its<br>
>>><br>
>>> html anchor<br>
>>>     pjaxCallback:[ self doSomethingYourClientsLove ] on: html<br>
>>> withSuccessScript: (JSStream on:'alert("I don't believe that worked")');<br>
>>>     with: 'Click me'<br>
>>><br>
>>><br>
>>><br>
>>><br>
>>> Paul DeBruicker wrote<br>
>>> > Oh man if only our past selves knew we really wanted Iliad apps what a<br>
>>> > world<br>
>>> > this would be....<br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> > In my apps I use<br>
>>> ><br>
>>> >      $.fn.updateUrl = function(anId){<br>
>>> ><br>
>>> >          if (typeof(window.history.pushState) == 'function') {<br>
>>> >              var relativeUrl = $(this).attr('href');<br>
>>> >              if(typeof(relativeUrl) =='undefined'){<br>
>>> >                  relativeUrl =<br>
>>> > document.location.pathname+document.location.search;<br>
>>> >              };<br>
>>> >              if(relativeUrl!=='javascript:void(0)' && relativeUrl !==<br>
>>> > window.history.state){<br>
>>> >                  window.history.pushState(relativeUrl, document.title,<br>
>>> > relativeUrl);<br>
>>> >              }<br>
>>> >          }<br>
>>> >      };<br>
>>> ><br>
>>> > and call it as part of the onClick: script on anchors and buttons<br>
>>> BEFORE<br>
>>> > loading content via ajax.<br>
>>> ><br>
>>> > So the onClick: is<br>
>>> ><br>
>>> ><br>
>>> > onClick: html jQuery this updateUrl, (self doContentLoadingMagicOn:<br>
>>> html)<br>
>>> ><br>
>>> ><br>
>>> > Also it doesn't need to be a jQuery function but is because it was<br>
>>> > different<br>
>>> > when I initially made it.<br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> > Not sure its what you want to do but it definitely sets the window<br>
>>> > location<br>
>>> > (url) to something that when you hit refresh gives you the same page<br>
>>> you<br>
>>> > see<br>
>>> > before you hit refresh (with updated server content, if anything there<br>
>>> has<br>
>>> > changed in the interim).<br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> > Siemen Baader wrote<br>
>>> >> Hi,<br>
>>> >><br>
>>> >> Not really answering your questions, but I think this is what Iliad<br>
>>> is<br>
>>> >> for. There you have access to a restful URL on every (xhr) request<br>
>>> and<br>
>>> >> every WAComponent is transparently updated via xhr, including when<br>
>>> you<br>
>>> >> use<br>
>>> >> call-answer.<br>
>>> >><br>
>>> >> Just FYI. :)<br>
>>> >><br>
>>> >> Siemen<br>
>>> >><br>
>>> >> Sent from my iPhone<br>
>>> >><br>
>>> >>> On 12 Apr 2019, at 17.23, Johan Brichau &lt;<br>
>>> ><br>
>>> >> johan@<br>
>>> ><br>
>>> >> &gt; wrote:<br>
>>> >>><br>
>>> >>> Hi Esteban,<br>
>>> >>><br>
>>> >>> I’m interested in this too.<br>
>>> >>> We have the same kind of application (I guess… using mostly Ajax<br>
>>> >>> updates,<br>
>>> >>> I mean) but I have just been postponing a retry to address the<br>
>>> >>> inconsistency with ajax, server state and the back button for<br>
>>> years….<br>
>>> >>><br>
>>> >>> Within a callback, you can get at the #actionUrl on the renderer.<br>
>>> >>> Is that what you want?<br>
>>> >>><br>
>>> >>> I’m trying to dive into what would be required here… trying to<br>
>>> >>> understand<br>
>>> >>> what you need:<br>
>>> >>> Ajax requests always update the same continuation state in Seaside<br>
>>> (i.e.<br>
>>> >>> they do not create a new continuation)… so if you push that url to<br>
>>> the<br>
>>> >>> history after each ajax update, the back button will essentially<br>
>>> request<br>
>>> >>> the same but updated continuation from Seaside, meaning you should<br>
>>> see<br>
>>> >>> the state as it is in on the server, and not how it was before you<br>
>>> made<br>
>>> >>> the ajax request (which is what happens now).<br>
>>> >>><br>
>>> >>> Does that correspond to what you are trying to fix?<br>
>>> >>><br>
>>> >>> Johan<br>
>>> >>><br>
>>> >>>> On 12 Apr 2019, at 15:17, Esteban Maringolo &lt;<br>
>>> ><br>
>>> >> emaringolo@<br>
>>> ><br>
>>> >> &gt; wrote:<br>
>>> >>>><br>
>>> >>>> I have a Seaside application that is almost 100% AJAX driven to<br>
>>> >>>> replace the visual components (no call/answer involved).<br>
>>> >>>><br>
>>> >>>> I plan to to use history.pushState() and history.replaceState()<br>
>>> >>>><br>
>>> >>>> My plan is to update the browser url after replacing some<br>
>>> components,<br>
>>> >>>> but using the URL building of the existing #updateUrl: (including<br>
>>> >>>> _s/_k parameters and whatnot).<br>
>>> >>>><br>
>>> >>>> Is it possible to obtain the URL of the session presenter within<br>
>>> the<br>
>>> >>>> ajax response?<br>
>>> >>>><br>
>>> >>>> Thanks in advance,<br>
>>> >>>><br>
>>> >>>> Esteban A. Maringolo<br>
>>> >>>> _______________________________________________<br>
>>> >>>> seaside mailing list<br>
>>> >>>><br>
>>> ><br>
>>> >> seaside@.squeakfoundation<br>
>>> ><br>
>>> >>>> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
>>> >>><br>
>>> >>> _______________________________________________<br>
>>> >>> seaside mailing list<br>
>>> >>><br>
>>> ><br>
>>> >> seaside@.squeakfoundation<br>
>>> ><br>
>>> >>> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
>>> >> _______________________________________________<br>
>>> >> seaside mailing list<br>
>>> ><br>
>>> >> seaside@.squeakfoundation<br>
>>> ><br>
>>> >> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> ><br>
>>> > --<br>
>>> > Sent from: <a href="http://forum.world.st/Seaside-General-f86180.html" rel="noreferrer" target="_blank">http://forum.world.st/Seaside-General-f86180.html</a><br>
>>> > _______________________________________________<br>
>>> > seaside mailing list<br>
>>><br>
>>> > seaside@.squeakfoundation<br>
>>><br>
>>> > <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
>>><br>
>>><br>
>>><br>
>>><br>
>>><br>
>>> --<br>
>>> Sent from: <a href="http://forum.world.st/Seaside-General-f86180.html" rel="noreferrer" target="_blank">http://forum.world.st/Seaside-General-f86180.html</a><br>
>>> _______________________________________________<br>
>>> seaside mailing list<br>
>>> <br>
> <br>
>> seaside@.squeakfoundation<br>
> <br>
>>> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
>> _______________________________________________<br>
>> seaside mailing list<br>
> <br>
>> seaside@.squeakfoundation<br>
> <br>
>> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
> <br>
> <br>
> <br>
> <br>
> <br>
> --<br>
> Sent from: <a href="http://forum.world.st/Seaside-General-f86180.html" rel="noreferrer" target="_blank">http://forum.world.st/Seaside-General-f86180.html</a><br>
> _______________________________________________<br>
> seaside mailing list<br>
<br>
> seaside@.squeakfoundation<br>
<br>
> <a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
<br>
<br>
<br>
<br>
<br>
--<br>
Sent from: <a href="http://forum.world.st/Seaside-General-f86180.html" rel="noreferrer" target="_blank">http://forum.world.st/Seaside-General-f86180.html</a><br>
_______________________________________________<br>
seaside mailing list<br>
<a href="mailto:seaside@lists.squeakfoundation.org" target="_blank">seaside@lists.squeakfoundation.org</a><br>
<a href="http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside" rel="noreferrer" target="_blank">http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside</a><br>
</blockquote></div>