locked
Best Way to preload images in a WinJS.UI.ListView

    Question

  • What is the best way to preload thumbnail images within a ListView control object? I would like to display a loading image while the desired image is loading and I would like to display an error image if the desired image doesn't load. 

    Here is my current item template HTML code used for the listView:

        <div class="itemtemplate" data-win-control="WinJS.Binding.Template">
            <div class="item">
                <div class="imageHolder">
                    <img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
                </div>
                <div class="item-info">
                    <h4 class="item-title" data-win-bind="textContent: title"></h4>
                    <h4 class="item-description" data-win-bind="textContent: description"></h4>
                </div>
            </div>
        </div>

    Also is there a way to request that the images in the ListView do not load until it is visible to the user? So if there are 200 images in the ListView but only 10 are visible at a time, then only 10 should be loaded until the user drags the ListView so other images are visible.

    Are these 2 features available with a built in WinJS control?


    • Edited by Jaxim Tuesday, September 30, 2014 12:38 AM added additional related question
    Monday, September 29, 2014 9:29 PM

Answers

  • Thanks, Matt.

    For those interested, I found another link which describes how to implement the rendering function:

    http://msdn.microsoft.com/en-us/library/windows/apps/hh700705.aspx

    While I was waiting for a response, I found two more solutions, which i will label as Solution #2 and 3. I will refer to Matt's solution as Solution #1.

    Solution #2:

    The ListView control has a "onloadingstatechanged" event which is triggered whenever its items are written to the page. I attached to the listView an event handler found in the pageControl. I attached the event handler in the following way:

    (in HTML of the ListView tag)

    <div class="itemslist win-selectionstylefilled" aria-label="List of this section's items" data-win-control="WinJS.UI.ListView" data-win-options="{ onloadingstatechanged: select('.pagecontrol').winControl.listLoaded,

    ...

    (In JavaScript of the PageControl's init function)

    			this.listLoaded = ui.eventHandler(this._listLoaded.bind(this));

    (In JavaScript of the PageControl, I create a _listLoaded function)

    Please note, that I am using jQuery in this example

    _listLoaded: function (args) {
    	if (_listView != null) {
    		if (_listView.loadingState == "viewPortLoaded") {	
    			var $images = $(".item");
    			$images.each(function (index) {
    				var $imageSrc = $(this).find(".imageSrc");
    				var sURL = $imageSrc.text();//This div's HTML text is bound to the item's source URL. Get the URL and then use it to load it in the image tag
    				var $image = $(this).find("img"); //find the image tag
    				if (sURL != null && sURL.length > 0 && ($image.attr("src").length <= 0 || $image.attr("src").indexOf("http") < 0)) {
    					//Because the "viewPortLoaded" event will be called whenever items are rewritten to the page, only load images that haven't yet been loaded.
    					$image.parent().addClass("loading");//add the "loading" CSS class so the image appears to be loading
    					$image.attr("src", sURL);
    
    					//I'm using a jQuery plugin here, but it's not necessary
    					//When the image has loaded, then the "loading" CSS class will be removed so the loading indicator is hiddebn and the loaded image is visible
    					$image.imagesLoaded({ done: onImageLoaded, fail: onImageError });
    				}
    			});
    		}
    
    	}
    }

    The advantage of Solution #2 over Solution #1 is that the template of the ListView's items is kept int he HTML, not JavaScript. I prefer the design to be kept in the HTML. The disadvantage is that this solution seems overly complicated, which leads me to the next solution.

    Solution #3

    I think this is the best of the 3 solutions. I just found it this morning. It has the advantage of Solution #2 that the design is kept in the HTML, but is not very complicated. Plus, one implementation of Solution #3 is reusable for other instances of ListViews in your app. Solution #1 seemed like it can only be used to create the template of a specific ListView. So unless all your ListViews share the same template, then you may not be able to reuse the implementation of Solution #1. However, Solution #3 only concerns itself about the loading of the image, so it can be reused for any ListView control or anything else in your code that requires a loading of an image.

    The first step is to create an HTML template for your ListView's items like you would normally do. But this time you should place your img tag within a parent tag that is used as a custom control.

    For example: 

    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
            <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader" 
                                              data-win-options="{root: 'cards'}">
                <img class="item-image" data-win-bind="src: logo; alt: name" src="#" />
            </div>
            ...

    Placing the custom "DelayImageLoader" control reference in the item template is detailed on this page:

    http://blogs.msdn.com/b/eternalcoding/archive/2012/04/19/how-to-cook-a-complete-windows-8-application-with-html5-css3-and-javascript-in-a-week-day-1.aspx

    The details on how to create a custom control is detailed on the following page. Within the function of the custom control, you can specify how to hide and load an item's image.

    http://blogs.msdn.com/b/eternalcoding/archive/2012/04/16/how-to-cook-a-complete-windows-8-application-with-html5-css3-and-javascript-in-a-week-day-0.aspx

    I'll copy and paste the custom control here in case the above link ever goes dead. The following code excerpt doesn't make it obvious, but the "loaded" CSS class ensures that the image is hidden until it has been loaded. The CSS class also ensures that a loading indicator is displayed until the image has loaded. Go to the above URL for a more detailed description. 

    (function () {
    
        var delayImageLoader = WinJS.Class.define(
                function (element, options) {
                    this._element = element || document.createElement("div");
                    this.element.winControl = this;
                    WinJS.Utilities.addClass(this.element, "imageLoader");
                    WinJS.Utilities.query("img", element).forEach(function (img) {
                        img.addEventListener("load", function () {
                            WinJS.Utilities.addClass(img, "loaded");
                        });
                    });
                },
                {
    
    
                    element: {
                        get: function () { return this._element; }
                    },
                });
    
        WinJS.Namespace.define("UrzaGatherer.Tools", {
            DelayImageLoader: delayImageLoader
        });
    })();

    I think I prefer Solution #3 because it allows me to separate the app's design using HTML, it's not overly complicated, and it can be reused in other places in the code where I want to load an image and display a loading indicator.



    • Marked as answer by Jaxim Tuesday, September 30, 2014 3:09 PM
    • Edited by Jaxim Wednesday, October 1, 2014 9:51 PM typos
    Tuesday, September 30, 2014 3:08 PM

All replies

  • From Using ListView (HTML) at http://msdn.microsoft.com/en-us/library/windows/apps/Hh781224.aspx:

    Custom render function

    You can define custom render function for converting a data record into its HTML representation. The advantages of implementing a custom renderer are:

    • It can perform customized element recycling.
    • It can supply placeholder elements.
    • It can render items progressively.
    • It can make incremental requests for data, if needed.


    Matt Small - Microsoft Escalation Engineer - Forum Moderator
    If my reply answers your question, please mark this post as answered.

    NOTE: If I ask for code, please provide something that I can drop directly into a project and run (including XAML), or an actual application project. I'm trying to help a lot of people, so I don't have time to figure out weird snippets with undefined objects and unknown namespaces.

    Tuesday, September 30, 2014 12:30 PM
    Moderator
  • Thanks, Matt.

    For those interested, I found another link which describes how to implement the rendering function:

    http://msdn.microsoft.com/en-us/library/windows/apps/hh700705.aspx

    While I was waiting for a response, I found two more solutions, which i will label as Solution #2 and 3. I will refer to Matt's solution as Solution #1.

    Solution #2:

    The ListView control has a "onloadingstatechanged" event which is triggered whenever its items are written to the page. I attached to the listView an event handler found in the pageControl. I attached the event handler in the following way:

    (in HTML of the ListView tag)

    <div class="itemslist win-selectionstylefilled" aria-label="List of this section's items" data-win-control="WinJS.UI.ListView" data-win-options="{ onloadingstatechanged: select('.pagecontrol').winControl.listLoaded,

    ...

    (In JavaScript of the PageControl's init function)

    			this.listLoaded = ui.eventHandler(this._listLoaded.bind(this));

    (In JavaScript of the PageControl, I create a _listLoaded function)

    Please note, that I am using jQuery in this example

    _listLoaded: function (args) {
    	if (_listView != null) {
    		if (_listView.loadingState == "viewPortLoaded") {	
    			var $images = $(".item");
    			$images.each(function (index) {
    				var $imageSrc = $(this).find(".imageSrc");
    				var sURL = $imageSrc.text();//This div's HTML text is bound to the item's source URL. Get the URL and then use it to load it in the image tag
    				var $image = $(this).find("img"); //find the image tag
    				if (sURL != null && sURL.length > 0 && ($image.attr("src").length <= 0 || $image.attr("src").indexOf("http") < 0)) {
    					//Because the "viewPortLoaded" event will be called whenever items are rewritten to the page, only load images that haven't yet been loaded.
    					$image.parent().addClass("loading");//add the "loading" CSS class so the image appears to be loading
    					$image.attr("src", sURL);
    
    					//I'm using a jQuery plugin here, but it's not necessary
    					//When the image has loaded, then the "loading" CSS class will be removed so the loading indicator is hiddebn and the loaded image is visible
    					$image.imagesLoaded({ done: onImageLoaded, fail: onImageError });
    				}
    			});
    		}
    
    	}
    }

    The advantage of Solution #2 over Solution #1 is that the template of the ListView's items is kept int he HTML, not JavaScript. I prefer the design to be kept in the HTML. The disadvantage is that this solution seems overly complicated, which leads me to the next solution.

    Solution #3

    I think this is the best of the 3 solutions. I just found it this morning. It has the advantage of Solution #2 that the design is kept in the HTML, but is not very complicated. Plus, one implementation of Solution #3 is reusable for other instances of ListViews in your app. Solution #1 seemed like it can only be used to create the template of a specific ListView. So unless all your ListViews share the same template, then you may not be able to reuse the implementation of Solution #1. However, Solution #3 only concerns itself about the loading of the image, so it can be reused for any ListView control or anything else in your code that requires a loading of an image.

    The first step is to create an HTML template for your ListView's items like you would normally do. But this time you should place your img tag within a parent tag that is used as a custom control.

    For example: 

    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
            <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader" 
                                              data-win-options="{root: 'cards'}">
                <img class="item-image" data-win-bind="src: logo; alt: name" src="#" />
            </div>
            ...

    Placing the custom "DelayImageLoader" control reference in the item template is detailed on this page:

    http://blogs.msdn.com/b/eternalcoding/archive/2012/04/19/how-to-cook-a-complete-windows-8-application-with-html5-css3-and-javascript-in-a-week-day-1.aspx

    The details on how to create a custom control is detailed on the following page. Within the function of the custom control, you can specify how to hide and load an item's image.

    http://blogs.msdn.com/b/eternalcoding/archive/2012/04/16/how-to-cook-a-complete-windows-8-application-with-html5-css3-and-javascript-in-a-week-day-0.aspx

    I'll copy and paste the custom control here in case the above link ever goes dead. The following code excerpt doesn't make it obvious, but the "loaded" CSS class ensures that the image is hidden until it has been loaded. The CSS class also ensures that a loading indicator is displayed until the image has loaded. Go to the above URL for a more detailed description. 

    (function () {
    
        var delayImageLoader = WinJS.Class.define(
                function (element, options) {
                    this._element = element || document.createElement("div");
                    this.element.winControl = this;
                    WinJS.Utilities.addClass(this.element, "imageLoader");
                    WinJS.Utilities.query("img", element).forEach(function (img) {
                        img.addEventListener("load", function () {
                            WinJS.Utilities.addClass(img, "loaded");
                        });
                    });
                },
                {
    
    
                    element: {
                        get: function () { return this._element; }
                    },
                });
    
        WinJS.Namespace.define("UrzaGatherer.Tools", {
            DelayImageLoader: delayImageLoader
        });
    })();

    I think I prefer Solution #3 because it allows me to separate the app's design using HTML, it's not overly complicated, and it can be reused in other places in the code where I want to load an image and display a loading indicator.



    • Marked as answer by Jaxim Tuesday, September 30, 2014 3:09 PM
    • Edited by Jaxim Wednesday, October 1, 2014 9:51 PM typos
    Tuesday, September 30, 2014 3:08 PM