How to group items in a ListView (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 ]

Learn how to group items in a ListView. To display group information, such as group headers and group boundaries, your ListView must use a grid layout. For grouping to work at all, the ListView control's loadingBehavior property must be set to "randomaccess" (which is the default value).

What you need to know

Technologies

Prerequisites

Instructions

Step 1: Create your data

Grouping requires two data sources: an IListDataSource that contains the items, and an IListDataSource that contains the groups. In the items IListDataSource, each item contains a groupKey property that links it to the group it belongs to in the group IListDataSource.

  1. Add a new JavaScript file to your project to contain your data. Name it "data.js".

  2. In the data.js file you just created, create the underlying data source that will supply your ListView control with data.

    One way to create an IListDataSource is to create a WinJS.Binding.List. Each WinJS.Binding.List has a dataSource property that returns an IListDataSource that contains your data.

    This example creates a WinJS.Binding.List from an array of JSON objects (myData):

    
    // Start of data.js
    (function () {
        "use strict";
    
    
    
        var myData = [
        { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Lavish Lemon Ice", text: "Sorbet", picture: "images/60Lemon.png" },
        { title: "Lavish Lemon Ice", text: "Sorbet", picture: "images/60Lemon.png" },
        { title: "Lavish Lemon Ice", text: "Sorbet", picture: "images/60Lemon.png" },
        { title: "Lavish Lemon Ice", text: "Sorbet", picture: "images/60Lemon.png" },
        { title: "Marvelous Mint", text: "Gelato", picture: "images/60Mint.png" },
        { title: "Marvelous Mint", text: "Gelato", picture: "images/60Mint.png" },
        { title: "Marvelous Mint", text: "Gelato", picture: "images/60Mint.png" },
        { title: "Marvelous Mint", text: "Gelato", picture: "images/60Mint.png" },
        { title: "Creamy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Creamy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Creamy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Creamy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Succulent Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Succulent Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Succulent Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Succulent Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Very Vanilla", text: "Ice Cream", picture: "images/60Vanilla.png" },
        { title: "Very Vanilla", text: "Ice Cream", picture: "images/60Vanilla.png" },
        { title: "Very Vanilla", text: "Ice Cream", picture: "images/60Vanilla.png" },
        { title: "Very Vanilla", text: "Ice Cream", picture: "images/60Vanilla.png" },
        { title: "Orangy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Orangy Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Absolutely Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Absolutely Orange", text: "Sorbet", picture: "images/60Orange.png" },
        { title: "Triple Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Triple Strawberry", text: "Sorbet", picture: "images/60Strawberry.png" },
        { title: "Double Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Double Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Double Banana Blast", text: "Low-fat frozen yogurt", picture: "images/60Banana.png" },
        { title: "Green Mint", text: "Gelato", picture: "images/60Mint.png" }
        ];
    
        // Create a WinJS.Binding.List from the array. 
        var itemsList = new WinJS.Binding.List(myData);
    

    Note  This data refers to several images. To get the images, download the ListView grouping and SemanticZoom sample, then copy the images from the sample into your project. You can also use your own images—just be sure to update the value of the picture property in your data.

     

    Tip  

    You aren't limited to using an WinJS.Binding.List: you could also use a a custom VirtualizedDataSource. (StorageDataSource does not support grouping.) For more info about creating a custom data source, see How to create a custom data source.

     

  3. Create a version of your data source that contains grouping info. If you're using a WinJS.Binding.List, you can call its createGrouped method to create a grouped version of the List.

    The createGrouped method takes 3 parameters:

    • getGroupKey: a function that, given an item in the list, returns the group key that the item belongs to.
    • getGroupData: a function that, given an item in the list, returns the data object that represents the group that the item belongs to.
    • compareGroups: a function used to sort groups so that group A comes before group B. The function takes two group keys as input, compares the two groups, and returns a value less than zero if the first group is less than the second group, zero if the groups are the same, and a positive value if the first group is greater than the second group.

    The createGrouped method returns an WinJS.Binding.List that contains two projections of the data from the original ungrouped list. The projections are dynamic, so, if you modify the list, modify the original.

    This example uses the List.createGrouped method to create a grouped version of the List. It uses the first letter of each item's title to define the groups.

        // Sorts the groups
        function compareGroups(leftKey, rightKey) {
            return leftKey.charCodeAt(0) - rightKey.charCodeAt(0);
        }
    
        // Returns the group key that an item belongs to
        function getGroupKey(dataItem) {
            return dataItem.title.toUpperCase().charAt(0);
        }
    
        // Returns the title for a group
        function getGroupData(dataItem) {
            return {
                title: dataItem.title.toUpperCase().charAt(0)
            };
        }
    
        // Create the groups for the ListView from the item data and the grouping functions
        var groupedItemsList = itemsList.createGrouped(getGroupKey, getGroupData, compareGroups);
    
  4. Make your data accessible from the global scope. That way, when you create your ListView, you can access the data declaratively (we show you how in Step 2.3).

    This example uses WinJS.Namespace.define to make the grouped list publicly accessible.

        WinJS.Namespace.define("myData",
            {
                groupedItemsList: groupedItemsList
            }); 
    
    
    })(); // End of data.js
    

Step 2: Create a ListView that uses the grid layout

Next, create a ListView and connect it to your data.

  1. In the head section of your HTML page that will contain the ListView, add a reference to the data file you created in the previous step.

    
    
        <!-- Your data file. -->
        <script src="/js/data.js"></script>
    
  2. In the body of your HTML file, create a ListView. Set its layout property to GridLayout.

    
    <div id="groupedListView"
        data-win-control="WinJS.UI.ListView" 
        data-win-options="{layout: {type: WinJS.UI.GridLayout}}"
    ></div>
    
  3. Set the ListView control's itemDataSource property to the grouped items data source.

    In Step1, you created a namespace member that contains the grouped items you want to display: myData.groupedItemsList. Calling this field returns a WinJS.Binding.List. To obtain an IListDataSource that the ListView can use, call it's dataSource property: myData.groupedItemsList.dataSource.

    
    <div id="groupedListView"
        data-win-control="WinJS.UI.ListView" 
        data-win-options="{itemDataSource: myData.groupedItemsList.dataSource, 
            layout: {type: WinJS.UI.GridLayout}}"
    ></div>
    
  4. Next, set the ListView control's groupDataSource property to the data source that contains the group data. You use the List object's groups property to obtain another List that contains the group information. To obtain an IListDataSource, you call myData.groupedItemsList.groups.dataSource.

    
    <div id="groupedListView"
        data-win-control="WinJS.UI.ListView" 
        data-win-options="{itemDataSource: myData.groupedItemsList.dataSource, 
            groupDataSource: myData.groupedItemsList.groups.dataSource,
            layout: {type: WinJS.UI.GridLayout}}">
    </div>
    

Run the app. Because you haven't specified item templates. the data isn't formatted:

A ListView displaying raw data for a group.

Step 3: Create an item template and a group header template

In your HTML page, before you define your ListView, create a WinJS.UI.Template named "mediumListIconTextTemplate" and set the ListView control's itemTemplate property to the name of this template.


<div id="mediumListIconTextTemplate" 
    data-win-control="WinJS.Binding.Template" 
    style="display: none">
    <div class="mediumListIconTextItem">
        <img class="mediumListIconTextItem-Image" data-win-bind="src: picture" />
        <div class="mediumListIconTextItem-Detail">
            <h4 data-win-bind="innerText: title"></h4>
            <h6 data-win-bind="innerText: text"></h6>
        </div>
    </div>
</div>

<div id="groupedListView"
    data-win-control="WinJS.UI.ListView" 
    data-win-options="{itemDataSource: myData.groupedItemsList.dataSource, 
        itemTemplate: select('#mediumListIconTextTemplate'),
        groupDataSource: myData.groupedItemsList.groups.dataSource,
        layout: {type: WinJS.UI.GridLayout}}">
</div>

(Note that the template specifies several styles. We define those later.)

Run the app. The items, but not the group headers, are formatted.

A ListView displaying raw data for a group.

Step 4: Create a group header template

Define a WinJS.UI.Template for the group header and give it an ID of "headerTemplate". Set the ListView control's groupHeaderTemplate property to this template.


<div id="headerTemplate" data-win-control="WinJS.Binding.Template" 
    style="display: none">
    <div class="simpleHeaderItem">
        <h1 data-win-bind="innerText: title"></h1>
    </div>
</div>

<div id="mediumListIconTextTemplate" 
    data-win-control="WinJS.Binding.Template" 
    style="display: none">
    <div class="mediumListIconTextItem">
        <img class="mediumListIconTextItem-Image" data-win-bind="src: picture" />
        <div class="mediumListIconTextItem-Detail">
            <h4 data-win-bind="innerText: title"></h4>
            <h6 data-win-bind="innerText: text"></h6>
        </div>
    </div>
</div>

<div id="groupedListView"
    data-win-control="WinJS.UI.ListView" 
    data-win-options="{itemDataSource: myData.groupedItemsList.dataSource, 
        itemTemplate: select('#mediumListIconTextTemplate'),
        groupDataSource: myData.groupedItemsList.groups.dataSource,
        groupHeaderTemplate: select('#headerTemplate'),
        layout: {type: WinJS.UI.GridLayout}}">
</div>

Run the app. The items and the group headers are now formatted.

A ListView displaying grouped data.

Step 5: Style your templates

If you want to specify in more detail how your items and headers look, you can your own CSS styles to your style sheet. The CSS in this example styles the items and headers and the ListView itself.


/* CSS for the ListView */
#groupedListView
{
    width: 600px;
    height: 300px;
    border: solid 2px rgba(0, 0, 0, 0.13);
}

/* Template for headers */
.simpleHeaderItem
{
    width: 50px;
    height: 50px;
    padding: 8px;
}   

/* Template for items */  
.mediumListIconTextItem
{
    width: 282px;
    height: 70px;
    padding: 5px;
    overflow: hidden;
    display: -ms-grid;
}

    .mediumListIconTextItem img.mediumListIconTextItem-Image 
    {
        width: 60px;
        height: 60px;
        margin: 5px;
        -ms-grid-column: 1;
    }

    .mediumListIconTextItem .mediumListIconTextItem-Detail
    {
        margin: 5px;
        -ms-grid-column: 2;
    }

When you run the app, your items are divided into groups:

A ListView with grouped items

You can also use the win-groupheader and win-container CSS classes to style your groups and items. For more info, see Styling the ListView and its items.

Remarks

Sorting and filtering items and groups

WinJS.Binding.List can sort and filter your items and groups. For more info, see the createSorted and createFiltered methods.

Create a zoomed-out view of your groups

Now that you know how to create a grouped ListView, there isn't much more you need to learn to use the SemanticZoom control to create a zoomed-out view of your groups.

A SemanticZoom control's zoomed-out and zoomed-in views

For instructions on using the SemanticZoom, see Quickstart: Adding SemanticZoom controls.

Grouped ListView controls that contain interactive headers

When your grouped ListView control contains interactive headers, we recommend that you support the Ctrl+Alt+G keyboard shortcut and use it to move the user into the group they are currently navigating. It should provide the same behavior as clicking or tapping the group header itself.

Deleting groups and scrolling

When a group is deleted, the ListView might scroll to an unexpected location, so call the ensureVisible method to scroll to a position that makes sense for your app.

Complete example

For a complete sample showing how to create a grouped ListView, see theListView grouping and SemanticZoom sample.

ListView grouping and SemanticZoom

Quickstart: Adding SemanticZoom controls