Return to Homepage / Simulate Navigation Click After Page Refresh

GOAL - Send user to homepage on hard / soft reset
If the user is on a data entry screen or a "detail" page in my app, editing data; and the user "soft refreshes" or "hard refreshes" the browser - this resets global variables rendering the page they were on pretty useless (the page relies on global variables to track IDs of what it is they are currently editing, a refresh kills these).

Within a module / app, I would like the ability to simulate a click on the "Navigation" component on page load in order to put the user back to the homepage. I've tried "utils.openUrl" as a script in the module after the DB Query that populates the navigation tree has completed, which works when you launch the app but not if you hard/soft refresh, or duplicate the tab, or open in new tab etc...

I cannot find a way to simulate a click of the Navigation component after the component has loaded in its navigation structure from a DB - there is no instruction anywhere saying there is JS Code to (for example) simulate a click of item 2 in the navigation tree), which I can put in the module code. There is also no "After Load" or "After Refresh" event in the Navigation component which I can use to trigger any code either.

** To repeat the scenario **
Create a Nav component with a few items, and place in a module, and place the module in an app. As part of the module code, create a JS function with a utils.openUrl command which runs on page load. Launch the app, and it works, refresh or hard refresh then you'll see in the debug log that the functions runs without failing, but doesn't actually make the page change / URL redirect.

Also, look at the Event Handlers for the Navigation component. You should notice there is only a handler for "Click" - and nothing for "After Load" or "After Populate" etc... and no command available to simulate a click of a particular indexed item in the navigation tree.

** Apologies **
I know it's wordy, but I find myself often picking up steam when using this and then suddenly hitting a brick wall with something that seems should be fairly intuitive / simple to achieve.

In an ideal world we'd have access to "Session" information / variables, we've these variables "independent" per browser tab.

Many thanks in advance!

Hey @xl_MURDOCH_lx!

I'm curious, when an end-user refreshes a page, do you not want the data they have entered thus far to adhere so that if they click a nav link to redirect to another app, that those values are updated?

Are you wanting those edits to not adhere if they decide to redirect to another page?

One intuitive solution I've used multiple times is assigning a column to the editable data row w/ a boolean data type with a false value by default called something like IS_DRAFT -- where that column will only flip to 'true' if the end-user clicks submit on the form.

Then, to ensure that all updates by the end-user adhere, I assign an update function set up through the Retool GUI that updates the value of a field on blur, which will update an input field when you click outside of it after typing some value.

Below you can see how I have my form set up to update fields after I type a value into them and then click somewhere else i.e. on another input or anywhere else on screen:


Attach your update event handler to each form input:

Assign each input to update on blur like the highlighted example for the F_NAME column in my table:




Does this potentially solve the issue you are currently facing?

Then in your table that contains all your data, you can add a filter via a switch where if the value of the switch is 'true' -- to include/append drafts to your list of rows in our table data with a simple transformer as your table's data source like so:

tableData transformer:

const data = {{ formatDataAsArray(yourQuery.data) }};
const includingDrafts = {{ switchComponent.value }};

if (includingDrafts) {
  // If the switch component is currently set to true...return all data
  return data;
} else {
  // Otherwise, the switch component is currently set to false, so only show rows where `IS_DRAFT = false`...
  return data.filter(row => row.IS_DRAFT = false);
}

Then just set the data source for your table to the name of your transformer. In this case, we would set the data source for our table as {{ tableData.value }}.

Hopefully this is along the lines of what you were looking for!

Hi,

I'm afraid this won't work for what I'm trying to achieve, I think perhaps I've not communicated it properly.

What I'm trying to achieve is to allow the app to be open on multiple tabs, with a user editing a different account / customer on each tab.

In order to do this, I would need to store a "tabID" against each tab for audit trailing purposes. Usually I access a server-side "SessionID" for this. However, ReTool does not allow this. I have tried generating a fake SessionID (tabID) variable on successful load of a side-navigation module in the app. However, storing this variable so it is consistent across the app is problematic...

  1. Storing as a global variable: These are not shared app-wide and are stuck inside the module they were generated in. Scope is module-wide not app-wide.

  2. localStorage: Again seems to be localised to the module it was generated in. Scope is module-wide, not app-wide.

  3. URL Params: Can only be set upon navigating from one page to another. As such the only way to use this is to return the user to the homepage upon screen refresh / browse in new tab / duplicate in new tab, where the tabID is not important. Then, when the users navigates away from the page, the tabID can be set as a hash in the URL.

So essentially, the only way I can see this is to navigate the user to the home page upon hard/soft refresh, open in new tab/browser, duplicate in new tab.

How can I do this please? I want to check along the lines of "if tabID is null, it is a new instance of the app, push user to homepage"

It seems like it should be simple, but I'm pulling my hair out.

What might help - is that I may be doing something wrong here.

if(str_TAB_ID.value===null){
  str_TAB_ID.setValue(uuid.v4());
}

localStorage.setValue({
  key: 'tab_id',
  newValue: str_TAB_ID.value
});

utils.showNotification({
  title: 'tab_id',
  description: localStorage.values.tab_id,
  notificationType: 'info',
  duration: 10,
});

When I look in the Debug tool, under "state" I see that tab_id = null. Is there any reason why my code isn't setting the local storage?

I've managed to sort out the "Save to Local Storage" code by dropping the ".value" function. It is now...

localStorage.setValue({
  key: "tab_id",
  newValue: str_TAB_ID
});

However, this has created more issues than it solves. When I change this value, it changes it in all open tabs. Therefore if I've got the app open in multiple tabs, and set this value - the value gets replaces for all open tabs, not just the Session in the currently open tab. As such, if I'm editing the details of different customers (for example) in 4 different tabs, and using the localStorage to hold the CustomerID, saving the details of a customer on one tab overwrites the customer details open on another tab.

Is there any way of scoping local storage to the current open browser window / tab? Either that, or can we access a "SessionID" (presuming a new server session is created for each open tab) so that we can store localStorage per session?

1 Like

Hey @xl_MURDOCH_lx thank you for the additional context! I think I am better understanding your issue...would this be able to be resolved using the js window.sessionStorage() method?

Link:
MDN documentation on session storage vs. local storage

Could you use the browser’s built‑in sessionStorage instead of localStorage? As session storage is automatically isolated per tab/window?

In a Retool JS query you can do something along the lines of:

const existing = window.sessionStorage.getItem('tab_id');
if (!existing) {
  const newId = uuid.v4();
  window.sessionStorage.setItem('tab_id', newId);
  utils.openUrl({ url: '/home' });
}

To run code on every page load you could wire that snippet or something similar into a JS query set to run on page load, hook it into your main DB‐query’s “Success” event handler, or have it run as a JS script from the window when the page loads from the app settings options?

That way, on a refresh or new tab, your sessionStorage check should fire and do the redirect.

As a fallback if Retool sandbox won’t let you call window.sessionStorage(), in a worst case—use a hidden custom component whose inline does the same check?

Fantastic idea, thanks @AJVancattenburch ! I'll let you know how I get on. Not sure if I've ever managed to get "window." to work though. Let me see! I'm quite excited now :slight_smile:

1 Like

"Failed to read the 'sessionStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."

Nooooooooooooo!!! :joy: :sob:

I was soooooo hoping we were onto a winner there.

"As a fallback if Retool sandbox won’t let you call window.sessionStorage(), in a worst case—use a hidden custom component whose inline does the same check?"

We've now gone beyond my JS abilities. "use a hidden custom component whose inline does the same check" may as well be Japanese. Is there any way you guys could expose the " window.sessionStorage" functionality to the sandbox?

1 Like

@xl_MURDOCH_lx man I wish I had that access! Unfortunately, I am not employed by Retool, I work at a company that utilizes their enterprise plan to build out applications.

So window really doesn't let you hit literally any targets? If you type {{ window. }}, you get no intellisense on any properties whatsoever? Not even something like {{ window.viewport }} auto populates when stepping into window?

Also I'm wondering where this existing ID is originally obtained from to have the capacity to edit and why something like retaining the ID of the selected row or item as a url parameter wouldn't work?

And that if one is not present when an end-user is on an edit or details page that would typically have an ID provided as a url parameter where one does not exist, that you could conditionally fire a query on page load that if there is no value for your url parameter, to send the end-user back to the home page?

Oh! I thought you worked at Retool. Well, it's very kind of you to try to help me.

Yes, unfortunately "{{window." comes up with absolutely nothing. No properties, objects, methods, nothing.

I've tried everything to do with URL parameter passing. It's just that very first screen after opening in new tab. I have to generate a new unique "tab_id" for that tab and then keep it alive via URL. However, Retool doesn't populate the new ID in the new tab URL until after the page loads meaning it's a bit useless on that first load of the new tab (as nothing can read it). It's only when you then post the page or change page that the URL parameter is picked up. This was why I was hoping to auto-redirect to the home page upon opening a new tab, as that page doesn't need the parameter in the url.

Does that make any sense?

1 Like

This is a really interesting problem, @xl_MURDOCH_lx. I've read through your back and forth with @AJVancattenburch and think I understand what you're wanting to do here.

In a simple recreation of the scenario that you're describing, running utils.openUrl from within a module query seems to run as expected even when refreshing the page. In theory, this should be just as effective as a method for triggering a navigation button.

Can you maybe share a JSON export of your app with hard coded query results? Doing so would help figure out why the same setup doesn't work for you.

1 Like

Hi @Darren If I upload the JSON App here, does it only go to you or does the whole community suddenly have access to it?

Also, do you know if my request to expose the localSession object beyond to the sandbox has gotten closer to fruition? I know it was added to the roadmap, I just don't know how far down the road it was placed!

Your JSON would be accessible to anybody if you upload here in the thread, so I recommend sharing it via DM instead. :+1:

And while we are tracking this request internally, I don't anticipate implementing access to sessionStorage anytime soon. It's not desirable from a security perspective and there's just not a very high volume of similar requests coming in. For what it's worth, though, I believe this is currently possible on a self-hosted instance that is configured with the correct environment variables.

What if you manually generated a unique identifier for each tab and used that as the key inside of localStorage? That would effectively scope your stored data, I imagine.