HTML5 New Feature Series: History

The rise of Ajax technology has brought new vitality to Internet since few years ago, in the meantime, it has achieved a qualitative leap on user experience. The page can easily fetch new data without enforced refreshing, also, it presents the fantastic views to user in a kind of more interactive way, so in a sense, this change has made a unbelievably outstanding contribution to the Internet as never before.

But with the large-scale use of the Ajax technology, an increasing number of developers begin to realize some problems. Due to the non-refreshing mechanism, some presentations of page view has no connection with URL, that means, the URL will not has any change after user has done a series of operations. As a consequence, it can't reserve the results generated by user's operations, and when user revisits the page, the view which user wants can not recur.

For instance, when we are browsing an album in the photography website, and in order to have a better experience, we can open a preview dialog by clicking the small picture in the album, and when we see a favorite picture, we probably feel excited and eager to share it with someone else, then copy the URL and paste it in social website, disappointingly, our friends can not see that picture after opening the link, what they will see is just a normal album that consists of a large collection of small pictures. Precisely for this reason, that page can not be fetched by search engine accurately, therefore we can't get our website page optimized, and at the same time, it will have an serious degradation on visitability.

And in order to resolve this issue, the web developers have been trying to use the URL hash tech to improve the visitability, also, some browsers began to provide event mechanisms related to URL hash, and some well-known Internet companies have made a convention with the mainstream search engine for being fetched perfectly according to the rules. But after all, it's not a standard technology, hence there're a lot of problems inevitably, so developers are expecting a new standard technology to resolve it thoroughly.

And fortunately, the History API has brought new hope to our developers, since it provides great support for non-refreshing operations based on URL, the SEO optimization has been solved perfectly, thus, there is no doubt that History API is going to be the standard of Web in the future, and a larger number of developers will develop their application with History API.

Then today we will get into details associated with History tech.

To present a better explanation with regard to this technology, we choose to start from an example program. As the following picture shows, we initially display three small images, and after clicking an arbitrary image, the preview image will show up, also, we can click the previous or next button to switch the current preview image.

This procedure is supposed to be easy to us, when clicking the small image, change the related CSS to show the elements for preview, get the corresponding data to render the preview image and description, and it's pretty easy to switch the current preview information.

That is the traditional Ajax solution, and there is a serious defect on it, we can't leave any trace about the information related to the current preview, imagine that, there probably has hundreds of small images, and we may find a fascinating image in preview dialog after switching many times, then we want to send it to our friends, or save it for ourself, now we will realize there is no information in current URL about this preview image, thus, coping the URL doesn't work at all.

So we extremely want to add some information to URL to record user's operation, and now it's the show time of History API. The API can manipulate the URL directly under the condition of non-refreshing. And next we will introduce two primary functions below:

history.pushState(stateObj, title, url);

This function will push a state record into the current history stack, and the 'stateObj' is a customizable object for representing some useful data, the 'title', as the name tells, it's a description of this state, and the last parameter represents the url we want to add to URL. For example, if we click the second image, our code is like this:history.pushState({id: 2}, 'img: 2', '?preview=2');

history.replaceState(stateObj, title, url);

This function is a little similar with 'pushState', the only difference is that the browser will replace the old record in history stack, instead of adding a new one. This function will be useful in some specific situations.

Now let's learn how to handle the browser history with the example.

As the above picture shows, initially there're three small images, if we click any one of them, a preview image will come out, here is the basic HTML structure:

<!DOCTYPE html>
<html>
	<head>
		<title>History</title>
		<link rel="stylesheet" type="text/css" href="css/main.css">
	</head>
	<body>
		<ul id="gallery">
			<li>
				<img src="img/1.jpg" data-id="1"/>
			</li>
			<li>
				<img src="img/2.jpg" data-id="2"/>
			</li>
			<li>
				<img src="img/3.jpg" data-id="3"/>
			</li>
		</ul>
		<div id="shadow-layer" class="for-dialog"></div>
		<div id="preview-panel" class="for-dialog">
			<img id="preview-img"/>
			<div id="preview-info"></div>
			<div id="preview-close" class="preview-button">Close</div>
			<div id="preview-prev" class="preview-button"><</div>
			<div id="preview-next" class="preview-button">></div>
		</div>
	</body>
	<script type="text/javascript" src="js/jquery.js"></script>
	<script type="text/javascript" src="js/main.js"></script>
</html>
And after the page's initialization, we need to bind some event handlers, just like this:

//click the small picture to preview
$('#gallery img').click(function() {
	preview(parseInt($(this).attr('data-id')));
});
//close the preview mode
$('#preview-close').click(function() {
	$('.for-dialog').hide();

	window.history.back();
});
//to preview the previous one
$('#preview-prev').click(function() {
	switchPrev();
});
//to preview the next one
$('#preview-next').click(function() {
	switchNext();
});

We can easily see that, when clicking small picture, the preview mode will be triggered; and after clicking the close button in current preview, the preview panel will hide, at the same time, the 'back' function of history will be invoked, we will refer to this function later; lastly, there're two switch buttons for changing the current image and the relevant information. Maybe the 'preview' invocation has caught our attention, when we click the small picture, this function will be executed, and that's exactly our focus. And here is the logic in preview function:

function preview(id, isSwitch) {

	$('.for-dialog').show();

	//get the data by id
	var data = getPreviewDataById(id);

	$('#preview-img').attr('src', data.img);
	$('#preview-info').html(data.info);

	//store the current ID for the utilization when switching
	$('#preview-panel').attr('data-prview-id', id);
	
	var stateObj = {id: id};
	var operation = isSwitch ? 'replaceState' : 'pushState';
	//push or replace a history record
	window.history[operation](stateObj, 'img: ' + id, '?preview=' + id);
}
The preview function above is mainly responsible for several important tasks, firstly showing the preview dialog, and then getting data by ID, at last updating the History object. Note that in the real development, the getPreviewDataById function will be executed in an Ajax way, therefore we need to put the data-processing codes into the callback function. And at the last of preview function, we push or replace a History with an object which contains the ID information, and meantime change the current URL. Put or replace, it depends on whether it's switching current preview picture, we will talk about it later. Note that although the URL has been changed, the current page will not be refreshed. Now let us take a look at the effects before and after the click event, for example on the second small picture.

Obviously, the URL has been changed, and that is exactly caused by our pushState operation:

window.history.pushState({id: 2}, 'img: 2', '?preview=2');
And there's one place we need to pay extra attention, that is the third parameter, which represents the current URL. According to the official guidance, it can be relative or absolute, therefore we can also use the absolute URL, and it has the same effect, just use it like this:
window.history.pushState({id: 2}, 'img: 2', 'http://localhost/history/?preview=2');
And there's an 'isSwitch' in parameter list of the preview function, if we pass a true value when calling this function, it will invoke the 'replaceState', instead of 'pushState', why do we need two different operations, now we're going to talk about it.

Do we still remember the event we bound on the close button for preview? Besides hiding the preview dialog, we also called the 'back' function of History. In fact, we want to utilize this function to pop the record which we pushed before, so that we can restore the status that before opening the preview dialog. Then here is the problem, if we switch the preview image frequently, there will be a lot of records in history stack, and it can not be easy to restore the initial status, surely it's unnecessary to keep so many records. So when user switches the preview image, we just need to call the replaceState function to replace the only record in history stack. Suppose we click the switch button on the right side, the code may be like this:

window.history.replaceState({id: 3}, 'img: 3', '?preview=3');
To call the replaceState function, we need to pass an 'isSwitch' parameter when invoking the preview function, let's figure it out how it works:
//preview the next one
function switchNext() {
	//get the next ID by the current
	var currId = parseInt($('#preview-panel').attr('data-prview-id'));
	currId++;
	currId > 3 && (currId = 1);

	preview(currId, true);
}
Now we will illustrate the stack structure by the following picture:

Thus we can make sure there always has single record in history stack, and when closing the preview dialog, we can immediately go back the original view.

And switching to the previous image is in the same way, here is the codes:

//preview the previous one
function switchPrev() {
	//get the previous ID by the current
	var currId = parseInt($('#preview-panel').attr('data-prview-id'));
	currId--;
	currId < 1 && (currId = 3);

	preview(currId, true);
}

Now I think we're both clear about how to handle with the history stack to change URL, and we may ask, how can we recover the exact view when revisiting the site with the URL, well, that is the basic processing, we just need to get the current ID parameter when window.onload function is being called, and then we invoke the preview function, the view will be presented. Here is the codes:

//get ID information of the current preview from URL
function getCurrPreviewId() {
	var search = location.search;
	if (!search) return -1;

	var params = {};

	var keyValues = search.substring(1).split('&');
	keyValues.forEach(function(item) {
		var parts = item.split('=');
		params[parts[0]] = parts[1];
	});
	if (!params['preview']) return -1;

	return params['preview'];
}

//when the page has been loaded, get the information about preview if there exists in URL
window.onload = function() {
	var id = getCurrPreviewId();
	if (id < 1 || id > 3) return;

	preview(id);
};
Now we almost has told every detail about the example, but there still has one thing we didn't cover, that is the onpopstate event in window object. When user clicks the back button or forward button of the browser, or when we call the history.back(), history.forward() and history.go() at runtime, the event will be triggered, and the onpopstate function can capture those events and obtain the current history status, thus we can use these useful information to complete our view processing. Then let's make an experiment to take a look at how it works:

window.onpopstate = function(e) {
	console.log('e.state:', e.state);
};
As we can see, in this function we will print the state object of the event, that is exactly the object we pushed earlier when executing the pushState function, now we're going to execute some commands on the console panel and see the results:


As the picture shows, we put two records, and the onpopstate function will be executed when back or forward, also, we can obtain the information of current state. So if our preview needs to record the information of every step, we can just get the current ID for preview in onpopstate, and then call the preview function directly.

At last, we should be aware of that, some browsers probably don't support History API very well, so we'd better detect the supportive condition:

var isHistoryStateSupported = 'pushState' in window.history;
Now we're done with the explanation of History API, hope we can learn it earnestly in its apply scenario, and put it into practice.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章