[Seaside] Updating browser history from an ajax response

Paul DeBruicker pdebruic at gmail.com
Mon Apr 29 17:58:03 UTC 2019


This could be a better way for instance: 

https://dockyard.com/blog/2018/12/12/phoenix-liveview-interactive-real-time-apps-no-need-to-write-javascript





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

> seaside at .squeakfoundation

> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside





--
Sent from: http://forum.world.st/Seaside-General-f86180.html


More information about the seaside mailing list