mrjayviper
mrjayviper

Reputation: 2388

weird issue with pushState/popstate - initial page seems to have been "pushed" twice

I'm having a weird issue with pushState/popstate where it seems my initial page is saved twice.

It's a bit hard to explain without an example.

something like this:

  1. current page is www.bing.com
  2. copy/paste my URL into URL bar and page loads
  3. click on a link on the page (this link fires an AJAX request which then manipulates the DOM to display the data retrieved.)
  4. press back button which brings me to the same page as in step2
  5. press back button again but I'm still on the the same page as step2/4
  6. press back button again and this bring me back to www.bing.com

As you can see, I need to press the back button twice to get me back to www.bing.com when it should have only needed 1.

a short explanation of my page (code is at the bottom of the page):

  1. when the page loads, initial list for the dropdown selection is retrieved via AJAX and populates the select tag.

  2. user has option to click on 3 links which determine the contents of the dropdown selection. Example: If I click on Option B link, this fires the same AJAX from step1 but only retrieves data suitable for Option B.

  3. The user makes a selection from the dropdown and presses search. This triggers another AJAX function which retrieves the relevant data and displays the formatted information inside the mysearchresults div.

  4. I tried playing around with placement (e.g. before or after AJAX call) of pushState and the placement now seems the one that give me the least problem. Although I do encounter the issue I mentioned above.

Here's my code. I had to strip out a lot of code/functions to make it short and readable but please be assured the AJAX calls/data display are working.

<div id="content">
    <p>
        <a class="coursetype" href="#" data-type="B">Option B</a>
        | <a class="coursetype" href="#" data-type="H">Option H</a>
        | <a class="coursetype" href="#" data-type="BG">Option BG</a>
    </p>

    <form id="myform">
        <label class="formlabel" for="course">Select your course</label>
        <br /><select id="course" name="course"></select>
        <button id="searchbutton" type="button">Search Me</button>
    </form>

    <div id="mysearchresults"></div>
</div>

$(document).ready(function() {
    var onClickCourseTypeHandler = function(event) {
        event.preventDefault();

        getCourses({ type:$(event.target).data("type") });

        window.history.pushState({ html:$("#content").html(), coursecode:$("#coursecode").val() }, "", main?type="+pagevars.type);
    }

    var onClicksearchbuttonHandler = function(event) {
        if ($("#coursecode").val().length > 0) {
            searchDB({ course:$("#course").val() });

            window.history.pushState({ html:$("#content").html(), coursecode:$("#coursecode").val() }, "", "/main?&course="+$("#course").val());
        }
    };

    $(document).on("click", ".coursetype", onClickCourseTypeHandler);

    $(document).on("click", "#searchbutton", onClicksearchbuttonHandler);

    try {
        window.addEventListener("popstate", function(event) {
            if (event.state) {
                $("#content").html(event.state.html);

                if (event.state.coursecode.length > 0) {
                    if ($("#course option[value='" + event.state.course + "']").length > 0) {
                        $("#course").val(event.state.course);
                    } else {
                        $("#course option:first-child").attr("selected", "selected");
                    }
                }
            }
        });
    } catch(exception) {
    }

    onLoad();

    function onLoad() {
        getCourses({ type:"" });

        window.history.pushState({ html:$("#content").html(), course:dbvars.course }, "", "/main");
    }

    function searchDB(parameters) {
        $.ajax({
            url: baseurl+"/searchDB"
            , cache: false
            , dataType: "json"
            , data: { format:"json", course:parameters.course }
            , success: function(data, status) {
                parameters.data = data;
                displaySearchResults(parameters);
            }
        });
    }

    function getCourses(parameters) {
        if (typeof(parameters.cacheflag) === "undefined") { parameters.cacheflag = false; };

        $.ajax({
            url: baseurl+"/getCourses"
            , cache: parameters.cacheflag
            , dataType: "json"
            , data: { format:"json", coursetype:parameters.type }
            , success: function(data, status) {
                parameters.data = data;
                populateCourses(parameters);
            }
        });
    }
});

Upvotes: 8

Views: 5888

Answers (4)

ATrainBrain
ATrainBrain

Reputation: 1

I found this worked great for a single page application

I decided to use this handy little function which allows pushing an item into the history if its empty or only if it has not been recalled from pressing the back button

(eliminates duplicates, so you dont have to hit the back button twice)

function historyPush(siteLocation){
    if (!history.state || history.state.sitelocation != siteLocation){
      var stateObj = { sitelocation: siteLocation };
      history.pushState(stateObj, (siteLocation + " page"), ("index.html"));
    }
}

Use: historyPush("Page1")

I then used switch case in the popstate listener function to load content back into the page:

window.addEventListener('popstate', function(e) {
        console.log(e.state.sitelocation);
        if (e.state.sitelocation){
            switch (e.state.sitelocation){
                case "Page1":
                    pageOne.loadContent();
                    break;
                case "Page2":
                    pageTwo.loadContent();
                    break;
                default:
                    console.log('Unknown Page');

            }
        }
});

It's not perfect for every situation such as if your site is 300 different cases you may want to head another route with the popstate listener. But for small sites, it gets the job done nicely.

Upvotes: 0

mantish
mantish

Reputation: 6678

When you load your page, it gets added to the History, so when you do history.pushState it's added a second time. Try replacing history.pushState with history.replaceState in the onload function.

Upvotes: 8

Tim Vermaelen
Tim Vermaelen

Reputation: 7059

Pass an object literal to pushState() on page load. This way you can always go back to your first created pushState.

Add to DOM ready

var obj = { html: '', coursecode: '' };

try {
    window.history.pushState({
        html: $("#content").html(),
        coursecode: $("#coursecode").val()
    }, 'just content or variable', window.location.href);
} catch (e) {
    console.log(e);
}

In your eventhandler to push state:

var onClicksearchbuttonHandler = function(event) {
    if ($("#coursecode").val().length > 0) {
        searchDB({ course:$("#course").val() });

        obj.html = $("#content").html();
        obj.coursecode = $("#coursecode").val();
        window.history.pushState(obj, "", "/main?&course="+$("#course").val());
    }
};

When going back to your original pushstate:

$(window).on('popstate', function (ev) {
    var originalState = ev.originalEvent.state || obj.html;
    if (!originalState) {
        // no history
        // do something
        return;
    } else {
        // do something
    }
});

Upvotes: 2

mbejda
mbejda

Reputation: 1471

I noticed your using hashes (#) in your hrefs. Try changing the hashes (#) in your a hrefs to Javascript:void(0).


    Javascript:void(0);

Hashes(#) in href will have an effect your pushState/popState.

Upvotes: 4

Related Questions