1

Having read a load of posts related to what I want to do, I'm pretty certain it is impossible but I'll ask the question anyway in the hopes that someone might have solved this problem before or can point me in the right direction to a solution.

I have a complex PHP/MySQL web application where I wish to keep browser tabs/windows independent. e.g. the user can run a search on the database in one tab and another search in another tab and various parameters set up and stored as the searches are conducted are linked only to the relevant browser tab. Reloading a tab or moving further through the search (a paging system limits the number of results stored) does not interfere with the other search tab and uses the parameters identified with the current tab only.

My idea is to store search and other parameters to do with the browser tab in a database table keyed by some unique identifier tied to the tab.

I cannot use PHP sessions as these are common across the tabs. PHP, being server-side, does not know from which tab/window a request comes from. Cookies, like sessions, are common across tabs.

The solution for keeping track of different tabs/windows appears to be javascript's sessionStorage which at first sight seems ideal – I can set a js session vaiable that is unique to the browser tab and echo it back to the PHP script and, in each tab, I can set and keep an independent value. Yet, I need to assign that js session value to a PHP variable and this needs to be available before the search part of the PHP script runs. There is no interaction on the part of the user (i.e. no form submit etc.) other than loading a URL.

I've come across solutions suggesting AJAX/JQuery but these all require a form and some type of user interaction after the PHP script has run and most examples I've seen write to the innerHtml of a div. None of this accomplishes what I want.

Lately, I've been toying with the idea of parsing each page of my application through a gateway URL that simply loads a javascript script that creates the js session (if not already created) and then redirects to the actual PHP script with the unique identifier as part of the querystring. But this seems to involve unnecessary overhead.

Any suggestions or even solutions would be gratefully accepted.

–––>

In response to ADyson (as the comment box is too short):

The application is here: https://testdrive.wikindx.com/

It's for academics/research students to manage references. They all work in different ways including some liking to open multiple tabs to do different searches and there's the problem. Each search, for example, produces results that are displayed back on the web page but also other information such as the search parameters (for reference). The paging system (i.e. displaying e.g. 10 results/page) is designed so that going to the next page utilizes a quicker and more efficient SQL call – all the heavy work being done on the initial search. Search parameters and summary results/totals are done on this first call and these must be saved somewhere as well as the core SQL subqueries. Basically, there are data that are common across different pages of the search that must be stored somewhere and that are unique to that search in that browser tab.

  • 2
    Why not simply using GET values? Unique per page, no weird storage issues, can be shared with others... – Martijn Nov 03 '20 at 10:26
  • Perhaps something like this: https://stackoverflow.com/questions/11896160/any-way-to-identify-browser-tab-in-javascript, then pass that in as parameter to bind tabs to individual 'sessions'. – Progrock Nov 03 '20 at 10:48
  • I'm aware of javascript's sessionStorage() and have suggested using it. The issue is getting the unique value back to PHP. – Mark Grimshaw-Aagaard Nov 03 '20 at 11:26
  • What you haven't really explained in all this is why you want to store search parameters in the session to begin with. Web applications are stateless by nature. Doing it in that way would cause the behaviour you're looking for as a natural consequence of the implementation. Each request to the search service should just contain all the parameters needed to carry out that particular search. – ADyson Nov 03 '20 at 12:25
  • 1
    I don't get your problem. I got you right so far: You *POST* the query parameters to create a new search result ressource with the prefetched SQL etc. and you respond accordingly with *CREATED* and the new ressources unique ressource identifier... What is with those tabs now? Why would you need a unique uri per Tab when you have one per query? And what do you want to store in JS? – derM - not here for BOT dreams Nov 03 '20 at 17:39
  • The only thing to store in the JS is a unique identifier key to a database table where search parameters and basic SQL are stored. Each tab might have a different search thus different parameters/SQL. A simple example: having done a search, a menu item is created that allows the user to go back to the last search (if they've done other things in the meantime). This is currently done by storing the last search in a PHP session. That works fine for one tab. Another tab does a different search but the last search menu item in both tabs will now refer to just the one tab. This I don't want. – Mark Grimshaw-Aagaard Nov 04 '20 at 08:35
  • As far as I can see though you could just put the unique identifier on the querystring instead. Then it would be unique, and the same search could be recalled even after the tab has closed. And it wouldn't rely on any kind of sessions, either server- or client-side. That would be truly stateless, truly RESTful, truly web-oriented design. – ADyson Nov 04 '20 at 08:58
  • That's my solution. However, javascript is still required to check if the ID is unique to the current tab/window. What if the user opens a link (with ID in the querystring) in a new tab/window? Without comparing the querystring ID to the tab's session (if yet set), then you end up with two tabs using the same ID and so the aim is not met. I have come up with a solution that seems to work well and with minimal overhead. In all tests so far, I get what I want. I will post my solution as an answer shortly. Thanks for the help and advice. – Mark Grimshaw-Aagaard Nov 05 '20 at 05:07
  • _" javascript is still required to check if the ID is unique to the current tab/window"_ . Why? If you're loading each search based on its ID, then the tabs are independent of each other. Even if someone opens two tabs with the same ID, they're still independent of each other - the settings for that search are simply loaded twice. (That's not very efficient of the user, but it's also not a big problem.). If the user updates the search parameters in one of the tabs, obviously you just save those under a new ID, because it's a new search. – ADyson Nov 05 '20 at 09:12
  • I'm not just dealing with forms so cannot use hidden fields to store a uuid then pass that around . PHP has no idea how many tabs I'm using or from which tab a request came – that information is only available in javascript. – Mark Grimshaw-Aagaard Nov 05 '20 at 09:27
  • Ok but none of that has any relevance to the point I just made. (And even if you're "not just dealing with forms", whatever that specifically means, it doesn't prevent you from rendering an ID somewhere else on the page where it's required. But if you recall, I suggested to make it part of the URL, so it wouldn't need to be in any kind of form or variable. And if you follow my suggestion, neither PHP or JavaScript will _need_ to know which tab things came from - that's the entire thing I'm getting at. I really feel you're over-engineering this.) – ADyson Nov 05 '20 at 09:42
  • If the ID is part of the URL, and thus part of a link that the user can, for example, click on to go back to a previous search after other operations (the application stores the last search precisely for this), then if the user opens that link in a new tab or window (right click etc.), the new tab gets the ID from the old tab. Thus the 'under-engineered' solution breaks down (I've tried it and it is so). In my system, even if the link is opened in a new tab, the javascript compares the ID to that in sessionStorage (null for a new tab) and thus redirects with a new unique ID – what I need. – Mark Grimshaw-Aagaard Nov 06 '20 at 11:52
  • "the new tab gets the ID from the old tab"...why is that a problem? Each search is unique. If they run it again, so what? If they update it, it should get a new ID, so it's still unique (I wonder if your version was missing this step out or something). Anyway you seem to have found an approach you're happy with so I'll stop arguing about it, but I think you may have misunderstood what I'm trying to describe. – ADyson Nov 06 '20 at 12:09
  • The issue is that, to save processing all parts of a search SQL are compiled the first time the search is done but only parts of the search are compiled subsequently. i.e. it's a 'partial' search after the main search. Say we get 1000 results but don't want to display all 1000 on the page – we page the results by grouping them in say 25 results/web page. It's far more efficient to do the 1000 search first then to break it up into blocks of 25 for the subsequent pages. This means, though, that some data from the initial search are stored to be used on the subsequent pages. . . . – Mark Grimshaw-Aagaard Nov 06 '20 at 13:08
  • . . . If this data is stored in a session, then multiple tabs cannot be used unless each of those data are ID'd by the particular search. So far so good. But, as mentioned, as soon as a user opens a link with an ID in its querystring into a new tab or window, the system breaks down. The only solution that works is to ensure the ID is unique not to the search but to the browser tab/window and that's where javascript and sessionStorage comes in. I don't know how else to explain it . . . ;) The answer I've provided below, does what is required with minimal overhead. – Mark Grimshaw-Aagaard Nov 06 '20 at 13:11
  • "as soon as a user opens a link with an ID in its querystring into a new tab or window, the system breaks down"...why, though? What actual specific problem does that cause? You still haven't explained why it's a problem. You keep saying it's an issue, but never explain why. I would expect in that situation, the server would simply load the stored data for that search into the new tab. (you can even make it go to a specific page, if you include a 2nd parameter to specify that.) Hard to see what would break due to simply loading some data. – ADyson Nov 06 '20 at 13:57
  • Thanks for your comments ADyson – they have helped to clarify my thinking and produce my solution. However, as you suggest earlier, I think we are talking at cross purposes and perhaps don't understand each other. I don't know how to explain more clearly what it is I want and why what you suggest is not the solution to that (although it might well be the solution elsewhere). For those looking for a way to uniquely identify a browser tab/window, I think the use of javascript's sessionStorage is the way to go and my answer below shows how to get a unique ID back to PHP as a variable. – Mark Grimshaw-Aagaard Nov 06 '20 at 16:47
  • Sure. But my point is I don't think you need to be able to identify tabs. You still haven't explained what problem occurs when the same search ID is opened in more than one tab. Is there a reason you can't/won't explain that fully? – ADyson Nov 06 '20 at 17:13
  • Perfectly happy to explain. But I think I have already in the comment(s) starting 'The issue is that . . .' It's about doing the full SQL upfront in the initial search then saving info. from that and the core, time-intensive SQL query results for the paging making the subsequent paging far faster. A unique ID in the querystring is fine and I use it. The problem is opening a link with that ID in the querystring in a new tab – the ID is then no longer unique and I cannot keep searches separate. My solution below does the job perfectly. – Mark Grimshaw-Aagaard Nov 07 '20 at 13:33
  • "The problem is opening a link with that ID in the querystring in a new tab – the ID is then no longer unique"...but that's not true. The search is still unique. The results are still unique. They're just displayed in two tabs at once. What does "cannot keep searches separate" actually mean? That isn't, by itself, a bug. We're not getting to the root of the issue. – ADyson Nov 07 '20 at 23:13
  • I'd envisage a solution like this: User visits search.php with no parameters, they get a blank search form. They submit a search. The server does the query and gets the results, and caches them, as you've described. It then saves a database row recording which parameters were searched for, and a reference to the location of the cached results. That record gets an ID. The results are returned to the user. The user can page through the results if they wish. – ADyson Nov 07 '20 at 23:15
  • If the user changes the search parameters and submits another search, that clearly will generate new results, and they are cached again and a new ID issued. If the user closes the browser, they can come back to their previous search by going to e.g. `search.php?id=123`. You can give them a list of all their previous searches to pick from, if you want. None of that would prevent them from opening the same cached search in two different tabs. But if they did do that for some reason, it wouldn't cause any kind of problem. Maybe your implementation differs from that in some significant way? – ADyson Nov 07 '20 at 23:17

2 Answers2

2

Something needs to be unique about the URL of the tabs. The internet is stateless and so the browser doesn't know after a refresh whether it's tab one or tab two unless the url tells it so.

If you had an identifier in the tab url, such as a number or hash, you could then use that to save and return specific information from the session:

$_SESSION['tabs'][0] etc

So launching new tabs / searches would require you to either track and increment the tab ID for the user:

$tabID = sizeof($_SESSION['tabs']); or use some random hash generator.

You've mentioned using ajax which is also a possible option. With this you could store the identifier in the hash of the url #tab-1 and otherwise use the same url. You'd then need to use javascript to grab the identifier from the tab's hash and send a request including that via ajax to ask the server (PHP) to generate you the specific search results / page which matches it and you can then use to populate the page with.

Harry B
  • 2,864
  • 1
  • 24
  • 44
  • I don't think PHP knows if a URL is being called from the same tab as one before or another one so how can a unique ID be generated in PHP and then kept throughout the use of any one tab? The solution must be javascript passed somehow to PHP. I'm tending to use of sessionStorage() in javascript via a gateway URL. This URL: 1. tests for existence of a session value – if FALSE, create one, if TRUE read value 2. redirect to new URL with requested parameters and session value new URL uses session value to access database for tab parameters. Can anyone see any holes in this method? – Mark Grimshaw-Aagaard Nov 03 '20 at 11:19
  • The application I'm referring to can be tested here: https://testdrive.wikindx.com/index.php All links (dropdown menus, icons etc.) will need the unique identifier. javascript's sessionStorage has the great advantage that it is tied to specific browser tabs and windows. I als think it is widespread enough now to use with confidence. – Mark Grimshaw-Aagaard Nov 03 '20 at 11:24
  • Your session knowns how many tabs have previously been created so can increment that number. You could also use a hash of sufficient length that it will be unique. The unique identifier for links etc would always be available in `$_GET` or by parsing the url when generating the page. – Harry B Nov 06 '20 at 10:04
  • 'Your session knowns how many tabs have previously been created' – can you elaborate please? As far as I'm aware, PHP sessions are common across the browser not to the tab/window only. Even if the PHP session were capable fo this, how would such a system deal with the user opening a link (with the ID in the querystring) in a new tab? – Mark Grimshaw-Aagaard Nov 06 '20 at 11:57
  • Take another look at my answer and let me know if you have a specific question about using something like a 'tabs' array in session to store a set of data. – Harry B Nov 06 '20 at 12:14
  • Yes. How does PHP know you have opened a new tab? If there is no unique ID in the URL you want to open, OK. But if you already have the unique ID in the URL and you right-click, CTRL click to open in a new tab then the problem arises. Javascript sessionStorage deals with this because it can compare the querystring ID to that stored in sessionStorage (unique to the tab/window) and so, if they don't match, recognize a new tab has been opened with an ID from another tab and so return a new ID. – Mark Grimshaw-Aagaard Nov 07 '20 at 13:22
  • You could do something like: '/tab/new' as a url which triggers a php script. The php checks the session to see how many tabs already exist and increments that number OR generates a unique hash THEN redirects the user to that new url. IF the user opened the same url in two tabs they should see the same details because php can use the unique hash OR id in the tab's url to find the details of the search in the user's session. – Harry B Nov 10 '20 at 10:11
0

Here's my solution. It uses javascript to check for the existence of a unique ID for the tab/window and sessionStorage to store that unique ID. I haven't yet tested that my application still works if a user has an old browser that does not use sessionStorage – this currently works in Firefox, Safari, and Chrome.

The business end of my index.php. The javascript is the second thing run once some basic environment configuration has been done (which is where WIKINDX_URL_BASE etc. comes from):

/**
  * Generate/return the unique browser tab/window identifier
  */
$script = WIKINDX_URL_BASE . '/gatekeeper.js?ver=' . WIKINDX_PUBLIC_VERSION;
$qs = $_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : FALSE;
$url = WIKINDX_URL_BASE . '/index.php' . $qs;
if (!array_key_exists('browserTabID', $_GET)) {
// go into gatekeeper for a redirect and addition of a browserTabID to the querystring
print <<< END
<script src="$script"></script>
<script>redirectSet("$url", "$qs")</script>
END;
}
else {
// go into gatekeeper to check if browserTabID is unique to the tab (perhaps user has `opened link with browserTabID in a new tab/window)`
$id = $_GET['browserTabID'];
print <<< END
<script src="$script"></script>
<script>getBrowserTabID("$url", "$qs", "$id")</script>
END;
}

And my gateway.js:

// gatekeeper.js
/**
 * No browserTabID yet set so generate one and redirect
 */
function redirectSet(url, qs)
{
    var browserTabID;
    if ((sessionStorage.getItem('browserTabID') == null) || !sessionStorage.getItem('browserTabID')) {
        browserTabID = uuidv4();
        sessionStorage.setItem('browserTabID', browserTabID);
    } else { // This shouldn't be necessary but is here for completeness. Perhaps of use in the future . . .
        browserTabID = sessionStorage.getItem('browserTabID');
    }
    if (qs) {
        url = url + '&browserTabID=' + browserTabID;
    } else {
        url = url + '?browserTabID=' + browserTabID;
    }
    window.location.href = url;
}

/**
 * browserTabID in the URL. 
 * If the same as the session, URL is opened in existing tab/window so return doing nothing.
 * If not the same as the session (session is null probably), URL is opened in a new tab/window so generate browserTabID and redirect
 */
function getBrowserTabID(url, qs, browserTabID)
{
    if (browserTabID == sessionStorage.getItem('browserTabID')) { // Continuing in same tab/window
        return;
    }
// User has opened a link in a new tab/window – generate a new ID
    browserTabID = uuidv4();
    sessionStorage.setItem('browserTabID', browserTabID);
    if (qs) {
        url = url + '&browserTabID=' + browserTabID;
    } else {
        url = url + '?browserTabID=' + browserTabID;
    }
    window.location.href = url;
}

/**
 * Code from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid
*/
function uuidv4() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

After the code has run in index.php, I then have access to $_GET['browserTabID'] which I can then use to access a database table row.

Mark