News

26th December 2015 by aegeuana_sjp_admin

Manipulating the browser history [HTML5]

Since the introduction of HTML5 it’s been possible to manage the browser history using the history and history.pushState API. It allows us to change the URL in the browser without the page refreshing. There are a lot of frameworks that already support the history and pushState API such as Backbone.js, Ember.js and Angular.js.
In this post, we’ll be looking at using the Browser push state without resorting to any framework. To do this we first need to have a way to check if the user has access to the history API. We can do this as follows:
[code language=”javascript”]
if(window.history && window.history.pushState){
// history API available
}
[/code]
We can navigate through the available history (this will refresh the page if the previous/next page hasn’t been saved by pushState or replaceState) and we can check how many entries we have available.

1. Basic history API

[code language=”javascript”]
window.history.back(); // Move history one entry back
window.history.forward(); // Move history one entry forward
window.history.go(-2); // Move history two entries back
window.history.go(2); // Move history two entries forward
window.history.length; // Check how many entries in the browser history
[/code]
If you will try to move history beyond the available boundaries the browser will handle this event and no action will be triggered.

2. history.pushState API

This allows us to change the actual page URL and it won’t refresh the page. This method is adding a new entry into the history. You can pass three arguments to the pushState method:
– data [required]: The JavaScript object that’s JSON serialized. It’s bound to the particular history entry. The maximum size of the object depends from the browser (Firefox: 640kB, IE: 1MB, Chrome: ~10MB). Each time the browser detects that the entry was inserted using pushState/replaceState the `popstate` event will be fired and the `data` object will be passed as an argument.
– title [optional]: The title of the entry.
– url [optional]: The new URL address that will be saved as the history entry. It won’t refresh the page though.
[code language=”javascript”]
var data = {html_content: ‘<div>Hello world</div>’}
history.pushState(data, "title", "/my-awesome-url.html");
[/code]

3. history.replaceState API

This method is replacing the actual history entry with the one we are passing. It is taking the same arguments as the `pushState` method and the same event `popstate` is fired when the browser detects that the particular history entry has been modified by this method.
[code language=”javascript”]
var data = {html_content: ‘<div>Hello world</div>’}
history.replaceState(data, "title", "/my-awesome-url.html");
[/code]

4. popstate event

This event fires each time the browser detects that the history entry was modified manually by the history pushState/replaceState API. The event’s `state` property contains a copy of the object passed as the first argument in the pushState/replaceState methods.
[code language=”javascript”]
window.onpopstate = function(ev){
console.log(ev);
// Handle popstate
}
[/code]

5. Workflow

To integrate the history API with our website we need to prepare not only the Javascript side of our app but also the server side. The example below will show you how to handle the `pushState` in a Django app.
We will need to prepare a basic HTML template with a container, and we will be managing this containers’ content dynamically.

5.1 HTML

5.1.1 Main template (file test.html)

[code language=”html”]
</pre>
<pre> <!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<body>
<div>
<h1>Our pushState page</h1>
<div id="dynamic_container">
<!– Content loaded here –>
{{ content|safe }}
</div>
<div id="nav">
<a class="ajax" href="/">Home</a>
<a class="ajax" href="?page=1">Page 1</a>
<a class="ajax" href="?page=2">Page 2</a>
</div>
</div>
<script type="text/javascript">
// JS here
</script>
</body>
</html>
[/code]

5.1.2 Items template (file items.html)

[code language=”html”]
<!doctype html>
<div>
<ul>
{% for obj in objects %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
</div>
[/code]

5.2 Javascript(using jQuery)

By doing this, we are replacing the initial state with the container content. Next we need to catch clicks to prevent page refresh, load content dynamically and replace the container content.
[code language=”javascript”]
var $container = $(‘#dynamic_container’);
$(document).ready(function(){
// Replace initial history state
history.replaceState({content: $container.html()}, "", window.location.toString());
});
// Catch clicks
$(‘a.ajax’).click(function(click_ev){
click_ev.preventDefault();
var a_url = $(this).attr(‘href’);
// Load content dynamically
$.ajax({
url: a_url,
dataType: ‘json’,
success: function(json_data){
$container.html(json_data.content);
history.pushState({content: json_data.content}, "", a_url);
}
});
});
window.onpopstate = function(ev){
var state_obj = ev.state;
if(state_obj){
$container.html(state_obj.content);
}
}
[/code]

5.3 Python

Handle ajax request on the server. The example below is a simple Django class based view:
[code language=”python”]
from django.http import HttpResponse
from django.shortcuts import render
from django.template import RequestContext
from django.template.loader import render_to_string
from django.views.generic import View
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
class TestView(View):
template_name = ‘templates/test.html’
template_items = ‘templates/items.html’
def get(self, request):
page = request.GET.get(‘page’, None)
objects = TestModel.objects.all()
paginator = Paginator(objects, 30)
try:
page = paginator.page(q_page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
page = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
page = paginator.page(paginator.num_pages)
# Render content html separately
items_html = render_to_string(self.template_items, {objects: page.object_list}, context_instance=RequestContext(request))
if request.is_ajax():
# If request is made by ajax we need to return just the container content
return HttpResponse(json.dumps({‘content’: items_html}));
# If it is normal request return response like always
return render(request, self.template_name, {content: items_html})
[/code]
Hope it helps.

Leave a Reply

Your email address will not be published. Required fields are marked *