locked
Problem using win-ring in grid template RRS feed

  • Question

  • Im using Grid template to build a simple RSS reader app. What I need is a win-ring that is shown while the app is downloading the posts and when the download completes the win-ring control should disappear.

    Well, I have done the above mentioned thing successfully by following code:

    HTML:

    <progress class="win-ring" id="win-ring"></progress>

    JavaScript to download the feed:

    function getItemsFromXml(articleSyndication, blogPosts, feed) { var items = articleSyndication.querySelectorAll("item"); // Process each blog post. for (var n = 0; n < items.length; n++) { var article = {}; // Get the title, author, and date published. article.title = items[n].querySelector("title").textContent; article.author = items[n].querySelector("creator").textContent; article.date = items[n].querySelector("pubDate").textContent; var thumbs = items[n].querySelectorAll("content"); article.content = items[n].querySelector("description").textContent; var staticContent = toStaticHTML(items[n].querySelector("description").textContent); if (thumbs.length > 1) { article.thumbnail = thumbs[thumbs.length - 1].attributes.getNamedItem("url").textContent; } else{ var firstindex = article.content.indexOf("<img"); if (firstindex !== 1) { var secondindex = article.content.indexOf("src=", firstindex) + 5; var thirdindex = article.content.indexOf("\"", secondindex); article.thumbnail = article.content.slice(secondindex, thirdindex); }

            document.getElementById('win-ring').style.visibility = "hidden";

    } blogPosts.push({ group: feed, key: feed.title, title: article.title, description: article.content, thumbnail: article.thumbnail, content: staticContent, creator: article.author, date: article.date }); } }

    I have shown the code in bold that I am using to hide the win-ring. It works fine but real problem starts when I click on any of the articles and navigate to detail page and then come back to this main page.....the win-ring re-appears!!!! and keeps on rotating behind my articles forever.

    How can I hide it permanently? I dont want it to come back when I navigate back from article page(itemDetail.html) to main page (groupedItems.html)

    I have tried above code in blank template and it works fine without any problem. I need help in Grid Template.

    Monday, February 18, 2013 8:41 PM

Answers

  • So having taken a look at your test project, I want to step back and look at the larger picture of this kind of app.

    Starting with the launch experience, what's happening in your code structure is that you're starting to download data as soon as data.js is loaded, which is well before the first page control (groupedItems) even comes into the scene.

    By the time that the ListView in the page control actually gets assigned the WinJS.Binding.List data source (the one created in data.js), there will already be some items inside of it. As items continue to be populated in that list, they will appear in the ListView.

    In this sense, the dynamically populating ListView on app launch is already serving pretty well as a progress indicator, the purpose of which is really to show the user that something is happening and the app just hasn't hung.

    Now to the specific problem of the control not being instantiated--this is happening because you've defined it in the page control, so it won't exist in the DOM until that page control is loaded. The earliest you could even try to address that element is in the processed method of the page control (see page 125 of my book). The ready method, where you're hiding the control, is actually the first event you can handle where you know that the control is in memory and also visible (that is, in the rendered DOM).

    So if you want something visible prior to that time, you'll need to define it in default.html, which will be loaded much sooner, rather than in the page control.

    You can then make it visible within the "activated" event listener in default.js. This is the WinJS.app.onactivated handler (sorry to have not made that clearer before). Note that in this case, you'll need to work the layout of default.html so that <progress> control is visible and not overlaid by the page control that otherwise occupies the whole screen. You might find it easier with such a layout to use a horizontal progress control instead of a ring, as you can have it appear on the top or bottom of the screen.

    Now when you get to the ready method in groupedItems.js, you don't necessarily know whether all the posts have been downloaded, so you can't really use it to hide the progress indicator. You only really know that all of it's complete when you hit WinJS.Promise.join(dataPromises) in data.js. That's when you can hide the progress indicator.

    (Note, by the way, that loading data.js in groupedItems.html and any other page control is unnecessary. It's already been loaded into the script context, and the page controls are just inserted into the DOM and thus share that context.)

    Overall, then, a progress indicator, if you still think you need one with the dynamically-populating list, is only needed when you're chewing on a data feed. When you navigate to a details page from the list, then navigate back, you're not rebuilding the WinJS.Binding.List again at all, just re-rendering that page control (and in such cases, it can be a better strategy to not navigate at all, but just render the details page in a div on top of the list page, so that you don't have to rebuild the list page's DOM all over again).

    If you later add a command that would refresh the feed, then you'd want to show the <progress> again while that feed is being processed. Again, you probably want a linear progress control along the top or bottom, because otherwise the ListView will obscure it.

    Hope this makes some sense.

    .Kraig

    Tuesday, February 26, 2013 6:46 AM

All replies

  • What's happening when you navigate back is that the HTML for your main page is being reloaded and re-rendered. Because the progress control is visible in that HTML, it will reappear. So you need to check whether the control should be visible in the groupedItems page's ready method, and set its visibility accordingly.

    I'd actually recommend that you reverse your strategy: style the control so it's hidden by default, and only make it visible when necessary. That way, when the page is reloaded, it won't appear at all.

    As an aside, you might want to use the display style instead of visibility. By setting display to "none" you remove the control from any layout; when using visibility, the element still participates in layout. See http://www.w3schools.com/css/css_display_visibility.asp.

    Kraig

    Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press


    • Proposed as answer by No.18MVP Monday, February 18, 2013 11:47 PM
    Monday, February 18, 2013 9:14 PM
  • Thanks for your reply Kraig

    I have reversed my strategy but its not working for me. Its display is set to "none" by default. I have tried the showing code everywhere in my feed download code so that as soon as it starts downloading the feed it shows up the ring using display : "block" but it doesn't work at all.

    Is there a way to add the <progress> tag programmatically and then remove it after feed download? Otherwise, as you said earlier, its in HTML and it will render it everytime I navigate back to that page.

    I followed this guide when I started to work on my application:
    http://msdn.microsoft.com/en-us/library/windows/apps/hh758329.aspx

    Tuesday, February 19, 2013 9:30 PM
  • Seeing more of your code would help, but here's something I've used successfully. First, in my HTML I have a simple progress element:

    <progress id="progressRing" class="win-ring win-large"></progress>

    I then use this function to show/hide:

    function showProgress(id, show) {
        var progress = document.getElementById(id);
        progress.style.display = show ? "" : "none";        
    }

    When the page is loaded, I call this function like so:

    showProgress("progressRing", false);

    Then when I start some operation where I need it to show, I call:

    showProgress("progressRing", true);

    And then call it again with false when everything is done.

    You can try this out in the ImageManipulation sample from Chapter 10 of my book (linked below--you need to download the companion content for it). In that chapter the code is a little simpler because I don't have the id parameter on the showProgress function; that variant is from Chapter 16 but I show it here because it's more generically useful.

     

    Kraig

    Author, Programming Windows 8 Apps with HTML, CSS, and JavaScript, a free ebook from Microsoft Press

    Tuesday, February 19, 2013 10:49 PM
  • I have tried the above mentioned solution but when I set it to true in my data.js (this is where im downloading the RSS feed) the code breaks and I get "0x800a138f - JavaScript runtime error: Unable to get property 'style' of undefined or null reference".

    Here is complete data.js code: Please suggest me where should I set it as true?


    (function () {
        "use strict";
        var dataPromises = [];
        var blogs;
        var blogPosts = new WinJS.Binding.List();
    
        function showProgress(id, show) {
            var progress = document.getElementById(id);
            progress.style.display = show ? "" : "none";
        }
    
        function getFeeds() {
            // Create an object for each feed.
            blogs = [
                {
                    key: "blog1", url:'http://www.exampleurl.com/RSS',
                    title: 'tbd', subtitle: 'subtitle', updated: 'tbd',
    
                    acquireSyndication: acquireSyndication, dataPromise: null
                }
            ]
            // Get the content for each feed in the blog's array.
            blogs.forEach(function (feed) {
                feed.dataPromise = feed.acquireSyndication(feed.url);
                dataPromises.push(feed.dataPromise);
            });
    
            // Return when all asynchronous operations are complete
            return WinJS.Promise.join(dataPromises).then(function () {
                return blogs;
            });
    
        };
    
        function acquireSyndication(url) {
            return WinJS.xhr({
                url: url, headers: {
                    "If-Modified-Since": "Fri, 30 Nov 2012 00:00:00 GMT"
                }
            });
        }
        function getBlogPosts() {
            getFeeds().then(function () {
    
                blogs.forEach(function (feed) {
                    feed.dataPromise.then(function (articlesResponse) {
                        var articleSyndication = articlesResponse.responseXML;
                        feed.title = articleSyndication.querySelector(
                            "title").textContent;
                        var ds = articleSyndication.querySelector(
                            "pubDate").textContent;
                        getItemsFromXml(articleSyndication, blogPosts, feed);
                        
                    });
                });   
            });
    
            return blogPosts;
        }
        function getItemsFromXml(articleSyndication, blogPosts, feed) {
            var items = articleSyndication.querySelectorAll("item");
            // Process each blog post.
            for (var n = 0; n < items.length; n++) {
                var article = {};
                article.title = items[n].querySelector("title").textContent;
                article.author = items[n].querySelector("creator").textContent;
                article.date = items[n].querySelector("pubDate").textContent;
                var thumbs = items[n].querySelectorAll("content");
                article.content = items[n].querySelector("description").textContent;
                var staticContent = toStaticHTML(items[n].querySelector("description").textContent);
                if (thumbs.length == 0) {
                    article.thumbnail = defaultimage;
                }
               
                if (thumbs.length > 1) {
                    article.thumbnail = thumbs[thumbs.length - 1].attributes.getNamedItem("url").textContent;
                }
                
                if(thumbs.length < 1) {
                    var firstindex = article.content.indexOf("<img");
                    if (firstindex !== 1) {
                        var secondindex = article.content.indexOf("src=", firstindex) + 5;
                        var thirdindex = article.content.indexOf("\"", secondindex);
                        article.thumbnail = article.content.slice(secondindex, thirdindex); 
                    }
                }
    
                // Store the post info we care about in the array.
                blogPosts.push({
                    group: feed, key: feed.title, title: article.title, description: article.content,
                    thumbnail: article.thumbnail, content: staticContent, creator: article.author, date: article.date
                });
    
            }
        }
    
        // Get a reference for an item, using the group key and item title as a
        // unique reference to the item that can be easily serialized.
        function getItemReference(item) {
            return [item.group.key, item.title];
        }
        function resolveGroupReference(key) {
            for (var i = 0; i < groupedItems.groups.length; i++) {
                if (groupedItems.groups.getAt(i).key === key) {
                    return groupedItems.groups.getAt(i);
                }
            }
        }
        function resolveItemReference(reference) {
            for (var i = 0; i < groupedItems.length; i++) {
                var item = groupedItems.getAt(i);
                if (item.group.key === reference[0] && item.title === reference[1]) {
                    return item;
                }
            }
        }
        // This function returns a WinJS.Binding.List containing only the items
        // that belong to the provided group.
        function getItemsFromGroup(group) {
            return list.createFiltered(function (item) { return item.group.key === group.key; });
        }
        var list = getBlogPosts();
        var groupedItems = list.createGrouped(
            function groupKeySelector(item) { return item.group.key; },
            function groupDataSelector(item) { return item.group; }
        );
        WinJS.Namespace.define("Data", {
            items: groupedItems,
            groups: groupedItems.groups,
            getItemsFromGroup: getItemsFromGroup,
            getItemReference: getItemReference,
            resolveGroupReference: resolveGroupReference,
            resolveItemReference: resolveItemReference
        });
    })();
    
    If it doesn't help I can send you a complete working example of it. 

    Wednesday, February 20, 2013 7:59 PM
  • You need to call showProgress when you know that the DOM has actually been created. If I read this code correctly, getBlogPosts is being called as soon as data.js is loaded. So if you just reference data.js at the top of an HTML file, it will be executed before the DOM gets created. Instead, export getBlogPosts from your Data namespace, then call that method from within the WinJS.Application.onactivated handler. (The activated event is always fired after DOMContentLoaded.)

    Then you can call showProgress(true) at the start of getBlogPosts and with false when it's complete or an error.

    Just to note, your getBlogPosts function is probably returning the blogPosts variable when the list is still empty. You have several async operations going on there, and so as soon as you call getFeeds.then you'll return from getBlogPosts. The completed function give to .then will be called sometime later, as will the completed functions for the feed.dataPromise.then methods.

    This is fine, though, as it means that the WinJS.Binding.List will be populated as feeds are processed, and if you have a WinJS ListView on that, the items should appear as they're ready. Just wanted to point that out.

    Wednesday, February 20, 2013 11:38 PM
  • Thanks, let me check this one.
    Thursday, February 21, 2013 8:36 AM
  • I have tried to call the show method from within the WinJS.Application.onactivated but no luck....I get same error: Unable to get property 'style' of undefined or null reference.

    I think I should leave the win-ring thing alone at the moment because I have spent almost 3 days on it. It should not be that much tricky in grid template while its simple to handle in blank and navigation templates.  

    Thursday, February 21, 2013 8:57 PM
  • Drop me your code somewhere when you have a chance and I'll take a look. I'm curious to know why this isn't working and having the answer in this post will help others as well.

    .Kraig

    Friday, February 22, 2013 6:17 PM
  • Thanks Kraig, I have a example VS solution file for this one. How can I send it to you?
    Friday, February 22, 2013 7:35 PM
  • It's probably small enough you can just email it to me (address removed)
    Friday, February 22, 2013 7:39 PM
  • I have just sent an email to you :)

    Friday, February 22, 2013 7:46 PM
  • So having taken a look at your test project, I want to step back and look at the larger picture of this kind of app.

    Starting with the launch experience, what's happening in your code structure is that you're starting to download data as soon as data.js is loaded, which is well before the first page control (groupedItems) even comes into the scene.

    By the time that the ListView in the page control actually gets assigned the WinJS.Binding.List data source (the one created in data.js), there will already be some items inside of it. As items continue to be populated in that list, they will appear in the ListView.

    In this sense, the dynamically populating ListView on app launch is already serving pretty well as a progress indicator, the purpose of which is really to show the user that something is happening and the app just hasn't hung.

    Now to the specific problem of the control not being instantiated--this is happening because you've defined it in the page control, so it won't exist in the DOM until that page control is loaded. The earliest you could even try to address that element is in the processed method of the page control (see page 125 of my book). The ready method, where you're hiding the control, is actually the first event you can handle where you know that the control is in memory and also visible (that is, in the rendered DOM).

    So if you want something visible prior to that time, you'll need to define it in default.html, which will be loaded much sooner, rather than in the page control.

    You can then make it visible within the "activated" event listener in default.js. This is the WinJS.app.onactivated handler (sorry to have not made that clearer before). Note that in this case, you'll need to work the layout of default.html so that <progress> control is visible and not overlaid by the page control that otherwise occupies the whole screen. You might find it easier with such a layout to use a horizontal progress control instead of a ring, as you can have it appear on the top or bottom of the screen.

    Now when you get to the ready method in groupedItems.js, you don't necessarily know whether all the posts have been downloaded, so you can't really use it to hide the progress indicator. You only really know that all of it's complete when you hit WinJS.Promise.join(dataPromises) in data.js. That's when you can hide the progress indicator.

    (Note, by the way, that loading data.js in groupedItems.html and any other page control is unnecessary. It's already been loaded into the script context, and the page controls are just inserted into the DOM and thus share that context.)

    Overall, then, a progress indicator, if you still think you need one with the dynamically-populating list, is only needed when you're chewing on a data feed. When you navigate to a details page from the list, then navigate back, you're not rebuilding the WinJS.Binding.List again at all, just re-rendering that page control (and in such cases, it can be a better strategy to not navigate at all, but just render the details page in a div on top of the list page, so that you don't have to rebuild the list page's DOM all over again).

    If you later add a command that would refresh the feed, then you'd want to show the <progress> again while that feed is being processed. Again, you probably want a linear progress control along the top or bottom, because otherwise the ListView will obscure it.

    Hope this makes some sense.

    .Kraig

    Tuesday, February 26, 2013 6:46 AM