The SkyDrive Windows 8 application allows a user to manage their SkyDrive account. In this post, I am going to show how to recreate the Folder/File User Interface in a WinJS (HTML & JavaScript) project. In a future post, I will look at other areas of functionality such as fetching the SkyDrive contents from the server.
CodeProject
The Goal
The UI of the SkyDrive app consists of the users folders displayed first in a multiple column list and the users files also in a multiple column list. For example, see the following screenshot. The folders are displayed as wide blue rectangles and the files are display afterwards as tall rectangles with some known file types having a specific icon and colour. This is how we want our app to display.
What Not To Do
WinJS has a ListView control that is one of the main building blocks for WinJS applications and supports layout as a grid view. My first thought was that this should be 2 ListView
controls, one after the other. This did not work. Thinking the ListView
was really just some styled HTML, my thought was that if I could force the 2 ListView
s onto the same row, then the first ListView
would increase as its size and push the second ListView
over.
I tried vanilla CSS (floats, inline display, etc.), the –ms-grid properties, the -ms-flex properties but none gave the desired result. The grids would have individual scroll bars or be displayed underneath each other or not at all. I realized I was going down the wrong path and tried a different way. (If you look at the ListView
in the DOM Explorer, it seems to add elements to create its own viewport so getting it to work may have involved targeting this).
The Solution
The answer is to use one ListView
to display both the folders and the files. We will use the ability of the ListView
control to group items and to span multiple cells to accomplish the goal.
Grouping items works by grouping items in the ListView
into separate sections, e.g., to display address book contacts under alphabetical headers. This gives us the two separate sections for folders and files. See this screenshot for any example of grouping and this tutorial for more information.
The ListView
control uses the –ms-grid
display properties to display its item in a grid layout. For this reason, the items must be the same size. However, items can span multiple cells so we still use this (with data binding of the CSS class name) to give us the different styles for folders and files. The following screenshot shows this and is taken from this sample.
The Setup
First, create a new JavaScript Windows Store application in Visual Studio using the “Navigation App” template. I’ve named mine SkyDrive. This project creates a default home page so we’ll just use that as our main page.
Now we’ll add the ListView
control to the page. Place the contents of the section element with the following HTML.
<div class="itemslist" aria-label="List of items" data-win-control="WinJS.UI.ListView"
data-win-options="{ selectionMode: 'none' }"></div>
Also let’s change the theme from dark to light to match SkyDrive. Change:
<link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />
to:
<link href="//Microsoft.WinJS.1.0/css/ui-light.css" rel="stylesheet" />
Also add the following CSS to the #contenthost
style in default.css:
background-color: #F4F4F4;
The app should now look like this:
Add Some Data
Binding to live data is a future exercise, so for now we’ll just create some dummy data and bind to that.
Open up the home.js page and add the following code in the ready function to create our dummy data.
var items = [];
for (var i = 1; i <= 6; i++) {
items.push({
Type: 'Folder',
CssClass: 'Folder',
Name: 'Folder ' + i
});
}
items.push({
Type: 'File',
CssClass: 'File ExcelFile',
Name: 'Book1'
});
for (var i = 1; i <= 3; i++) {
items.push({
Type: 'File',
CssClass: 'File TextFile',
Name: 'File' + i
});
}
Next, we need to bind this data to the ListView
. We use the WinJS.Binding.List object to wrap our data array and assign it to the ListView
.
var data = new WinJS.Binding.List(items);
var listView = element.querySelector(".itemslist").winControl;
listView.itemDataSource = data.dataSource;
This ListView
should now display each of our items. It’s just going to display plain text representing the JSON objects so the next step is to use a template to style the items.
Styling the Items
We can use templates to style the ListView
items. A template is a fragment of HTML that is hidden in the actual page but cloned and used to render each item. This allows us to use HTML to define each item. In addition, we can use data binding to declaratively set the attributes of the HTML elements to the values for the current data item.
Insert the following HTML template into the home.html page anywhere under the body
element.
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<div data-win-bind="className: Type">
<h2 class="item-title" data-win-bind="textContent: Name"></h2>
</div>
</div>
This template uses data binding to set the className
of the wrapping div
element and the inner HTML of the h2
element. (The div
element with the itemtemplate
class is not used in the item, it is just used to define the HTML as a template and to grab a reference to it).
Next, we will assign the template to the ListView
in the home.js file. Include the following line after the listView
variable has been assigned.
listView.itemTemplate = element.querySelector(".itemtemplate");
Lastly, add the following CSS to the home.css stylesheet to provide some initial styling for the items.
.itemslist
{
height: 100%;
}
.Folder, .File
{
height: 200px;
width: 200px;
}
The app now displays each item as a square with the name of the folder/file.
Grouping Folders and Files
The next step is to group the folders and files to give us the 2 distinct sections for folders and files.
To group the items in a ListView
, we need to create a grouped projection. We can do this using the createGrouped method on the WinJS.Binding.List
object. This method takes 3 parameters (each are functions): the first returns the key for the group given the item, the second returns the data for the group given the item, and the last is a comparer used to order the groups. Insert the following code after the binding list is created. This code will split the items into ‘Folder
’ and ‘File
’ groups and ensure the ‘Folder
’ group comes first.
var groupedData = data.createGrouped(
function getGroupKey(dataItem) {
return dataItem.Type;
},
function getGroupData(dataItem) {
return {
Type: dataItem.Type
};
},
function compareGroups(leftKey, rightKey) {
if (leftKey === rightKey) return 0;
if (leftKey === "Item") return -1;
return 1;
}
);
Next, we need to bind the ListView
using the grouped projection rather than the original binding list so remove the previous assignment of itemDataSource
and insert the following code in its place.
listView.itemDataSource = groupedData.dataSource;
listView.groupDataSource = groupedData.groups.dataSource;
Lastly, we don’t actually want to display the group headers so insert the following CSS into the home.css stylesheet to hide the group headers.
.itemslist .win-groupheader
{
display: none;
}
Great, we should now have a application that displays the folders first in one group and the files next in a second group.
Applying Different Styles to the Items
Now we need to display the individual items differently. We have already bound the className
property of the template to the data items CssClass
property so we can go ahead and start creating different CSS rules for each. However, you would soon notice that each item is forced to be the same size. This is because each item is rendered using the grid layout. However, we can use cell spanning to show each item as different sizes. If you look closely at the SkyDrive app, you will notice that the folder elements are double the width of the file elements and the file elements are double the height of the folder elements. So we can see in both cases that cell spanning has been used.
To enable cell spanning, we first need to setup the groupInfo property on the ListViews
layout. We can use this to define the height and width of the cells. Insert the following JavaScript into the home.js file.
listView.layout.groupInfo = WinJS.Utilities.markSupportedForProcessing(function groupInfo() {
return {
enableCellSpanning: true,
cellWidth: 185,
cellHeight: 100
};
});
Now we can assign different heights and width to the items. Replace the .Folder
, .File
CSS rule with this CSS.
.Folder
{
height: 100px;
width: 370px;
}
.File
{
height: 200px;
width: 185px;
}
Our app should now look like this:
Almost there! We now have the layout we were after and are ready to style the items.
Polishing It Off
We’ll make some changes to the template HTML to allow us to style it better. We’ll add different elements for folders and files and only show the element we want. We’’ll also add an icon for files. So the template HTML looks like this:
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<div data-win-bind="className: CssClass">
<div class="folder-overlay">
<h3 class="folder-title" data-win-bind="textContent: Name"></h3>
</div>
<div class="file-image">
<img src="#" data-win-bind="src: Image; alt: Name" />
</div>
<div class="file-overlay">
<h3 class="file-title" data-win-bind="textContent: Name"></h3>
</div>
</div>
</div>
Next, we’’ll modify the CSS stylesheet to attain a look similar to SkyDrive so our CSS now looks like this. We are making use of the grid and the flexible box layouts here.
.itemslist
{
height: 100%;
}
.itemslist .win-groupheader
{
display: none;
}
h3
{
font-family: Segoe UI Semibold;
font-size: 11pt;
color: white;
}
.Folder
{
height: 100px;
width: 370px;
background: rgba(9,74,178,1);
display: -ms-grid;
-ms-grid-rows: 1fr 35px;
}
.Folder .folder-overlay
{
-ms-grid-row: 2;
margin: 10px;
}
.Folder .file-image
{
display: none;
}
.Folder .file-overlay
{
display: none;
}
.File
{
height: 200px;
width: 185px;
background: rgba(147,149,152,1);
display: -ms-grid;
-ms-grid-rows: 1fr 50px;
-ms-grid-columns: 1fr;
}
.File .file-image
{
-ms-grid-row: 1;
display: -ms-flexbox;
-ms-flex-align: center;
-ms-flex-pack: center;
}
.File .file-image img
{
-ms-flex: 0 auto;
}
.ExcelFile
{
background: rgba(67,148,103,1);
}
.File .file-overlay
{
-ms-grid-row: 2;
background: rgba(117,117,117,1);
padding: 10px;
}
.File .folder-overlay
{
display: none;
}
Lastly, we need to set the icon for each file so the JavaScript code to create the dummy file now also assigns an Image
property for each item.
// Add some files
items.push({
Type: 'File',
CssClass: 'File ExcelFile',
Name: 'Book1',
Image: 'images/excel.png'
});
for (var i = 1; i <= 3; i++) {
items.push({
Type: 'File',
CssClass: 'File TextFile',
Name: 'File' + i,
Image: 'images/file.png'
});
}
Voila, our app now closely resembles the SkyDrive app.
To Do
There is still some UI functionality to add such as the current location drop down on the header, the tooltips on hover and the number of items in the current and child folders. We’ll leave the implementation of these for a future post as well as the functionality to retrieve the data from a users actual SkyDrive account and opening of the files.