[Seaside] Updating browser history from an ajax response

Esteban Maringolo emaringolo at gmail.com
Wed May 1 20:10:24 UTC 2019


Hi Paul,

Yes, such alternative was presented in the "Combining Seaside with React"
thread some weeks ago.

However it seems to be a big leap from the basic updating of URL using
traditional request/response XHR requests.

The morphdom(), however, seems trivial to implement as an alternative to
jQuery's replace().

Regards!

ps: I haven't pursued the PJAX approach not because I chose something else,
but because my backlog got filled with more important things :)

Esteban A. Maringolo


On Mon, Apr 29, 2019 at 2:58 PM Paul DeBruicker <pdebruic at gmail.com> wrote:

> 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
> _______________________________________________
> seaside mailing list
> seaside at lists.squeakfoundation.org
> http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/seaside/attachments/20190501/fd6c0668/attachment-0001.html>


More information about the seaside mailing list