Skip to content

Instantly share code, notes, and snippets.

@adamduncan
Created December 10, 2015 16:36
Show Gist options
  • Save adamduncan/15659b15182e36a8aec5 to your computer and use it in GitHub Desktop.
Save adamduncan/15659b15182e36a8aec5 to your computer and use it in GitHub Desktop.
//----------------------------------------------------------------------
// HISTORY API
// Base Package
//----------------------------------------------------------------------
var HistoryAPI = {
contentId: 'content',
content: document.getElementById('content'),
animateClass: 'history-content--animate-out',
rootLoadingClass: 'html--page-loading',
animationDuration: 300,
init: function () {
// exit early if no support
if (!Modernizr.history) return;
// loop over local links, and pass callback to bind clicks
this.localLinksLoop(this.bindClickEvent);
// bind popstate event
this.bindPopstate();
},
bindPopstate: function () {
// set timeout (? check Dive into HTML5 as to why this is needed)
window.setTimeout(function () {
// add event listener event to window
window.addEventListener("popstate", function (e) {
// don't pop content for hashchanges - may need removing hash improvements
if (!window.location.hash) {
// use same loadEvents method as pushState, passing new pop'd href
HistoryAPI.loadEvents(window.location.href);
}
}, false);
}, 1);
},
localLinksLoop: function (callback) {
// store all link elements and localhost value
var allLinks = document.querySelectorAll('a'),
localHost = window.location.host;
// loop over all links on page
for (var i = 0; i < allLinks.length; i++) {
var thisLink = allLinks[i];
// if internal link and not hashed and target isn't _blank
if (thisLink.host === localHost && !thisLink.hash && thisLink.target !== '_blank') {
// run callback to, passing link
callback(thisLink);
}
}
},
bindClickEvent: function (link) {
// add click event listener
link.addEventListener('click', function (e) {
// store link href and normalized trailing slash version (no querystring, no slash, no extension)
var linkHref = link.href,
hasQuery = (linkHref.indexOf('?') > -1),
hasSlash = linkHref.substr(-1) === '/',
hasExtension = linkHref.split('/')[linkHref.split('/').length - 1].indexOf('.') > -1,
slashedHref = (!hasQuery && !hasSlash && !hasExtension) ? linkHref + '/' : linkHref;
// prevent default for all
e.preventDefault();
// if current page, do nothing further
if (window.location.href === linkHref) {
return;
// otherwise call to get content and push state
} else {
// call to get content, passing callback
HistoryAPI.loadEvents(slashedHref);
// call to push history
HistoryAPI.historyPush(slashedHref);
};
}, false);
},
loadEvents: function (href) {
// call to run unload state
HistoryAPI.pageUnload();
// call to get content, passing href and callback
HistoryAPI.getContent(href, HistoryAPI.pageLoad);
},
getContent: function (href, callback) {
// construct new request
var request = new XMLHttpRequest();
// open request, passing method, url and async boolean
request.open('GET', href, true);
// set up ready event handler
request.onreadystatechange = function () {
// test response codes
if (this.readyState === 4) {
if (this.status >= 200 && this.status < 400) {
// success
// run callback, passing response
callback(this.responseText);
} else {
// error handling TBD
console.log('Error: ' + this.statusText);
// potentially force-send user to target page?
window.location.href = href;
}
}
};
// send the request and clear
request.send();
request = null;
},
pageUnload: function () {
// add class to root for any loading-related styles
Constants.root.classList.add(HistoryAPI.rootLoadingClass);
// add class to animate content out with CSS3
HistoryAPI.content.classList.add(HistoryAPI.animateClass);
},
pageLoad: function (response) {
// set timeout to allow animation delay play out
var animateTimeout = setTimeout(function () {
// create element to parse response as html
var newElement = document.createElement('div');
// populate new element
newElement.innerHTML = response;
// update content in page
HistoryAPI.content.innerHTML = newElement.querySelector('#content').innerHTML;
// remove class to root for any loading-related styles
Constants.root.classList.remove(HistoryAPI.rootLoadingClass);
// remove animation classes to show
HistoryAPI.content.classList.remove(HistoryAPI.animateClass);
// loop over local links, and pass callback to bind clicks
HistoryAPI.localLinksLoop(HistoryAPI.bindClickEvent);
}, HistoryAPI.animationDuration);
},
historyPush: function (link) {
// push state via history api
history.pushState(null, null, link);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment