How to Create a Slideshow with Ajax

  • google plus

In Brief...

Google Maps (http://maps.google.com) was one of the applications that brought so much attention to Ajax. One of the cool things about it is it allows the user to drag maps around the screen seamlessly loading new sections. It does this by preloading the sections around the map that the user is likely to drag on to the screen. This same concept can be applied to other applications, such as slideshows. Create a slideshow with Ajax in the following steps.

Take our Ajax Training course for free.

See the Course Outline and Register

Instructions

For these examples to work, more files are required than are available in this how-to. To see these examples in action, take the Ajax training course.

  1. First, take a look at the slideshow shown below.
    Slideshow
    When the user clicks the Previous or Next buttons, the page makes an XMLHttpRequest to the server, which returns XML as shown below:
    XML HTTP pRequest
  2. The callback function creates the next slide from this XML. The code is shown below.
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Slide Show</title>
    <link href="SlideShow.css" type="text/css" rel="stylesheet">
    <script type="text/javascript" src="lib.js"></script>
    <script type="text/javascript">
    	function prevSlide() {
    		var curSlide = document.getElementById("CurSlideNum").innerHTML;
    		getSlide(curSlide - 1);
    	}
    
    	function nextSlide() {
    		var curSlide = Number(document.getElementById("CurSlideNum").innerHTML);
    		getSlide(curSlide + 1);
    	}
    
    	function getSlide(curSlide) {
    		var xmlhttp = new XMLHttpRequest();
    		var btnPrev = document.getElementById("PrevButton");
    		var btnNext = document.getElementById("NextButton");
    
    		btnPrev.disabled = true;
    		btnNext.disabled = true;
    
    		xmlhttp.open("get", "SlideShow?Slide=" + curSlide, true);
    
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				changeSlide(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function changeSlide(xmlhttp) { //Callback function creates slide
    			var docElem = xmlhttp.responseXML.documentElement;
    			var slideText = document.getElementById("SlideText");
    			var slideImage = document.getElementById("SlideImage");
    			var numSlides = document.getElementById("TotalSlideNum").innerHTML;
    			var name, years;
    			removeWhitespace(docElem, true);
    			name = docElem.firstChild.firstChild.nodeValue;
    			years = docElem.childNodes[1].firstChild.nodeValue;
    			slideText.innerHTML = name + "<br>" + years;
    			slideImage.src = "Slides/" + docElem.childNodes[2].firstChild.nodeValue;
    			slideImage.alt = name;
    			document.getElementById("CurSlideNum").innerHTML = curSlide;
    			if (curSlide != 1) {
    				btnPrev.disabled = false;
    			}
    			if (curSlide != numSlides) {
    				btnNext.disabled = false;
    			}
    		}
    	}
    
    	observeEvent(window, "load", function() {
    		var btnPrev = document.getElementById("PrevButton");
    		var btnNext = document.getElementById("NextButton");
    		var totalSlides = 10;
    		observeEvent(btnPrev, "click", prevSlide);
    		observeEvent(btnNext, "click", nextSlide);
    		document.getElementById("TotalSlideNum").innerHTML = totalSlides;
    		getSlide(1);
    	});
    </script>
    </head>
    
    <body>
    <h1>First 10 Presidents</h1>
    <div id="Slide">
    	<img id="SlideImage">
    	<div id="SlideText"></div>
    	<hr>
    	<button id="PrevButton">Previous</button>
    	Slide <span id="CurSlideNum">1</span> of <span id="TotalSlideNum"></span>
    	<button id="NextButton">Next</button>
    	<hr>
    	<div id="SlideMessage"></div>
    </div>
    </body>
    </html>
  3. Notice how the changeSlide() callback function changes the text of the slide and the image src and alt value based on the XML returned. Although this is pretty cool in and of itself, it can be made better by preloading the preceding and following images, so the user experiences no delay when navigating from slide to slide. In this case, the server-side script needs to return more data. Our script, the SlideShow response route from AjaxApplications/Demos/server.js, is shown below; the response is rendered via AjaxApplications/Demos/SlideShow.xml.jade
    app.get('/SlideShow', function(req, res) {
    	presidentsdb.serialize(function() {
    		var Slide = req.param('Slide');
    		var sql = "SELECT FirstName, LastName, StartYear, EndYear, ImagePath FROM Presidents WHERE PresidentID=" + Slide;
    		res.setHeader('Content-type', 'text/xml');
    		presidentsdb.all(sql, function(err, row) {
    			if(err !== null) {
    			res.status(500).send("An error has occurred -- " + err);
    			} else {
    			res.render('SlideShow.xml.jade', {presidents: row}, function(err, xml) {
    				res.status(200).send(xml);
    				});
    			}
    		});
    	});
    });
    Notice that the SQL query will return records for the chosen president, the preceding president, and the following president. The resulting XML will look something like this:
    SQL XML
  4. Now we need to change the code to handle the preloaded slides and change the HTML to have hidden locations for the preloaded data. The code below shows how this is done.
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Slide Show</title>
    <link href="SlideShow.css" type="text/css" rel="stylesheet">
    <script type="text/javascript" src="lib.js"></script>
    <script type="text/javascript">
    	function prevSlide() {
    		var curSlide = document.getElementById("CurSlideNum").innerHTML;
    		getSlide(curSlide - 1, "prev");
    	}
    
    	function nextSlide() {
    		var curSlide = Number(document.getElementById("CurSlideNum").innerHTML);
    		getSlide(curSlide + 1, "next");
    	}
    
    	function getSlide(curSlide, slideSource) {
    		var slideText = document.getElementById("CurSlideText");
    		var slideImage = document.getElementById("CurSlideImage");
    		var curSlideNum = document.getElementById("CurSlideNum");
    		var btnPrev = document.getElementById("PrevButton");
    		var btnNext = document.getElementById("NextButton");
    		var xmlhttp, url;
    
    		btnPrev.disabled = true;
    		btnNext.disabled = true;
    
    		if (slideSource == "prev") {
    			slideText.innerHTML = document.getElementById("PrevSlideText").innerHTML;
    			slideImage.src = document.getElementById("PrevSlideImage").src;
    			slideImage.alt = document.getElementById("PrevSlideImage").alt;
    			curSlideNum.innerHTML = curSlide;
    			message("Loading from Prev");
    		} else if (slideSource == "next") {
    			slideText.innerHTML = document.getElementById("NextSlideText").innerHTML;
    			slideImage.src = document.getElementById("NextSlideImage").src;
    			slideImage.alt = document.getElementById("NextSlideImage").alt;
    			curSlideNum.innerHTML = curSlide;
    			message("Loading from Next");
    		}
    
    		xmlhttp = new XMLHttpRequest();
    		url = "SlideShow-preloaded?Slide=" + curSlide;
    		xmlhttp.open("get", url, true);
    
    		xmlhttp.onreadystatechange = function() {
    			if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
    				changeSlide(xmlhttp);
    			}
    		}
    		xmlhttp.send(null);
    
    		function changeSlide(xmlhttp) { //Callback function creates slide
    			var docElem = xmlhttp.responseXML.documentElement;
    			var numSlides = document.getElementById("TotalSlideNum");
    			var prevSlidePos = (curSlide == 1) ? null : 0;
    			var curSlidePos = (curSlide == 1) ? 0 : 1;
    			var nextSlidePos = (curSlide == 1) ? 1 : (curSlide == 10) ? null : 2;
    
    			removeWhitespace(docElem, true);
    
    			if (prevSlidePos !== null) {
    				prevSlideNode = docElem.childNodes[prevSlidePos];
    				loadSlide("Prev", prevSlideNode);
    				btnPrev.disabled = false;
    			}
    
    			if (nextSlidePos !== null) {
    				nextSlideNode = docElem.childNodes[nextSlidePos];
    				loadSlide("Next", nextSlideNode);
    				btnNext.disabled = false;
    			}
    
    			if (slideSource == "ajax") {
    				curSlideNode = docElem.childNodes[curSlidePos];
    				loadSlide("Cur", curSlideNode);
    				message("Loading from Ajax");
    			}
    
    			curSlideNum.innerHTML = curSlide;
    		}
    	}
    
    	function loadSlide(slide, node) {
    		var slideElem = document.getElementById(slide + "Slide");
    		var slideText = slideElem.getElementsByTagName("div")[0];
    		var slideImage = slideElem.getElementsByTagName("img")[0];
    		var name = node.firstChild.firstChild.nodeValue;
    		var years = node.childNodes[1].firstChild.nodeValue;
    		slideText.innerHTML = name + "<br>" + years;
    		slideImage.src = "Slides/" + node.childNodes[2].firstChild.nodeValue;
    		slideImage.alt = name;
    	}
    
    	function message(msg) {
    		var output = document.getElementById("SlideMessage");
    		output.innerHTML = msg;
    	}
    
    	observeEvent(window, "load", function() {
    		var btnPrev = document.getElementById("PrevButton");
    		var btnNext = document.getElementById("NextButton");
    		var totalSlides = 10;
    		observeEvent(btnPrev, "click", prevSlide);
    		observeEvent(btnNext, "click", nextSlide);
    		document.getElementById("TotalSlideNum").innerHTML = totalSlides;
    		getSlide(1, "ajax");
    	});
    </script>
    </head>
    
    <body>
    <h1>First 10 Presidents</h1>
    <div id="PrevSlide">
    	<img id="PrevSlideImage">
    	<div id="PrevSlideText"></div>
    </div>
    <div id="CurSlide">
    	<img id="CurSlideImage">
    	<div id="CurSlideText"></div>
    	<hr>
    	<button id="PrevButton">Previous</button>
    	Slide <span id="CurSlideNum">1</span> of <span id="TotalSlideNum"></span>
    	<button id="NextButton">Next</button>
    	<hr>
    	<div id="SlideMessage"></div>
    </div>
    <div id="NextSlide">
    	<img id="NextSlideImage">
    	<div id="NextSlideText"></div>
    </div>
    </body>
    </html>
    Notice these two divs in the HTML body.
    <div id="PrevSlide">
    <img id="PrevSlideImage"/>
    <div id="PrevSlideText"></div>
    </div>
    <div id="NextSlide">
    <img id="NextSlideImage"/>
    <div id="NextSlideText"></div>
    </div>
    These divs are simply there to hold the incoming data. SlideShow.css has a commented out line that sets the display property of these divs to "none", so the page appears as below: :
    Preloaded Slideshow
  5. If you were to remove the comment marks, the previous and next slide divs would be hidden. The images in the upper corners are preloaded (by being in the browser cache, so that new slides load seamlessly). We do it this way for demonstrative purposes only. You could just as easily store the preloaded images in JavaScript variables.
  6. In the JavaScript code, we have to know the source of the current slide, in other words, from where to get the data for the current slide:
    1. When the page first loads, we'll get the data using an Ajax call.
    2. When the Next button is clicked, we'll get the data from the "NextSlide" div.
    3. When the Previous button is clicked, we'll get the data from the "PrevSlide" div.
  7. So now, when we call getSlide(), we will pass in the slide source:
    1. "ajax" when the page first loads.
    2. "next" when the Next button is clicked.
    3. "prev" when Previous button is clicked.
  8. If the slide source is "prev" or "next," we load the current slide from the data in either the "Next" or "Previous" div based on which button was pushed:
    if (slideSource == "prev") {
    	slideText.innerHTML = document.getElementById("PrevSlideText").innerHTML;
    	slideImage.src = document.getElementById("PrevSlideImage").src;
    	slideImage.alt = document.getElementById("PrevSlideImage").alt;
    	curSlideNum.innerHTML=curSlide;
    	message("Loading from Prev");
    } else if (slideSource == "next") {
    	slideText.innerHTML = document.getElementById("NextSlideText").innerHTML;
    	slideImage.src = document.getElementById("NextSlideImage").src;
    	slideImage.alt = document.getElementById("NextSlideImage").alt;
    	curSlideNum.innerHTML=curSlide;
    	message("Loading from Next");
    }
    Even if we do load the current slide from the "cache", we still need to make our Ajax call, to repopulate the "caching" divs. In the changeSlide() callback function, we do the following:
  9. Determine the position of each slide within the XML nodes returned. Usually, three nodes get returned and the first (position 0 in JavaScript) is the data for the previous slide, the second (position 1) is the data for the current slide, and the third (position 2) is the data for the next slide. But this isn't the case, when the current slide is the first slide or the last slide. In either of these cases, only two XML nodes are returned. Our code has to know which slides they match:
    var prevSlidePos = (curSlide == 1) ? null : 0;
    var curSlidePos = (curSlide == 1) ? 0 : 1;
    var nextSlidePos = (curSlide == 1) ? 1 : (curSlide == 10) ?
    null : 2;
  10. We then populate the "caching" divs if their position (as assigned above) is not null:
    if (prevSlidePos !== null) {
    prevSlideNode = docElem.childNodes[prevSlidePos];
    loadSlide("Prev",prevSlideNode);
    btnPrev.disabled=false;
    }
    if (nextSlidePos !== null) {
    nextSlideNode = docElem.childNodes[nextSlidePos];
    loadSlide("Next",nextSlideNode);
    btnNext.disabled=false;
    }
  11. We then only populate the current slide in the callback function if the slide source is "ajax." Otherwise, it was already populated from "cache":
    if (slideSource == "ajax") {
    curSlideNode = docElem.childNodes[curSlidePos];
    loadSlide("Cur",curSlideNode);
    message("Loading from Ajax");
    }

Author: Chris Minnick

Chris is a prolific author and trainer, and the CEO of WatzThis?. His published books include Writing Computer Code, JavaScript for Kids, Coding with JavaScript For Dummies, Beginning HTML5 and CSS3 For Dummies, Webkit For Dummies, CIW eCommerce Certification Bible, and New Riders' XHTML.

Discuss