[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