Backbone fetch failure notification - javascript

We're having an issue with our backbone application. We want to provide a user with a notification when a fetch fails (timeout or general error), but we want to display a dialog over the previous page's content rather than showing an error message in the new page (how Facebook/LinkedIn etc. do it)
To trigger a request for the new content, we have to navigate to the new URL first. We can't really change this without a rework, so we want to avoid this if possible. What we need to do is send the user back to the previous URL when there is a connection error, which would cause the route to fire, re-requesting the previous content. We really want to avoid doing this however.
We're aware that we can send a user back using a navigate without triggering a route, but this will mess up the browser history, making backwards become forwards in this case. We could also force a browser back, keeping the history trail correctly, but this would force a re-fetch of the content.
We've also investigated setting a flag of some kind telling our router not to re-request data on the next route change, but this would cause issues when browser back is used to go to a previous screen on which the fetch fails. In this instance we'd need to send the user 'forwards' in their journey instead. As far as we know, this isn't possible using the browser's history manager.
Is there any way of having a dialog how we want, or will we have to go the same way as Facebook/LinkedIn and co.?

Do you have an example of your code / what you have tried?
Going off what you have said, if there is an error fetching the model data after your URL has changed you can silently redirect the user back to the previous URL using the router, e.g:
window.product_v = Backbone.View.extend({
render: function() {
this.model.fetch({
processData: true,
data: this.model.attributes,
success : function(d){
},
error : function(d) {
MyRouter.previous();
}
})
}
});
Then in your router could keep an array of your history so that the route isn't 'triggered' on redirect. or by simply doing:
Backbone.history.navigate(route, {trigger: false, replace: true});
The below question/answer describes this perfectly:
Silently change url to previous using Backbone.js
class MyRouter extends Backbone.Router
constructor: (options) ->
#on "all", #storeRoute
#history = []
super options
storeRoute: ->
#history.push Backbone.history.fragment
previous: ->
if #history.length > 1
#navigate #history[#history.length-2], true

Related

query props lost when refreshing page in Next js [duplicate]

I have a Link where I want to pass certain params in the URL but I don't want the browser to display the params.
I'm using Link's as for this:
<Link href={`/link?foo=bar`} as ={`/link`}>
<a>Link</a>
</Link>
But when I click this link and I try to access the params via router, I can't access foo=bar:
const router = useRouter()
console.log(router.query)
Returns
{
slug: ["link"],
}
And not
{
slug: ["link"],
foo: "bar",
}
So how can I access the URL params in href when using as for Link?
TL;DR You can't use as like that.
This is an incorrect usage of href and as. It would be cool if we could hide state from the end users to keep our URLs nice, clean, and compact, but obviously if you do that, you'll actually lose the state when copy/pasting the URL. That's why you can't hide query parameters in anyway (except for excluding them).
Here's the docs on href and as (dynamic routes, has little to do with hiding query params):
https://nextjs.org/docs/tag/v9.5.2/api-reference/next/link#dynamic-routes
And to further bring up my point, imagine if we could hide state, and we redirect to this URL:
https://example.com/stateful/
Presumably there would be some behind-the-scenes browser action that persists the state.
Now we copy/paste the URL:
https://example.com/stateful/
Oops! We don't have the state anymore because the browser has no previous state to keep track of! That's why you use query parameters, because they keep the state in the URL itself.

Rails: Turbolinks not clearing cache

I've got four issues.
First case:
In some cases I append notices and alerts to the page, those should obviously be removed from the cache. In order to do that I added the required line of JavaScript:
function componentsNotificationsFlashModalHide() {
setTimeout(function() {
if ( $('.flash-modal.flash:not(.invisible):hover').length != 0 ) {
componentsNotificationsFlashModalHide();
} else {
$('.flash-modal.flash:not(.invisible)').fadeOut(250);
Turbolinks.clearCache();
};
}, 4500);
};
Essentially, when the notification fades out, the cache should get cleared, so the next visit will fetch a new version of the page from the server.
Instead, the cached version remains as the notification does whenever coming back to the page, until I issue a full reload of the page manually.
Second case:
In my app the root template exists in two states on two separate templates and controllers depending on whether the user is signed in or not. I tried to prevent any problems by appending ...
%meta{ name: 'turbolinks-cache-control', content: 'no-cache' }
... to both document's head's.
Still, whenever a user signs in and visits the root document, the wrong version is being displayed and vice versa.
Third case:
In some cases after signing in (and getting redirected to the previous or root url) the server issues an additional redirect from the page the user lands on.
So for example a userflow could look like:
root_url -> login_url => root_url =!> some_other_url
-> manual
=> redirect
=!> redirect which is not working
That's clearly turbolinks related. After issuing a redirect manually, the server immediately redirects the user to some_other_url.
Fourth case:
When clicking a link with remote: true and method: :put, it appears that turbolinks responds automatically without providing a template for that specific format with:
Turbolinks.clearCache()
Turbolinks.visit("http://lvh.me:3000/", {"action":"replace"})
But instead of reloading the page and adjusting it for any new changes, it fetches the cached version which in this case cannot be intended.
Rails version: 5.1.0.rc2

Backbone router.navigate doesn't redirect

Honestly I feel a bit stupid asking this question because should be a very simple thing...
In my app init, I have:
window.myApp =
Models: {}
Collections: {}
Views: {}
Routers: {}
initialize: ->
window.router = new Backbone.Router
Backbone.history.start(pushState: true)
And then in my View, when I click a button, I do:
router.navigate('/profile')
I have tried also with
router.navigate('/profile', true)
Now...I know that this isn't the best way of use backbone routes, but I need that my rails app manage the routes and I use backbone routes only for have routes history...
If I do window.href = '/profile' it redirects correctly.
What I'm doing wrong with backbone routes ? I've used this way time ago, but I don't find out why now it doesn't work anymore.
EDIT: The new url is always correctly show in the navigation bar, but it doesn't redirect to the new page...only show the new url in the navigation bar.
Backbone router.navigate doesn't redirect
No, it doesn't. That's not what router.navigate even does. It's has nothing to do with redirecting, it's for updating the address bar.
The new url is always correctly show in the navigation bar, but it doesn't redirect to the new page...only show the new url in the navigation bar.
Yes, this is exactly what router.navigate is documented to do. That is its express purpose. You use it to update the URL to reflect the current state of the page, not to redirect the page to something else.
If you want to redirect to a new page, you're supposed to use window.location.href = '/profile'.
If you want to update the address bar and then trigger a fresh round of Backbone routing, use router.navigate('/profile', { trigger: true });, but again, that's not how Backbone is meant to work.
We had an issue with this, and found out that sometimes even window.location.href does not work.
We were lucky to find this post Backbone: Refresh the same route path for twice and we were actually only able to make it work using a combination of the presented solution :
Navigate to an inexistent route : Backbone.history.navigate('xpto');
Then use: window.location.href = location;

Backbone/Marionette: why shouldn't I trigger route handler on navigate?

I'm reading David Sulc's A gentle introduction to Maionette, and came across the following:
It’s important to note that the route-handling code should get
fired only when a user enters the application by a URL, not each time the
URL changes. Put another way, once a user is within our Marionette app,
the route-handling shouldn’t be executed again, even when the user
navigates around;
What's the problem with triggering a handler on navigate?
There is no difference IF you aren't already in your Marionette app. So say we are first getting into our Marionette app and we want it to initially route to the posts index page. Initially we can either
call navigate({trigger: true) or
call navigate (to update the URL) and then call App.vent to trigger the call.
Both of them will resolve in our controller's API.list function and behave exactly the same way (fetch our list of posts and then display it). So calling trigger: true when initially entering your app/routing to the first page is totally fine. I think David just tries to make it a practice to not do so to re-enforce the power of Marionette's pub/sub infastructure since with it you don't need to pass trigger: true.
However, let's say we're now in the list view displaying a list of posts. We've already spent the time of fetching our list of posts from the server when initially entering our app. Now we click on a post and want to view the show view of that post. The post already exists in memory so we can just do a App.vent.trigger "post:clicked", post to use the post already in memory to display it. If we were to instead utilize the navigate({trigger: true}) route instead we'd end up on the same page but we would have to re-fetch the individual post instead using the one already in memory.
So the main reason is because you don't need to - triggering the page would cause a reload, re-fetch, etc. It would make your app feel slow and kind of defeat the purpose of a responsive web app/single page application.
Here's what your router should look like - you always want it setup so that you can just navigate to the page via a App.vent call when inside your app AND able to handle the manual browser refresh/navigating to the route directly (which is what the trigger would do, but this is the slow load that you'd kind of expect when initially fetching resources/entering the application. When in your app you want it to be the fast responsive piece that the pub/sub infrastructure affords).
#SampleApp.module "PostsApp", (PostsApp, App, Backbone, Marionette, $, _) ->
class PostsApp.Router extends Marionette.AppRouter
appRoutes:
"" : "list"
":id" : "show"
API =
list: ->
new PostsApp.List.Controller
show: (id, post) ->
new PostsApp.Show.Controller
id: id
post: post
App.vent.on "posts:list:clicked", ->
App.navigate "/"
API.list()
App.vent.on "post:clicked", (post) ->
App.navigate "/" + post.id
API.show post.id, post
App.addInitializer ->
new PostsApp.Router
controller: API
Then to navigate there you'd just call App.vent.trigger "posts:list:clicked" from wherever you want (like after clicking a "View all posts" button and bubbling the event up to the controller and active on that event).
#listenTo bannerView, "posts:list:button:clicked", (args) ->
model = args.model
App.vent.trigger "posts:list:clicked"
EDIT:
In the controller handling the show call to avoid the re-fetch:
#SampleApp.module "PostsApp.Show", (Show, App, Backbone, Marionette, $, _) ->
class Show.Controller extends App.Controllers.Application
initialize: (options) ->
{ post, id } = options
post or= App.request "post:entity", id
App.execute "when:fetched", post, =>
#layout = #getLayoutView()
#listenTo #layout, "show", =>
#panelRegion post
#postRegion post
#bannerRegion post
#show #layout

History.js getState() at pageload

I'm a little confused about how History.js works at page-load. I've done a few experiments but the results seem indeterministic.
My website is a search engine and the query is stored in the URL parameters: ?Query=cats. The site is written purely in javascript. History.js works great when I do a new search, the new query is updated, and the state is pushed.
My problem is how to create an initial state if the user manually enters in a URL including a Query parameter. Every way I try to do this ends up resulting in running the search query twice in some case. The two use-cases that seem to conflict are:
User manually enters URL (mydomain.com?Query=cats) into address bar and hits enter.
User navigates to an external page, and then clicks the back button
In both cases, the javascript loads, and therefore looks to the URL parameters to generate an initial state.
However, in the second case, History.js will trigger the statechange event as well.
Necessary code:
History.Adapter.bind(window,'statechange',function() { // Note: We are using statechange instead of popstate
var s = History.getState();
if(s.data["Query"]){
executeQuery(s.data);
}
});
and in $(document).ready I have
// Get history from URL
s = getQueryObjectFromUrl(location.href);
if(s["Query"]){
History.pushState(s,'',$.param(s))
}
Is there a better way to handle creating an initial state from URL parameters?
As I had a similar problem to to yours, what i did was to define the function bound to a statechange as a named function, and then all I had it running when the page load as well.
It worked better than trying to parse the URI or anything else, hope it helps.
This is the way I chose to do it (based on Fabiano's response) to store the initial state parameters
var renderHistory = function () {
var State = History.getState(), data = State.data;
if (data.rendered) {
//Your render page methods using data.renderData
} else {
History.replaceState({ rendered: true, renderData: yourInitData}, "Title You Want", null);
}
};
History.Adapter.bind(window, 'statechange', renderHistory);
History.Adapter.onDomLoad(renderHistory);
Of course if you are using a different on DOM load like jquery's you can just place renderHistory(); inside of it, but this way doesn't require any additional libraries. It causes a state change only once and it replaces the empty initial state with one containing data. In this way if you use ajax to get the initData inside the else, and it will not need to get it the next time the person returns to the page, and you can always set rendered to false to go back to initial page state / refresh content.

Categories

Resources