Extending the AS3/Flash9 Slideshow with XML In this tutorial we're going to add some more features to our the slideshow application. Those would be forwarding and rewinding slides, play and pause the slideshow, linkable slides and a description. We also fix the memory leak from the previous tutorial. [b]Adjust XML structure[/b] To store the link of each slide, we just add it as an attribute to the xml file, or to be more accurate, on the image nodes. We also set the target window of the link, which might be neccesary to set in some cases. And we also seperate the description and the title. Like this, you can have a short title and a long description for the slides. So, our xml is looking now like this:
[b]Adding new objects to the stage[/b] Since we know, how the xml looks like, we can move on to the knew stuff on the stage. In the mcInfo movieclip we've added 4 new buttons. Pause/Play and Forward/Rewind, which will later be assigned to event listeners. There is also a movieclip called mcDescription which will later hold the description of the slide. On the main stage is a invisible button as big as the size of the slides which will be used to open the links defined in the xml. Alright, that's already enough to go on with the coding part. [b]Adding new variables[/b] First of all, we need to add some new variables to our script. To know, if the slideshow is currently playing or paused, we set a flag:
var bolPlaying:Boolean = true;And we also store the current slide link and target window in a variable:
var strLink:String = ""; var strTarget:String = "";[b]Init function[/b] This is all we need for the variable. Now let's take a look about what's changing in the init function of the slideshow. We added the following two lines at the beginning of the function to prevent the user to click any button as long as the xml file hasn't been loaded completely:
mcInfo.visible = false; btnLink.visible = false;We've also changed the function that will be called once the slideTimer event is fired. Now the function nextSlide() will be called, which we'll create later:
slideTimer.addEventListener(TimerEvent.TIMER, nextSlide);On the last line of the initSlideshow() function we add the event listeners for the buttons. The play and button pause event listeners will be assigned to the same function togglePause(). The next and previous button will be asssigned to the nextSlide() and previousSlide() function. And last of all, we connect the invisible link button that covers the slide area with the goToWebsite(), showDescription() (on hovering) and hideDescription() (on roll out) function. And since our slideshow will automatically be playing at the beginning, we hide the play button.
btnLink.addEventListener(MouseEvent.CLICK, goToWebsite); btnLink.addEventListener(MouseEvent.ROLL_OVER, showDescription); btnLink.addEventListener(MouseEvent.ROLL_OUT, hideDescription); mcInfo.btnPlay.addEventListener(MouseEvent.CLICK, togglePause); mcInfo.btnPause.addEventListener(MouseEvent.CLICK, togglePause); mcInfo.btnNext.addEventListener(MouseEvent.CLICK, nextSlide); mcInfo.btnPrevious.addEventListener(MouseEvent.CLICK, previousSlide); mcInfo.btnPlay.visible = false;[b]onXMLLoadComplete function[/b] Once the xml is completely loaded, we need to show the mcInfo and the button link again. So we add the following two line of code to the function onXMLLoadComplete()
mcInfo.visible = true; btnLink.visible = true;And since our switchSlide() function will now need to have the index number of the slide, we add a zero as the parameter. We'll explain the changes in the switchSlide() function later.
switchSlide(0);[b]NextSlide/PreviousSide function[/b] Let's take a look at the nextSlide() and previousSlide() functions. They are called when clicking the next and previous button and the nextSlide() function is also called when the Timer event of the sliderTimer has been fired. The nextSlide() function checks, if there are any slides left to move on. If so, the current switchSlide() function will be called with the next slide index. If there are no more slides left, then the parameter will be zero standing for the first slide. The previousSlide() function works the same way. If there are slide we can go back, then the switchSlide() function will be called with the previous slide index. If we're already on the first slide, then we start the slideshow from the last slide with calling the switchSlide() function with the parameter of the total slide count minus one since the array count begins with zero.
function nextSlide(e:Event = null):void {
if(intCurrentSlide + 1 < intSlideCount)
switchSlide(intCurrentSlide + 1);
else
switchSlide(0);
}
function previousSlide(e:Event = null):void {
if(intCurrentSlide - 1 >= 0)
switchSlide(intCurrentSlide - 1);
else
switchSlide(intSlideCount - 1);
}
[b]SwitchSlide function[/b]
Since the nextSlide() and previousSlide() functions are now handling the checking for the slides, we can take out this part in the switchSlide() function.
To prevent the user from clicking too fast on the next and previous button, we check, if the tweener is still fading in the slides. If so, we just ignore the action. We're doing this by adding the following if-statement to the first line of the switchSlide() function.
if(!Tweener.isTweening(currentContainer)) {
The line before we create a new loader, we call a new function, which deletes contents that has been loaded previously.The function will be explained later.
clearLoader();Now we need to set the new link, the target of it and the description of the slide to the description movieclip. This will be done exactly the same way as setting the title of the slide.strLink = xmlSlideshow..image[intCurrentSlide].@link; strTarget = xmlSlideshow..image[intCurrentSlide].@target; mcInfo.mcDescription.lbl_description.htmlText = xmlSlideshow..image[intCurrentSlide].@desc;[b]FadeSideIn function[/b] The fadeSlideIn() function has also some additions. First of all, we replace the line that adds the slider content directly to the current container with this one calling the addSlideContent() function which we will look at the end of the tutorial.addSlideContent();Then we check if the slideshow is currently playing and show the number of seconds to the next slide. If the slideshow is paused, we show a status message. The text will be assigned to the lbl_info label.if(bolPlaying) { mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec."; } else { mcInfo.lbl_loading.text = "Slideshow paused"; }[b]OnSlideFadeIn function[/b] On the onSlideFadeIn() function we now need to check, if the slideshow is playing. If so, we can start the timer again:if(bolPlaying && !slideTimer.running) slideTimer.start();[b]TogglePause function[/b] The togglePause() function will be called when the user clicks on the play and pause button. First, we check, if the slideshow is playing, if so, we show the play button, set the bolPlaying variable to false, change the status message of the lbl_info label to "Slideshow paused" and stop the timer. If the slideshow is currently paused, we show the pause button, set the bolPlaying variable to true again, show the time to the next slide and restart the timer.function togglePause(e:MouseEvent):void { if(bolPlaying) { mcInfo.btnPlay.visible = true; mcInfo.btnPause.visible = false; bolPlaying = false; mcInfo.lbl_loading.text = "Slideshow paused"; slideTimer.stop(); } else { mcInfo.btnPlay.visible = false; mcInfo.btnPause.visible = true; bolPlaying = true; mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec."; slideTimer.reset(); slideTimer.start(); } }[b]ClearLoader and addSlideContent functions (Removing Memory Leaks)[/b] The clearLoader function is needed to delete the loaded content from the loader itself. To do that, we get the loaderInfo object from the slideLoader and dispose the bitmap data. Like this we are sure, that no content is left in the loader. You can read more about the memory leak in the loader class over at dreaminginflash.function clearLoader():void { try { // get loader info object var li:LoaderInfo = slideLoader.contentLoaderInfo; // check if content is bitmap and delete it if(li.childAllowsParent && li.content is Bitmap){ (li.content as Bitmap).bitmapData.dispose(); } } catch(e:*) {} }There was an issue with the first part of the tutorial. It actually had a memory leak. We we're only adding the content to the sliders but never removed them. So before we add our content, we remove it by disposing the bitmap data and removing the childs from the current container. Then we create a new bitmap from the loader content, clone it and add it to the slider container. The cloning is necessary because we're clearing the content from the slideLoader. So we need to have a copy rather than just a reference to the bitmap data.function addSlideContent():void { // empty current slide and delete previous bitmap while(currentContainer.numChildren){Bitmap(currentContainer.getChildAt(0)).bitmapData.dispose(); currentContainer.removeChildAt(0);} // create a new bitmap with the slider content, clone it and add it to the slider container var bitMp:Bitmap = new Bitmap(Bitmap(slideLoader.contentLoaderInfo.content).bitmapData.clone()); currentContainer.addChild(bitMp); }[b]ShowDescription and hideDescription functions[/b] These two functions are either called when rolling over the invisible button or rolling out of it. Both functions are fist deleting all the tweens for the description movieclip. Then they add a new tween with tweener for showing or hiding the movieclip.function showDescription(e:MouseEvent):void { Tweener.removeTweens(mcInfo.mcDescription); Tweener.addTween(mcInfo.mcDescription, {alpha:1, time:0.5, y: -37}); } function hideDescription(e:MouseEvent):void { Tweener.removeTweens(mcInfo.mcDescription); Tweener.addTween(mcInfo.mcDescription, {alpha:0, alpha:1, time:0.5, y: 25}); }[b]GoToWebsite function[/b] The last function we need to define is the goToWebsite() function. This function will be called once the user clicks on the invisible button. It will check, if the strLink variable is empty or null. If not, the link will be opened.function goToWebsite(e:MouseEvent):void { if(strLink != "" && strLink != null) { navigateToURL(new URLRequest(strLink), strTarget); } }We've already reached the end of the second part of the slideshow tutorial. We hope that you enjoyed reading it and we appreciate any kind of feedback. [b]Note[/b] The feature for clicking the invisible button only works, when you're running the flash on a webserver or in the flash sdk. [b]Full code with comments[/b]// import tweener import caurina.transitions.Tweener; // delay between slides const TIMER_DELAY:int = 5000; // fade time between slides const FADE_TIME:Number = 1; // flag for knowing if slideshow is playing var bolPlaying:Boolean = true; // reference to the current slider container var currentContainer:Sprite; // index of the current slide var intCurrentSlide:int = -1; // total slides var intSlideCount:int; // timer for switching slides var slideTimer:Timer; // slides holder var sprContainer1:Sprite; var sprContainer2:Sprite; // slides loader var slideLoader:Loader; // current slide link var strLink:String = ""; // current slide link target var strTarget:String = ""; // url to slideshow xml var strXMLPath:String = "slideshow-data.xml"; // slideshow xml loader var xmlLoader:URLLoader; // slideshow xml var xmlSlideshow:XML; function initSlideshow():void { // hide buttons, labels and link mcInfo.visible = false; btnLink.visible = false; // create new urlloader for xml file xmlLoader = new URLLoader(); // add listener for complete event xmlLoader.addEventListener(Event.COMPLETE, onXMLLoadComplete); // load xml file xmlLoader.load(new URLRequest(strXMLPath)); // create new timer with delay from constant slideTimer = new Timer(TIMER_DELAY); // add event listener for timer event slideTimer.addEventListener(TimerEvent.TIMER, nextSlide); // create 2 container sprite which will hold the slides and // add them to the masked movieclip sprContainer1 = new Sprite(); sprContainer2 = new Sprite(); mcSlideHolder.addChild(sprContainer1); mcSlideHolder.addChild(sprContainer2); // keep a reference of the container which is currently // in the front currentContainer = sprContainer2; // add event listeners for buttons btnLink.addEventListener(MouseEvent.CLICK, goToWebsite); btnLink.addEventListener(MouseEvent.ROLL_OVER, showDescription); btnLink.addEventListener(MouseEvent.ROLL_OUT, hideDescription); mcInfo.btnPlay.addEventListener(MouseEvent.CLICK, togglePause); mcInfo.btnPause.addEventListener(MouseEvent.CLICK, togglePause); mcInfo.btnNext.addEventListener(MouseEvent.CLICK, nextSlide); mcInfo.btnPrevious.addEventListener(MouseEvent.CLICK, previousSlide); // hide play button mcInfo.btnPlay.visible = false; } function onXMLLoadComplete(e:Event):void { // show buttons, labels and link mcInfo.visible = true; btnLink.visible = true; // create new xml with the received data xmlSlideshow = new XML(e.target.data); // get total slide count intSlideCount = xmlSlideshow..image.length(); // switch the first slide without a delay switchSlide(0); } function fadeSlideIn(e:Event):void { // add loaded slide from slide loader to the // current container addSlideContent(); // clear preloader text mcInfo.lbl_loading.text = ""; // check if the slideshow is currently playing // if so, show time to the next slide. If not, show // a status message if(bolPlaying) { mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec."; } else { mcInfo.lbl_loading.text = "Slideshow paused"; } // fade the current container in and start the slide timer // when the tween is finished Tweener.addTween(currentContainer, {alpha:1, time:FADE_TIME, onComplete:onSlideFadeIn}); } function onSlideFadeIn():void { // check, if the slideshow is currently playing // if so, start the timer again if(bolPlaying && !slideTimer.running) slideTimer.start(); } function togglePause(e:MouseEvent):void { // check if the slideshow is currently playing if(bolPlaying) { // show play button mcInfo.btnPlay.visible = true; mcInfo.btnPause.visible = false; // set playing flag to false bolPlaying = false; // set status message mcInfo.lbl_loading.text = "Slideshow paused"; // stop the timer slideTimer.stop(); } else { // show pause button mcInfo.btnPlay.visible = false; mcInfo.btnPause.visible = true; // set playing flag to true bolPlaying = true; // show time to next slide mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec."; // reset and start timer slideTimer.reset(); slideTimer.start(); } } function switchSlide(intSlide:int):void { // check if the last slide is still fading in if(!Tweener.isTweening(currentContainer)) { // check, if the timer is running (needed for the // very first switch of the slide) if(slideTimer.running) slideTimer.stop(); // change slide index intCurrentSlide = intSlide; // check which container is currently in the front and // assign currentContainer to the one that's in the back with // the old slide if(currentContainer == sprContainer2) currentContainer = sprContainer1; else currentContainer = sprContainer2; // hide the old slide currentContainer.alpha = 0; // bring the old slide to the front mcSlideHolder.swapChildren(sprContainer2, sprContainer1); // delete loaded content clearLoader(); // create a new loader for the slide slideLoader = new Loader(); // add event listener when slide is loaded slideLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, fadeSlideIn); // add event listener for the progress slideLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, showProgress); // load the next slide slideLoader.load(new URLRequest(xmlSlideshow..image[intCurrentSlide].@src)); // show description of the next slide mcInfo.lbl_description.text = xmlSlideshow..image[intCurrentSlide].@title; // set link and link target variable of the slide strLink = xmlSlideshow..image[intCurrentSlide].@link; strTarget = xmlSlideshow..image[intCurrentSlide].@target; mcInfo.mcDescription.lbl_description.htmlText = xmlSlideshow..image[intCurrentSlide].@desc; // show current slide and total slides mcInfo.lbl_count.text = (intCurrentSlide + 1) + " / " + intSlideCount + " Slides"; } } function showProgress(e:ProgressEvent):void { // show percentage of the bytes loaded from the current slide mcInfo.lbl_loading.text = "Loading..." + Math.ceil(e.bytesLoaded * 100 / e.bytesTotal) + "%"; } function goToWebsite(e:MouseEvent):void { // check if the strLink is not empty and open the link in the // defined target window if(strLink != "" && strLink != null) { navigateToURL(new URLRequest(strLink), strTarget); } } function nextSlide(e:Event = null):void { // check, if there are any slides left, if so, increment slide // index if(intCurrentSlide + 1 < intSlideCount) switchSlide(intCurrentSlide + 1); // if not, start slideshow from beginning else switchSlide(0); } function previousSlide(e:Event = null):void { // check, if there are any slides left, if so, decrement slide // index if(intCurrentSlide - 1 >= 0) switchSlide(intCurrentSlide - 1); // if not, start slideshow from the last slide else switchSlide(intSlideCount - 1); } function showDescription(e:MouseEvent):void { // remove tweens Tweener.removeTweens(mcInfo.mcDescription); // fade in the description Tweener.addTween(mcInfo.mcDescription, {alpha:1, time:0.5, y: -37}); } function hideDescription(e:MouseEvent):void { // remove tweens Tweener.removeTweens(mcInfo.mcDescription); // fade out the description Tweener.addTween(mcInfo.mcDescription, {alpha:0, alpha:1, time:0.5, y: 25}); } function clearLoader():void { try { // get loader info object var li:LoaderInfo = slideLoader.contentLoaderInfo; // check if content is bitmap and delete it if(li.childAllowsParent && li.content is Bitmap){ (li.content as Bitmap).bitmapData.dispose(); } } catch(e:*) {} } function addSlideContent():void { // empty current slide and delete previous bitmap while(currentContainer.numChildren){Bitmap(currentContainer.getChildAt(0)).bitmapData.dispose(); currentContainer.removeChildAt(0);} // create a new bitmap with the slider content, clone it and add it to the slider container var bitMp:Bitmap = new Bitmap(Bitmap(slideLoader.contentLoaderInfo.content).bitmapData.clone()); currentContainer.addChild(bitMp); } // init slideshow initSlideshow();