Re: Updating browser history from an ajax response

Paul DeBruicker <[email protected]>
Newsgroups gmane.comp.lang.smalltalk.squeak.seaside
Message-ID <[email protected]>
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 &lt;
> 
>> pdebruic@
> 
>> &gt; 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 &lt;
>>> >
>>> >> johan@
>>> >
>>> >> &gt; 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 &lt;
>>> >
>>> >> emaringolo@
>>> >
>>> >> &gt; 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
>>> >>>>
>>> >
>>> >> [email protected]
>>> >
>>> >>>> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>>> >>>
>>> >>> _______________________________________________
>>> >>> seaside mailing list
>>> >>>
>>> >
>>> >> [email protected]
>>> >
>>> >>> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>>> >> _______________________________________________
>>> >> seaside mailing list
>>> >
>>> >> [email protected]
>>> >
>>> >> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>>> >
>>> >
>>> >
>>> >
>>> >
>>> > --
>>> > Sent from: http://forum.world.st/Seaside-General-f86180.html
>>> > _______________________________________________
>>> > seaside mailing list
>>>
>>> > [email protected]
>>>
>>> > http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>>>
>>>
>>>
>>>
>>>
>>> --
>>> Sent from: http://forum.world.st/Seaside-General-f86180.html
>>> _______________________________________________
>>> seaside mailing list
>>> 
> 
>> [email protected]
> 
>>> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>> _______________________________________________
>> seaside mailing list
> 
>> [email protected]
> 
>> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
> 
> 
> 
> 
> 
> --
> Sent from: http://forum.world.st/Seaside-General-f86180.html
> _______________________________________________
> seaside mailing list

> [email protected]

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





--
Sent from: http://forum.world.st/Seaside-General-f86180.html
_______________________________________________
seaside mailing list
[email protected]
http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.