locked
WinJS Listview . Win Interactive Keyboard selection issue RRS feed

  • Question

  • Hi all,

    I have a grouped list view in grid layout. There are 4 groups of data available. There are two templates that I use (I use a converter to decide which template to be shown at run time).

    The two templates are as follows:

    1. A single template 0f 210px * 100px (width * height) with a logo and some text (to display details)

    2. A split template which is of same size as above, but internally has three divs (left, right and center). The left div is 110px *100px (width * height). The right div is 110px *100px (width * height). The center div is 10px width and placed in center. The left and right div have some text to display details. the left and right div have "win-interactive" assigned so that each div can be individually clicked.

    On both the template on hover I change the border from 0px to 1px solid light gray.

    The problem is that when user goes to page and use a tab to go to the item and uses the arrow keys to navigate through the list items:

    1. He sees a dark black border across the template. I want that to be shown as 1px solid light gray as in hover.

    2. When user goes to split template the whole template shows this border (210px *100px) and not specifically the div (100px *100px). This looks odd.

    Is there any way to overcome this.

    - Girija

    Thursday, March 7, 2013 10:48 PM

Answers

  • Hi Girija,

    Try this. Add the tabindex around the following div elements in groupedItems.html and then add the following in the groupedItems.css like this:

    groupedItems.html:
    ==================
    <div class="item" data-win-bind="style.display: group.key Converters.DisplayOneItem" tabindex="1">


    <div class="itemone win-interactive" data-win-bind="style.background: item1.backgroundclr;detail: item1" tabindex="2">

    <div class="itemtwo win-interactive" data-win-bind="style.background: item2.backgroundclr;detail: item2" tabindex="3">


    groupedItems.css:
    =================
    .item {
        outline: none;
    }
    .item:focus {
        border:2px solid red;
    }

    .itemone:focus {
        outline: none;
        border:2px solid blue;
    }
    .itemsplit {
        outline: none;
    }
    .itemtwo:focus {
        outline: none;
        border:2px solid green;
    }
    .win-container:focus {
        outline:none;
    }
    .groupeditemslist {
        outline:none;
    }

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:43 PM
    Friday, March 15, 2013 1:27 AM
    Moderator
  • When you use the keyboard up-down arrow keys, there is a function of the ListView control inside ui.js that calls the _drawFocusRectangle and when the focus moves out the _clearFocusRectangle is called. The _drawFocusRectangle creates a <div> tag on the fly that populates over your div and gets the style with the black border. You can however override the _drawFocusRectangle of the ListView control by doing something like this:

    In default.js:
    ===============
        WinJS.UI.ListView.prototype._drawFocusRectangle = function (item)
        {
            var thisWinUI = WinJS.UI;
            var utilities = WinJS.Utilities;

            var wrapper = utilities.hasClass(item, WinJS.UI._wrapperClass) ? item : item.parentNode;
            //#DBG _ASSERT(utilities.hasClass(wrapper, WinJS.UI._wrapperClass));
            if (wrapper.querySelector("." + thisWinUI._itemFocusOutlineClass)) {
                return;
            }
            utilities.addClass(wrapper, thisWinUI._itemFocusClass);
            var outline = document.createElement("div");
            wrapper.appendChild(outline);
        };

    In groupedItems.css:
    ====================
    .win-focused {
        outline: 2px solid orange;
    }

    With this you should see a orange colored box around the selected items when you use the up/down arrow keys. The example I have given just shows how you can override the default black box and selects all the 4 div (even the one with child items). You can however special case the above function to draw the box only when required by probably checking what "item" the focus will be placed upon.

    Thanks,

    Prashant.

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:43 PM
    Friday, March 15, 2013 10:22 PM
    Moderator
  • Hi,

    We can also use this property to remove that black border :

    .groupeditemslist.win-listview .win-focusedoutline { outline: none; }

    For my case I used a combination of this styling (above) as well as tab index.

    Thanks all !!

    - Girija

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:45 PM
    Tuesday, March 19, 2013 9:45 PM

All replies

  • Hello,

    If I understand correctly, you want to select your items using keyboard and then on tab, you want the selected item to show the color? Do you have a simple app you could share to demonstrate your issue?

    Thanks,

    Prashant.

    Friday, March 8, 2013 10:00 PM
    Moderator
  • Hi prasanth,

    No that is not what I want. By default when I tab, the first item gets selected. I can use the arrow buttons to traverse items. The selected item has a black border around it. I do not want this. I want the custom gray border. This is the first issue.

    The second issue is when I use the arrow and move to the "SPLIT" template (Please see my post above, I am using two templates). the border appears around the whole split template and not individual divs in that template. Because of that it looks like one single selection.

    I do not want that. I would rather disable the keyboard navigation if I cannot individually select the divs in split template and cannot solve the border issu.

    If you still need a sample I can do that for you.

    - Girija

    Friday, March 8, 2013 11:11 PM
  • Yes, a sample would be most useful.
    Monday, March 11, 2013 4:55 PM
    Moderator
  • Hi,

    Below is the code for a sample I have built. It just used the default grid app and built my sample on that. I have just enough data and code in the sample below to highlight the issue. At the end of this post I have steps on how to reproduce my issue

    How to repro :

    There are two groups (Group1 and Group2). The group2 uses a split template. If you hover over items in group1 then you will see a single template (with border) but if you hover over items in Group2 you will see hover on individual divs in that template.

    Now, press the tab key so that a black border appears on the first item. Once the black border appears use the arrow keys in keyboard to navigate across the items and groups. When you go into the items of Group2 you will see that the black border appears across the whole template and not individual divs.

    Basically what I want is the same behavior as to when you hover over the items using mouse.

    - Girija

    Create a sample WinJS Grid application (in VS). If you want (Optional): Remove the folders (groupDetail and itemDetail) under "pages" folder.

    Replace contents in the following file.

    1. groupedItems.css

    .groupeditemspage section[role=main] {
        -ms-grid-row: 1;
        -ms-grid-row-span: 2;
    }
    
    .groupeditemspage .groupeditemslist {
        height: 100%;
        ;
        width: 100%;
        z-index: 0;
    }
    
        /* This selector is used to prevent ui-dark/light.css from overwriting changes
           to .win-surface. */
        .groupeditemspage .groupeditemslist .win-horizontal.win-viewport .win-surface {
            margin-bottom: 60px;
            margin-left: 45px;
            margin-right: 115px;
            margin-top: 128px;
        }
    
        .groupeditemspage .groupeditemslist.win-rtl .win-horizontal.win-viewport .win-surface {
            margin-left: 115px;
            margin-right: 45px;
        }
    
        .groupeditemspage .groupeditemslist .win-groupheader {
            padding: 0;
        }
    
        /* Use grid and top level layout for truncation */
        .groupeditemspage .groupeditemslist .group-header {
            -ms-grid-columns: minmax(0, max-content) 7px max-content;
            -ms-grid-rows: max-content;
            display: -ms-inline-grid;
            line-height: 1.5;
        }
    
        /* Override default button styles */
        .groupeditemspage .groupeditemslist .group-header, .group-header:hover, .group-header:hover:active {
            background: transparent;
            border: 0;
            margin-bottom: 1px;
            margin-left: 5px;
            margin-right: 5px;
            margin-top: 1px;
            min-height: 0;
            padding: 0;
        }
    
            .groupeditemspage .groupeditemslist .group-header .group-title {
                display: inline-block;
            }
    
            .groupeditemspage .groupeditemslist .group-header .group-chevron {
                -ms-grid-column: 3;
                display: inline-block;
            }
    
                .groupeditemspage .groupeditemslist .group-header .group-chevron::before {
                    content: "\E26B";
                    font-family: 'Segoe UI Symbol';
                }
    
                .groupeditemspage .groupeditemslist.win-rtl .group-header .group-chevron:before {
                    content: "\E26C";
                }
    
        .groupeditemspage .groupeditemslist .item {
            -ms-grid-columns: 1fr;
            -ms-grid-rows: 1fr 90px;
            display: -ms-grid;
            height: 250px;
            width: 410px;
            background:lightgreen;
        }
    
            .groupeditemspage .groupeditemslist .item:hover { 
                border:2px solid gray;
            }
    
        .groupeditemspage .groupeditemslist .win-container:hover { 
            outline:none
        }
    
         .groupeditemspage .groupeditemslist .itemsplit {
            height: 250px;
            width: 410px;
            background:white;
        }  
    
         .groupeditemspage .groupeditemslist .itemsplit .itemone {
            height: 250px;
            width: 200px;
            background:lightblue;
            float:left;
        }
    
            .groupeditemspage .groupeditemslist .itemsplit .itemone:hover { 
                border:2px solid gray;
            }
    
         .groupeditemspage .groupeditemslist .itemsplit .barrier {
            height: 250px;
            width: 6px;
            background:white;
            float:left;
        }  
    
         .groupeditemspage .groupeditemslist .itemsplit .itemtwo {
            height: 250px;
            width: 200px;
            background:yellow;
            float:right;
        }  
    
         .groupeditemspage .groupeditemslist .itemsplit .itemtwo:hover { 
                border:2px solid gray;
            }

    2. groupedItems.html

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>groupedItemsPage</title>
    
        <!-- WinJS references -->
        <link href="//Microsoft.WinJS.1.0/css/ui-light.css" rel="stylesheet" />
        <script src="//Microsoft.WinJS.1.0/js/base.js"></script>
        <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
    
        <link href="/css/default.css" rel="stylesheet" />
        <link href="/pages/groupedItems/groupedItems.css" rel="stylesheet" />
        <script src="/js/data.js"></script>
        <script src="/pages/groupedItems/groupedItems.js"></script>
    </head>
    <body>
        <!-- These templates are used to display each item in the ListView declared below. -->
        <div class="headertemplate" data-win-control="WinJS.Binding.Template">
            <button class="group-header win-type-x-large win-type-interactive" data-win-bind="groupKey: key"
                role="link" tabindex="-1" type="button">
                <span class="group-title win-type-ellipsis" data-win-bind="textContent: title"></span>
                <span class="group-chevron"></span>
            </button>
        </div>
        <div class="itemtemplate" data-win-control="WinJS.Binding.Template">
            <div class="item" data-win-bind="style.display: group.key Converters.DisplayOneItem">
                <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>
            <div class="itemsplit" data-win-bind="style.display: group.key Converters.DisplaySplitItem">
                <div class="itemone win-interactive" data-win-bind="style.background: item1.backgroundclr;detail: item1">
                    <div class="item-overlay">
                        <h4 class="item-title" data-win-bind="textContent: item1.title"></h4>
                        <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: item1.subtitle"></h6>
                    </div>
                </div>
                <div class="barrier"></div>
                <div class="itemtwo win-interactive" data-win-bind="style.background: item2.backgroundclr;detail: item2">
                    <div class="item-overlay">
                        <h4 class="item-title" data-win-bind="textContent: item2.title"></h4>
                        <h6 class="item-subtitle win-type-ellipsis" data-win-bind="textContent: item2.subtitle"></h6>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- The content that will be loaded and displayed. -->
        <div class="fragment groupeditemspage">
            <header aria-label="Header content" role="banner">
                <button class="win-backbutton" aria-label="Back" disabled type="button"></button>
                <h1 class="titlearea win-type-ellipsis">
                    <span class="pagetitle">App10</span>
                </h1>
            </header>
            <section aria-label="Main content" role="main">
                <div class="groupeditemslist win-selectionstylefilled" aria-label="List of groups" 
                    data-win-control="WinJS.UI.ListView" data-win-options="{ selectionMode: 'none' }"></div>
            </section>
        </div>
    </body>
    </html>

    3. groupedItems.js

    (function () {
        "use strict";
    
        var appView = Windows.UI.ViewManagement.ApplicationView;
        var appViewState = Windows.UI.ViewManagement.ApplicationViewState;
        var nav = WinJS.Navigation;
        var ui = WinJS.UI;
    
        ui.Pages.define("/pages/groupedItems/groupedItems.html", {
    
            // This function is called whenever a user navigates to this page. It
            // populates the page elements with the app's data.
            ready: function (element, options) {
                var listView = element.querySelector(".groupeditemslist").winControl;
                listView.groupHeaderTemplate = element.querySelector(".headertemplate");
                listView.itemTemplate = element.querySelector(".itemtemplate");
                listView.oniteminvoked = this._itemInvoked.bind(this);
    
                // Set up a keyboard shortcut (ctrl + alt + g) to navigate to the
                // current group when not in snapped mode.
                listView.addEventListener("keydown", function (e) {
                    if (appView.value !== appViewState.snapped && e.ctrlKey && e.keyCode === WinJS.Utilities.Key.g && e.altKey) {
                        var data = listView.itemDataSource.list.getAt(listView.currentItem.index);
                        this.navigateToGroup(data.group.key);
                        e.preventDefault();
                        e.stopImmediatePropagation();
                    }
                }.bind(this), true);
    
                this._initializeLayout(listView, appView.value);
                listView.element.focus();
            },
    
            // This function updates the page layout in response to viewState changes.
            updateLayout: function (element, viewState, lastViewState) {
                /// <param name="element" domElement="true" />
    
                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);
                        this._initializeLayout(listView, viewState);
                    }
                }
            },
    
            // This function updates the ListView with new layouts
            _initializeLayout: function (listView, 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();
                } else {
                    listView.itemDataSource = Data.items.dataSource;
                    listView.groupDataSource = Data.groups.dataSource;
                    listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });
                }
            },
    
            _itemInvoked: function (args) {
                
            }
        });
    })();

    4. Replace the contents in data.js with the below

    (function () {
        "use strict";
    
        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);
        });
    
        WinJS.Namespace.define("Data", {
            items: groupedItems,
            groups: groupedItems.groups,
            getItemReference: getItemReference,
            getItemsFromGroup: getItemsFromGroup,
            resolveGroupReference: resolveGroupReference,
            resolveItemReference: resolveItemReference
        });
    
        /// <summary>
        /// The converters definition. 
        /// </summary>
        WinJS.Namespace.define("Converters", {
    
    
            DisplayOneItem: WinJS.Binding.converter(function (value) {
                return value === "group1" ? "block" : "none";
            }),
    
            DisplaySplitItem: WinJS.Binding.converter(function (value) {
                return value === "group2" ? "block" : "none";
            })
        });
    
        // 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) {
            for (var i = 0; i < groupedItems.groups.length; i++) {
                if (groupedItems.groups.getAt(i).key === key) {
                    return groupedItems.groups.getAt(i);
                }
            }
        }
    
        // 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;
                }
            }
        }
    
        // Returns an array of sample data that can be added to the application's
        // data list. 
        function generateSampleData() {
            // Each of these sample groups must have a unique key to be displayed
            // separately.
            var sampleGroups = [
                { key: "group1", title: "Group Title: 1", subtitle: "Group Subtitle: 1" },
                { key: "group2", title: "Group Title: 2", subtitle: "Group Subtitle: 2" }
            ];
    
            // Each of these sample items should have a reference to a particular
            // group.
            var sampleItems = [
                { group: sampleGroups[0], title: "Item Title: 1", subtitle: "Item Subtitle: 1" },
                { group: sampleGroups[0], title: "Item Title: 2", subtitle: "Item Subtitle: 2" },
    
                { group: sampleGroups[1], item1: { title: "Item Title: 11", subtitle: "Item Subtitle: 11" }, item2: { title: "Item Title: 22", subtitle: "Item Subtitle: 22" } },
                { group: sampleGroups[1], item1: { title: "Item Title: 33", subtitle: "Item Subtitle: 33" }, item2: { title: "Item Title: 44", subtitle: "Item Subtitle: 44" } }
            ];
    
            return sampleItems;
        }
    })();


    Wednesday, March 13, 2013 12:41 AM
  • hi,

    Anyone has any suggestions for this ? I am kind of stuck here ?

    Thursday, March 14, 2013 7:49 PM
  • Hi Girija,

    Try this. Add the tabindex around the following div elements in groupedItems.html and then add the following in the groupedItems.css like this:

    groupedItems.html:
    ==================
    <div class="item" data-win-bind="style.display: group.key Converters.DisplayOneItem" tabindex="1">


    <div class="itemone win-interactive" data-win-bind="style.background: item1.backgroundclr;detail: item1" tabindex="2">

    <div class="itemtwo win-interactive" data-win-bind="style.background: item2.backgroundclr;detail: item2" tabindex="3">


    groupedItems.css:
    =================
    .item {
        outline: none;
    }
    .item:focus {
        border:2px solid red;
    }

    .itemone:focus {
        outline: none;
        border:2px solid blue;
    }
    .itemsplit {
        outline: none;
    }
    .itemtwo:focus {
        outline: none;
        border:2px solid green;
    }
    .win-container:focus {
        outline:none;
    }
    .groupeditemslist {
        outline:none;
    }

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:43 PM
    Friday, March 15, 2013 1:27 AM
    Moderator
  • Hi Prasanth,

    I tried your suggestion but that doesnot work.

    Try this :

    1. Run the app

    2. Press "Tab" the first item will get highlighted (Red border).

    3. Now use the Arrow keys from key board and navigate to second group. You will see the same issue appearing.

    If you will use the tab only then it works to a certain extent, but the problem is that I can restrict the user to use only the tab key.

    - Girija

    Friday, March 15, 2013 6:08 AM
  • When you use the keyboard up-down arrow keys, there is a function of the ListView control inside ui.js that calls the _drawFocusRectangle and when the focus moves out the _clearFocusRectangle is called. The _drawFocusRectangle creates a <div> tag on the fly that populates over your div and gets the style with the black border. You can however override the _drawFocusRectangle of the ListView control by doing something like this:

    In default.js:
    ===============
        WinJS.UI.ListView.prototype._drawFocusRectangle = function (item)
        {
            var thisWinUI = WinJS.UI;
            var utilities = WinJS.Utilities;

            var wrapper = utilities.hasClass(item, WinJS.UI._wrapperClass) ? item : item.parentNode;
            //#DBG _ASSERT(utilities.hasClass(wrapper, WinJS.UI._wrapperClass));
            if (wrapper.querySelector("." + thisWinUI._itemFocusOutlineClass)) {
                return;
            }
            utilities.addClass(wrapper, thisWinUI._itemFocusClass);
            var outline = document.createElement("div");
            wrapper.appendChild(outline);
        };

    In groupedItems.css:
    ====================
    .win-focused {
        outline: 2px solid orange;
    }

    With this you should see a orange colored box around the selected items when you use the up/down arrow keys. The example I have given just shows how you can override the default black box and selects all the 4 div (even the one with child items). You can however special case the above function to draw the box only when required by probably checking what "item" the focus will be placed upon.

    Thanks,

    Prashant.

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:43 PM
    Friday, March 15, 2013 10:22 PM
    Moderator
  • Hi,

    We can also use this property to remove that black border :

    .groupeditemslist.win-listview .win-focusedoutline { outline: none; }

    For my case I used a combination of this styling (above) as well as tab index.

    Thanks all !!

    - Girija

    • Marked as answer by Girija Beuria Tuesday, March 19, 2013 9:45 PM
    Tuesday, March 19, 2013 9:45 PM