locked
Grid Layout with different sized items RRS feed

  • Question

  • I wonder if its possible to display items with different width in a listview with GridLayout? Something like the Start menu does.

    | Item 1       |      Item 2 |
    | Item 3   | Item 4 | Item 5 |
    | Item 6   | Item 7 | Item 8 |
    


    Tuesday, February 21, 2012 10:09 AM

All replies

  • Hi Phil,

    The start menu actually does something like this (subtle difference but important to note):

    | Item 1   | **** Item 2 ***|
    | Item 3   | Item 4 | Item 5 |
    | Item 6   | Item 7 | Item 8 |
    

    I am not sure how they are accomplishing this (it is probably a custom control) but you could definately do this many ways.  You could use colspan=2 if this was a table or if using msGrid you could also do something similar.

    Also that start menu looks like a grid that is using grouping and if you have seen some of the videos of the later versions it looks like a semantic zoom allows you to get the 1000 foot view of the items.

    -jeff


    Jeff Sanders (MSFT)

    Tuesday, February 21, 2012 2:11 PM
    Moderator
  • The ListView control figures out the rendered size of an item and uses that to calculate the layout of the rest of the grid. It then absolutely positions every item and sets a specific width, height, top and left attribute directly to the element's style attribute. That means you have almost no chance of having different sized items through CSS, and accomplishing the feat via JavaScript would basically amount to you having to develop a new Layout object for the ListViewControl (instead of GridLayout, it'd be PhilLayout, and you’d do all the work to render variable sized items).

    I think it's totally ironic that the ListView control is "old school" and does not us the Grid or FlexBox layout that debuts in the same release. Grid or FlexBox would have given app-devs more flexibility to adjust the layout with simple CSS. But I suppose position: absolute was the surest way to guarantee the feature worked without having to take a dependency on a simultaneously developed feature.

    Anyway, I digress. Probably the easiest way to get what you want is to look at just using Grid or FlexBox. You can still use Templates to render each item, but you're going to write your own loop to iterate through your collection, databind each item with a Template, and then append the result to a parent Grid or FlexBox.

    If all you need is a sort of "column span" like you have for "Item 2" then I would highly recommend a multiline FlexBox: http://msdn.microsoft.com/en-us/library/hh673531(v=vs.85).aspx

    With FlexBox you don't have to set which Row and Column each item appears, you just apply a set of CSS rules on how and in what order you want your items to be laid out.

    Unfortunately FlexBox doesn't have a concept of RowSpan (or LineSpan), so something like a double tall item would just result in a double tall line of items, and shorter items would look funny on that line. If you need RowSpan then you want a Grid, but that means you're going to have to do some arithmetic in your loop and set what row and column you want each item to appear in. This isn't a big deal but it's just less flexible.


    Senior Dev for Windows Phone Services

    Wednesday, February 22, 2012 12:04 AM
  • Now that we have the beta, do we have a different answer here, i.e. do we allow multiple sized items in the ListView? Is there a sample?

    Wednesday, March 7, 2012 3:45 AM
  • I highly doubt this would be doable. You're only recourse might be some kind of dynamic ms-grid. Then you can do rows, columns, spans, etc.

    When you see this stuff in places like the Store app, they aren't dynamic, but are static grid sizes. You can tell because the Windows Store app doesn't scale to 1920x1200 or anything, it stays in a 1366x768-friendly layout. Now if you go to the Store's category page, it's a ListView, and it will scale to fill.

    Wednesday, March 7, 2012 9:53 PM
  • Yes you can now have different sized items. It's a little wonky but it basically works like this. In the GridLayout you can specify a "groupInfo" property with a function like this:

    function makeGroupInfo() {
        return {
            enableCellSpanning: true,
            cellWidth: 50,
            cellHeight: 100
        };
    };
    
    var myGridLayout = new WinJS.UI.GridLayout({ groupHeaderPosition: "top", groupInfo: makeGroupInfo });

    The idea here is that your "cell" width and height are going to be the greatest common denominator for your various items' widths and heights. So for example: your two item templates could be "100px wide by 100px high" and "150px wide by 200px high". The resulting greatest denominators would then be { cellWidth: 50, cellHeight: 100 }.

    The next thing you do is write a custom async function for selecting and rendering each item's template:

    function templateRenderer(itemPromise) {
        return new WinJS.Promise(function (complete, error) {
            itemPromise.then(function (currentItem) {
                var item = currentItem.data; // this is one of the original individual items in your list.
                var itemTemplate;
    
                // Let's pretend your item has a size property, but you can use whatever logic you want.
                if (item.size === "big") {
                    itemTemplate = document.querySelector(".bigItemTemplate").winControl;
                } else {
                    itemTemplate = document.querySelector(".normalItemTemplate").winControl;
                }
    
                // render the template and complete the promise.
                itemTemplate.render(item).then(function _templateComplete(element) {
                    complete(element);
                }, function _templateError(e) {
                    error(e);
                });
            });
        });
    };

    Then assign the new function and layout as the listViews options:

    // Setup items listview.
    WinJS.UI.setOptions(_listView, {
        layout: myGridLayout,
        itemTemplate: templateRenderer,
        groupHeaderTemplate: headerTemplate
    });

    I've had to modify my code a lot to make this easier to understand, so I'm not sure if it all works seamlessly in this example form, you'll have to play with it and mess with your CSS a bit, but I know it will work.


    • Edited by Bryan Thomas Wednesday, June 6, 2012 6:29 PM Formatting
    • Proposed as answer by Bryan Thomas Wednesday, June 6, 2012 6:29 PM
    Wednesday, June 6, 2012 6:26 PM
  • Hi Bryan,

    Thanks for sample !! Can you please clarify one more thing? Is this example only applicable to Grid with grouped items? I tried to implement it and somehow it's

    not changing size properly. So each grid item ends up occupying same space occupied by first item.

    CSS:

    .mediumitemtemplate
            {
                width: 100px;
                height: 100px;
                -ms-grid-columns: 1fr;
                -ms-grid-rows: 1fr 60px;
                display: -ms-grid;
                overflow: hidden;
                border: 1px solid green;
            }       
            .largeitemtemplate
            {
                width: 150px;
                height: 200px;
                overflow: hidden;
                -ms-grid-columns: 1fr;
                -ms-grid-rows: 1fr 90px;
                display: -ms-grid;
                border: 1px solid red;
            }

    JS:

    var items = new WinJS.Binding.List([{ 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 1', 'subtitle': 'Medium Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 2', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 3', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 4', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 5', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 6', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 7', 'subtitle': 'Large Template' },
                { 'backgroundImage': 'url(/images/logo.png)', 'title': 'Title 8', 'subtitle': 'Large Template' }]);
    
                
                var tempNewTitle = ['NewTitle1', 'NewTitle2', 'NewTitle3', 'NewTitle4'];
    
                productsListView.winControl.itemDataSource = items.dataSource;
                productsListView.layout = new WinJS.UI.GridLayout({ groupInfo: makeGroupInfo });
                productsListView.winControl.itemTemplate = 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.
                        
                        // For the first item, use the largest template.
                        if (currentItem.index != 0) {
                            result.className = "largeitemtemplate"
                        }
                        else {
                            result.className = "mediumitemtemplate"
                        }
                        
                        // 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-title")[0].textContent = currentItem.data.title;
                        result.getElementsByClassName("item-subtitle")[0].textContent = currentItem.data.subtitle;
                        return result;
                    });
                }

    First Medium Template (100X100)

    Tuesday, August 7, 2012 8:35 PM
  • If your items are not grouped you probably need to set the itemInfo function instead of the groupInfo function:

    Displaying items with a template or render function

    See the "cell spanning" at the bottom.


    Tuesday, August 7, 2012 8:59 PM
  • Wednesday, August 8, 2012 1:43 PM
  • Thanks for reply Bryan !! Looks like I am not able to clearly find what all property I need to set using documentation (Below link).  Can you please point me to right resource which can explain me what all properties are being exposed by itemInfo object and how can I set those?

    http://msdn.microsoft.com/en-us/library/windows/apps/hh758348.aspx

    Wednesday, August 8, 2012 2:52 PM
  • Thanks JSP !! Examples so far listed all talk about scenario with grouped data. I am trying to achieve same without grouped data and that's where I am not able to find any references. I tried to read documentation but couldn't make much out of it. Can you please point me to example which points me to itemInfo function.
    Wednesday, August 8, 2012 3:15 PM
  • Hi Bryan,

    I tried to play with itemInfo property and somehow its not behaving same as groupInfo. On further investigation I found post "http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/40aeca89-60a0-4276-86d2-ee7cc4176d7f/" which describes itemInfo as below.

    "As for the itemInfo property, I'm told that you can do column breaking with this, that is, the GridLayout will do its usual top-to-bottom then left-to-right layout, but will try to infill smaller items where there are gaps. Having an itemInfo function that returns an object with a breakColumn property supposedly forces a new column, leaving holes in the previous column as they are. I haven't tried this yet, but that's what I know about it."

    Can you please point me to right path?
    Wednesday, August 8, 2012 4:53 PM