Introduction
During Ajax communication, page content is often modified in some way or another. Since Ajax requests are sent through a client side script, the browser address bar remains unchanged even if the page content is being changed. Although this behavior doesn’t create any problem for an application’s functionality, it has pitfalls of its own. That’s where History API comes to your rescue. History API allows you to programmatically change the URL being shown in the browser’s address bar. This article demonstrates how History API can be used with an example of a slide show.
Overview of History API
Browsers keep a track of the URLs you visit in the history object. The history object tracks only those URLs that you actually visited, usually by entering them in the browser’s address bar. While this serves its purpose in most cases, Ajax driven web pages face a problem of their own. Such pages make Ajax calls to the server and often change the content of the page dynamically. Let’s understand this with an example. Suppose you wish to develop an Ajax driven slide show. This slide show consists of previous and next buttons and an image element to display the slide. In order to navigate between two or more slides the slide show needs to make an Ajax call to the server to fetch the slide information to be displayed. Irrespective of the slide being displayed, the browser address bar always shows the URL of the web page that houses the slide show. Now imagine that a user is on the third slide and bookmarks that page with the intention to revisit later. If the user visits the bookmarked URL later, the URL won’t fetch the third slide but will fetch the initial slide. This is because the browser’s address bar was always pointing to the main page and the history object tracked only the URL of the main page.
History API allows you to programmatically change the URL being shown in the browser’s address bar. While doing so you are also adding entries to the history object. Assuming that the slide show is using History API, the third slide will have its own unique URL even though the slide is being displayed through an Ajax call. History API consists of two methods and one event. They are listed below:
- pushState() : The pushState() method is used to add an entry to the browser’s history and thus changes the browser’s address bar.
- replaceState() : The replaceState() method is used to update an existing entry in the browser’s history with new details.
- popstate : The popstate event is raised by the window object when a user visits an entry from the history object.
Ajax Driven Slide Show
To illustrate the use of History API, let’s develop an Ajax driven slide show. The slide show exhibits only the basic functionality of showing slides as you click on next and previous buttons. For the sake of simplicity we won’t add any fancy effects, configuration or error handling to the slide show. Of course, you can add these features once you finish this basic example. A slide show sample developed in this example is shown below:
Slide Show
As you can see each slide has a title, image and description. Clicking on the Previous or Next button causes an Ajax request to be sent to the server and details of the next or previous slide are fetched accordingly. Also, the browser’s address bar reflects a unique URL for each slide being displayed even though the slide is being displayed using Ajax. For example, the above figure shows the URL for the second slide as /slides/index/2.
The slide show shown above can be developed in seven easy steps. Let’s see what they are…
1. Create a Database Table to Store Slide Information
The slide show stores all the information about slides in a database. This example uses a SQL Server database but you can use any other RDBMS for storing this information. The following figure shows the Slides table used for this purpose:
Slides Table
As you can see the slides table consists of four columns – Id, Title, Description and ImageUrl. These columns store slide ID, slide title, description of a slide and URL of the slide’s image respectively.
2. Create HTML Page and Style Sheet
Next, you need to create an HTML page that houses the slide show. The slide show application needs some server side processing (fetch data from database when Ajax calls are made) and hence you need to use some server side processing engine to display this HTML page. This example assumes ASP.NET MVC as the server side technology but that’s not mandatory. You can use any other server side framework (such as PHP) that allows you to execute the required server side logic. The following markup shows the HTML needed to display the slide information. In this example this markup goes inside a Razor view (Index.cshtml).
@model HistoryAPISlideShow.Models.Slide ... <body> <form> <input id="slideId" type="hidden" value="@Model.Id" /> <h1 id="slideTitle" class="Title">@Model.Title</h1> <div><img id="slideImage" src="@Model.ImageUrl" /></div> <div id="slideDescription">@Model.Description</div> <div> <input id="prevButton" type="button" value="Previous" /> <input id="nextButton" type="button" value="Next" /> </div> </form> </body> </html>
Let’s examine this markup. The markup consists of a <form> that houses all the DOM elements making the slide show. The hidden field – slideId – is used to store the slide ID and is used in the client script (you will learn about it later). The <h1> element displays a slide’s title. The slideImage <img> element displays the slide’s image and the slideDescription <div> displays the slide’s description. Two buttons – prevButton and nextButton – represent the Previous and Next buttons respectively.
Notice that a Slide object acts as a model to this view. The Id, Title, ImageUrl and Description properties of the Slide object are rendered inside the slideId, slideTitle, slideImage and slideDescription elements respectively. You will create the Slide model class later in this article.
The DOM elements discussed above also have some CSS styles attached with them. These CSS rules are defined in a style sheet and are shown below:
#slideTitle { font-family:Arial; font-size:21px; padding:5px; } #slideDescription { font-family:Arial; font-size:15px; padding:10px; text-align:justify; } #slideImage { padding:10px; border:2px solid #808080; } #prevButton, #nextButton { width:100px; padding:5px; }
Make sure to add these CSS rules to a style sheet file and link that style sheet from the HTML page you just developed.
3. Write Server Side Code to Display the Initial Slide
When a user visits the web page that houses the slide show, the slide show must display the first slide. The user can then use Previous and Next navigation buttons to navigate between the slides. To fetch and supply this initial slide to the HTML markup you need to write some server side code that retrieves the slide information from the Slides table you created earlier. This example uses ASP.NET MVC and Entity Framework for this purpose but again that’s not mandatory. You can easily port this logic on whatever server side framework you are using. The code that serves the initial slide is shown below:
public class SlidesController : Controller { public ActionResult Index(int id = 0) { SlideDbEntities db = new SlideDbEntities(); IQueryable<Slide> data = null; if (id == 0) { data = (from item in db.Slides orderby item.Id ascending select item).First(); } else { data = from item in db.Slides where item.Id == id select item; } return View(data.SingleOrDefault()); } }
The above code shows the Index() action method from the Slides controller. The Index() method takes the id parameter and its default value is set to 0. The Index() method servers two purposes. Firstly, it serves the first slide when the slide show page is visited. In this case no id will be passed to it. Secondly, it serves a slide with a specific ID. This variation is used when History API pops a slide URL added to the history object.
We won’t go into too much detail of the Index() method. Suffice it to say that if the id parameter is 0, Index() method fetches the first slide data from the Slides table, otherwise it fetches a slide with the specified id. In both cases the slide information is stored in Slide object (Slide is an entity class for the Slides table). The Slide object is then passed to the Index view. Recollect that Index view contains the HTML markup that displays the slide information.
4. Write Server Side Code to Serve a Specific Slide to an Ajax Call
As discussed earlier, clicking on the Previous and Next buttons cause an Ajax call to be made to the server in an attempt to fetch the slide details. To handle this Ajax call you need some server side code that serves a specified slide to the client side script. The following code shows another action method of the Slides controller class that does this job:
public JsonResult GetSlide(int slideid,string direction) { SlideDbEntities db = new SlideDbEntities(); IQueryable<Slide> data = null; if (string.IsNullOrEmpty(direction)) { data = from item in db.Slides where item.Id == slideid select item; } if (direction == "N") { data = (from item in db.Slides where item.Id > slideid orderby item.Id ascending select item).Take(1); } if (direction == "P") { data = (from item in db.Slides where item.Id < slideid orderby item.Id descending select item).Take(1); } Slide slide = data.SingleOrDefault(); return Json(slide); }
The GetSlide() action method accepts two parameters – slideid and direction). The slideid parameter indicates the ID of the slide that is currently displayed in the browser. The direction parameter indicates whether the Previous or Next button was clicked. Its value can be P or N accordingly. If a user clicks on the Next button you need to fetch the slide whose ID is next to the current slide. The slide IDs may not be in sequence and hence the code needs to fetch a slide whose ID is higher than the current slide ID. Similarly, logic needs to be executed if the user clicks on the Previous button. This time, however, you need to fetch a slide that is immediately before the current slide.
Once the slide is retrieved, it is passed to the client using the Json() method. The Json() method converts the Slide object into the equivalent JSON format. Notice that the return value of GetSlide() is JsonResult because it will be accessed by an Ajax call.
5. Load and Display Slides Using jQuery $.ajax()
Now comes the important part – displaying slides using Ajax. To display slides when a user clicks the Previous or Next button we will use jQuery $.ajax() method. So, add a <script> reference to the jQuery library in the head section of the HTML page you created earlier. Also add a <script> block in the head section. The following code shows how the <script> reference and the <script> block look:
<script src="~/Scripts/jquery-2.0.0.js"></script> <script type="text/javascript"> $(document).ready(function () { $("#prevButton").click(function () { GetSlide("P"); }); $("#nextButton").click(function () { GetSlide("N"); }); });
The <script> block consists of the jQuery ready() handler function. The ready() handler wires click event handlers of the prevButton and the nextButton using the click() method. Both the click event handlers call a custom function – GetSlide(). The GetSlide() function fetches a slide from the server by making an Ajax request and accepts either P or N depending on the button pressed. The following code shows the GetSlide() function:
function GetSlide(direction) { var data = {}; data.slideid = $("#slideId").val(); if (direction !== undefined) { data.direction = direction; } $.ajax({ url: "/Slides/GetSlide", type: "POST", data: JSON.stringify(data), dataType: "json", contentType:"application/json", success: function (slide) { $("#slideId").val(slide.Id); $("#slideTitle").html(slide.Title); $("#slideDescription").html(slide.Description); $("#slideImage").attr("src", slide.ImageUrl); }, error: function () { alert(err.status + " - " + err.statusText); } }) }
The GetSlide() function retrieves the value of the current slide ID from the slideId hidden field. It then creates a JavaScript object (data) and stores the slideid and direction into it. Then the $.ajax() method is called to make an Ajax call to the GetSlide() server side method (see step 4). Various configuration options are passed to the $.ajax() method. The url setting points to the URL of the GetSlides() action method. The type setting specifies the request type to be POST. The data setting holds the JSON stringified version of the data JavaScript object. The dataType setting specifies the data type of the response and is set to json. The contentType property specifies the content type of the request and is set to application/json. The success function is called when the Ajax call succeeds and receives the Slide object as its parameter (recollect that GetSlide() action method returns Slide object). The success function then sets the values of slideId, slideTitle, slideDescription and slideImage according to the slide data returned from the server. Finally, the error function displays an error message (if any) in case there is any error while making the Ajax call.
6. Add an Entry in the Browser’s History Using pushState()
In its current form the slide show will be displayed as expected. That means clicking on Previous and Next buttons will fetch the slides accordingly and display them in the page. However, the browser address bar remains unchanged throughout this navigation. That’s where the History API comes into the picture. To change the browser address bar when a user navigates to the new slide, add the following line to the success handler function you created in step 5.
... success: function (slide) { $("#slideId").val(slide.Id); $("#slideTitle").html(image.Title); $("#slideDescription").html(slide.Description); $("#slideImage").attr("src", slide.ImageUrl); history.pushState(slide, slide.Title, "/slides/index/" + slide.Id); } ...
Notice the line of code marked in bold letters. The code calls the pushState() method of the history object. The pushState() method accepts three parameters. These parameters are described below:
- statedata : The first parameter indicates the state associated with the new entry being added. You can access this state inside the popstate event handler (discussed later). In this case you store the entire slide object into the state.
- title : The second parameter is a title that you wish to assign to the new entry. This title can be displayed by the browser in the History menu. Here we add slide’s Title as the title.
- url : The third parameter is a URL that you wish to add to the history object. In this example you add URLs of the form /slides/index/<slide_id> to the history. Notice that these URLs point to the Index() action method of the Slides controller you created in step 3. This is also the URL that will be displayed in the browser’s address bar as soon as the pushState() call is made.
After adding this code if you run the web page again, you will observe that as soon as the pushState() call is made the browser’s address bar reflects the URL as mentioned in the third parameter. This URL can be bookmarked by the end user or navigated to later during the same session using the browser’s back button.
7. Handle Popstate Event
The popstate event is raised by the window object whenever the current history entry changes. The popstate event object has state property containing the statedata value you added while calling pushState(). In our example you use this value as follows:
$(document).ready(function () { window.addEventListener("popstate", function (evt) { $("#slideId").val(evt.state.Id); GetSlide(); }, false); ...
As you can see the above code has been added to the ready() handler. The addEventListener() method wires an event handler for the popstate event. The popstate event handler sets the value of slideId hidden field to the ID of the slide that is being displayed from the history. Notice how the evt.state.Id is used to read a slide ID from the state object. GetSlide() method is then called so as to make an Ajax call to the server in an attempt to retrieve the latest slide information.
That’s it! You can run the slide show and test whether it functions as expected by clicking on the navigation buttons and browser’s back and forward buttons.
Summary
Modern web applications heavily use Ajax. While using Ajax the page URL remains the same even though its content might get change dynamically. History API allow you to programmatically add entries to history. Ajax calls can take advantage of History API and add entries that uniquely identify an Ajax call. The browser address bar also reflects these URLs. History API provides pushState() method to add an entry to the browser’s history. When any item from the browser’s history is accessed popstate event is raised on the window so that the page content can be synchronized (if required) as per the URL being displayed in the browser’s address bar.