How to preload the next 2 pages in a Flipbook/Flexbook

Hi experts!

This is kind of the second part of a general question I had about the best way/how-to preload images in Flex.

My project is a custom version of Ely Greenfield's Flexbook and I'm pretty much done but there's still one big question remaining: how do I preload the next 2 pages in the book?

I think I understand how the preload process works, creating the images object (that will be used as pages) and then using the preloadContent function. This way, I would have no problem preloading every images of the book. However, this is not what I need.

The reason I only want to preload the next 2 pages is because I don't want to use a loading images of creating a low version of every images I have. I plan of having around 100 pages and making a low version of every pages would be a waste of time.

Also, the images can't be embed, so I'll have to load them externally using a function to get the filename. They will always have the same filename, something like "picture001.jpg, picture002.jpg, picture003.jpg..."

With the help of the experts julianopolito on my last question, here's what I've done so far.

Thanks a lot for the help. Have a nice evening.
Frank

<?xml version="1.0" encoding="iso-8859-2"?>
<mx:Application 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="vertical" 
    xmlns:local="*" 
    xmlns:qs="qs.controls.*" 
    xmlns:effects="qs.effects.*"
    xmlns:containers="qs.containers.*"
    creationComplete="initApp()">
 
<mx:Script>
<![CDATA[
	import mx.controls.Image;
	import qs.caching.ContentCache;
	import qs.controls.flexBookClasses.FlexBookEvent;
	import qs.controls.flexBookClasses.FlexBookPage;
 
 
	private function initApp():void{
	/* This function is called when the application is loaded. */     		
	var pages:XMLList = pages;//gets all the images to preload. This is where my problem comes into play...
 
	for(var i:int=0; i<pages.length(); i++){
	ContentCache.getCache().preloadContent(pages[i]);//preload every pages but I need to load only the next 2...
	}
      
	myIssueID=Application.application.parameters.myissueid;
	sessionID=Application.application.parameters.sessionid;
	}
 
	/* Returns the session ID used to build the image path */
	private function getSessionID():String{
		return Application.application.parameters.sessionid;
	}
                
	/* Returns the book ID used to build the image path */
	private function getMyBookID():String{
		return Application.application.parameters.myissueid;
	}           
        	  
	/* Returns the root used to build the image path */         
	private function getRootPath():String{
		return Application.application.parameters.rootpath;
	}           
         
	/* This is the function that builds the image path */
	private function getFile(fileName:String):String{
		return this.getRootPath() + "VirtualIssue/fileStream.aspx?filename=" + fileName + "&SessionID="+this.getSessionID()+"&MyIssueID="+this.getMyIssueID();
	}
 
	private function loadContent(event:FlexBookEvent):void{
	//this event is dispatched by each page that appears after turn. It's the function that is gonna created the next pages based on the preloaded images.
	//Right now it replace the image filename to load a high resolution one instead, but since I'm only gonna have 1 version of each one of them, this is useless. I simply don't know how to correctly load the cached image!
	
	//in this case the event.renderer is the mx:Image      
	var page:MyImagePage = MyImagePage(event.renderer);
 
	//Check to see it is not null
	if(Boolean(page)){
          
		//change its source to the high res version
		page.theImage.source = String(page.theImage.source).replace("_low","");                             
	}
	}
]]>
</mx:Script>
 
<mx:XMLList id="pages" xmlns="">
	<images>
	<cover><image high="getFile('picture001.jpg')" /></cover>
	<back><image high="getFile('picture008.jpg')" /></back>
	<image high="getFile('picture002.jpg')" />
	<image high="getFile('picture003.jpg')" />
	<image high="getFile('picture004.jpg')" />
	<image high="getFile('picture005.jpg')" />
	<image high="getFile('picture006.jpg')" />
	<image high="getFile('picture007.jpg')" />
	</images>
</mx:XMLList>
 
	<qs:FlexBook id="book" y="47" top="40" horizontalCenter="0" width="718" height="500"
                animateCurrentPageIndex="true"
                showCornerTease="true"
                edgeAndCornerSize="30"
                itemSize="halfPage"
                hardbackCovers="false"
                hardbackPages="false"
		turnEnd="loadContent(event)"
		>
					
		<qs:itemRenderer>
			<mx:Component className="MyImagePage">
              			<mx:VBox>                                                
                        	<qs:Zoomer>
                      			<mx:Image id="theImage" source="{data}" />
				</qs:Zoomer>
                  		</mx:VBox>
			</mx:Component>
		</qs:itemRenderer>
	</qs:FlexBook>		
</mx:Application>

Open in new window

The_Kingpin08Asked:
Who is Participating?
 
julianopolitoConnect With a Mentor Commented:
I have updated the example that I wrote above using the new class. The file above uses the cache manager to preload some images, and then get the cachedData arraycolection to show images on screen, but in order to do that in the example, I added the loader instance directly into a uicomponent, which resulted in not that good result. Instead of that, I updated the example so instead of adding loader into uicomps, I use loader.content property to create image components, which are FAR better than the older version. here it goes
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
layout="vertical"
creationComplete="init()">
<mx:Script>
	<![CDATA[
		import mx.controls.Image;
		import mx.core.UIComponent;
		import polito.cache.CacheManager;
		import polito.cache.events.CacheProgressEvent;
		//URL list of files to cache
		private var myCacheItems:Array = ["images/area.jpg",
		"images/area.jpg",
		"images/bar.jpg",
		"images/bubble.jpg",
		"images/candlehloc.jpg",
		"images/column.jpg",
		"images/hloc.jpg",
		"images/legend.jpg",
		"images/line.jpg",
		"images/pie.jpg",
		"images/plot.jpg"];
		//CacheManager instance
		private var cm:CacheManager;
		
		private function init():void{
			//creates cachemng instance
			//pass it the url list, and tells it to cache in memory
			cm = new CacheManager(myCacheItems,true);
			//mode to download files
			cm.mode = CacheManager.MODE_SEQUENCIAL;
			//format of the cached files
			cm.dataFormat = CacheManager.FORMAT_IMAGE;
			//waits for the completion of all files being downloaded
			cm.addEventListener(Event.COMPLETE,onComplete);
			//listens to the progression of loading files
			cm.addEventListener(CacheProgressEvent.CACHE_PROGRESS,onProgress);
			//starts caching process
			cm.start();
		}
		
		private function onComplete(e:Event):void{
			//Here you call the method to start whatever you
			//need when images have been cached
			//you can also use the cachedData to retrieve 
			//Image/Text/ByteArray items cached 
			//Here is an example of imediately after completing
			//adds all cached images to the aplication 
			for each(var l:Loader in cm.cachedData){
				var ui:Image = new Image();
				ui.source = l.content;
				addChild(ui);
			}
		}
		private function onProgress(e:CacheProgressEvent):void{
			trace("loaded "+(e.itemsLoaded/e.itemsTotal*100)+" %");
			//here you can display some progress
		}	
	]]>
</mx:Script>
</mx:Application>

Open in new window

0
 
julianopolitoCommented:
I think we could work with the currentPageIndex and the XMLList, so whenever a page is opened we start the preload of the next 2 pages too. I'll make an example for you using the caching used by the flexbook example as you asked me. Just gimme sometime cause I won't be able to look at this til tomorrow night
0
 
The_Kingpin08Author Commented:
Thanks a lot again julianopolito.

Can't wait to have your example!
0
Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

 
Robinsonx6Commented:
Hi to continue from my previous help i provided on this project, why not simply embed the images into the application this is whatwe do and it pre loads all the pages with the apllications loader
0
 
julianopolitoCommented:
hi robinson, I think the problem here is that the pages are dynamic, each book can contain any set of pages which can be changed through external data source, so you never know which pages are coming.
0
 
Robinsonx6Commented:
yes then i would agree with your previous about the caching of the pages, its clean and simple these points should be going to you in this case, but ill be interested to see if your implementation code is cleaner than we use, (allways good to improve);


Just a thought how about assigning an arraylist of images before CreationComplete and embedding them there?
0
 
The_Kingpin08Author Commented:
Yes the pages are going to be dynamic.

As for the arraylist, what would be the way to proceed? Insert all the images path in the arraylist, then create a function with a marker that would check the current page and load the next 2 pages based on the marker?
0
 
julianopolitoCommented:
I don't know if I understand what you say, but EMBED is a compiler action, that means anyway of embedding will force you to know which images are comig. The arraylist won't make any difference here. The way of caching Frank is looking for is the one used in the flexbook sample (not really different than the one I've made to him before, but has some tweaks). In my opinion the best way is this:
- Load an xml containing all image urls.
- Iterate through each image node and tel flex to load it (usng urlloder for example)
- Add event listener to all urlloaders created in the loop above, for the complete event.
- Add to a variable each time an image complete. Than you have a progress variable you can compare to the total xml nodes to know the percentage loaded.

Well , anyhow, there are lot's of approachs, depending on what you need. You could also create sequencial image preloading, that way instead of iterating through all the array creating urlloaders for each image, you create 1 urlloader, loads first image, wait until finished , ask the next image , until the end. This would be more bandwidth friendly.
0
 
The_Kingpin08Author Commented:
The approach your described is exactly what I'm looking for. Now that I understand how it should work, I'm trying to create is in my project. I'll post something as soon as I can.

Thanks,
Frank
0
 
Robinsonx6Commented:
Julianopolito, i agree totally with your sequencial image preloading and feel this would be an excellent solution,

sorry for my previous i actually meant an arrayCollection my mistake there, my only tweak would be

- Load an xml containing all image urls.  

- Iterate through each image node and tel flex to load it (usng urlloder for example

---once load event is picked up add the image to the arrayCollection then use the arrayCollection events, just would keep things cleaner due to image indexing and page indexing especially if you are adding objects like Virtual tours to specific pages which can't run as an swf loader, not every control we use in the books can be added to a custom page. Whichever method i used i would still add them to an arrayCollection.

But this comment is just to add thought.
0
 
julianopolitoCommented:
Don't worry Robinson, I think it is really ok. Using an arraycollection would be great. Today I'll try to write down some code. I'll try to write sequencial and parallel preloading algorithms so we have options to discuss.
0
 
julianopolitoCommented:
OK here I am again. I wrote a class instead of just a script. I have commented evrything , so I think you will understand how to use it and how that was made (my main point was to explain to you how to do this).  Any doubts just ask.

DO NOT FORGET to save that file into the package folder:

polito/cache/CacheManager.as
package polito.cache{
	import flash.net.*;
	import mx.collections.*;
	import flash.events.*;
	
	[Event(name="complete", type="flash.events.Event")]
	[Event(name="progress", type="flash.events.ProgressEvent")]
	/**
	 * CacheManager Class - by Juliano Polito (julianopolito@gmail.com)
	 * version 0.1
	 * Intended to pre cache files
	 * 
	 * It can be used 2 ways:
	 * 
	 * MODE_SEQUENCIAL - bandwidth saver, loads one file after another
	 * MODE_PARALLEL - starts all downloads of files altogether 
	 * 
	 * mode:String - choose from one of the above constants
	 * 
	 * keepData:Boolean (default=false) - must be set in constructor. Indicates
	 * if data must be kept in memory
	 * 
	 * cahedData:ArrayCollection - keeps data in memory when keepData is set to true;
	 * 
	 * start():void - starts the caching process according to mode selected
	 * 
	 * Events
	 * 
	 * complete - dispatched when ALL items have been loaded
	 * progress - dispatched when each item is loaded.It is ProgressEvent but:
	 * bytesLoaded=number of items loaded (not bytes)
	 * bytesTotal = total number of items to be loaded (not bytes)
	 * 
	 * 
	 * Example usage:
	 * 
	<?xml version="1.0" encoding="utf-8"?>
	<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
		layout="absolute"
		creationComplete="init()">
		<mx:Script>
			<![CDATA[
				import polito.cache.CacheManager;
				private var myCacheItems:Array = ["images/area.jpg",
				"images/area.jpg",
				"images/bar.jpg",
				"images/bubble.jpg",
				"images/candlehloc.jpg",
				"images/column.jpg",
				"images/hloc.jpg",
				"images/legend.jpg",
				"images/line.jpg",
				"images/pie.jpg",
				"images/plot.jpg"];
				
				private var cm:CacheManager;
				
				private function init():void{
					cm = new CacheManager(myCacheItems,true);
					cm.mode = CacheManager.MODE_PARALLEL;
					cm.start();
					cm.addEventListener(Event.COMPLETE,onComplete);
					cm.addEventListener(ProgressEvent.PROGRESS,onProgress);
				}
				private function onComplete(e:Event):void{
					//Here you call the init method to start whatever you
					//need when images have been cached
					//you can also use the cachedData to retrieve 
					//ByteArray items cached 
				}
				private function onProgress(e:ProgressEvent):void{
					trace("loaded "+(e.bytesLoaded/e.bytesTotal*100)+" %");
					//here you can display some progress
				}	
			]]>
		</mx:Script>
	</mx:Application>
	 * 
	 * 
	 * 
	 * 
	 * 
	 * 
	 * */
	public class CacheManager extends EventDispatcher{
		//mode constants to define the way cache will work
		static public const MODE_SEQUENCIAL:String = "sequencial";
		static public const MODE_PARALLEL:String = "parallel";
		
		//loader instance
		private var loader:URLLoader;
		private var loaders:Array = [];
		//array of url strings to cache
		private var items:Array = [];
		//count for items downloaded
		private var itemsLoaded:int = 0;
		//count items to be downloaded
		private var itemsTotal:int = 0;
		//indicates if items loaded should be kept in memory
		private var keepData:Boolean = false;
		
		//Bindable ArrayCollection where cachedData is kept as binary
		[Bindable]
		public var cachedData:ArrayCollection = new ArrayCollection();
		public var mode:String = MODE_PARALLEL;
		
		
		//Constructor - creates a new cache manager instance and configure it
		public function CacheManager(items:Array, keepData:Boolean = false){
			this.keepData = keepData;
			this.items = items;
			itemsTotal = items.length;
		}
		
		//Starts caching process
		public function start():void{
			//Resets variables
			itemsLoaded = 0;
			cachedData = new ArrayCollection();
			//verify cache mode to start correct method
			switch(mode){
				case MODE_PARALLEL:
					startParallel()
					break;
				case MODE_SEQUENCIAL:
					startSequencial()
					break;
			}
		}
		/**
		 * Parallel mode
		 * This mode creates n instances of URLLoader , one for each cache file
		 * and starts downloading all of them simultaneously
		 * 
		 */
		
		private function startParallel():void{
			for each(var url:String in items){
				loader = new URLLoader();
				loader.dataFormat = URLLoaderDataFormat.BINARY;
				loader.addEventListener(Event.COMPLETE,onLoadPComplete);
				loader.addEventListener(IOErrorEvent.IO_ERROR,onLoadPError);
				loader.addEventListener(HTTPStatusEvent.HTTP_STATUS,onLoadPStatus);
				loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onLoadPSecurity);
				loader.load(new URLRequest(url));
				loaders.push(loader);
			}
		}
		//Listener for parallel loading
		private function onLoadPComplete(e:Event):void{
			itemsLoaded++;
			if(keepData){
				cachedData.addItem(URLLoader(e.target).data);
			}
			progress();
			verifyParallelComplete();
		}
		private function onLoadPError(e:IOErrorEvent):void{
			//trace(e.text);
		}
		private function onLoadPStatus(e:HTTPStatusEvent):void{
			//trace(e.status);
		}
		private function onLoadPSecurity(e:SecurityErrorEvent):void{
			//trace(e.text);
		}
		//verifies if needs to dispatch complete event
		private function verifyParallelComplete():void{
			if(itemsLoaded == itemsTotal){
				complete();
			}
		}
		
		/**
		 * Sequencial Mode
		 * 
		 * This mode starts downloading the first cache file and 
		 * it waits for it to finish loading, then starts next loading
		 * and so on.
		 * This process goes on until all items are loaded
		 * 
		 */ 
		private function startSequencial():void{
			loader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE,onLoadSComplete);
			loader.addEventListener(IOErrorEvent.IO_ERROR,onLoadSError);
			loader.addEventListener(HTTPStatusEvent.HTTP_STATUS,onLoadSStatus);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onLoadSSecurity);
			loadNext();
		}
		private function loadNext():void{
			loader.load(new URLRequest(items[itemsLoaded]));
		}
		//Listener for sequencial loading
		private function onLoadSComplete(e:Event):void{
			itemsLoaded++;
			if(keepData){
				cachedData.addItem(URLLoader(e.target).data);
			}
			progress();
			verifySequencialComplete();
		}
		private function onLoadSError(e:IOErrorEvent):void{
			//here we could place some logic to reload when problems happens
		}
		private function onLoadSStatus(e:HTTPStatusEvent):void{
			//trace(e.status);
		}
		private function onLoadSSecurity(e:SecurityErrorEvent):void{
			//trace(e.text);
		}
		private function verifySequencialComplete():void{
			//verify if got to the end of loading
			if(itemsLoaded == itemsTotal){
				complete();
			}else{
				//if not loads next item
				loadNext();
			}
		}
		
		//dispatches progress events
		private function progress():void{
			dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS,false,false,itemsLoaded,itemsTotal));
		}
		//dispatches complete event when finished
		private function complete():void{
			dispatchEvent(new Event(Event.COMPLETE));
			
		}
	}
}

Open in new window

0
 
Robinsonx6Commented:
Wow nice, even got the arrayCollection in :-) nice work juliano
0
 
julianopolitoCommented:
but do not forget as this is a ALL PURPOSE cache manager, this will load ANY type of file, not just images. the cachedData collection will have its items in RAW BINARY format, so you should not bind it directly, first you must convert that to the correct object type, like Images or text.
0
 
julianopolitoCommented:
or, you could just think of extending the class and change the collection logic so that will have the objects already in the collection. I'll do that and re send the class.
0
 
julianopolitoCommented:
** NEW VERSION **

Here we go again. I had the time to improve that class a little bit now. I did not want to use external libraries, but I think I have got a pretty good result. Now instead of just caching in memory as binary, it caches in image format (as a flash.display.Loader class instance, it is possible to load jpg, png, gif and swf), text (allowing to change loaded charset) and binary. I also improved the example, comments and documentation. There are 2 classes: polito.cache.CacheManager and polito.cache.events.CacheProgressEvent so we can have some more accurate info about the progress.

Change the extension to zip. do not forget to put in correct directory structure in class path
polito/cache/CacheManager.as
polito/cache/events/CacheProgressEvent.as

any thoughts will be appreciated.

About preloading next 2 pages, any time page is turned, call the cachemanager. We could think of implementing a "paged" loading, so we could have a list of urls, but load them by groups. I'll think of that and when I have the time I improve it .



polito.zip.pdf
0
 
The_Kingpin08Author Commented:
Wow thanks a lot, gonna try this tonight!
0
 
The_Kingpin08Author Commented:
Ok I have a basic question  to help me understand what you did:

How does it know what images to load next? Can the application loads the next 2 or 10 images, or it has to load them all entirely?

Also, in the array myCacheItems do I have to insert all my images or only the ones that are gonna be loaded?
0
 
julianopolitoCommented:
well:

1 - it does not know he need to load next 2 or 3 images, it loads all. But all images you put in myCacheItems, so you could just create a CacheManager each time you need to cache 2 items, and start. Also remember not to keep them in memory if you are going to load in pieces
0
 
The_Kingpin08Author Commented:
Ok so let's say I have 5 images the I want to cache.

First I would declare the array of items with the 5 URL to the images.
Then, in the init function I would create a new CacheManager that would cache all of the images inside the array.
Finally, when I the user turns a page, I call the function onComplete to load the correct images in the cache

That's it?
0
 
The_Kingpin08Author Commented:
Ok I made it work and I must say that you did some amazing job. I am really impress with the results!
The only problem I have now is that the cached images are loaded directly on the screen, not in the book.

I think it's because of addChild(ui), and I'm trying to find a way to set the source of the image to the loaded URL.

Also, I'm trying to make a function that will dynamically add URL into the array like you suggest, but I don't know how to insert items into it. I try using the push but only got a couple system error...

Thanks a lot again for the help, I really appreciate what you've done so far.
Frank
private function createCacheArray():void {
   var myCacheItems:Array;
			
   // Dynamically add the next 10 URL into the array	
   for(var i:int = book.currentPageIndex; i < (book.currentPageIndex + 10); i++) {
      myCacheItems.push(this.getFile(i));
   }
}

Open in new window

0
 
julianopolitoCommented:
Hi Frank!

You do not need to use this code that adds the image to screen!
for each(var l:Loader in cm.cachedData){
      var ui:Image = new Image();
      ui.source = l.content;
      addChild(ui);
}


this was just an example of how to do it. Once you receive the complete event, it means that the images were already preloaded, and there is no need to add them to the screen.

The problem in your function is only that you forgot to initialize the array

private function createCacheArray():void {
   var myCacheItems:Array = new Array();//HERE you must init the array before using its methods
                  
   // Dynamically add the next 10 URL into the array      
   for(var i:int = book.currentPageIndex; i < (book.currentPageIndex + 10); i++) {
      myCacheItems.push(this.getFile(i));
   }
}

Also you could do some like this:

for each(var l:Loader in cm.cachedData){
      var ui:Image = new Image();
      ui.source = l.content;
      book.content.addItem(ui);//this would add new image to the content property of the book
}

I just do not remember if addItem would apply here, because I do not have the code in hand now to see which type is content property.

But the main thing to understand here is: CacheManager preloads files to the browser cache, and can also save the preloaded data to an arraycollection. If you save images to array collection, you must convert each image (loader object) to an Image component, using the formula: image.source = loader.content.  Each image can then be added to the book, or you could just preload images without saving in memory, and use the book as usual, calling URLs, because they are already in browser cache.

Hope it helps.





0
 
The_Kingpin08Author Commented:
Thanks for the enlightenment julianopolito, I understand where my mistakes were. I modified my application so now it caches the images and not the URL, but I just can't seem to load them in the pages. The addItem property doesn't exists and I can't find a way to link them.

Based on what you just explained, here's how I want to work:
- The application initializes and creates every pages of the book (so the user can go straight to the last page if he wants)
- While creating every pages of the book, the application also create a new image object and inserts it into the myCacheItems array
- Then, the application creates the CacheManager, sets it's properties and eventListeners
- When a page is turned, the cacheManager is started and loads the next 10 images objects of the array (based on the index of the current page) directly in the next 10 pages.

Am I completely lost or is this supposed to work?
I've attached the script part of my Flexbook with only the cache functions, so if you have time to take a look I would really appreciate!

Thanks a lot for your help, the book is almost done!
Frank


Flexbook-script.mxml.txt
0
 
julianopolitoCommented:
I'll take a look later in the day, and let you know.
0
 
The_Kingpin08Author Commented:
Thanks a lot.

By the way, can you email me at f_parent@hotmail.com there's something I'd like to ask you.

Thanks,
Frank
0
 
The_Kingpin08Author Commented:
Hi Juliano!

Did you have time today to check my problem? Really don't want to rush, I just can't wait to finish this part of the book.

Thanks again!
Frank
0
 
julianopolitoCommented:
BTW, have you received my email?
0
 
julianopolitoCommented:
I took a look at the code, but it is missing too much code so I can understand some key aspects of that application. If you could send complete code I'd better understand it to give you more info.
0
 
The_Kingpin08Author Commented:
Ya I received your email, thanks a lot!

There you go. Let me know if you need anything else.

CustomFlexbook.mxml.txt
0
 
julianopolitoCommented:
Hi Frank. I took a look at your code, but I'm still thinking how to explain that to you in few words. As soon as I can I send you a post.
0
All Courses

From novice to tech pro — start learning today.