This tutorial is
going to be split into 4 sections. The
goal of this tutorial is to create a simple web site that has a top bar with a
static Height, a left tool bar with a static width, and a variable
"display area" which will take up the rest of the space.
This tutorial will
start where the last tutorial finished which added some ajaxy goodness using
jQuery load. Now it's time to use the
HTML 5 history tool.
I also made a video of this tutorial check it out at
http://youtu.be/tcX1MfxwoBs
The other tutorials can be found here
1st http://www.whiteboardcoder.com/2014/04/jquery-site-setup-positioning-1-of-4.html
2nd http://www.whiteboardcoder.com/2014/05/jquery-site-setup-positioning-2-of-4.html
3rd http://www.whiteboardcoder.com/2014/05/jquery-site-setup-positioning-3-of-4.html
One problem with the
web app at this point is its history, there is none!
If you click the
back button you are taken to the last site you were at. This can be very problematic. Lucky for us in HTML 5 there is a very nice
history API we can use for Browser
history management.
I am not going to go
into the specifics a great deal here, but rather show a practical example.
For more details
check out these sites.
Install History.js
First install
History.js https://github.com/browserstate/history.js/ [1]
History.js provides
cross-browser support for html5 browser history management and also provides
tools for handling browser history for html 4 browsers. (I will not be going over the html 4 browser
history in this tutorial)
Create a directory and download the History.js for jQuery (these links may change in the future if so
just head to the main github site and search for download)
> mkdir js/history
> cd js/history
> wget https://github.com/browserstate/history.js/zipball/master
> unzip master
> mv
browserstate-history.js-cce9589/scripts/uncompressed/history.js .
> mv mv
browserstate-history.js-cce9589/scripts/uncompressed/history.adapter.jquery.js
.
> rm -r
browserstate-history.js-cce9589/
> rm master
> cd ../..
|
Edit the index.html
to add the new javascripts
> vi index.html
|
Update it to the
following
<html>
<head>
<script type="text/javascript">
document.write("<base
href='http://"
+
document.location.host + "/004/'
/>");
</script>
<link rel="stylesheet" href="css/main.css" type="text/css"/>
</head>
<body>
<div id="nav">
<div id="nav-top">
<h1>This is the Top Area</h1>
</div>
<div id="nav-left-outer">
<div id="nav-left">
<img id="nav-1" src="img/handshake-128.png" class="nav-left"/>
<img id="nav-2" src="img/plus-128.png" class="nav-left"/>
<img id="nav-3" src="img/youtube-2-128.png" class="nav-left"/>
</div>
</div>
<div id="nav-display-area-outer">
<div id="nav-display-area">
<h1>Resizeable Display Area</h1>
</div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="js/underscore.js"></script>
<script src="js/history/history.js"></script>
<script src="js/history/history.adapter.jquery.js"></script>
<script src="js/history.js"></script>
<script src="js/main.js"></script>
<script src="js/animate.js"></script>
<script src="js/ajax-load.js"></script>
</body>
</html>
|
The first script is
just there to set the base directory, this simplifies things later when you are
using relative paths in the history (you could use absolute paths and not worry
about this, but my site is located at .com/004 so I chose this method)
<script type="text/javascript">
document.write("<base href='http://"
+
document.location.host + "/004/'
/>");
</script>
|
Now add your own
history.js file
> vi js/history.js
|
And place the
following in it
(function (window, undefined)
{
History.Adapter.bind($(this), 'statechange', function(){
alert("A History State has
changed")
})
})(this)
|
… What does this
do? The statechange event will fire
whenever a
pushState
popState
replaceState
event occurs.
You could have bound
a function to each event but I think it makes more sense just to use the single
event listener for all of them.
Edit ajax-load.js
> vi js/ajax-load.js
|
And place the
following in it
(function (window, undefined)
{
$(function () {
$("#nav-1").click(function () {
$("#nav-display-area").load("shaking-hands")
})
$("#nav-2").click(function () {
window.History.pushState(
null,
null,
"add-tool/adder/")
$('<link/>', {
rel: 'stylesheet',
type: 'text/css',
href: 'css/add.css'
}).appendTo('head')
$("#nav-display-area").load("tools/add")
$.getScript("js/add.js")
})
$("#nav-3").click(function () {
$("#nav-display-area").load("youtube/")
})
})
})(this)
|
Reload the page and
click on the + icon
The URL will change
and the alert will fire off.
What is going on
here?
The pushState
function is pushing the URL http://demo.whiteboardcoder.com/004/add-tool/adder onto
the history stack.
window.History.pushState(
null,
null,
"add-tool/adder/")
|
After the push
occurs an event is fired which is picked up by the listener bound to
'statechange'
Whose function has a
simple alert message
Now… because of some
unintended side effects the div is not updated like it should be. (Basically since we changed the URL the
relative locations of the files are in the wrong location)
Now if you click the
back button it works…. Kinda….
You will not be
taken out of the site, with a single click, but nothing really changes either….
In this simple case
all that really occurred was you added a URL to the history that did not update
the page. I could add 100 URLs like this
and all it would create is the need to hit the back button 100 times to get out
of the site.
To get this point
across a little clearer some code changes are needed.
Just URLs
Edit history.js
> vi js/history.js
|
And place the
following in it
(function (window, undefined)
{
History.Adapter.bind($(window), 'statechange',
function () {
var State = History.getState()
alert("The current URL on the history stack is \n'"
+ State.url
+ "'")
})
})(this)
|
Edit ajax-load.js
> vi js/ajax-load.js
|
And place the
following in it
(function (window, undefined)
{
$(function () {
$("#nav-1").click(function () {
window.History.pushState(null, null, "shake/")
})
$("#nav-2").click(function () {
window.History.pushState(null, null, "add-tool/adder/")
})
$("#nav-3").click(function () {
window.History.pushState(null, null, "youtube/")
})
})
})(this)
|
Reload the page and
click on the icon buttons
Now the URL updates
in the browser and you get an alert showing what the current URL is.
No part of the page
updates but there is now a history you can go back and forth through using the
forward and back button.
Great! So I have a
history that is correct but the page does not update…. What is the use of that?
You need to make use
of the data field in the function
Create and use the State data Object
The State data Object
you can pass to pushState can help solve this.
You can store some
json data in the State data Object that can be used during the event listener.
Edit ajax-load.js
> vi js/ajax-load.js
|
And place the
following in it
(function(window, undefined)
{
$(function () {
$("#nav-1").click(function() {
var data = {
elemId: "#nav-display-area",
url: "shaking-hands"
}
window.History.pushState(data,
null, "shake/")
})
$("#nav-2").click(function() {
var data = {
elemId: "#nav-display-area",
url: "tools/add"
}
window.History.pushState(data,
null, "add-tool/adder/")
})
$("#nav-3").click(function() {
var data = {
elemId: "#nav-display-area",
url: "youtube/view"
}
window.History.pushState(data,
null, "youtube/")
})
})
})(this)
|
Edit history.js
> vi js/history.js
|
And place the
following in it
(function (window, undefined)
{
History.Adapter.bind($(window),
'statechange', function () {
var State = History.getState()
if (State.data.hasOwnProperty("elemId")
&&
State.data.hasOwnProperty("url")) {
$(State.data.elemId).load(State.data.url)
}
})
})(this)
|
Reload the page and
click on some of the icon buttons.
Now the web app is
updating like it should and the URL history is being added.
Since the
instructions for loading the ajaxy data is in the State Object when the forward
or back button is clicked the 'statechange' event is triggered and the page is
updated correctly. Try clicking the
buttons then clicking on the forward and back button a few times.
Loading CSS and javascript dynamically
The + tool has its
own CSS and javascript that need to be loaded dynamically this too can be
handled with the 'statechange' event.
Edit ajax-load.js
> vi js/ajax-load.js
|
And place the
following in it
(function(window, undefined)
{
$(function () {
$("#nav-1").click(function() {
var data = {
elemId: "#nav-display-area",
url: "shaking-hands"
}
window.History.pushState(data,
null, "shake/")
})
$("#nav-2").click(function() {
var data = {
elemId: "#nav-display-area",
url: "tools/add",
css: "css/add.css",
script: "js/add.js"
}
window.History.pushState(data,
null, "add-tool/adder/")
})
$("#nav-3").click(function() {
var data = {
elemId: "#nav-display-area",
url: "youtube/view"
}
window.History.pushState(data,
null, "youtube/")
})
})
})(this)
|
Edit history.js
> vi js/history.js
|
And place the
following in it
(function (window, undefined)
{
History.Adapter.bind($(window),
'statechange', function () {
var State = History.getState()
if (State.data.hasOwnProperty("css")) {
$('<link/>', {
rel: 'stylesheet',
type: 'text/css',
href: State.data.css
}).appendTo('head')
}
if (State.data.hasOwnProperty("elemId")
&&
State.data.hasOwnProperty("url")) {
$(State.data.elemId).load(State.data.url)
}
if (State.data.hasOwnProperty("script")) {
$.getScript(State.data.script)
}
})
})(this)
|
Reload the page and
click on some of the icon buttons.
Now the JavaScript
and CSS are loaded when you click the + button.
One last problem… the URLs
The last part to be
dealt with is the actual URLs themselves.
If you click on the + button the URL will be updated to http://demo.whiteboardcoder.com/004/add-tool/adder/ in my
example. If you reload this page or try
to open the location in another window.
You will get the 404
error since the actual URL does not exist.
The URL needs to be
legitimate and to open the web app to the correct tool….
I will attempt to
fix this with some static pages, just to show it can be done.
> mkdir shake
> mkdir add-tool
> mkdir youtube
> ln -s `pwd`/index.html
shake/index.html
> ln -s `pwd`/index.html youtube/
> ln -s `pwd`/index.html
add-tool/adder/
|
Reload the page
click on the + button then reload the page
Well at least there
is no 404 error, but reloading the page sets you back to square one, when it
should open the tool again.
To fix that some we
need some more JavaScript Code.
Edit main.js
> vi js/main.js
|
And place the
following in it
(function (window, undefined)
{
$(window).load(function () {
handleURL()
setSizes()
})
$(window).resize(_.debounce(function () {
setSizes()
}, 250))
function setSizes() {
priorHeight = $("#nav-display-area").height()
priorWidth = $("#nav-display-area").width()
$("#nav-display-area").height(
$(window).height() -
$("#nav-top").height() - 10)
.width($(window).width() -
$("#nav-left-outer").width() - 10)
console.log("(" + $(window).width()
+ ",
"
+ $(window).height() + ")")
if (priorHeight > $("#nav-display-area").height()
|| priorWidth > $("#nav-display-area").width())
{
//Call setSizes again
setSizes()
}
}
function handleURL() {
switch
(location.pathname.toLowerCase()) {
case '/004/shake/':
loadPage("shake/",
{ url: "shaking-hands" }); break;
case '/004/add-tool/adder/':
loadPage("add-tool/adder/",
{ url: "tools/add", css: "css/add.css",
script: "js/add.js" }); break;
case '/004/youtube/':
loadPage("youtube/",
{ url: "youtube/view" }); break;
}
}
function loadPage(url,
data) {
//Add the elemId and a random element to assure
//it will
load (if the state Object is identical
//to one on top of the stack it won't replace it.
$.extend(data, { elemId: "#nav-display-area",
ran:
window.Math.random() })
window.History.replaceState(data, null, url)
}
})(this)
|
When loaded the
handleURL function will determine the current pathname and if it's one of the
tool URLS it will replace the current state on the history stack.
Reload the page and
try it and copy and paste a URL and try it.
That is the end of
this tutorial, hope you found it helpful.
References
[1] Pushing
and Popping with the History API
Mike Robinson
Accessed
03/2014
[2] HTML5
History: Clean URLs for Deep-linking Ajax Applications
Craig
Shoemaker
Accessed
03/2014
[3] Can I
use Session history management?
Accessed
03/2014
[4] History.js
github site
Accessed
03/2014
No comments:
Post a Comment