How to customize Visual Studio template data (HTML)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

In the Hub/Pivot, Hub, Pivot, Grid, and Split templates, the code that obtains the data needed for the app is in the data.js file. This file represents a sample data source for the app. The data.js file includes static data that you typically need to replace with dynamic data. For example, if your app makes a single xhr request to obtain RSS or JSON data, you may want to include this code in data.js. Including the code there enables you to easily use your own data without changing the data model present in the templates.

Tip  The Hub/Pivot, Hub, and Pivot templates also retrieve static data from .resjson files that support globalization. For more info, see Example of binding data to the UI in the Hub/Pivot, Hub, and Pivot template.

 

You need to be aware of a few things when you add your own data to an app:

  • Groups and items are linked intrinsically. The app expects item data to be organized in groups. You can unlink the two in your own implementation, but you need to modify code to make that implementation work. This topic shows how groups are used in the template data model.
  • When you implement custom data for an app in data.js, you need to make sure that property names inherent to your custom data are mapped to the property names that are used by the template. You can change the names used by the template, but this requires more code revision. This topic shows a few examples of how to do that.

Items and groups

Template data is stored in a WinJS.Binding.List. This code shows the declaration of a list in the data.js file.

var list = new WinJS.Binding.List();

An array of item data (sampleItems in this example) is passed to the WinJS.Binding.List by the push function, as shown here:

generateSampleData.forEach(function (item) {
    list.push(item);
});

WinJS.Binding.List includes internal logic to handle grouping of data. The sampleItems array includes a group property that identifies the group that the item belongs to (in the sample data, groups are specified in the sampleGroups array). Here is the array of item data in the generateSampleData function:

function generateSampleData() {
    // . . .
    var sampleGroups = [
        { key: "group1", title: "Group Title: 1", // . . .
        // . . .
    ];

    var sampleItems = [
        { group: sampleGroups[0], title: "Item Title: 1", // . . .
        // . . .
    ];

    return sampleItems;
}

When you modify the app for your custom data, you might want to follow the same pattern for grouping your data. For smaller data sets, we recommend that you use WinJS.Binding.List for ListView. If you don't group your items, you can still use a WinJS.Binding.List, but you need to modify template code wherever the template expects to find group-based data.

Tip  WinJS.Binding.List is a synchronous data source that uses a JavaScript array. For very large data sets, which could be a few thousand items, you may need to use an asynchronous data source. For more info, see Using ListView.

 

The createGrouped function of the WinJS.Binding.List specifies how to group the items by using a group key and an item group value. This function is called in data.js. key and group are both property names that are specified in the sample data arrays.

var groupedItems = list.createGrouped(
    function groupKeySelector(item) { return item.group.key; },
    function groupDataSelector(item) { return item.group; }
);

When the template app needs a list of items, it calls getItemsFromGroup, which returns a WinJS.Binding.List that contains only the items that belong to the specified group.

function getItemsFromGroup(group) {
    return list.createFiltered(function (item) {
        return item.group.key === group.key;
    });
}

Tip  Functions such as getItemsFromGroup, which call createFiltered, create a new projection of the WinJS.Binding.List, and you may need to dispose of the returned object if you navigate away from a page. To dispose of the object, call the WinJS.Binding.List.dispose method.

 

The Windows Library for JavaScript function define, exposes the data for use in the app by specifying a namespace named Data along with a set of public member functions.

WinJS.Namespace.define("Data", {
    items: groupedItems,
    groups: groupedItems.groups,
    getItemReference: getItemReference,
    getItemsFromGroup: getItemsFromGroup,
    resolveGroupReference: resolveGroupReference,
    resolveItemReference: resolveItemReference
});

If you want to define a different data source for each page in your app, or a different data model, you will have to replace all calls to these members in the JavaScript code.

Binding group and item data to the UI

The following code shows an example of markup for the ListView control. The data source for the ListView is specified in the itemDataSource propety, shown here. This example is from split.html in the Split template.


<div class="itemlist win-selectionstylefilled" aria-label="List of this group's items" data-win-control="WinJS.UI.ListView" data-win-options="{
    layout: {type: WinJS.UI.ListLayout},
    currentItem: {type: WinJS.UI.ObjectType.item, index: 0, hasFocus: true},
    selectionMode: 'single',
    swipeBehavior: 'none',
    tapBehavior: 'toggleSelect',
    itemDataSource: select('.pagecontrol').winControl.itemDataSource,
    itemTemplate: select('.itemtemplate'),
    onselectionchanged: select('.pagecontrol').winControl.selectionChanged
    }">
</div>

In the preceding code, an itemDataSource property associated with the page is assigned to the itemDataSource property of the ListView control.

In the templates, data is typically bound to the UI either in the init function or in the ready function that's defined in the .js file associated with each HTML page. The following code is contained in the init function for split.html . In this code, the app obtains a group reference, and then calls getItemsFromGroup, which is implemented in data.js. As mentioned earlier, getItemsFromGroup returns a WinJS.Binding.List that contains only the items in the specified group.

this._group = Data.resolveGroupReference(options.groupKey);
this._items = Data.getItemsFromGroup(this._group);

We then bind the list returned from getItemsFromGroup to the page's itemDataSource property, which binds the data to the ListView, and we also specify the handler for item selection (_selectionChanged).


this.itemDataSource = this._items.dataSource;
this.selectionChanged = ui.eventHandler(this._selectionChanged.bind(this));

To display each item in the ListView, the app associates a template with the ListView, as illustrated here. This code appears in the markup for the ListView control, and uses the itemTemplate property to specify a DIV element with a class name of itemtemplate.

itemTemplate: select('.itemtemplate')

WinJS templates, which are based on WinJS.Binding.Template, are used to format and display multiple instances of data. The most common template used in the Grid and Split templates is the item template that's used to display items in a ListView. Like every WinJS template object, you declare it by adding a data-win-control attribute and setting the attribute to WinJS.Binding.Template. Here's the HTML code for the itemtemplate in split.html:

<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
    <div class="item">
        <img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
        <div class="item-info">
            <h3 class="item-title win-type-ellipsis" 
                data-win-bind="textContent: title"></h3>
            <h6 class="item-subtitle win-type-ellipsis"
                data-win-bind="textContent: author"></h6>
        </div>
    </div>
</div>

The itemtemplate is used for arbitrary ListView items. The ListView items might be groups or individual data items, depending on context. In items.html, for example, the ListView items are groups.

Important  Templates that you create by using WinJS.Binding.Template are not related to Visual Studio project and item templates, such as Grid and Split.

 

The project templates expect certain properties to be present in the data, and these properties are explicitly named in the HTML. In the preceding HTML code for itemtemplate, you can find properties such as title, subtitle. If your custom app data doesn't use these property names, you need to do one of the following:

  • Map your data to these property names (typically in data.js), or
  • Fix all the HTML and .js code references to these properties in the template code, so that they match the property names used in your data. The properties used in the templates include:
    • title, subtitle, description, and backgroundImage (group and item properties)
    • group and content (item properties)
    • key (group property)

Following the same WinJS template pattern, the Grid App template also uses a headerTemplate in some of its HTML pages.

Example of binding data to the UI in the Hub/Pivot, Hub, and Pivot template

In Visual Studio, the Hub/Pivot, Hub, and Pivot project templates demonstrate how to implement two different data sources:

  • Globalized static data stored in .resjson resource files. This data is used in some of the app sections (PivotItem or HubSection controls.
  • Sample data in data.js, which represents the data model. This file is the same as in the Grid and Split templates. The sample data is used in the ListView control in one of the app sections.

Declarative functions in the HTML are used to obtain sample data initially, and the data model is synchronous by default. Customizing the templates to use dynamic data in all the sections requires a few changes to hub.html, hub.js, and other files. The following sample apps show how to customize the Hub/Pivot and Hub templates to support asynchronous data:

Because the globalized data in the .resjson file is easily replaced, the sample apps leave this resource file unmodified. In the sample apps, data for the <img> elements and the ListView control present in the Hub/Pivot sections are retrieved asynchronously.

For more info on providing globalized data in .resjson files, see Quickstart: translating UI resources

To support binding asynchronous data to the Hub/Pivot's ListView control, first replace these global variables in hub.js that call the data model:

var section3Group = Data.resolveGroupReference("group4");
var section3Items = Data.getItemsFromGroup(section3Group);

with these variable declarations:


var section3Group = "group2";
var section3Items;

You also have to modify the implementation of the declarative functions in hub.js. In the default template implementation, these functions depend on data that is already available (for example, the call to section3Items.dataSource). Replace this code:

section3DataSource: section3Items.dataSource,

section3HeaderNavigate: util.markSupportedForProcessing(function (args) {
    nav.navigate("/pages/section/section.html", { title: args.detail.section.header, 
        groupKey: section3Group.key });
}),

section3ItemNavigate: util.markSupportedForProcessing(function (args) {
    var item = Data.getItemReference(section3Items.getAt(args.detail.itemIndex));
    nav.navigate("/pages/item/item.html", { item: item });
}),

with this:


section3DataSource: null,

section3HeaderNavigate: util.markSupportedForProcessing(function (args) {
    nav.navigate("/pages/section/section.html", { title: args.detail.section.header,
        groupKey: section3Group });
}),

section3ItemNavigate: util.markSupportedForProcessing(function (args) {
    var itemSet = section3Items;
    var itemObj = itemSet.getAt(args.detail.itemIndex);
    var item = [itemObj.group.key, itemObj.title, itemObj.backgroundImage];

    nav.navigate("/pages/item/item.html", { item: item });
}),

This code sets the section3DataSource function to null, to avoid trying to bind the data before it is ready. We will set the data source later in a data binding function (_bindData or bindListView, depending on the sample app).

The data binding function is called once the data is available. To enable this, we add a listener for the data model's dataReady event, which is defined in the sample app in data.js.


this._observer = Data.getObservable();
this._observer.addEventListener('dataReady', this.onDataCompleted.bind(this));

The app calls the data binding function from the onDataCompleted event handler (not shown).The code for the Hub template sample's _bindData function is shown here. In this code, we set the itemDataSource property of the ListView.


_bindData: function (context, grp1Items, grp2Items) {

    var self = context;

    // . . .

    self._items = grp2Items;
    section3Items = self._items;
    self._section3lv.itemDataSource = self._items.dataSource;

    // . . .   

},

If the Back button is used to navigate to the page, the data binding function is called directly from the page's initialization function, because there is no need to wait for new data.

Tip  In the Hub template code, to query the DOM for the ListView element (stored in _section3lv), the app calls a _hubReady function from the Hub control's loadingstatechanged event handler. This event activates only when the hub page has finished loading. Using this event handler allows us to query the DOM to get the nested DIV element associated with the ListView.

 

For the complete code to make the asynchronous data work in the Hub/Pivot and Hub templates, see JSON Web reader that uses the Hub/Pivot template and JSON Web reader that uses the Hub template. In addition to the customizations described here, we made the following changes to the sample app:

  • Included code in the data model (data.js) to retrieve data using an xhr request and to parse JSON data (from Flickr).
  • Included code in the data model to handle multiple data requests and to activate the dataReady event when data returns.
  • Included an input box in the UI to request new data.
  • Included a progress bar to show the status of the data request.
  • Made CSS style additions for the input box and progress bar.
  • Included functions to initialize the page after the Hub control is fully loaded (such as _hubReady or _hubReadyPhone).
  • Modified the Hub/Pivot's or Hub's <img> elements to support click events (modified files are specific to the template).
  • Modified files to bind asynchronous data to the Hub/Pivot's or Hub's <img> elements (modified files are specific to the template).
  • Modified hub.js to support navigation to images from the Hub's <img> elements (modified files are specific to the template).
  • Modified item.html and item.js to support viewing of single images.

Example of binding data to the UI (Grid and Split)

This section shows how to implement your own data source in the Grid and Split project templates. The sample code here uses an xhr request to generate RSS data.

Important  To implement asynchronous data in the Hub template, see Binding data to the UI in the Hub template.

 

Updating data.js

  1. Create a new project in Visual Studio. Use either the Split App or Grid App project template.

  2. In data.js, add the following variables near the beginning of the file, after the use strict statement:

    var lightGray = "data:image/png;base64,
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
    var mediumGray = "data:image/png;base64,
        iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";
    
  3. In data.js, remove the generateSampleData function that contains these arrays: sampleGroups and sampleItems.

    We'll replace this data with RSS data. We won't be using most of the placeholder variables like groupDescription, but we'll re-use placeholder images, lightGray and mediumGray, for the new code to work.

  4. In the same location where you removed generateSampleData, add the following code to data.js:

    
    function getFeeds() {
        // Create an object for each feed.
        blogs = [
            {
                key: "blog1", url: 
           'https://windowsteamblog.com/windows/b/developers/atom.aspx',
                title: 'tbd', subtitle: 'subtitle', updated: 'tbd',
                backgroundImage: lightGray,
                acquireSyndication: acquireSyndication, dataPromise: null
            },
            {
                key: "blog2", url: 
           'https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx',
                title: 'tbd', subtitle: 'subtitle', updated: 'tbd',
                backgroundImage: lightGray,
                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": "Mon, 27 Mar 1972 00:00:00 GMT" }               
        });
    }
    
    function getBlogPosts() {
        getFeeds().then(function () {
            // Process each blog.
            blogs.forEach(function (feed) {
                feed.dataPromise.then(function (articlesResponse) {
                    var articleSyndication = articlesResponse.responseXML;
    
                    // Get the blog title and last updated date.
                    if (articleSyndication) {
                        // Get the blog title and last updated date.
                        feed.title = articleSyndication.querySelector(
                            "feed > title").textContent;
                        var ds = articleSyndication.querySelector(
                            "feed > updated").textContent;
                        var date = ds.substring(5, 7) + "-" +
                            ds.substring(8, 10) + "-" + ds.substring(0, 4);
                        feed.updated = "Last updated " + date;
                        // Process the blog posts.
                        getItemsFromXml(articleSyndication, blogPosts, feed);
                    }
                    else {
                        // There was an error loading the blog. 
                        feed.title = "Error loading blog";
                        feed.updated = "Error";
                        blogPosts.push({
                            group: feed,
                            key: "Error loading blog",
                            title: feed.url,
                            author: "Unknown",
                            month: "?",
                            day: "?",
                            year: "?",
                            content: "Unable to load the blog at " + feed.url
                        });
                    }
                });
            });
        });
    
        return blogPosts;
    }
    
    function getItemsFromXml(articleSyndication, blogPosts, feed) {
        var posts = articleSyndication.querySelectorAll("entry");
        // Process each blog post.
        for (var postIndex = 0; postIndex < posts.length; postIndex++) {
            var post = posts[postIndex];
            // Get the title, author, and date published.
            var postTitle = post.querySelector("title").textContent;
            var postAuthor = post.querySelector("author > name").textContent;
            var pds = post.querySelector("published").textContent;
            var postDate = pds.substring(5, 7) + "-" + pds.substring(8, 10)
                + "-" + pds.substring(0, 4);
            // Process the content so that it displays nicely.
            var staticContent = toStaticHTML(post.querySelector(
                "content").textContent);
            // Store the post info we care about in the array.
            blogPosts.push({
                group: feed, key: feed.title, title: postTitle, 
                author: postAuthor, pubDate: postDate, 
                backgroundImage: mediumGray, content: staticContent
            });
        }
    }
    
  5. In data.js, replace this code:

    var list = new WinJS.Binding.List();
    var groupedItems = list.createGrouped(
        function groupKeySelector(item) { return item.group.key; },
        function groupDataSelector(item) { return item.group; }
    );
    
    // TODO: Replace the data with your real data.
    // You can add data from asynchronous sources whenever it becomes available.
    generateSampleData.forEach(function (item) {
        list.push(item);
    });
    

    with this:

    var dataPromises = [];
    var blogs;
    
    var blogPosts = new WinJS.Binding.List();
    
    var list = getBlogPosts();
    var groupedItems = list.createGrouped(
        function groupKeySelector(item) { return item.group.key; },
        function groupDataSelector(item) { return item.group; }
    );
    

    We'll re-use the code in createGrouped that specifies the grouping—the groupKeySelector and groupDataSelector functions.

    Because we've changed some of the property names used in the templates, we'll need to make a few updates to the HTML pages. Specifically, for any subtitle properties that reference an item (not a group) we need to change subtitle to author. For any description properties that reference an item, we need to change description to pubDate.

    To implement these changes in the UI, see one of the following sections:

    • Binding example data to the UI in the Split template
    • Binding example data to the UI in the Grid template

Binding example data to the UI in the Split template

  1. To use the example code in the Split template, open split.html.

  2. In split.html, we need to changes some lines in the DIV element that has a class name of itemtemplate. Change this line:

    
    <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
    

    to this:

    <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: author"></h6>
    
  3. Also in split.html, the article section (articlesection) has header information that we need to update. Change this line:

    <h4 class="article-subtitle" data-win-bind="textContent: subtitle"></h4>
    

    to this:

    <h4 class="article-subtitle" data-win-bind="textContent: author"></h4>
    
  4. Open items.html.

    The WinJS item template defined in the HTML code contains arbitrary ListView items. In items.html, the template is used to show groups (blogs). The only group property we need to change here is subtitle.

    <h6 class="item-subtitle win-type-ellipsis" 
        data-win-bind="textContent: subtitle"></h6>
    
  5. Change the subtitle property to updated, as shown here:

    <h6 class="item-subtitle win-type-ellipsis"
        data-win-bind="textContent: updated"></h6>
    
  6. Save the project, and then press F5 to debug the app.

    You see the page title immediately, but there's a short delay while the feed data is retrieved. When all the promises are fulfilled, you see each blog in the home page. Click one of the blogs to see the blog posts in master/detail view.

Binding example data to the UI in the Grid template

Before following these steps, update the data.js project file as described in Example of binding data to the UI.

  1. To use the RSS example code in the Grid template, open groupDetail.html.

    This page displays a single group (a blog) and the individual items (blog posts) that are part of the group.

  2. In groupDetail.html, we need to change some lines in the DIV element that has a class name of item-info. Change these lines:

    
    <h6 class="item-subtitle win-type-ellipsis"
        data-win-bind="textContent: subtitle"></h6>
    <h4 class="item-description" 
        data-win-bind="textContent: description"></h4>
    

    to these:

    <h6 class="item-subtitle win-type-ellipsis" 
        data-win-bind="textContent: author"></h6>
    <h4 class="item-description" 
        data-win-bind="textContent: pubDate"></h4>
    

    In groupDetail.html, the header template describes group information, not individual items. So we don't have to change the subtitle property. Here's the header template:

    
    <div class="headertemplate" data-win-control="WinJS.Binding.Template">
        <h2 class="group-subtitle" data-win-bind="textContent: subtitle"></h2>
        <img class="group-image" src="#" 
            data-win-bind="src: backgroundImage; alt: title" />
        <h4 class="group-description" data-win-bind="innerHTML: description"></h4>
    </div>
    
  3. However, we don't have a description property for each group (we have this property for items), so we have to change this property to updated in the preceding code, as shown here.

    <h4 class="group-description" data-win-bind="innerHTML: updated"></h4>
    
  4. Open groupedItems.html, which displays all the groups and their individual blog posts.

  5. In this page, the generic WinJS item template displays individual items (blog posts), so we need to update the subtitle property. Change this:

    <h6 class="item-subtitle win-type-ellipsis" 
        data-win-bind="textContent: subtitle"></h6>
    

    to this:

    <h6 class="item-subtitle win-type-ellipsis" 
        data-win-bind="textContent: author"></h6>
    
  6. Save the project and then press F5 to debug the app.

    You see the page title immediately, but there's a short delay while the feed data is retrieved. When the data returns, fulfilling the promises, you see the items in each blog in the home page. Click a group heading to see the group page, or click on an item to see an individual blog post.

Code listing for data.js

Here is the complete code listing for data.js. The same data.js file is used for both the Grid and the Split template examples shown previously. For the data.js file for the Hub/Pivot template, see JSON Web reader that uses the Hub/Pivot template. For the data.js file for the Hub template, see JSON Web reader that uses the Hub templates.


(function () {
    "use strict";

    
    var lightGray = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
    var mediumGray = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";


    var dataPromises = [];
    var blogs;

    var blogPosts = new WinJS.Binding.List();

    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,
        getItemReference: getItemReference,
        getItemsFromGroup: getItemsFromGroup,
        resolveGroupReference: resolveGroupReference,
        resolveItemReference: resolveItemReference
    });

    // 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];
    }

    // 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; });
    }

    // Get the unique group corresponding to the provided group key.
    function resolveGroupReference(key) {
        return groupedItems.groups.getItemFromKey(key).data;
    }

    // Get a unique item from the provided string array, which should contain a
    // group key and an item title.
    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;
            }
        }
    }



    function getFeeds() {
        // Create an object for each feed.
        blogs = [
            {
                key: "blog1", url:
           'https://windowsteamblog.com/windows/b/developers/atom.aspx',
                title: 'tbd', subtitle: 'subtitle', updated: 'tbd',
                backgroundImage: lightGray,
                acquireSyndication: acquireSyndication, dataPromise: null
            },
            {
                key: "blog2", url:
           'https://windowsteamblog.com/windows/b/windowsexperience/atom.aspx',
                title: 'tbd', subtitle: 'subtitle', updated: 'tbd',
                backgroundImage: lightGray,
                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": "Mon, 27 Mar 1972 00:00:00 GMT" }
        });
    }

    function getBlogPosts() {
        getFeeds().then(function () {
            // Process each blog.
            blogs.forEach(function (feed) {
                feed.dataPromise.then(function (articlesResponse) {
                    var articleSyndication = articlesResponse.responseXML;

                    if (articleSyndication) {
                        // Get the blog title and last updated date.
                        feed.title = articleSyndication.querySelector(
                            "feed > title").textContent;
                        var ds = articleSyndication.querySelector(
                            "feed > updated").textContent;
                        var date = ds.substring(5, 7) + "-" +
                            ds.substring(8, 10) + "-" + ds.substring(0, 4);
                        feed.updated = "Last updated " + date;
                        // Process the blog posts.
                        getItemsFromXml(articleSyndication, blogPosts, feed);
                    }
                    else {
                        // There was an error loading the blog. 
                        feed.title = "Error loading blog";
                        feed.updated = "Error";
                        blogPosts.push({
                            group: feed,
                            key: "Error loading blog",
                            title: feed.url,
                            author: "Unknown",
                            month: "?",
                            day: "?",
                            year: "?",
                            content: "Unable to load the blog at " + feed.url
                        });
                    }
                });
            });
        });

        return blogPosts;
    }

    function getItemsFromXml(articleSyndication, blogPosts, feed) {
        var posts = articleSyndication.querySelectorAll("entry");
        // Process each blog post.
        for (var postIndex = 0; postIndex < posts.length; postIndex++) {
            var post = posts[postIndex];
            // Get the title, author, and date published.
            var postTitle = post.querySelector("title").textContent;
            var postAuthor = post.querySelector("author > name").textContent;
            var pds = post.querySelector("published").textContent;
            var postDate = pds.substring(5, 7) + "-" + pds.substring(8, 10)
                + "-" + pds.substring(0, 4);
            // Process the content so that it displays nicely.
            var staticContent = toStaticHTML(post.querySelector(
                "content").textContent);
            // Store the post info we care about in the array.
            blogPosts.push({
                group: feed, key: feed.title, title: postTitle,
                author: postAuthor, pubDate: postDate,
                backgroundImage: mediumGray, content: staticContent
            });
        }
    }

})();

JavaScript project templates

JavaScript item templates

Adding data to a project template (C#, VB, and C++)