204

I am creating a photo gallery, and would like to be able to change the query string and title when the photos are browsed.

The behavior I am looking for is often seen with some implementations of continuous/infinite page, where while you scroll down the query string keeps incrementing the page number (http://x.com?page=4) etc.. This should be simple in theory, but I would like something that is safe across major browsers.

I found this great post, and was trying to follow the example with window.history.pushstate, but that doesn't seem to be working for me. And I'm not sure if it is ideal because I don't really care about modifying the browser history.

I just want to be able to offer the ability to bookmark the currently viewed photo, without reloading the page every time the photo is changed.

Here is an example of infinite page that modifies query string: http://tumbledry.org/

UPDATE found this method:

window.location.href = window.location.href + '#abc';
Sonic Soul
  • 23,855
  • 37
  • 130
  • 196
  • Can you post a link to some example site that updates its query string dynamically? I don't think it can be done, but you *can* change the hash value and that might be enough to get what you want. – Pointy Jun 10 '12 at 15:50
  • possible duplicate of [how to manipulate the URL with javascript/jquery?](http://stackoverflow.com/questions/6601609/how-to-manipulate-the-url-with-javascript-jquery) – Quentin Jun 10 '12 at 15:52
  • possible duplicate of [How does GitHub change the URL but not the reload?](http://stackoverflow.com/questions/4973777/how-does-github-change-the-url-but-not-the-reload) – Quentin Jun 10 '12 at 15:53
  • possible duplicate of [Why the new web Dropbox can change the URL without page refresh?](http://stackoverflow.com/questions/9717801/why-the-new-web-dropbox-can-change-the-url-without-page-refresh) and the three questions it is marked as a duplicate of – Quentin Jun 10 '12 at 15:54
  • Note that `window.location.hash` gives you access to the hash directly, and that's supported pretty much everywhere. – Pointy Jun 10 '12 at 17:26

9 Answers9

296

If you are looking for Hash modification, your solution works ok. However, if you want to change the query, you can use the pushState, as you said. Here it is an example that might help you to implement it properly. I tested and it worked fine:

if (history.pushState) {
    var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?myNewUrlQuery=1';
    window.history.pushState({path:newurl},'',newurl);
}

It does not reload the page, but it only allows you to change the URL query. You would not be able to change the protocol or the host values. And of course that it requires modern browsers that can process HTML5 History API.

For more information:

http://diveintohtml5.info/history.html

https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history

Fabio Nolasco
  • 7,122
  • 6
  • 35
  • 32
  • 20
    I believe you can shorten `window.location.protocol + '//' + window.location.host` to just: `window.location.origin`. – Garrett Mar 20 '16 at 22:32
  • 9
    Also, for an addition bit of info, relative paths work as well with `history.pushState()`. No actual `state` argument is required either. Both of these mean you can do something simple like `history.pushState(null, '', '/foo?bar=true')` if your new url is on the same host/port. – Blaskovicz Nov 02 '16 at 19:38
  • 44
    Note that, if you don't want the new state to appear on the browser history, you can use `history.replaceState()` with the same arguments. – Blackus Nov 25 '16 at 14:06
  • 8
    USE history.replaceState() otherwise you need to click BACK button twice in your browser – Zhenya May 24 '17 at 10:49
  • It causes the infinite digest in AngularjJS. I don't know why and how to fix it. – mimic Jul 26 '17 at 18:53
  • I advise you to use only `window.location.host + "?foo='bar'`. If you are using `onpopstate` it will throw only `/some/pathname/` and not the whole domain name. If you use `window.location.protocol + "//" + window.location.host + window.location.pathname + '?myNewUrlQuery=1';` the return of `onpopstate` event will be something like `http://www.foo.cz/bar/` which is wrong and it's not expected to be like that. – Kuba Šimonovský Dec 19 '17 at 12:26
  • 11
    in modern browsers you can just do: ```if (window.history.pushState) { const newURL = new URL(window.location.href); newURL.search = '?myNewUrlQuery=1'; window.history.pushState({ path: newURL.href }, '', newURL.href); }``` – Allan Baptista Jan 21 '18 at 02:24
  • 2
    In Firefox, you are required to use a fully-qualified URL. Path only will generate a security error. – Neil Monroe Jan 23 '19 at 19:41
  • 1
    You should also add `window.location.hash` add the end of the string for `newurl`, otherwise you will have the query applied but you might overwrite an existing hash value. – Benny Code Aug 15 '19 at 15:40
62

I want to improve Fabio's answer and create a function which adds custom key to the URL string without reloading the page.

 function insertUrlParam(key, value) {
        if (history.pushState) {
            let searchParams = new URLSearchParams(window.location.search);
            searchParams.set(key, value);
            let newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + searchParams.toString();
            window.history.pushState({path: newurl}, '', newurl);
        }
    }

// to remove the specific key
export function removeUrlParameter(paramKey) {
  const url = window.location.href
  console.log("url", url)
  var r = new URL(url)
  r.searchParams.delete(paramKey)
  const newUrl = r.href
  console.log("r.href", newUrl)
  window.history.pushState({ path: newUrl }, '', newUrl)
}
JaydeepW
  • 3,237
  • 1
  • 25
  • 27
48

Old question, modern answer to help future devs; using the URL interface:

const url = new URL(window.location);
url.searchParams.set('key', value);
window.history.pushState(null, '', url.toString());

This makes sure you really only change the desired query-parameter.

bbunmp
  • 480
  • 4
  • 4
10

If we simply want to update the query parameter without touching other parts of URL, there is no need to build the URL again. This is what I use:

const addQueryParam = (key, value) => {
  const url = new URL(window.location.href);
  url.searchParams.set(key, value);
  window.history.pushState({}, '', url.toString());
};

const getQueryParam = (key) => {
  const url = new URL(window.location.href);
  return url.searchParams.get(key) || '';
};
Vaibhav Nigam
  • 1,334
  • 12
  • 21
8

Building off of Fabio's answer, I created two functions that will probably be useful for anyone stumbling upon this question. With these two functions, you can call insertParam() with a key and value as an argument. It will either add the URL parameter or, if a query param already exists with the same key, it will change that parameter to the new value:

//function to remove query params from a URL
function removeURLParameter(url, parameter) {
    //better to use l.search if you have a location/link object
    var urlparts= url.split('?');   
    if (urlparts.length>=2) {

        var prefix= encodeURIComponent(parameter)+'=';
        var pars= urlparts[1].split(/[&;]/g);

        //reverse iteration as may be destructive
        for (var i= pars.length; i-- > 0;) {    
            //idiom for string.startsWith
            if (pars[i].lastIndexOf(prefix, 0) !== -1) {  
                pars.splice(i, 1);
            }
        }

        url= urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : "");
        return url;
    } else {
        return url;
    }
}

//function to add/update query params
function insertParam(key, value) {
    if (history.pushState) {
        // var newurl = window.location.protocol + "//" + window.location.host + search.pathname + '?myNewUrlQuery=1';
        var currentUrlWithOutHash = window.location.origin + window.location.pathname + window.location.search;
        var hash = window.location.hash
        //remove any param for the same key
        var currentUrlWithOutHash = removeURLParameter(currentUrlWithOutHash, key);

        //figure out if we need to add the param with a ? or a &
        var queryStart;
        if(currentUrlWithOutHash.indexOf('?') !== -1){
            queryStart = '&';
        } else {
            queryStart = '?';
        }

        var newurl = currentUrlWithOutHash + queryStart + key + '=' + value + hash
        window.history.pushState({path:newurl},'',newurl);
    }
}
jmona789
  • 2,711
  • 6
  • 24
  • 57
7

I've used the following JavaScript library with great success:

https://github.com/balupton/jquery-history

It supports the HTML5 history API as well as a fallback method (using #) for older browsers.

This library is essentially a polyfill around `history.pushState'.

Ian Newson
  • 7,679
  • 2
  • 47
  • 80
  • awesome! did you test it with all the browsers?? – Sonic Soul Jun 10 '12 at 16:25
  • My implementation was tested in IE7+, Firefox 3.6+, Safari 5 and Chrome 16+. It probably works with other browsers too, but I haven't had any complaints in the several systems deployed using it. – Ian Newson Jun 10 '12 at 16:28
  • great. so right now.. simply putting a # instead of a & when writing to window.location.href works for me. in that it doesn't reload the page. i am sure it will break once i test it in IE.. at which point i'll go with the library you suggested. thanks – Sonic Soul Jun 10 '12 at 16:41
  • The # method is a good one in that it has very wide browser support. Things can get complicated when you need to include that information in requests to the server, as the # part of the URL isn't sent by the browser. There are ways round that, which includes the library I referenced. In addition, using the HTML5 history API helps make your URLs shorter overall and requires less client side work to restore the state. – Ian Newson Jun 10 '12 at 16:48
  • The # method is also problematic for social media sharing; Twitter, Facebook, and possibly others don't play well with anchor tags when creating previews or links back to the share. – mirth23 Oct 24 '18 at 04:55
5

Since everyone answering this seems to forget the hash, I want to add the code I'm using to keep all URL parameters:

const urlParams = new URLSearchParams(window.location.search);

/// Change some part of the URL params

if (history.pushState) {
  const newurl =
    window.location.protocol +
    "//" +
    window.location.host +
    window.location.pathname +
    "?" +
    urlParams.toString() +
    window.location.hash;
  window.history.replaceState({ path: newurl }, "", newurl);
} else {
  window.location.search = urlParams.toString();
}
yspreen
  • 1,759
  • 2
  • 20
  • 44
0

Then the history API is exactly what you are looking for. If you wish to support legacy browsers as well, then look for a library that falls back on manipulating the URL's hash tag if the browser doesn't provide the history API.

chucktator
  • 1,828
  • 12
  • 16
0

I thought I'd add a bit to Fabio and Aram's answers. I thought I might sometimes like to preserve the hash in the url. But usually not, so I set that parameter to default to false.

replaceState still does not set the page title on Chrome. So I added a couple lines to change the title, if one is provided.

function insertUrlParam(key, value, title = '', preserve_hash = false) {
    if (history.pushState) {
        let searchParams = new URLSearchParams(window.location.search);
        searchParams.set(key, value);
        let newurl = window.location.protocol + "//" + window.location.host + window.location.pathname
            + '?' + searchParams.toString();
        if(preserve_hash) newurl = newurl + window.location.hash;
        let oldTitle = document.title;
        if(title !== '') {
            window.history.replaceState({path: newurl}, title, newurl);
            if(document.title !== title) { // fallback if above doesn't work
                document.title = title;
            }
        } else { // in case browsers ever clear titles set with empty string
            window.history.replaceState({path: newurl}, oldTitle, newurl);
        }
    }
}
Buttle Butkus
  • 9,206
  • 13
  • 79
  • 120