Monday, May 28, 2012

Metro: Dynamically Switching Templates with a WinJS ListView

Metro: Dynamically Switching Templates with a WinJS ListView:
Imagine that you want to display a list of products using the WinJS ListView control. Imagine, furthermore, that you want to use different templates to display different products. In particular, when a product is on sale, you want to display the product using a special “On Sale” template.

clip_image002

In this blog entry, I explain how you can switch templates dynamically when displaying items with a ListView control. In other words, you learn how to use more than one template when displaying items with a ListView control.


Creating the Data Source

Let’s start by creating the data source for the ListView. Nothing special here – our data source is a list of products. Two of the products, Oranges and Apples, are on sale.

(function () {
    "use strict";

    var products = new WinJS.Binding.List([
            { name: "Milk", price: 2.44 },
            { name: "Oranges", price: 1.99, onSale: true },
            { name: "Wine", price: 8.55 },
            { name: "Apples", price: 2.44, onSale: true },
            { name: "Steak", price: 1.99 },
            { name: "Eggs", price: 2.44 },
            { name: "Mushrooms", price: 1.99 },
            { name: "Yogurt", price: 2.44 },
            { name: "Soup", price: 1.99 },
            { name: "Cereal", price: 2.44 },
            { name: "Pepsi", price: 1.99 }
    ]);

    WinJS.Namespace.define("ListViewDemos", {
        products: products
    });

})();
The file above is saved with the name products.js and referenced by the default.html page described below.

Declaring the Templates and ListView Control

Next, we need to declare the ListView control and the two Template controls which we will use to display template items. The markup below appears in the default.html file:

<!-- Templates -->
<div id="productItemTemplate" data-win-control="WinJS.Binding.Template">
    <div class="product">
        <span data-win-bind="innerText:name"></span>
        <span data-win-bind="innerText:price"></span>
    </div>
</div>

<div id="productOnSaleTemplate" data-win-control="WinJS.Binding.Template">
    <div class="product onSale">
        <span data-win-bind="innerText:name"></span>
        <span data-win-bind="innerText:price"></span>
        (On Sale!)
    </div>
</div>

<!-- ListView -->
<div id="productsListView"
    data-win-control="WinJS.UI.ListView"
    data-win-options="{
            itemDataSource: ListViewDemos.products.dataSource,
            layout: { type: WinJS.UI.ListLayout }

        }">
</div>
In the markup above, two Template controls are declared. The first template is used when rendering a normal product and the second template is used when rendering a product which is on sale. The second template, unlike the first template, includes the text “(On Sale!)”.
The ListView control is bound to the data source which we created in the previous section. The ListView itemDataSource property is set to the value ListViewDemos.products.dataSource. Notice that we do not set the ListView itemTemplate property. We set this property in the default.js file.

Switching Between Templates

All of the magic happens in the default.js file. The default.js file contains the JavaScript code used to switch templates dynamically.
Here’s the entire contents of the default.js file:

(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

            WinJS.UI.processAll().then(function () {
                var productsListView = document.getElementById("productsListView");
                productsListView.winControl.itemTemplate = itemTemplateFunction;

            });;

        }
    };

    function itemTemplateFunction(itemPromise) {
        return itemPromise.then(function (item) {
            // Select either normal product template or on sale template
            var itemTemplate = document.getElementById("productItemTemplate");
            if (item.data.onSale) {
                itemTemplate = document.getElementById("productOnSaleTemplate");
            };

            // Render selected template to DIV container
            var container = document.createElement("div");
            itemTemplate.winControl.render(item.data, container);
            return container;
        });
    }

    app.start();
})();
In the code above, a function is assigned to the ListView itemTemplate property with the following line of code:

productsListView.winControl.itemTemplate = itemTemplateFunction;


The itemTemplateFunction returns a DOM element which is used for the template item. Depending on the value of the product onSale property, the DOM element is generated from either the productItemTemplate or the productOnSaleTemplate template.

Using Binding Converters instead of Multiple Templates

In the previous sections, I explained how you can use different templates to render normal products and on sale products. There is an alternative approach to displaying different markup for normal products and on sale products. Instead of creating two templates, you can create a single template which contains separate DIV elements for a normal product and an on sale product.
The following default.html file contains a single item template and a ListView control bound to the template.

<!-- Template -->
<div id="productItemTemplate" data-win-control="WinJS.Binding.Template">
    <div class="product" data-win-bind="style.display: onSale ListViewDemos.displayNormalProduct">
        <span data-win-bind="innerText:name"></span>
        <span data-win-bind="innerText:price"></span>
    </div>

    <div class="product onSale" data-win-bind="style.display: onSale ListViewDemos.displayOnSaleProduct">
        <span data-win-bind="innerText:name"></span>
        <span data-win-bind="innerText:price"></span>
        (On Sale!)
    </div>
</div>

<!-- ListView -->
<div id="productsListView"
    data-win-control="WinJS.UI.ListView"
    data-win-options="{
            itemDataSource: ListViewDemos.products.dataSource,
            itemTemplate: select('#productItemTemplate'),
            layout: { type: WinJS.UI.ListLayout }
        }">
</div>
The first DIV element is used to render a normal product:

<div class="product" data-win-bind="style.display: onSale ListViewDemos.displayNormalProduct">
    <span data-win-bind="innerText:name"></span>
    <span data-win-bind="innerText:price"></span>
</div>
The second DIV element is used to render an “on sale” product:

<div class="product onSale" data-win-bind="style.display: onSale ListViewDemos.displayOnSaleProduct">
    <span data-win-bind="innerText:name"></span>
    <span data-win-bind="innerText:price"></span>
    (On Sale!)
</div>
Notice that both templates include a data-win-bind attribute. These data-win-bind attributes are used to show the “normal” template when a product is not on sale and show the “on sale” template when a product is on sale. These attributes set the Cascading Style Sheet display attribute to either “none” or “block”.
The data-win-bind attributes take advantage of binding converters. The binding converters are defined in the default.js file:

(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            WinJS.UI.processAll();
        }
    };

    WinJS.Namespace.define("ListViewDemos", {
        displayNormalProduct: WinJS.Binding.converter(function (onSale) {
            return onSale ? "none" : "block";
        }),

        displayOnSaleProduct: WinJS.Binding.converter(function (onSale) {
            return onSale ? "block" : "none";
        })

    });

    app.start();
})();
The ListViewDemos.displayNormalProduct binding converter converts the value true or false to the value “none” or “block”. The ListViewDemos.displayOnSaleProduct binding converter does the opposite; it converts the value true or false to the value “block” or “none” (Sadly, you cannot simply place a NOT operator before the onSale property in the binding expression – you need to create both converters).
The end result is that you can display different markup depending on the value of the product onSale property. Either the contents of the first or second DIV element are displayed:
clip_image003

Summary

In this blog entry, I’ve explored two approaches to displaying different markup in a ListView depending on the value of a data item property. The bulk of this blog entry was devoted to explaining how you can assign a function to the ListView itemTemplate property which returns different templates. We created both a productItemTemplate and productOnSaleTemplate and displayed both templates with the same ListView control.
We also discussed how you can create a single template and display different markup by using binding converters. The binding converters are used to set a DIV element’s display property to either “none” or “block”. We created a binding converter which displays normal products and a binding converter which displays “on sale” products.

Metro Walkthrough: Creating a Task List with a ListView and IndexedDB

Metro Walkthrough: Creating a Task List with a ListView and IndexedDB:
The goal of this blog entry is to describe how you can work with data in a Metro style application written with JavaScript. In particular, we create a super simple Task List application which enables you to create and delete tasks.

Here’s a video which demonstrates how the Task List application works:

clip_image001

In order to build this application, I had to take advantage of several features of the WinJS library and technologies including:

  • IndexedDB – The Task List application stores data in an IndexedDB database.

  • HTML5 Form Validation – The Task List application uses HTML5 validation to ensure that a required field has a value.

  • ListView Control – The Task List application displays the tasks retrieved from the IndexedDB database in a WinJS ListView control.

Creating the IndexedDB Database

The Task List application stores all of its data in an IndexedDB database named TasksDB. This database is opened/created with the following code:

var db;
var req = window.msIndexedDB.open("TasksDB", 1);
req.onerror = function () {
    console.log("Could not open database");
};
req.onupgradeneeded = function (evt) {
    var newDB = evt.target.result;
    newDB.createObjectStore("tasks", { keyPath: "id", autoIncrement:true });
};
The msIndexedDB.open() method accepts two parameters: the name of the database to open and the version of the database to open. If a database with a matching version already exists, then calling the msIndexedDB.open() method opens a connection to the existing database. If the database does not exist then the upgradeneeded event is raised.
You handle the upgradeneeded event to create a new database. In the code above, the upgradeneeded event handler creates an object store named “tasks” (An object store roughly corresponds to a database table). When you add items to the tasks object store then each item gets an id property with an auto-incremented value automatically.
The code above also includes an error event handler. If the IndexedDB database cannot be opened or created, for whatever reason, then an error message is written to the Visual Studio JavaScript Console window.

Displaying a List of Tasks

The TaskList application retrieves its list of tasks from the tasks object store, which we created above, and displays the list of tasks in a ListView control. Here is how the ListView control is declared:

<div id="tasksListView"
    data-win-control="WinJS.UI.ListView"
    data-win-options="{
        itemDataSource: TaskList.tasks.dataSource,
        itemTemplate: select('#taskTemplate'),
        tapBehavior: 'toggleSelect',
        selectionMode: 'multi',
        layout: { type: WinJS.UI.ListLayout }
    }">
</div>
The ListView control is bound to the TaskList.tasks.dataSource data source. The TaskList.tasks.dataSource is created with the following code:

// Create the data source
var tasks = new WinJS.Binding.List();

// Open the database
var db;
var req = window.msIndexedDB.open("TasksDB", 1);
req.onerror = function () {
    console.log("Could not open database");
};
req.onupgradeneeded = function (evt) {
    var newDB = evt.target.result;
    newDB.createObjectStore("tasks", { keyPath: "id", autoIncrement:true });
};

// Load the data source with data from the database
req.onsuccess = function () {
    db = req.result;
    var tran = db.transaction("tasks");
    tran.objectStore("tasks").openCursor().onsuccess = function(event) {
        var cursor = event.target.result;
        tasks.dataSource.beginEdits();
        if (cursor) {
            tasks.dataSource.insertAtEnd(null, cursor.value);
            cursor.continue();
        } else {
            tasks.dataSource.endEdits();
        };
    };  

};

// Expose the data source and functions
WinJS.Namespace.define("TaskList", {
    tasks: tasks
});
Notice the success event handler. This handler is called when a database is successfully opened/created. In the code above, all of the items from the tasks object store are retrieved into a cursor and added to a WinJS.Binding.List object named tasks.
Because the ListView control is bound to the WinJS.Binding.List object, copying the tasks from the object store into the WinJS.Binding.List object causes the tasks to appear in the ListView:
clip_image002

Adding a New Task

You add a new task in the Task List application by entering the title of a new task into an HTML form and clicking the Add button. Here’s the markup for creating the form:

<form id="addTaskForm">
    <input id="newTaskTitle" title="New Task" required />
    <button>Add</button>
</form>
Notice that the INPUT element includes a required attribute. In a Metro application, you can take advantage of HTML5 Validation to validate form fields. If you don’t enter a value for the newTaskTitle field then the following validation error message is displayed:
clip_image003
For a brief introduction to HTML5 validation, see my previous blog entry:
http://stephenwalther.com/blog/archive/2012/03/13/html5-form-validation.aspx
When you click the Add button, the form is submitted and the form submit event is raised. The following code is executed in the default.js file:

// Handle Add Task
document.getElementById("addTaskForm").addEventListener("submit", function (evt) {
    evt.preventDefault();
    var newTaskTitle = document.getElementById("newTaskTitle");
    TaskList.addTask({ title: newTaskTitle.value });
    newTaskTitle.value = "";
});
The code above retrieves the title of the new task and calls the addTask() method in the tasks.js file. Here’s the code for the addTask() method which is responsible for actually adding the new task to the IndexedDB database:

// Add a new task
function addTask(taskToAdd) {
    var transaction = db.transaction("tasks", IDBTransaction.READ_WRITE);
    var addRequest = transaction.objectStore("tasks").add(taskToAdd);
    addRequest.onsuccess = function (evt) {
        taskToAdd.id = evt.target.result;
        tasks.dataSource.insertAtEnd(null, taskToAdd);
    }
}
The code above does two things. First, it adds the new task to the tasks object store in the IndexedDB database. Second, it adds the new task to the data source bound to the ListView. The dataSource.insertAtEnd() method is called to add the new task to the data source so the new task will appear in the ListView (with a nice little animation).

Deleting Existing Tasks

The Task List application enables you to select one or more tasks by clicking or tapping on one or more tasks in the ListView. When you click the Delete button, the selected tasks are removed from both the IndexedDB database and the ListView.
For example, in the following screenshot, two tasks are selected. The selected tasks appear with a teal background and a checkmark:
clip_image004
When you click the Delete button, the following code in the default.js file is executed:

// Handle Delete Tasks
document.getElementById("btnDeleteTasks").addEventListener("click", function (evt) {
    tasksListView.winControl.selection.getItems().then(function(items) {
        items.forEach(function (item) {
            TaskList.deleteTask(item);
        });
    });
});
The selected tasks are retrieved with the TaskList selection.getItem() method. In the code above, the deleteTask() method is called for each of the selected tasks.
Here’s the code for the deleteTask() method:

// Delete an existing task
function deleteTask(listViewItem) {
    // Database key != ListView key
    var dbKey = listViewItem.data.id;
    var listViewKey = listViewItem.key;

    // Remove item from db and, if success, remove item from ListView
    var transaction = db.transaction("tasks", IDBTransaction.READ_WRITE);
    var deleteRequest = transaction.objectStore("tasks").delete(dbKey);
    deleteRequest.onsuccess = function () {
        tasks.dataSource.remove(listViewKey);
    }
}
This code does two things: it deletes the existing task from the database and removes the existing task from the ListView. In both cases, the right task is removed by using the key associated with the task. However, the task key is different in the case of the database and in the case of the ListView.
In the case of the database, the task key is the value of the task id property. In the case of the ListView, on the other hand, the task key is auto-generated by the ListView.
When the task is removed from the ListView, an animation is used to collapse the tasks which appear above and below the task which was removed.

The Complete Code

Above, I did a lot of jumping around between different files in the application and I left out sections of code. For the sake of completeness, I want to include the entire code here: the default.html, default.js, and tasks.js files.
Here are the contents of the default.html file. This file contains the UI for the Task List application:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Task List</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
    <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
    <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>

    <!-- TaskList references -->
    <link href="/css/default.css" rel="stylesheet">
    <script src="/js/default.js"></script>
    <script type="text/javascript" src="js/tasks.js"></script>

    <style type="text/css">
        body {
            font-size: x-large;
        }

        form {
            display:  inline;
        }

        #appContainer {
            margin:  20px;
            width:  600px;
        }

        .win-container {
            padding:  10px;
        }

    </style>

</head>
<body>
    <div>

        <!-- Templates -->
        <div id="taskTemplate"
            data-win-control="WinJS.Binding.Template">
            <div>
                <span data-win-bind="innerText:title"></span>
            </div>
        </div>

        <h1>Super Task List</h1>

        <div id="appContainer">
            <form id="addTaskForm">
                <input id="newTaskTitle" title="New Task" required />
                <button>Add</button>
            </form>
            <button id="btnDeleteTasks">Delete</button>

            <div id="tasksListView"
                data-win-control="WinJS.UI.ListView"
                data-win-options="{
                    itemDataSource: TaskList.tasks.dataSource,
                    itemTemplate: select('#taskTemplate'),
                    tapBehavior: 'toggleSelect',
                    selectionMode: 'multi',
                    layout: { type: WinJS.UI.ListLayout }
                }">
            </div>
        </div>

    </div>
</body>
</html>
Here is the code for the default.js file. This code wires up the Add Task form and Delete button:

(function () {
    "use strict";

    var app = WinJS.Application;

    app.onactivated = function (eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {

            WinJS.UI.processAll().then(function () {

                // Get reference to Tasks ListView
                var tasksListView = document.getElementById("tasksListView");

                // Handle Add Task
                document.getElementById("addTaskForm").addEventListener("submit", function (evt) {
                    evt.preventDefault();
                    var newTaskTitle = document.getElementById("newTaskTitle");
                    TaskList.addTask({ title: newTaskTitle.value });
                    newTaskTitle.value = "";
                });

                // Handle Delete Tasks
                document.getElementById("btnDeleteTasks").addEventListener("click", function (evt) {
                    tasksListView.winControl.selection.getItems().then(function(items) {
                        items.forEach(function (item) {
                            TaskList.deleteTask(item);
                        });
                    });

                });

            });
        }
    };

    app.start();
})();
Finally, here is the tasks.js file. This file contains all of the code for opening, creating, and interacting with IndexedDB:

(function () {
    "use strict";

    // Create the data source
    var tasks = new WinJS.Binding.List();

    // Open the database
    var db;
    var req = window.msIndexedDB.open("TasksDB", 1);
    req.onerror = function () {
        console.log("Could not open database");
    };
    req.onupgradeneeded = function (evt) {
        var newDB = evt.target.result;
        newDB.createObjectStore("tasks", { keyPath: "id", autoIncrement:true });
    };

    // Load the data source with data from the database
    req.onsuccess = function () {
        db = req.result;
        var tran = db.transaction("tasks");
        tran.objectStore("tasks").openCursor().onsuccess = function(event) {
            var cursor = event.target.result;
            tasks.dataSource.beginEdits();
            if (cursor) {
                tasks.dataSource.insertAtEnd(null, cursor.value);
                cursor.continue();
            } else {
                tasks.dataSource.endEdits();
            };
        };  

    };

    // Add a new task
    function addTask(taskToAdd) {
        var transaction = db.transaction("tasks", IDBTransaction.READ_WRITE);
        var addRequest = transaction.objectStore("tasks").add(taskToAdd);
        addRequest.onsuccess = function (evt) {
            taskToAdd.id = evt.target.result;
            tasks.dataSource.insertAtEnd(null, taskToAdd);
        }
    }

    // Delete an existing task
    function deleteTask(listViewItem) {
        // Database key != ListView key
        var dbKey = listViewItem.data.id;
        var listViewKey = listViewItem.key;

        // Remove item from db and, if success, remove item from ListView
        var transaction = db.transaction("tasks", IDBTransaction.READ_WRITE);
        var deleteRequest = transaction.objectStore("tasks").delete(dbKey);
        deleteRequest.onsuccess = function () {
            tasks.dataSource.remove(listViewKey);
        }
    }

    // Expose the data source and functions
    WinJS.Namespace.define("TaskList", {
        tasks: tasks,
        addTask: addTask,
        deleteTask: deleteTask
    });

})();

Summary

I wrote this blog entry because I wanted to create a walkthrough of building a simple database-driven application. In particular, I wanted to demonstrate how you can use a ListView control with an IndexedDB database to store and retrieve database data.

Could not find a part of the path ... bin\roslyn\csc.exe

I am trying to run an ASP.NET MVC (model-view-controller) project retrieved from TFS (Team Foundation Server) source control. I have added a...