Search code examples
javascriptjqueryhistory.js

Change global variable using History.js


I've been trying to implement History.js. I've got some understanding of how getting and pushing states work, however I'm having particular trouble with the data storing component of the history along with using global variables.

As a simple example, I decided to try and set up a script which would change the colour of a html box upon being clicked. This would also trigger the history - essentially creating a history for clicking the box (and its colour being changed on each state of the history).

Is there any way to update a global variable based on the data (in this case, updating i per click) supplied in History State's data?

HTML:

<div id="box">CLICK ME</div>
<button id="back">Back</button>
<button id="forward">Forward</button>

CSS:

#box {
    width: 300px;
    height: 300px;
    background-color: black;
    color: white;
    vertical-align: middle;
    text-align: center;
    display: table-cell;
}

JavaScript:

var History = window.History;
var i = 0;

if (History.enabled) {
    var State = History.getState();
    History.pushState({count:i}, $("title").text(), State.urlPath);
} else {
    return false;
}

// Bind to StateChange Event
History.Adapter.bind(window,'statechange', function(){
    State = History.getState();
    console.log(State.data, State.title, State.url);
    $(this).css('background-color', getColour());
});

// Trigger the change
$("#div").on("click", function() {
    i++;
    History.pushState({count:i},"State " + i,"?state=" + i);
});

function getColour() {
    var colours = ["red", "orange", "yellow", "green", "aqua","blue", "purple", "magenta","black"];
    if (i > colours.length - 1) {
        i = 0;
    }
    if (i < 0) {
        i = colours.length - 1;
    }
    return colours[i];
}

$("#back").on("click", function() {
    History.back();
});

$("#forward").on("click", function() {
    History.forward();
});

I'm also using JQuery, ajaxify-html5.js and scrollto.js as per recommendation by other threads.

Editable JSFiddle | Viewable JSFiddle


Solution

  • After playing around with this a ton (and reading more questions), I've figured it out. I'll detail what the solution means to any others who come across this.

    JSFIDDLE VIEW SOLUTION | JSFIDDLE VIEW SOLUTION

    First here's the final code. Note that the JavaScript has document.ready extras to get it working outside of JSFiddle.

    It's also worth noting I took out ajaxify-html5.js and scrollto.js out, as they weren't needed (and were breaking the code somewhere).

    HTML:

    <div id="box">
        <div id="count"></div>
        <div id="colour"></div>
    </div>
    <button id="back">Back</button>
    <button id="forward">Forward</button>
    

    CSS:

    #box {
        width: 300px;
        height: 300px;
        background-color: white;
        color: white;
        vertical-align: middle;
        text-align: center;
        display: table-cell;
    }
    button {
        width: 148px;
        height: 40px;
    }
    
    #count, #colour {
        background-color: black;
        font-family: "Consolas";
    }
    

    JavaScript:

    var History = window.History;
    var i = 0;
    var colour = getColour();
    var colourName = getColourName();
    
    $(document).ready(function() {
    
        if (History.enabled) {
            changeHistory();
        }
        else {
            return false;
        }
    
        // Bind to StateChange Event
        History.Adapter.bind(window,'statechange', function(){  
            State = History.getState();
            i = State.data.count;
            colour = State.data.colour;
            colourName = State.data.colourName;
            changeHistory();
        });
    
        // Trigger the change
        $(document).on("click", "#box", function() {
            i = i + 1;
            colour = getColour();
            colourName = getColourName();
            changeHistory();
        });
    
        $(document).on("click", "#back", function() {
            History.back();
        });
    
        $(document).on("click", "#forward", function() {
            History.forward();
        });
    
    
    });
    
    function getColour() {
        var colours = ["rgb(220,45,45)", "orange", "rgb(230,230,50)", "rgb(15,210,80)", "rgb(100,220,220)","rgb(50,80,210)", "rgb(140,20,180)", "rgb(230,70,110)","grey"];
        if (i > colours.length - 1) {
            i = 0;
        }
        if (i < 0) {
            i = colours.length - 1;
        }
        return colours[i];
    }
    
    function getColourName() {
        var colourNames = ["Red","Orange","Yellow","Green","Light Blue","Blue","Purle","Pink","Grey"];
        return colourNames[i];
    }
    
    // Make the changes
    function changeHistory () {
        $("#colour").html(colourName);
        $("#count").html(i);
        $("#box").css('background-color', colour);
        History.pushState({count:i, colour: colour, colourName: colourName},"A Shade of " + colourName,"?colour=" + colourName);
    }
    

    So going back to what I wanted to achieve with the question:

    • Clicking the box would add history
    • Each history would hold variables required to affect global variables

    Its worth noting the solution specifically uses variables from each iteration of the history to power the global variables, whereas the program itself uses the global variables. The variables used to power the interface never access the ones stored in history.

    Let's break up the program into separate and simpler processes and functions. Much like other history.js solutions, there's things you require to get it working:

    • History.getState(): Gets the latest history item "from the stack"
    • History.Adapter.bind(window,'statechange', function() {}: An event listener which will trigger a function when the window has a statechange (history change in this case)
    • History.pushState({data}, title, url): Pushes a state into the "history stack". Holds an object (data), title (of the tab/window) and url to display.

    Setting the history:

    When a user clicks on the box, the program should:

    • increment the counter (i)
    • change the colour and colourName
    • add the new history stack object in

    I decided to separate the first two features from the last one. The function changeHistory() is responsible for updating the contents of the box (from global variables) and pushing a new history object in (using global variables).

    changeHistory() gets called whenever I want to add a new item of history in and update the contents in the box to reflect the new history - so at launch at when the box is clicked.

    When the box is clicked, the first two criteria get met. Using the existing global variables and functions, the new colour and name are retrieved and set as the global variables.

    This is how it should behave:

    Box Click -> Increment i, Change variables -> Push History

    Listen for the history change:

    Once a history change has been made (either by clicking the box, pressing back/forward buttons or browser buttons), a change needs to occur.

    By creating the variable State = History.getState(), we have an easy way of accessing the data from the latest history stack object.

    We'll use this data from the history object.data to assign to the global variables. After updating the variables, we'll update the view using changeHistory().

    This is how the model should work:

    History changed -> Update globals from history -> Update View

    History change will occur whenever someone presses back, forwards or the box, accounting for all possible changes.