Re: Updating browser history from an ajax response
Esteban Maringolo <[email protected]>
| Newsgroups | gmane.comp.lang.smalltalk.squeak.seaside |
|---|---|
| Message-ID | <CAJMgPC+DeW7zRNAYqzJaKi1jJOHG3EEceVuUbm4R0Ro4ePU5Ow@mail.gmail.com> |
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 <[email protected]> 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 > >>> >>>> > >>> > > >>> >> [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 > _______________________________________________ seaside mailing list [email protected] http://lists.squeakfoundation.org/cgi-bin/mailman/listinfo/seaside