How to add common functionality to the grid template (Windows Store apps using JavaScript and HTML)

Windows 8: This topic applies only to Windows 8. For more information about using a hierarchical navigation pattern in Windows 8.1, see Hierarchical navigation, start to finish (HTML).

You can extend the Microsoft Visual Studio Grid Application template to include additional core functionality that adds value to your application. This tutorial is meant to be used with Microsoft Visual Studio 2012 on Windows 8. You can find the app package for the completed tutorial here.

This is the default Grid Application template landing page.

Screen shot of the default grid application template landing page

After completing the tutorial, your app will have multi-sized items that use different templates.

Screen shot of grid app with multi-sized items with different templates

It will also have an app bar that shows contextual commands when items are selected.

Screen shot of app with an app bar and contextual commands when items are selected

It will implement the SemanticZoom control.

Screen shot of app that implements the Semantic Zoom control

And it will animate fluidly among all the pages in the app.

The grid application template

Using a Visual Studio app template is a great way to get started quickly on an app with "baked-in" Microsoft design style. Because not all developers’ needs are the same, not all functionality is included in the templates. This tutorial shows you how to customize and add value to a template-based app while still following the guidelines for Microsoft design style. Specifically, it shows how to customize an app based on the Grid Application template.

Each section of this tutorial focuses on how to add a specific feature to the Grid Application template. We add transition page animations, an app bar with global and contextual commands, the SemanticZoom control, and multi-sized items in the grid. We explain the motivation behind each addition, and provide step-by-step guidance on how to add the feature to the template. Although we focus on the Grid Application template, you can also apply many of the lessons you learn here to the Split Application template.

Getting started

Before you start this tutorial, please complete the following steps.

  1. Launch Visual Studio 2012 and select File > New Project.
  2. Create a new JavaScript project using the Grid Application template.

Adding page transition animations

Apps with multiple pages should animate page transitions when the user navigates among pages. In this section, we add page transition animations to pages in the Grid Application template.

Motivation

The enter page animation ensures that navigating between pages feels fast and fluid. You should include these animations whenever the user transitions from one page to another.

Implementation

In each page of the template, we add a function that returns the elements on the page we wish to animate, in the order they should be animated. Then we add a function to Navigator.js that ensures that the system calls the animations each time the user navigates to a page.

To learn more about page transition animations, check out the HTML animation library sample.

  1. In GroupedItems.js, add a function to ui.Pages.define that returns an array of HTML elements. This function provides the header and the page section that contains the grid view.

    getAnimationElements: function () {
        return [[this.element.querySelector("header")], [this.element.querySelector("section")]];
    },
    

    Note  As the previous example shows, you need to add a comma at the end of this function unless it is the last function in the list. If it is the last function, you need to add a comma before the function. The same rule applies to the following steps.

  2. In GroupDetail.js, add a function to ui.Pages.define that returns an array of HTML elements to animate.

    getAnimationElements: function () {
        return [[this.element.querySelector("header")], [this.element.querySelector("section")]];
    },
    
  3. In ItemDetail.js, add a function to ui.Pages.define that returns an array of HTML elements to animate.

    getAnimationElements: function () {
        return [[this.element.querySelector("header")], [this.element.querySelector(".content")]];
    },
    
  4. In Navigator.js, add a function to WinJS.Namespace.define that attempts to retrieve the elements on a page to animate. If the page does not provide a function that returns an array of elements, we animate the whole page.

    _getAnimationElements: function () {
        if (this.pageControl && this.pageControl.getAnimationElements) {
            return this.pageControl.getAnimationElements();
        }
        return this.pageElement;
    },
    
  5. To set the correct UI focus after the animation is completed, we need to add a single function to each page. We call this function after the enter animation is completed.

    In GroupedItems.js, add a function to ui.Pages.define that sets the focus of the UI to a control in the page.

    setPageFocus: function () {
        this.element.querySelector(".groupeditemslist").winControl.element.focus();
    },
    

    Note  As the previous example shows, you need to add a comma at the end of this function unless it is the last function in the list. If it is the last function, you need to add a comma before the function. The same rule applies to the following steps.

    In GroupDetail.js, add a function to ui.Pages.define that sets the focus of the UI to a control in the page.

    setPageFocus: function () {
        this.element.querySelector(".itemslist").winControl.element.focus();
    },
    

    In ItemDetail.js, add a function to ui.Pages.define that sets the focus of the UI to a control in the page.

    setPageFocus: function () {
        this.element.querySelector(".content").focus();
    },
    

    Update the ready function in each page (ItemDetail.js, GroupDetail.js, and GroupedItems.js) to remove the following lines:

    • GroupedItems.js: listView.element.focus();
    • GroupDetail.js: listView.element.focus();
    • ItemDetail.js: element.querySelector(".content").focus();
  6. Finally, add the enter page animation. In Navigator.js, add the following code to the top of the navigated function (not _navigated) to set the animations in motion.

    navigated: function () {
        // Add the following two lines of code.
        var that = this;
        WinJS.UI.Animation.enterPage(that._getAnimationElements(), null).done(function () { 
            that.pageControl.setPageFocus.bind(that.pageControl); 
            });
    
        var backButton = this.pageElement.querySelector("header[role=banner] .win-backbutton");
    
        ...
    

Add an app bar

Here we add an app bar that contains global or contextual commands that the user might want to perform.

Motivation

One of the principles of the Microsoft design style is "content before chrome". This is the idea that less is more, and that only the most relevant elements should be on screen. In line with this principle, Windows 8 apps can make use of the app bar, a common control that allows apps to put relevant and contextual commands in an easy-to-reach location off the screen. Frequently used commands are near the right and left edges for easy reach by hand. The app bar allows the app surface to focus on content rather than controls. Because the app bar is used throughout the system, users will be familiar with app bar interactions. This increases the usability of your app and makes the whole system feel more cohesive.

In practice

The Website to Windows Store app case study shows how app developers can re-think how to support commands, by using the app bar. Below is a walkthrough of how to upload a photo. On the left is a step-by-step walkthrough of how this would be done on the web. On the right is the equivalent scenario that makes use of Microsoft design style.

Screen shots of steps to upload a photo

In the preceding walkthrough, the website shows the command to Upload your photos on the page. In contrast, the Windows Store app uses the app bar to show the action to upload a photo. Then the command in the app bar opens up the file picker. This is just one example of how apps can take actions and put them in the app bar to do more with less and choose content over chrome.

Guidance

Commands in the app bar can vary depending on the page the user is viewing or the context of the app. Global commands appear when the user navigates to a page, while contextual (or selection) commands appear when certain items are selected. We’ll look at contextual commands in Making the grid selectable and showing contextual app bar commands.

Note  Commands that the user needs to complete a workflow, such as sending an email or purchasing a product, are an exception and can be placed on the canvas.

Example overview

In this part of the tutorial, we add an app bar to the Grouped Items page and the Item Detail page of the Grid Application template. We add commands that let the user change the size of the labels on the Grouped Items page, and the size of the body text on the Item Detail page. We only show commands that are relevant to the page being viewed, and we won't show the app bar on the Group Detail page.

In the following screen shot, the app bar on the Grouped Items page shows the Increase label size command:

Screen shot of the Grouped Items page showing the Increase label size command in the app bar

This screen shot shows the Item Detail page with the app bar showing the Increase text size command:

Screen shot of the Item Detail page showing the Increase text size command in the app bar

Implementation

In the following steps we add a global app bar with global app bar buttons. We show and hide the buttons in the ready functions of each page. We hook up the commands to event listeners that call event handlers to perform the functionality described previously. We disable the app bar on the Group Detail page because that page contains no commands to show.

  1. In Default.html, you'll find an app bar in the template, but it’s been commented out. Remove the comments to make the app bar part of the HTML.

  2. The app bar div element contains a placeholder button. Replace it with the following buttons.

    <button id="labelSize" data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'labelSize', section:'global', label:'Label size', icon:'add'}"></button>
    <button id="textSize" data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'textSize', section:'global', label:'Text size', icon:'add'}"></button>
    
  3. In Default.js, add the following event listeners.

    app.addEventListener("activated", function (args) {
        if (args.detail.kind === activation.ActivationKind.launch) {
            if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                // TODO: This application has been newly launched. Initialize your 
                // application here.
    
                // Add the following two lines of code.                                 
                document.getElementById("labelSize").onclick = increaseLabelSize;
                document.getElementById("textSize").onclick = increaseTextSize;
            } else {
    
            ...
    
  4. In Default.js, add the following functions next to the other functions (scoped to the file).

    function increaseLabelSize() {
        var titles = document.getElementsByClassName("item-title");
        var subtitles = document.getElementsByClassName("item-subtitle");
        var i;
        for (i = 0; i < titles.length; i++) {
            var prevTitleAttributes = titles[i].getAttribute("style");
            var prevSubtitleAttributes = subtitles[i].getAttribute("style");
            if (prevTitleAttributes != null)
                titles[i].setAttribute("style", prevTitleAttributes + "font-size:20px");
            else
                titles[i].setAttribute("style", "font-size:20px");
            if (prevSubtitleAttributes != null)
                subtitles[i].setAttribute("style", prevSubtitleAttributes + "font-size: 14px");
            else
                subtitles[i].setAttribute("style", "font-size: 14px");
        }
    };
    
    function increaseTextSize() {
        var content = document.getElementsByClassName("item-content");
        content[0].setAttribute("style", "font-size:20px");
    };
    
  5. Ensure that we only show the relevant commands in each page.

    In GroupedItems.js, add the following code to the ready function:

    appbar.winControl.disabled = false;
    appbar.winControl.hideCommands([textSize]);
    appbar.winControl.showCommands([labelSize]);
    

    In GroupDetail.js, add the following line of code to the ready function.

    appbar.winControl.disabled = true;
    

    In ItemDetail.js, add the following code to the ready function.

    appbar.winControl.disabled = false;
    appbar.winControl.hideCommands([labelSize]);
    appbar.winControl.showCommands([textSize]);
    

Making the grid selectable and showing contextual app bar commands

Here we add a ListView to let the user select items that they want to act upon. To learn more about the ListView, check out the HTML ListView sample pack.

Motivation

The user may want to perform a number of different actions on an item in the ListView. We divide the actions into a primary action, and a number of secondary actions. A primary action can be performed by tapping or clicking on an item. This typically causes the user to navigate to the item.

The user performs a secondary action by selecting an item and using the app bar to perform a command on the item. The user can perform a variety of secondary actions on an item in the grid, such as deleting an item, adding an item to Start, marking an item as a favorite, marking an item as read, renaming an item, and so on. The user can select items by sliding perpendicular to the panning direction, right-clicking with the mouse, or pressing the space bar on the keyboard.

In practice

The iPad to Windows 8 Windows Store app case study shows how apps can use a selectable grid and an app bar to re-think contextual commands. Following is a walkthrough of how to delete photos from a photo journal app. On the left is a step-by-step walkthrough of how this would be done on the iPad. On the right is the equivalent scenario that uses Microsoft design style.

Screen shots of steps to delete photos on the iPad and in a Windows Store app

In the iPad app, the user first enters selection mode, and then the contextual commands appear on the top of the screen. In the Windows Store app, the contextual commands are hidden off-screen in the app bar. In step A, the user invokes the app bar. When it is first invoked, no items are selected in the grid, so only the global command is shown. In step B, the user begins to select items in the grid, which shows the contextual commands on the left portion of the app bar.

Description

When an item is selected, the app bar should be invoked to show the commands that are contextual to the selected item. As multiple items are selected, the app bar should remain up and should show commands that are contextual to all selected items. If the user deselects items, when the last item is deselected, the app bar should be hidden.

When showing the app bar with contextual commands, global commands should always continue to be shown. Global commands should be right-aligned in the app bar, and contextual commands should be left-aligned.

You can choose to allow only one or multiple items to be selected at a time.

When an app is snapped, the app should maintain as much state as possible. When the app bar is up, if the app is snapped, the app bar should remain up. If an item is selected and the app is snapped, the item should remain selected, if possible.

However, when snapping the Grouped Items page, the snapped view shows the zoomed out view of the groups. As a result, it isn't possible to show selection of individual items while in the snapped state. Because individual items cannot be shown, any contextual commands to previously selected items should be hidden.

Example overview

In this section of the tutorial, we make the items in the grid selectable. This allows multiple items to be selected at a time. After an item is selected, the app bar is immediately invoked, and the contextual command to mark the item as read appears. This turns the title of all selected items gray. Deselecting the item hides the app bar. Choosing to mark items as read performs the action, deselects the items, and hides the app bar.

This screen shot shows the Grouped Items page with multiple items selected and an app bar showing the contextual command Mark as read, and the global command Increase label size:

Screen shot showing the Grouped Items page with multiple items selected, and an app bar showing a contextual command and a global command

Implementation

The following steps we enable selection of grid items. We also add an event listener that displays an app bar that contains contextual commands for the selected item.

  1. In GroupedItems.html, make the GridView items selectable by changing the selection mode of the ListView from 'none' to 'multi'.

    data-win-options="{ selectionMode: 'multi' }"
    
  2. In Default.html, add the following contextual app bar command next to the other app bar commands.

    <button id="markItem" data-win-control="WinJS.UI.AppBarCommand" data-win-options="{id:'markItem', section:'selection', label:'Mark as read', icon:'accept'}"></button>
    
  3. In Default.js, add the following event listeners next to the other app bar event listeners in app.onactivated handler.

    document.getElementById("markItem").onclick = markItem;
    
  4. In Default.js, add the markItem function next to the other app bar command functions.

    function markItem() {
        var titles = document.getElementsByClassName("item-title");
        var subtitles = document.getElementsByClassName("item-subtitle");
        var listView = document.querySelector(".groupeditemslist").winControl;
        var items = listView.selection.getItems();
        var i;
        for (i = 0; i < items._value.length; i++) {
            var key = parseFloat(items._value[i].key);
            if (titles[0].innerHTML != "") {
                if (key == 0) continue;
                key--;
            }
            var prevTitleAttributes = titles[key].getAttribute("style");
            var prevSubtitleAttributes = subtitles[key].getAttribute("style");
            if (prevTitleAttributes != null)
                titles[key].setAttribute("style", prevTitleAttributes + "color:gray");
            else
                titles[key].setAttribute("style", "color:gray");
    
            if (prevSubtitleAttributes != null)
                subtitles[key].setAttribute("style", prevSubtitleAttributes + "color:gray");
            else
                subtitles[key].setAttribute("style", "color:gray");
        }
        listView.selection.clear();
    }
    
  5. In ItemDetail.js, modify the call to the hideCommands function to also hide the Mark Item button.

    appbar.winControl.hideCommands([labelSize, markItem]);
    
  6. In GroupedItems.js, modify the call to the hideCommands function to also hide the Mark Item button.

    appbar.winControl.hideCommands([textSize, markItem]);
    
  7. In GroupedItems.js, add the following functions before the ui.Pages.define function. These functions programmatically show and hide the app bar.

    function showAppBar(currentItem) {
        // Get the app bar.
        var element = document.activeElement;
        var appbar = document.getElementById("appbar");
    
        // Keep the app bar open after it's shown.
        appbar.winControl.sticky = true;
    
        // Set the app bar context.
        showItemCommands();
    
        // Show the app bar.
        appbar.winControl.show();
    
        // Return focus to the original item which invoked the app bar.
        if (element != null) element.focus();
    }
    
    function hideAppBar() {
        var element = document.activeElement;
        var appbar = document.getElementById("appbar");
        appbar.winControl.sticky = false;
        appbar.winControl.hide();
        hideItemCommands();
        if (element != null) element.focus();
    }
    
  8. In GroupedItems.js, add the following functions next to the preceding functions. The following functions show and hide the app bar commands.

    function showItemCommands() {
        appbar.winControl.showCommands([markItem]);
    }
    
    function hideItemCommands() {
        appbar.winControl.hideCommands([markItem]);
    }
    
  9. In GroupedItems.js, add code to programmatically open and close the app bar. In the ready function, add a selection changed event handler for the ListView on the page.

    listView.onselectionchanged = this.itemSelected.bind(this);
    
  10. In GroupedItems.js, add the following functions to handle selection from the ListView.

    itemSelected: function (eventObject) {
        var listView = document.querySelector(".groupeditemslist").winControl;
    
        // Check for selection.
        if (listView.selection.count() === 0) {
            hideAppBar();
        } else {
            listView.selection.getItems().then(function (items) {
                showAppBar(items[0]);
            });
        }
    },
    
  11. In GroupedItems.js, add a call to the hideItemCommands function from updateLayout. This call hides the contextual command in the app bar when the app is snapped. The contextual command should not appear when the app is snapped because the item is not shown as selected in the snapped state.

    updateLayout: function (element, viewState, lastViewState) {
        /// <param name="element" domElement="true" />
        /// <param name="viewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
        /// <param name="lastViewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
    
        var listView = element.querySelector(".groupeditemslist").winControl;
        if (lastViewState !== viewState) {
            if (lastViewState === appViewState.snapped || viewState === appViewState.snapped) {
                var handler = function (e) {
                    listView.removeEventListener("contentanimating", handler, false);
                    e.preventDefault();
                }
            listView.addEventListener("contentanimating", handler, false);
    
            // Add the following five lines of code.        
            this.initializeLayout(listView, viewState);
            hideItemCommands();
            if (viewState === appViewState.snapped)
                listView.selectionMode = "none";
            else
                listView.selectionMode = "multi";
            }
        }
    }
    

Adding the SemanticZoom control

In this section we add a SemanticZoom control to provide a semantic view for the items in the ListView grid. Each item in the zoomed out view corresponds to a group of items in the grid.

Screen shot showing items in the ListView grid

To learn more about Semantic Zoom, check out the HTML ListView grouping and SemanticZoom sample.

Motivation

The Grid Application template uses a hierarchical navigation pattern. This means that interesting content from each major section of an app can be promoted on the landing page. This landing page can grow to a large number of groups or a large number of items. Although the user can pan the view to browse the content, consider adding Semantic Zoom to let the user quickly jump to different sections in the page.

The SemanticZoom control lets the user zoom out of the grid into a semantic, rather than optical, view of their groups. From this view, the user can select a group to zoom back in on. Semantic Zoom lets the user view large groups of items without panning, and also gives the user an overall view of the groups in the grid.

The zoomed out view can also provide meta information on the groups, to guide the user to interesting sections on the page. For instance, the zoomed out view could show the number of items in the group that have a number overlay.

In Practice

The iPad to Windows 8 Windows Store app case study shows how apps can use Semantic Zoom to let the user quickly jump between groups of items. Below is a walkthrough of how to use Semantic Zoom to show a semantic view of groups. On the left is a step-by-step walkthrough of how this would be done on the iPad. On the right is the equivalent scenario that makes use of Microsoft design style.

Screen shot showing how Semantic Zoom compares to iPad for switching between groups of items

In the iPad app, the user can tap on the Years button on the top navigation bar to show a popup with a list of years. In the Windows Store app, the user can use a pinch gesture to use Semantic Zoom to zoom out from the groups. In this example, this semantic view has been configured to show the number of comments in the Comments group, and to show which months have photos.

Implementation

In the following steps we add the Semantic Zoom container to the Grouped Items page and add a template for the Semantic Zoom groups.

We hook up the semantic groups to the group data source, and then apply Cascading Style Sheets (CSS) to style the groups appropriately.

  1. In GroupedItems.html, find the ListView control in the main content section:

    <section aria-label="Main content" role="main">
        <div class="groupeditemslist" aria-label="List of groups" data-win-control="WinJS.UI.ListView" data-win-options="{ selectionMode: 'multi' }"></div>
    </section>
    

    At its most basic, the SemanticZoom control is a container that displays one of its child elements, depending on the current scale factor. Here we'll add a SemanticZoom control as the "root" of the section and place two ListView controls in the SemanticZoom control.

    First, change the main content section as follows:

    <section aria-label="Main content" role="main">
        <div class="sezoDiv" data-win-control="WinJS.UI.SemanticZoom" data-win-options="{ zoomFactor: 0.5, initiallyZoomedOut: false }">
            <div class="groupeditemslist" aria-label="List of groups" data-win-control="WinJS.UI.ListView" data-win-options="{selectionMode: 'multi'}"></div>
            <div class="groupeditemslistZoomOut groupeditemslist" aria-label="List of groups" data-win-control="WinJS.UI.ListView" data-win-options="{selectionMode: 'none'}"></div>
        </div>
    </section>
    
    • Now that we've set up the controls on the page, we'll add code to ensure the correct content is displayed.

      In GroupedItems.js, make the following additions and changes to the ready function. Essentially, we simply duplicate what already exists for the first list view and apply it to our new zoomed out ListView.

      ready: function (element, options) {
          var listView = element.querySelector(".groupeditemslist").winControl;
      
          // Add the following two lines of code.
          var listViewZoomOut = element.querySelector(".groupeditemslistZoomOut").winControl;
          var semanticZoom = element.querySelector(".sezoDiv").winControl;
      
          listView.groupHeaderTemplate = element.querySelector(".headerTemplate");
          listView.itemTemplate = element.querySelector(".itemtemplate");
      
          // Add the following line of code.
          listViewZoomOut.itemTemplate = element.querySelector(".itemtemplate");
      
          listView.oniteminvoked = this.itemInvoked.bind(this);
      
          // Add the following line of code.
          listViewZoomOut.oniteminvoked = this.groupInvoked.bind(this)
      
          // Change the second and third parameters of the following call as shown.
          this.initializeLayout(listView,listViewZoomOut, semanticZoom, appView.value);
      },
      
  2. In GroupedItems.js, update the inializeLayout function to accept the second list view. The function also handles the semantic zoom control in snapped view.

    // Add the listViewZoomOut and semanticZoom parameters as shown.
    initializeLayout: function (listView, listViewZoomOut, semanticZoom, viewState) {
        /// <param name="listView" value="WinJS.UI.ListView.prototype" />
    
        if (viewState === appViewState.snapped) {
            listView.itemDataSource = Data.groups.dataSource;
            listView.groupDataSource = null;
            listView.layout = new ui.ListLayout();
    
            // Add the following three lines of code.
            semanticZoom.zoomedOut = false;
            semanticZoom.forceLayout();
            semanticZoom.locked = true;
        } else {
            listView.itemDataSource = Data.items.dataSource;
            listView.groupDataSource = Data.groups.dataSource;
            listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
    
            // Add the following four lines of code.
            listViewZoomOut.itemDataSource = Data.groups.dataSource;
            listViewZoomOut.layout = new ui.GridLayout({ maxRows: 1 });
            semanticZoom.forceLayout();
            semanticZoom.locked = false;
        }
    },
    
  3. In GroupedItems.js, update the updateLayout function to configure the new ListView, to display the list of groups using the grouped data source.

    updateLayout: function (element, viewState, lastViewState) {
        /// <param name="element" domElement="true" />
        /// <param name="viewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
        /// <param name="lastViewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
        var listView = element.querySelector(".groupeditemslist").winControl;
    
        // Add the following two lines of code.
        var listViewZoomOut = element.querySelector(".groupeditemslistZoomOut").winControl;
        var semanticZoom = element.querySelector(".sezoDiv").winControl;
    
        if (lastViewState !== viewState) {
            if (lastViewState === appViewState.snapped || viewState === appViewState.snapped) {
                var handler = function (e) {
                    listView.removeEventListener("contentanimating", handler, false);
                        e.preventDefault();
                }
                listView.addEventListener("contentanimating", handler, false);
    
                // Add the listViewZoomOut and semanticZoom parameters as shown.
                this.initializeLayout(listView, listViewZoomOut, semanticZoom, viewState);
            }
    
            // Add the following four lines of code.
            if (lastViewState === appViewState.snapped) {
                semanticZoom.zoomedOut = true;
                semanticZoom.forceLayout();
            }
        }
    },
    
  4. In GroupedItems.js, add the following function to handle when the user taps or clicks an item in the zoomed out view.

    groupInvoked: function (args) {
        var group = Data.groups.getAt(args.detail.itemIndex);
        nav.navigate("/pages/groupDetail/groupDetail.html", { groupKey: group.key });
    },
    
  5. Now it’s time to add CSS to make the items look good.

    Note  To implement the user experience (UX) guidelines for Semantic Zoom, we use a CSS grid with only one row and column. We place the header and section content into this grid so they can be overlapped. This means that the SemanticZoom control can cover the entire screen, including the page header.

    In GroupedItems.css, add the following styles to make the SemanticZoom control take up the available space on the screen. This changes the layout of the overall fragment, so that the page header content is positioned beneath the section content, which contains the sezo div.

    .groupeditemspage {
        /* Change the display type to have only one row */
        -ms-grid-rows: 1fr;
        display: -ms-grid;
        height: 100%;
        width: 100%;
    }
    
        .groupeditemspage header[role=banner] {
            /* Position this at the top of the screen */
            -ms-grid-row: 1;    
            -ms-grid-columns: 120px 1fr;
            display: -ms-grid;
        }
    
        .groupeditemspage section[role=main] {
            /* Position this at the top of the screen */
            -ms-grid-row: 1;
            height: 100%;
            width: 100%;
        }
    
    .sezoDiv
     {
        height: 100%;
        width: 100%;
    }
    
  6. Next, we need to resolve some issues that occur because the SemanticZoom control is positioned using a negative margin.

    To see the page header, move the list view down 133 pixels.

    .groupeditemspage .groupeditemslist .win-horizontal.win-viewport .win-surface {
        margin-left: 45px;
        /* Add the following line. */
        margin-top: 133px;
        margin-bottom: 60px;
    }
    
    /* Add the following lines near the end of the CSS file. */
    .groupeditemspage .groupeditemslistZoomOut .win-horizontal.win-viewport .win-surface {
        margin-left: 116px;
        margin-top: 133px;
    }
    
  7. Now we need to style the item to show it in the zoomed out view. Add the following CSS code, ensuring that it is declared after the groupeditemslist.win-item style that is already part of the file:

        .groupeditemspage .groupeditemslistZoomOut .win-item {
            -ms-grid-columns: 1fr;   
            -ms-grid-rows: 1fr 90px;
            display: -ms-grid;
            height: 250px;
            width: 250px;
        }
    .groupeditemspage .groupeditemslistZoomOut .win-item {
        -ms-grid-rows: 1fr 180px;
        display: -ms-grid;
        height: 580px;
        width: 250px;
    }
    
  8. Now we need to fix the snapped and portrait views because we made changes that affect them. Add all of the following styles to the @media screen and (-ms-view-state: snapped) section of GroupedItems.css.

    Add the following style to re-align the header section.

    .groupeditemspage header[role=banner] {
        -ms-grid-columns: auto 1fr;
        margin-left: 20px;
    }
    

    Add a margin to the top of the surface so the list doesn't cover the title.

    .groupeditemspage .groupeditemslist .win-vertical.win-viewport .win-surface {
        margin-bottom: 30px;
        /* Add the following line. */
        margin-top: 133px;
        margin-left: 6px;
    }
    
  9. The issues in the snapped view are now fixed. Next, we need to update the portrait view.

    In GroupedItems.css, add the following styles to the @media screen and (-ms-view-state: fullscreen-portrait) section.

    .groupeditemspage .groupeditemslistZoomOut .win-horizontal.win-viewport .win-surface {
        margin-left: 96px;
    }
    

Presenting multi-sized items in the grid

Presenting a grid with items of various sizes is a great way to customize the grid and differentiate an app. With multi-sized items, an app can emphasize some items and de-emphasize others, choosing larger sizes for more important items.

Items of different size can also use different templates. For instance, you might want to show images with a text overlay for larger items, and only text for smaller items.

In Practice

The iPad to Windows 8 Windows Store app case study shows how apps can use multi-sized items and various templates to customize the grid template and brand their app. The following examples compare an iPad app landing page and a Windows Store app landing page that customized the grid template.

Screen shots comparing iPad and Windows Store app landing pages

In the iPad app, all content is presented in a uniform grid of pictures. In the Windows Store app, items are presented in various sizes and with different templates. The first group contains items with a small picture and descriptive text side-by-side. The rest of the groups consist of large and small images overlaid with text of various sizes.

Example Overview

The example in this section customizes the grid on the Grouped Items page to use items of three different sizes. The default grid uses 200-by-200-pixel items. We'll use items that are 310-by-310-pixels, 310-by-150-pixels, and 150-by-150-pixels. The larger items will use the default image plus text overlay layout, and the smaller items will contain only text.

Here is the Grouped Items page with the three different item sizes:

Screen shot of the Grouped Items page showing the three different item sizes

Implementation

To achieve the desired user experience for this scenario, we need to define our own rendering function for the list view. We also need to add CSS for the different sized items.

For the rendering function, we read in a pre-defined HTML template instead of creating one manually in the function, but either approach is fine.

When choosing multiple item sizes, be aware that the sizes need to obey the following formula: itemSize = ((slotSize + margin) x multiplier) – margin.

  1. In GroupedItems.html, add the following item template.

    <div class="multisizebaseitemtemplate" data-win-control="WinJS.Binding.Template">
        <img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
        <div class="item-overlay">
            <h4 class="item-title" data-win-bind="textContent: title"></h4>
            <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
        </div>
    </div>
    
  2. In GroupedItems.js, add the following function before the page definition (ui.Pages.define).

    function multisizeItemTemplateRenderer(itemPromise) {
        return itemPromise.then(function (currentItem) {
            var content;
            // Grab the default item template used on the groupeditems page.
            content = document.getElementsByClassName("multisizebaseitemtemplate")[0];
            var result = content.cloneNode(true);
    
            // Change the CSS class of the item depending on the group, then set the size in CSS.
            switch (currentItem.groupKey) {
                case "group1":
                    {
                        // For the first item, use the largest template.
                        if (currentItem.index == 0) {
                            result.className = "largeitemtemplate"
                        }
                        else {
                            result.className = "mediumitemtemplate"
                        }
                        break;
                    }
                case "group2":
                    {
                        result.className = "mediumitemtemplate"
                        break;
                    }
                default:
                    {
                        result.className = "smallitemtemplate"
                    }
            }
            // Because we used a WinJS template, we need to strip off some attributes 
            // for it to render.
            result.attributes.removeNamedItem("data-win-control");
            result.attributes.removeNamedItem("style");
            result.style.overflow = "hidden";
    
            // Because we're doing the rendering, we need to put the data into the item.
            // We can't use databinding.
            result.getElementsByClassName("item-image")[0].src = currentItem.data.backgroundImage;
            result.getElementsByClassName("item-title")[0].textContent = currentItem.data.title;
            result.getElementsByClassName("item-subtitle")[0].textContent = currentItem.data.subtitle;
            return result;
        });
    }
    
  3. In GroupedItems.js, add the groupInfo function below, also outside the page definition. This function tells the ListView to use multi-sized items in the view, and indicates the "base size" of the items. The base size is the smallest item shown in the list. Other items must be multiples of this size for the layouts to work correctly.

    function groupInfo() {
        return {
            enableCellSpanning: true,
            cellWidth: 150,
            cellHeight: 150
        };
    }
    
  4. Now we need to hook these new functions up with the ListView controls on the Grouped Items page. Because we do not want to use variable sized images in the snapped view, we need to change how templates are applied to the list view code in both the ready function and the updateLayout function.

    In GroupedItems.js, change the initializeLayout function so that when the page is in snapped view, we show a flat list of groups. In the landscape view, however, we use multi-sized items.

    // Add the itemTemplate parameter as shown.
    initializeLayout: function (listView, listViewZoomOut, semanticZoom, viewState, itemTemplate) {
        /// <param name="listView" value="WinJS.UI.ListView.prototype" />
    
        if (viewState === appViewState.snapped) {
            listView.itemDataSource = Data.groups.dataSource;
            listView.groupDataSource = null;
    
            // Add the followign line of code.
            listView.itemTemplate = itemTemplate;
    
            listView.layout = new ui.ListLayout();
            semanticZoom.zoomedOut = false;
            semanticZoom.forceLayout();
            semanticZoom.locked = true;
        } else {
            listView.itemDataSource = Data.items.dataSource;
            listView.groupDataSource = Data.groups.dataSource;
    
            // Add the following two lines of code.
            listView.itemTemplate = multisizeItemTemplateRenderer;
            listView.layout = new ui.GridLayout({ groupInfo: groupInfo, groupHeaderPosition: "top" });
    
            listViewZoomOut.itemDataSource = Data.groups.dataSource;
            listViewZoomOut.layout = new ui.GridLayout({ maxRows: 1 });
            semanticZoom.forceLayout();
            semanticZoom.locked = false;
        }
    },
    

    Remove the line that assigns an item template to the ListView, and make the changes indicated by the comments in the following code.

    ready: function (element, options) {
        var listView = element.querySelector(".groupeditemslist").winControl;
        var listViewZoomOut = element.querySelector(".groupeditemslistZoomOut").winControl;
        var semanticZoom = element.querySelector(".sezoDiv").winControl;
    
        // Add the following line of code.
        var itemTemplate = element.querySelector(".itemtemplate");
    
        listView.groupHeaderTemplate = element.querySelector(".headerTemplate");
    
        listView.oniteminvoked = this.itemInvoked.bind(this);
        listViewZoomOut.itemTemplate = element.querySelector(".itemtemplate");
        listViewZoomOut.oniteminvoked = this.groupInvoked.bind(this)
    
        // Change the last argument of the following function to itemTemplate.
        this.initializeLayout(listView,listViewZoomOut, semanticZoom, appView.value, itemTemplate);
        listView.element.focus();
     },
    
    // This function updates the page layout in response to viewState changes.
    updateLayout: function (element, viewState, lastViewState) {
        /// <param name="element" domElement="true" />
        /// <param name="viewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
        /// <param name="lastViewState" value="Windows.UI.ViewManagement.ApplicationViewState" />
        var listView = element.querySelector(".groupeditemslist").winControl;
        var listViewZoomOut = element.querySelector(".groupeditemslistZoomOut").winControl;
        var semanticZoom = element.querySelector(".sezoDiv").winControl;
    
        // Add the following line of code.
        var itemTemplate = element.querySelector(".itemtemplate");
    
        if (lastViewState !== viewState) {
            if (lastViewState === appViewState.snapped || viewState === appViewState.snapped) {
                var handler = function (e) {
                    listView.removeEventListener("contentanimating", handler, false);
                    e.preventDefault();
                }
                listView.addEventListener("contentanimating", handler, false);
    
                // Change this line to pass through the item template.
                this.initializeLayout(listView, listViewZoomOut, semanticZoom,viewState,itemTemplate);
            }
            if (lastViewState === appViewState.snapped) {
                semanticZoom.zoomedOut = true;
                semanticZoom.forceLayout();
            }
        }
    },
    
  5. The code for this area is now finished. Next, we need to add styling for the items.

    In GroupedItems.css, add the following styles for the small, medium, and large item templates.

    .groupeditemspage .smallitemtemplate
    {
        width: 150px;
        height: 150px;
        overflow: hidden;
        -ms-grid-columns: 1fr;
        -ms-grid-rows: 0px 1fr;
        display: -ms-grid;
    }
    .smallitemtemplate .item-overlay .item-title {
        position: absolute; 
        padding-right: 6px;
        bottom: 10px; 
        font-size: 16pt;
    }
    .smallitemtemplate .item-overlay .item-subtitle {
        display: none;
    }
    .groupeditemspage .mediumitemtemplate
    {
        width: 310px;
        height: 150px;
        -ms-grid-columns: 1fr;
        -ms-grid-rows: 1fr 60px;
        display: -ms-grid;
        overflow: hidden;
    }       
    .groupeditemspage .largeitemtemplate
    {
        width: 310px;
        height: 310px;
        overflow: hidden;
        -ms-grid-columns: 1fr;
        -ms-grid-rows: 1fr 90px;
        display: -ms-grid;
    }
    
  6. Remove the following CSS code to prevent it from overwriting our styling.

    .groupeditemspage .groupeditemslistZoomOut .win-item {
        -ns-grid-columns: 1fr;
        -ms-grid-rows: 1fr 90px;
        display: -ms-grid;
        height: 250px;
        width: 250px;
    }
    

    Note  If you didn't implement the Semantic Zoom section, just delete the above CSS from the file to prevent it from overriding the CSS that we just added for the multi-sized items.

 

 

Build date: 1/6/2014