[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