locked
Load certain listview fields asynchronously when listview datasource is already async

    Question

  • Hi all,

    my Goal: load product from a REST datasource, and once displayed, asynchronously load price info via another REST service.

    Now for what I already did:

    I have a Windows 8 JavaScript application, in which I am binding a VirtualDataSource retrieving products from a RESTful service to a Listview.

    //fetch data as user scrolls
    itemsFromIndex: function (requestIndex, countBefore, countAfter) {

    <code omitted>

                   return WinJS.xhr(options).then(
                        //Callback for success
                        function (request) {
                            var results = [], count;
                            console.log("request.responseText: " + request.responseText);
                            // Use the JSON parser on the results, safer than eval
                            var obj = JSON.parse(request.responseText);

                            console.log("obj: " + obj);

                            // Verify if the service has returned images
                            if (obj.length > 0) {
                                var items = obj;
                                console.log("returned items: " + items.length);
                                console.log("returned items: " +items[0].productid);
                                // Data adapter results needs an array of items of the shape:
                                // items =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
                                // Form the array of results objects
                                for (var i = 0, itemsLength = items.length; i < itemsLength; i++) {
                                    var dataItem = items[i];
                                    var a = fetchPrice(dataItem.productid);
                                    console.log("shortText = " + a.text);
                                    results.push({
                                        key: (fetchIndex + i).toString(),
                                        data: {
                                            productId: dataItem.productid,
                                            price: a
                                        }
                                    });
                                }


    so far so good... now for the trouble... once I have retrieved all products, I need to display e.g. the price of a product by calling another webservice, but within the same listview template.
    What is the best way of going about this? As you may see, I already added code like fetchPrice, which is another WinJS.xhr call. Is there a better way of doing this? Can I bind a price html-element to a productid which will only become available at runtime?

    Please help!

    Thanks,
    Thomas

    --

    Actually got a bit further now:

                            var obj = JSON.parse(request.responseText);

                            console.log("obj: " + obj);

                            // Verify if the service has returned images
                            if (obj.length > 0) {
                                var items = obj;
                                console.log("returned items: " + items.length);
                                console.log("returned items: " + items[0].productid);
                                var priceText;
                                that.getPriceText(items[0].productid).done(function (b) { priceText = b; console.log("in function : " + priceText); });
                                console.log("priceText is " + priceText);
                                // console.log("var b = " + b.isPrototypeOf);
                                // Data adapter results needs an array of items of the shape:
                                // items =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
                                // Form the array of results objects
                                for (var i = 0, itemsLength = items.length; i < itemsLength; i++) {
                                    var dataItem = items[i];
                                   
                                    results.push({
                                        key: (fetchIndex + i).toString(),
                                        data: {
                                            productId: dataItem.productid,
                                            price: priceText
                                        }
                                    });
                                }

    The console output of the second line saying console.log (console.log("priceTextis " + priceText)) returns undefined: this is expected, as the priceText async call has not yet finished.

    The priceText output within the .done function of the async call is output to the console correctly. (Line reading ("in function : " etc.)

    How can I directly pass the value of the async call into the price field in the lines where items are being pushed onto the return array?
    I have already tried setting price to the .done Promise return value:

    price : that.getPriceText(items[0].productid).done(function (b) { return b; })

    however, in my WebApp, price is then still "undefined", I assume because the first promise returning all items finished and displays all results.
    This is actually what I want: display all product details, and then asynchronously retrieve the price.
    Can anyone suggest the best method to achieve this?

    Thanks!

    Wednesday, March 27, 2013 9:26 PM

Answers

  • Instead of using a HTML item template, use an basic item renderer. (Page 207 of Microsoft Press free ebook Programming Windows 8 Apps with HTML, CSS and Javascript)

    The renderer will be called for each item and within it, since its a function, you can reference each element of the rendered HTML and use Promises to load the async values.

    Quick example below:

    //example of a basic renderer
    var basicRenderer = function(itemPromise){
    return itemPromise.then(function(item){
    //item is each of your items on your data source
    //You can access any property as item.data.Property
    
    var result = document.createElement("div");
    result.innerHTML = "<h2>" + item.data.productId+ "</h2>";
    //now make your async call to get the price
    //for example
    that.getPrice(item.data.productId).then(function(b){
    result.innerHTML+="<p>"+b+"</p>";
    });
    return result; }); }; //Initialization of the listview, commonly on the "ready" handler of the page var listview = document.querySelector("#myListView").winControl; listview.itemDataSource = (new WinJS.Binding.List(results )).dataSource; //your current datasource listview.itemTemplate = basicRenderer;


    • Edited by Ealsur Thursday, March 28, 2013 2:20 AM
    • Marked as answer by thomashoefkens Monday, April 01, 2013 6:39 PM
    Thursday, March 28, 2013 2:19 AM

All replies

  • Instead of using a HTML item template, use an basic item renderer. (Page 207 of Microsoft Press free ebook Programming Windows 8 Apps with HTML, CSS and Javascript)

    The renderer will be called for each item and within it, since its a function, you can reference each element of the rendered HTML and use Promises to load the async values.

    Quick example below:

    //example of a basic renderer
    var basicRenderer = function(itemPromise){
    return itemPromise.then(function(item){
    //item is each of your items on your data source
    //You can access any property as item.data.Property
    
    var result = document.createElement("div");
    result.innerHTML = "<h2>" + item.data.productId+ "</h2>";
    //now make your async call to get the price
    //for example
    that.getPrice(item.data.productId).then(function(b){
    result.innerHTML+="<p>"+b+"</p>";
    });
    return result; }); }; //Initialization of the listview, commonly on the "ready" handler of the page var listview = document.querySelector("#myListView").winControl; listview.itemDataSource = (new WinJS.Binding.List(results )).dataSource; //your current datasource listview.itemTemplate = basicRenderer;


    • Edited by Ealsur Thursday, March 28, 2013 2:20 AM
    • Marked as answer by thomashoefkens Monday, April 01, 2013 6:39 PM
    Thursday, March 28, 2013 2:19 AM
  • Hi Ealsur,

    Thank you for the tip. That does the trick!

    Best regards,

    Thomas

    Monday, April 01, 2013 6:40 PM