Deila á Facebook
Deila á Twitter
Ingi Gauti
Ingi Gauti

Changing regular website to a pushState website

For the past six months I've been moving our website from the classic type website to more modern technology ajax type website using pushState instead reloading the page each time you click a link.

The trouble that comes up when changing the website to use pushState are different then if you had designed the site originally as a ajax site. You want to try to use as much the same content as you can possible do. These are few of the issue that I had to address.

Relative links

These are bad. Always link to files on your server from the root directory. Why is this? Lets say a user clicks a link, he goes from http://bland.is/ to http://bland.is/album/, when using pushState, you simply load the content that you need, the url in the addressbar changes but the links in the content that you just loaded thinks that they should be linking from http://bland.is/ instead http://bland.is/album/. So a link pointing to http://bland.is/album/new.htm that is setup like this <a href="new.htm"> will not work. To make it work, set it as <a href="/album/new.htm">

You may be able use some sort of javascript hack and change the base address each time you load.

Clicking links  

Going from the classic app to a more modern type, you have to decide how you want to load those pages and call pushState. The way I went was using jQuery and the delegate function on all click event on <a>. Here is the code

$(document).ready(function() { if (history.pushState) { $('body').delegate('a', 'click', function (e) { var that = $(this); var url = (typeof that.attr('href') != 'undefined') ? that.attr('href').replace('http://bland.is', '') : ''; if (allowPush(e, url, that)) { e.preventDefault(); history.pushState({load:true}, '', url); loadContent(lastObj, url); } }); } });

This is simply to set the click event on <a>. Then I have the allowPush function, there are simply put 14 reasons why you shouldn't use the pushState. Maybe more with time

 

function allowPush(e, url, that) { return (!e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && url != '' && url.indexOf('#')== -1 && url.indexOf('javascript') == -1 && url.indexOf('http://') == -1 && url.indexOf('https://') == -1 && (typeof that.attr('target') == 'undefined' || that.attr('target') == '')  && !that.hasClass('nobbq') && typeof $.data(that.get(0), 'events') == 'undefined' && isHtmlPage(url) && typeof disablePush == 'undefined') }
  • You need !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey to allow people to Ctrl/Cmd click a link to open in a new window
  • Don't do anything if there is an anchor, url.indexOf('#')==-1
  • Don't do anything if there is javascript: in the link, url.indexOf('javascript') == -1
  • Don't do anything if there is http in the link, this is an external site then, url.indexOf('http://') ==-1
  • Don't do anything if there is a target on the link, (typeof that.attr('target') == 'undefined' || that.attr('target') == '')
  • Don't do anything if there is a event binded to a link, typeof $.data(that.get(0), 'events') == 'undefined' 
  • Don't do anything if the link is a static page
  • Enable the site that is loaded to disable pushState, typeof disablePush == 'undefined', sometimes you just want that.
  • The !that.hasClass('nobbq') is a leftover from using BBQ, but if I set that class on the link it will not use pushState

So if that all works out, I load the content

function loadContent(obj, url) { if (currentXhr != null && typeof currentXhr != 'undefined') { currentXhr.abort(); } $('body').css('cursor', 'progress'); var that = null; if (obj != null) { that = $(obj); } currentXhr = $.get(url, function (data) { currentXhr = null; if (typeof unbindWindow != 'undefined') unbindWindow(); $("#loading").hide(); $('#centercontent').html(data); $('html, body').animate({ scrollTop: $("#userInfoBar").offset().top }, 0); $('body').css('cursor', 'auto'); }); }

At the begining, start to cancel any previous xhr request, this is one of the great things about having access to request, so if the user is impatient and starts clicking ten links, only the last click will be active. This saves the server load, specially when the server usually needs it most, when it's slow and users usually make it worse by being impatient.

Now you load the content using $.get function, here comes the most important function, unbindWindow(), if there is anything that you need to take from this post, this is the function. Check it out below.

Then I simply load the content into the main content, called #centercontent, and scroll the user to the top. 

Unbind events (unbindWindow())

At first I didn't realize this, but boy this is important. When you load a content using pushState, the document.ready event may run a script something like this

$('#somebutton').click(function() {

//do stuff

});

You are binding a click event to #somebutton button, now the user clicks some link so the pushState happens and it loads some new content into the main content, that click event is still active in the browser, but the button doesn't exists anymore, creating a memory leak(event leak?). So when you have multiple binding on links, buttons, images, etc.

You must remember to unbind the event when you unload the content. If you forget that, with time the browser and the site will come extremly slow. Also, lets say that you don't unbind that click event, and you load the same page 3 times, each time the click event will be binded to $('#somebutton'), so when the button is click, it will fire 3 times. Not the result that you want.

So on the page that you load with pushState, you need to have the unbindWindow() function, to unbind all the binding that you have made. In the above example it would be

function unbindWindow() {

$('#somebutton').unbind('click');

}

The other content

Now that you have pushState, there are some troubles. Like the content around the main content that never changes. You are always loading the main content, but the header of the site never changes, and if you have left/right sidebar, that never changes as well. Depending on your site design this can be a problem, for me it was and still is. 

For instance, in the header I have the login information, your username, access to private messages, notification, etc. but since you never reload the header, just the content, you may be logged out of the site and not know it. If a logged in user moves away from the computer and comes back after 2 hours, he expects that the next time he clicks a link he will be logged out, because of behavoiur from the old web. But even though he is logged out, he thinks that he is logged in, since the header doesn't change.

Then you have problem like advertises, since you never reload the page again, you are always displaying the same ads, so you need to write an extra module that loads new advertises every XX seconds.

Benefits of pushState

Faster. Smaller content over the wire. Faster. Lower server load. Faster. Fewer DOM for browser to parse. Faster. Enough said.

 

0 | 7.7.2011 11:57:15 #