Rails: Turbolinks not clearing cache - javascript

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

Related

Nextjs creating a url that only processes code with no view

I am creating a new site using NextJS, the issue i am having is in regards to a password reset verification endpoint.
When a user triggers a password reset, it goes to the API, does all the processing and then returns them to the NextJS frontend at /verifyreset, which saves a code into localstorage, does a small bit of processing and then forwards them onto another page.
The issue is that there is a Default layout wrapping the component in my _app.js like so;
function MyApp({ Component, pageProps }) {
return (
<DefaultLayout><Component {...pageProps} /></DefaultLayout>
);
}
which means that the layout shows on the /verifyreset endpoint, and I only want that endpoint to process data.
Is there any way around this? to have an endpoint that can access localstorage but not be a 'page' so to speak
I did partially understand your question, it would have been clear if you had attached more code snippets in the question.
Anyway, from your statement below:
When a user triggers a password reset, it goes to the API, does all
the processing and then returns them to the NextJS frontend at
/verifyreset, which saves a code into localstorage, does a small bit
of processing and then forwards them onto another page.
what I understood is:
User triggers a password reset [lets say from PageA]
API is invoked; some processing happen
API then, redirects user to /verifyreset page [lets say it PageB]
Navigating to the page, information is saved into localstorage
Once that is completed, user is redirected to another page [lets say it PageC]
Correct me if I am wrong, so your question is, how could you actually skip users to view /verifyreset page but do the things like save to localstorage and other background operations.
Answer 1: The api is being invoked from PageA (see 1). Instead of the api redirecting user to /verifyreset page on the frontend, send some data (JSON or XML) to the calling function (in PageA components..). Based on that data, do the processing and once every thing is complete, redirect the user to PageC. [no need to worry about PageB i.e. /verifyreset page]. Please find the code snippet below:
**API End Point**
async resetPassword(req, res) {
try {
const model = model.body || {};
let data = await PasswordBusiness.reset(model);
//data needs to have information that you require on frontend
return res.json({success: true, data: data});
} catch (error) {
return res.json({success: false, error: error});
}
}
** Frontend - pageA **
import Router from 'next/router';
const resetPassword = (model) => {
callApiEndPoint(model).then(data) {
// do what you want to do with data
//finally navigate to page c
Router.push('url-to-page-c');
});
};
return <button onClick={resetPassword}> Reset </button>
Answer 2: If you require redirecting to the page any how from the API [I think you don't necessary require this], once operation/processing is completed on API end, redirect the user directly to the pageC with some query params with data (if they are not security vulnerable data). e.g. /pagec?token=sometokens&otherinfos=otherinfos and do things on pageC itself. Once completed, remove the query string from the page without refreshing the page.
You have to put /verifyreset at the api folder.
This is what Next.js said in their documentation :
Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page.
Reference : https://nextjs.org/docs/api-routes/introduction

Updating a web page to reflect deleted data from CRUD app without hard refresh

I have a to-do app that functions - I can create an item, check off that it's been completed, and delete the item using an X button next to it. When I create an item, it pops up immediately on the list. But when I delete an item, I have to manually refresh the page for it to update. What am I missing that would get it to update in real time?
index.html:
const deletebtns = document.getElementsByClassName('deletebutton');
for (let i = 0; i < deletebtns.length; i++) {
const deletebutton = deletebtns[i];
deletebutton.onclick = function(e) {
const todoId = e.target.dataset['id'];
fetch('/todos/' + todoId, {
method: 'DELETE',
});
}
}
app.py:
#app.route('/todos/<todo_id>', methods=['DELETE'])
def delete_todo(todo_id):
try:
Todo.query.filter_by(id=todo_id).delete()
db.session.commit()
except:
db.session.rollback()
finally:
db.session.close()
return jsonify({ 'success': True })
location.reload ();
Should do it.
If you call this method from anywhere in the web page, the web page will reload. This reload will occur through the web cache. What is a cache exactly? It’s a temporary space your browser reserves to store documents, images and other data that it retrieves from a server. Caching of data allows a browser to speed up your browsing and lets you reload often-visited sites faster.
You can change the default cache reload by setting a forceGet parameter. This will cause the browser to reload the webpage by fetching the data from the server anew instead of using the cache. This can be accomplished by using the following code:
location.reload(true);
By default, the value of the forceGet parameter is false. This means that the location.reload method always looks like this:
location.reload(false)

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

Backbone fetch failure notification

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

Facebook Connect - Single Sign On Causes Infinite Loop :(

I have a Facebook Connect (FBML) web application that has been live for a while now.
All is working well, however user's are reporting issues with the single sign on (and i have seen the issue on their computer, but have not been able to replicate locally).
With FBC, there is a "event" you can hook into to automatically determine if the user is authenticated to Facebook.
That is achieved via this:
FB.Connect.ifUserConnected(onUserConnected, null);
The onUserConnected function can then do whatever it needs to attempt a single sign on.
For me, what i do is a AJAX call to the server to see if the user has an active website account (based on the Facebook details - user id, which i store in my system when they connect).
If they do, i show a jQuery modal dialog ("Connecting with Facebook") and do a window.location.reload().
The server then kicks in, and does the Single Sign On. This code is executed on every page:
public static void SingleSignOn(string redirectUrl)
{
if (!HttpContext.Current.Request.IsAuthenticated) // If User is not logged in
{
// Does this user have an Account?
if (ActiveFacebookUser != null)
{
// Get the user.
var saUser = tblUserInfo.GetUserByUserId(ActiveFacebookUser.IdUserInfo);
if (saUser != null)
{
// Get the Membership user.
MembershipUser membershipUser = Membership.GetUser(saUser.UserID);
if (membershipUser != null && membershipUser.IsApproved)
{
// Log Them Automically.
FormsAuthentication.SetAuthCookie(membershipUser.UserName, true);
// At this point, the Forms Authentication Cookie is set in the HTTP Response Stream.
// But the current HTTP Context (IsAuthenticated) will read HTTP Request Cookies (which wont have the new cookie set).
// Therefore we need to terminate the execution of this current HTTP Request and refresh the page to reload the cookies.
HttpContext.Current.Response.Redirect(
!string.IsNullOrEmpty(redirectUrl) ? redirectUrl : HttpContext.Current.Request.RawUrl,
true);
}
else
{
HandleUnregisteredFacebookUser();
}
}
else
{
HandleUnregisteredFacebookUser();
}
}
else
{
HandleUnregisteredFacebookUser();
}
}
}
I have never experienced an issue with this, but user's are reporting an "infinite" loop where the dialog gets shown, the window is refreshed, dialog is shown, window is refreshed, etc.
Basically, my AJAX call is saying the user exists, but my single sign on isn't.
Which is hard to believe because the code is very similar:
This is the AJAX Web Service:
if (!HttpContext.Current.Request.IsAuthenticated) // If user is not yet authenticated
{
if (FacebookConnect.Authentication.IsConnected) // If user is authenticated to Facebook
{
long fbId;
Int64.TryParse(Authentication.UserId, out fbId);
if (fbId > 0)
{
tblFacebook activeUser = tblFacebook.Load(facebookUniqueId: fbId);
if (activeUser != null && activeUser.IsActive) // If user has an active account
{
return true;
}
}
}
}
So if the response of this WS is 'true', i do a window.location.reload();
So i have no idea what the issue is (as i cant replicate), sounds like the Single Sign On isn't adding the Forms Authentication cookie properly to the response stream, or the Response.Redirect(Request.RawUrl) isn't reloading the cookie properly.
How do other's handle this?
This is what should happen:
User logs into Facebook (on Facebook itself)
User comes to my site (which has been previously authorised)
My site compares the FBID with the FBID in my DB, and signs them in.
My site is an ASP.NET 4.0 Web Forms application, using the "old" Facebook Connect JavaScript API (FeatureLoader.js), and Forms Authentication.
The only other solution to an AJAX call/window.reload i can think of is an AJAX UpdatePanel.
Can anyone help me out?
EDIT
Also, i realise that i can also use 'reloadIfSessionStateChanged':true to do the reload (which stops the infinite loop), but the problem with this is i cannot show my nice fancy dialog.
So i found a couple of issues.
Firstly, i shouldn't be setting a persistent cookie:
FormsAuthentication.SetAuthCookie(membershipUser.UserName, true);
We should only do this when the user ticks a box such as "Remember Me".
Secondly, i was checking the FB Auth status on every page on the server, so this could have been getting out of sync in the client-side.
Here is my new solution - which is better, and has a failsafe for the dreaded 'infinite loop'.
I no longer check the FB Auth status on the server, i do on the client:
FB.Connect.ifUserConnected(onUserConnected, null); // runs on every page request, on client-side
In the onUserConnection function i do the following:
Call web service to see if user CAN be signed in automatically (same WS as above)
If ok, check a special "Single Sign On" cookie has not been set.
If it hasn't been set, redirect to FacebookSingleSignOn.aspx.
FacebookSingleSignOn.aspx does the following:
Signs the user in using Forms Authentication (same as above)
Creates a special "Single Sign On" cookie to signify a SSO has been attempted
Redirects to the homepage
So, at point 3 above - this is where the infinite loop "could" happen. (as the onUserConnected will run again)
But it is now impossible (i hope) for it to happen, as it will only attempt to do the SSO if they havent already tried.
I clear the SSO cookie on the Facebook Logout action.
Now works well, logic makes sense - and it's done on the client-side (where it should be, as this is where FB does it's magic).
Hope that helps someone else out.

Categories

Resources