Need to click twice to go back in history (using pushState)
Asked Answered
R

2

28

I've got a page with some select-fields. Once the user changes the value of a select-field, a new history-object is created using the values of all select-fields as a state object:

$(".chosen-select-field").chosen().change(function() {
    $(".chosen-select-field").each( function ( index ) {
        parameterNames[index].value = $( this ).val();
    });
    history.pushState(parameterNames, '', newUrl);
});

parameterNames is an array of objects containing a key and value e.g.

parameterNames.push({key:"name", value:"foobar"});

The following code restores the state when the user clicks on the back or forward-button in the browser. It works but behaves unexpectedly.

For example, I change three select-fields (creating three history entries). Then I go back. restoreState is executed, one select-field is changed accordingly. But the browser itself remains at the same position in history (can't go forward, still the same number of back-history entries).

Then I click again on the back button. This time the state-object is the same as the one delivered the list time I clicked. The browser moves back one entry in the history though.

The third time I click on the back button the next select-field is changed but the browser stays again at the state unless I click a 4th time.

Can anyone explain what I'm doing wrong?

var restoreState = function(event) {
    event = event.originalEvent;
    parameterNames = event.state;
    $(".chosen-select-field").each( function ( index ) {
        if ($( this ).val() != parameterNames[index].value){
            $( this ).val(parameterNames[index].value).change();
            $( this ).trigger('chosen:updated');
        }
    });
};

// Bind history events
$(window).bind("popstate",restoreState);
Royston answered 19/5, 2014 at 13:44 Comment(2)
on popstate (e.g. back), you are calling "restoreState", which loops the fields, and potentially calls .change(), which does history.pushState again. I suggest doing replaceState if popstate event is fired.Withdrawn
#20157439Narceine
C
1

When the popstate event is fired and you loop through the select fields, if the condition $( this ).val() != parameterNames[index].value is satisfied, the current select value will be altered, but will also fire the change event, which, in turn, will call again the function that pushes a new state into the history. That way, if you go back in the history, you will get the same state-object.

Hence, the solution would be check if the popstate event was fired, and if so, do not call history.pushState, but, instead, history.replaceState.

function handleHistoryStates(popstateEventWasFired = false) {
    $(".chosen-select-field").each( function ( index ) {
        parameterNames[index].value = $( this ).val();
    });
    
    if (popstateEventWasFired) {
        history.replaceState(parameterNames, '', newUrl);
    } else {
        history.pushState(parameterNames, '', newUrl);
    }
}

$(".chosen-select-field").chosen().change(handleHistoryStates);


var restoreState = function(event) {
    event = event.originalEvent;
    parameterNames = event.state;
    $(".chosen-select-field").each( function ( index ) {
        if ($( this ).val() != parameterNames[index].value){
            $( this ).val(parameterNames[index].value);
            handleHistoryStates(true);
            $( this ).trigger('chosen:updated');
        }
    });
};

// Bind history events
$(window).bind("popstate",restoreState);
Clair answered 17/6, 2021 at 20:30 Comment(0)
F
1

I guess you are Pushing the current state when you reach the state,Instead of that you Push the previous state when u reach current state.. That why when u first press the back button your poping the current state,And u need to pop the previous state and go there ...

let boxes = Array.from(document.getElementsByClassName('box'));

function selectBox(id) {
  boxes.forEach(b => {
    b.classList.toggle('selected', b.id === id);
  });
}

boxes.forEach(b => {
  let id = b.id;
  b.addEventListener('click', e => {
    history.pushState({
      id
    }, `Selected: ${id}`, `./selected=${id}`)
    selectBox(id);
  });
});

window.addEventListener('popstate', e => {
  selectBox(e.state.id);
});

history.replaceState({
  id: null
}, 'Default state', './');
.boxes {
  display: flex;
}

.desc {
  width: 100%;
  display: flex;
  align-items: center;
  padding: 3rem 0;
  justify-content: center;
  font-weight: bold;
}

.box {
  --box-color: black;
  width: 50px;
  height: 50px;
  margin: 20px;
  box-sizing: border-box;
  display: block;
  border-radius: 2px;
  cursor: pointer;
  color: white;
  background-color: var(--box-color);
  border: 5px solid var(--box-color);
  font-size: 14px;
  font-family: sans-serif;
  font-weight: bold;
  text-align: center;
  line-height: 20px;
  transition: all 0.2s ease-out;
}

.box:hover {
  background-color: transparent;
  color: black;
}

.box.selected {
  transform: scale(1.2);
}

#box-1 {
  --box-color: red;
}

#box-2 {
  --box-color: green;
}

#box-3 {
  --box-color: blue;
}

#box-4 {
  --box-color: black;
}
<div class="boxes">
  <div class="box" id="box-1">one</div>
  <div class="box" id="box-2">two</div>
  <div class="box" id="box-3">three</div>
  <div class="box" id="box-4">four</div>
</div>
<div class="desc">
  Click to select Each box and u can navigate back using browser back button
</div>

Check this codepen : https://codepen.io/AmalNandan/pen/VwmrQJM?editors=1100

Fragrance answered 9/7, 2021 at 6:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.