Documentation and design notes for the ListView's occupancyMap object

Introduction

The ListView control's GridLayout (Layouts.js) supports the concept of items, laid out together, that are of different sizes. This is known as variable-size layout and is implemented in the VariableSizeDecorator class. This type of layout is accomplished by defining a grid of slots that are of a fixed size. This dimension is specified by the application in the groupInfo property that it provides to the ListView constructor. For example:


    var listView = new WinJS.UI.ListView(element, {
        layout: { groupInfo: getGroupInfo, ... },
        ...
    });

    function getGroupInfo(groupItem) {
        // cellWidth and cellHeight only honored in groups where enableCellSpanning: true
        return {
            enableCellSpanning: !groupItem || groupItem.data.enableCellSpanning,
            cellWidth: 88,
            cellHeight: 88
        };
    }
        

When the groupInfo.enableCellSpanning property has a value of true, the group is capable of displaying variable-size items. It is at this point that an occupancy map is created for the group. The occupancy map's purpose is to track the allocation of slots to items -- that is, to map which items are displayed where on the slot grid. The occupancy map is also used to determine where the next item can be placed during the layout rendering process.

Population of the occupancy map

Callstack

VariableSizeDecorator.markSlotAsFull
VariableSizeDecorator.addItemToMap
VariableSizeDecorator.ensureInMap
VariableSizeDecorator.calcItemPosition
GridLayout._calcItemPosition
GridLayout._layoutItem

When the GridLayout executes its _layoutItem function, it asks the group decorator to calculate the item's position via the calcItemPosition interface function. In the case of the VariableSizeDecorator, this code path leads to the markSlotAsFull function. This function contains the only line of code that actually modifies the occupancyMap object:


    markSlotAsFull: function (layout, index, itemEntry) {
        var coordinates = layout._indexToCoordinate(index, this.slotsPerColumn);
        for (var r = coordinates.row, toRow = coordinates.row + itemEntry.rows; r < toRow; r++) {
            for (var c = coordinates.column, toColumn = coordinates.column + itemEntry.columns; c < toColumn; c++) {
                this.occupancyMap[this.coordinateToIndex(layout, c, r)] = itemEntry;
            }
        }
    }
        

The markSlotAsFull function obtains the slot grid coordinates of the specified item's upper left corner, then iterates over each slot occupied by the item and writes a data object to the corresponding occupancyMap array entry. The occupancy map reads top-down, left-to-right.

Layout Example

The example below shows the variable-size slot grid with items of two different sizes laid out within it. The cellHeight and cellWidth in this example are 60px. The small items, A and C, are defined as 60x60 pixels, the same size as the slots. The large item, B, is 180x120 pixels. Note that there is just one item B, and it occupies six slots. The superscript values indicate the array index that corresponds to each slot.

Item A
0
 
4
 
8
Item B
1
Item B
5
Item B
9
Item B
2
Item B
6
Item B
10
Item C
3
 
7
 
11

And here is the corresponding occupancy map array. Notice that there are four undefined array indexes − [4], [7], [8], [11] − because no item currently occupies those slots:

[0] = { "Item A", index = 0, columns = 1, rows = 1 }
[1] = { "Item B", index = 1, columns = 3, rows = 2 }
[2] = { "Item B", index = 1, columns = 3, rows = 2 }
[3] = { "Item C", index = 2, columns = 1, rows = 1 }
[5] = { "Item B", index = 1, columns = 3, rows = 2 }
[6] = { "Item B", index = 1, columns = 3, rows = 2 }
[9] = { "Item B", index = 1, columns = 3, rows = 2 }
[10] = { "Item B", index = 1, columns = 3, rows = 2 }