Archive

Archive for the ‘jQuery’ Category

Creating a custom Knockout binding for the jQuery Mobile ListView

September 22, 2012 3 comments

Introduction

A shift towards more client side web development is happening in the IT industry. This allows developers to build quicker responding and more user friendly GUIs for their end users. This shift is made possible with the arrival of HTML5 and more powerful JavaScript frameworks. Features like camera access, local storage, drag & drop … previously only available in native applications are now also possible in web applications.

In the past I have already written about jQuery Mobile. It is an excellent JavaScript framework to develop mobile web applications or hybrid applications. Another useful framework for the development of modern web applications is Knockout. This framework makes the MVVM design pattern possible in web applications. MVVM stands for Model-View-ViewModel. It is a software pattern of Microsoft commonly used in WPF and Silverlight applications.

In this article I will explain how to build a custom Knockout binding for the jQuery Mobile ListView.

Context

Why do we need a custom binding for the jQuery Mobile ListView? With what is provided out of the box, it is already possible to bind to a ListView. Unfortunately, we are then rather limited in functionality. Imagine the following example: I have a JavaScript array of food. The food can be divided in a number of categories (fruit, vegetables and snacks). I want to show the food in a jQuery Mobile ListView and show a divider per category.

foodapp

With the foreach binding that is provided out of the box in Knockout, I would have to divide my food in 3 arrays. If not, it would not be possible to add a divider per category.

function FoodViewModel(name, category, image) {
    var self = this;

    self.name = name;
    self.category = category;
    self.image = image;
};

function MainViewModel() {
    var self = this;

    self.fruit = ko.observableArray([
        new FoodViewModel("Apple", "Fruit", "/Images/apple.jpg"),
        new FoodViewModel("Banana", "Fruit", "/Images/banana.jpg"),
        new FoodViewModel("Pear", "Fruit", "/Images/pear.jpg")
    ]);

    self.vegetables = ko.observableArray([
        new FoodViewModel("Carrot", "Vegetables", "/Images/carrot.jpg"),
        new FoodViewModel("Tomato", "Vegetables", "/Images/tomato.jpg"),
    ]);

    self.snacks = ko.observableArray([
        new FoodViewModel("Cookie", "Snacks", "/Images/cookie.jpg")
    ]);
};
<ul data-role="listview" data-divider-theme="b">
    <li data-role="list-divider">Vegetables</li>
    <!-- ko foreach: vegetables -->
    <li>
        <a href="#">
            <img data-bind="attr: { src: image }" />
            <h3 data-bind="text: name"></h3>
        </a>
    </li>
    <!-- /ko -->

    <li data-role="list-divider">Fruit</li>
    <!-- ko foreach: fruit -->
    <li>
        <a href="#">
            <img data-bind="attr: { src: image }" />
            <h3 data-bind="text: name"></h3>
        </a>
    </li>
    <!-- /ko -->

    <li data-role="list-divider">Snacks</li>
    <!-- ko foreach: snacks -->
    <li>
        <a href="#">
            <img data-bind="attr: { src: image }" />
            <h3 data-bind="text: name"></h3>
        </a>
    </li>
    <!-- /ko -->
</ul>

I found this quite limiting, because I preferred to have one list containing all my food.

The jqmListView custom binding

In order to resolve this issue, I decided to develop a custom binding for the jQuery Mobile ListView. I based myself on the source of the foreach binding and customized it to have support for the features of the ListView.

It can be applied in two ways:

  1. <ul data-role=”listview” data-divider-theme=”b” data-bind=”jqmListView: food”>
  2. <ul data-role=”listview” data-divider-theme=”b” data-bind=”jqmListView: { data: food, divider: generateDivider, dividerCompareFunction: sortFood, itemCompareFunction: sortItems }”>

The properties “divider”, “dividerCompareFunction” and “itemCompareFunction” are optional.

divider

The property “divider” allows to customize the generation of the dividers for the ListView. It can be assigned a function that will called for every item in the list bound to the ListView. The function must return the category name for each item.

An example:

self.generateDivider = function (data) {
	return data.category;
};

dividerCompareFunction

The property “dividerCompareFunction” allows to customize the sorting of the dividers. By default they are sorted alphabetically.

An example:

self.sortFood = function (divider1, divider2) {
	var weights = new Object;
	weights["Vegetables"] = 1;
	weights["Fruit"] = 2;
	weights["Snacks"] = 3;

	return weights[divider1] - weights[divider2];
};

itemCompareFunction

The property “itemCompareFunction” allows to customize the sorting of the items of a given category. By default they are not sorted in order of appearance in the JavaScript list.

An example:

self.sortItems = function (item1, item2) {
	return item1.name.localeCompare(item2.name);
}

Sample Code

If we use the jqmListView binding, we can rewrite our sample application:

function FoodViewModel(name, category, image) {
    var self = this;

    self.name = name;
    self.category = category;
    self.image = image;
};

function MainViewModel() {
    var self = this;

    self.food = ko.observableArray([
        new FoodViewModel("Carrot", "Vegetables", "/Images/carrot.jpg"),
        new FoodViewModel("Apple", "Fruit", "/Images/apple.jpg"),
        new FoodViewModel("Pear", "Fruit", "/Images/pear.jpg"),
        new FoodViewModel("Tomato", "Vegetables", "/Images/tomato.jpg"),
        new FoodViewModel("Banana", "Fruit", "/Images/banana.jpg"),
        new FoodViewModel("Cookie", "Snacks", "/Images/cookie.jpg")
    ]);

    self.generateDivider = function (data) {
        return data.category;
    };

    self.sortFood = function (divider1, divider2) {
        var weights = new Object;
        weights["Vegetables"] = 1;
        weights["Fruit"] = 2;
        weights["Snacks"] = 3;

        return weights[divider1] - weights[divider2];
    };

    self.sortItems = function (item1, item2) {
        return item1.name.localeCompare(item2.name);
    }
};
<ul data-role="listview" data-divider-theme="b" data-bind="jqmListView: { data: food, divider: generateDivider, dividerCompareFunction: sortFood, itemCompareFunction: sortItems }">
    <li>
        <a href="#">
            <img data-bind="attr: { src: image }" />
            <h3 data-bind="text: name"></h3>
        </a>
    </li>
</ul>

The food sample application is a very simple application were the differences between the foreach and the jqmListView bindings are minor. But the jqmListView is a lot more flexible then the foreach binding in more complex cases. For example: if the data is coming from the server using a REST call and we don’t know beforehand all the available categories that will send to the browser.

You can download the sample from: http://sdrv.ms/Ol0LRY

jqmListView Code

Below you can find the source code  of my jqmListView binding. I will also upload this to GitHub.

(function () {
    ko.bindingHandlers.jqmListView = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            // Support anonymous templates
            var bindingValue = ko.utils.unwrapObservable(convertToBindingValue(valueAccessor));

            if ((element.nodeType == 1 || element.nodeType == 8)) {
                // It's an anonymous template - store the element contents, then clear the element
                var templateNodes = element.nodeType == 1 ? element.childNodes : ko.virtualElements.childNodes(element),
                    container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
                new ko.templateSources.anonymousTemplate(element)['nodes'](container);
            }
            return { 'controlsDescendantBindings': true };
        },
        update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var bindingValue = ko.utils.unwrapObservable(convertToBindingValue(valueAccessor));

            // Clean the current children
            $(element).empty();

            var dataArray = (bindingValue['data']) || [];
            var dividerFor = bindingValue['divider'];

            var dividerDictionary = new Object();
            for (var i = 0; i < dataArray.length; i++) {
                var dividierName = dividerFor(dataArray[i]);

                dividerDictionary[dividierName] = (dividerDictionary[dividierName]) || [];
                dividerDictionary[dividierName].push(dataArray[i]);
            }

            $.each(sortKeys(dividerDictionary, bindingValue.dividerCompareFunction), function (index, key) {
                if (key !== "") {
                    $(element).append('<li data-role="list-divider">' + key + '</li>');
                }

                var tempElement = document.createElement("div"); // Create temp DOM element to render templating
                ko.renderTemplateForEach(element, dividerDictionary[key].sort(bindingValue.itemCompareFunction), /* options: */bindingValue, tempElement, bindingContext);
                $(element).append($(tempElement).children()); // Add data to listview
            });

            $(element).listview('refresh');
        }
    };

    function convertToBindingValue(valueAccessor) {
        /// <summary>Standardizes the properties of the of the valueAccessor object.</summary>
        /// <returns>An object containing standardized binding properties.</returns>

        var bindingValue = ko.utils.unwrapObservable(valueAccessor());

        // If bindingValue is the array, just pass it on its own
        if ((!bindingValue) || typeof bindingValue.length == 'number')
            return {
                'data': bindingValue,
                'divider': function () { return ""; },
                'templateEngine': ko.nativeTemplateEngine.instance
            };

        // If bindingValue.data is the array, preserve all relevant options
        return {
            'data': ko.utils.unwrapObservable(bindingValue['data']),
            'divider': bindingValue['divider'] || function () { return ''; },
            'dividerCompareFunction': bindingValue['dividerCompareFunction'],
            'itemCompareFunction': bindingValue['itemCompareFunction'],
            'includeDestroyed': bindingValue['includeDestroyed'],
            'afterAdd': bindingValue['afterAdd'],
            'beforeRemove': bindingValue['beforeRemove'],
            'afterRender': bindingValue['afterRender'],
            'templateEngine': ko.nativeTemplateEngine.instance
        };
    };

    function sortKeys(data, compareFunction) {
        /// <summary>Convert the properties of a given object to an array and return them in sorted order.</summary>
        /// <param name="data">The object who's properties must be sorted.</param>
        /// <param name="compareFunction">The compare function that must be used during sorting.</param>
        /// <returns type="Array">The sorted properties of the object.</returns>

        var keys = Array();

        for (var key in data) {
            keys.push(key);
        }

        return keys.sort(compareFunction);
    }
})();
Categories: jQuery, MVVM

Implementing an HTML5 drag & drop based file upload in ASP.NET MVC 3

May 21, 2012 31 comments

Introduction

HTML5 makes it possible to develop more powerful and more user friendly web applications than we could do ever before. One of the interesting new features is support for drag and drop of files. In the past, if your application needed to provide the possibility to upload files you had to use a file selection chooser. Although this works without issues in all browsers, it is far from user friendly. In native applications, users can interact with files by using drag and drop which is much more intuitive. Luckily for us, HTML5 now also supports this and it is already supported in a number of browsers (Chrome, Firefox, Safari, …).

In this article, I will show you how we can implement drag & drop in an ASP.NET MVC3 web application. We will create a webpage containing a simple drop area that changes of color when the user is dragging a file over the page and we will update the content of the page if the file upload was successful. In order to implement the client side code, we will make use of jQuery and a jQuery plugin called “jquery-filedrop” that simplifies implementing drag & drop based file upload.

You can download “jquery-filedrop” from:
https://github.com/weixiyen/jquery-filedrop

Implementation

We start by creating a new ASP.NET MVC 3 web application and we add the file “jquery-filedrop.js” to the Scripts folder of the project. After this is done we modify the default layout “Views/Shared/_Layout.cshtml” to reference our newly added JavaScript library. In order to do is, add the following line to the head tag.

<script src="@Url.Content("~/Scripts/jquery.filedrop.js")" type="text/javascript"></script>

I also choose to upgrade to the latest version of jQuery with NuGet; but this is not strictly required…

Now that everything is in place, we can start writing our applicative code. First create a HomeController and an “Index” view for this controller.

Solution View

In the view we will create a div with the id “dropZone”. This will be the area in which users can drop files. Next, we will write some jQuery code that will make the div “droppable”. Thanks to the jQuery plugin we are using, the required code for this is trivial. We just have to call the method “filedrop” with a number of needed parameters.

For our sample application, we provide the URL for the file upload, the parameter name of the files to be used in the HTTP POST, the maximum allowed number of files that can be upload simultaneously and we will listen to some events to add some dynamic behavior to the page. More specifically, if the user is dragging a file over the page we will change the color of the drop zone and we will update a list of successfully updated files.

@{
    ViewBag.Title = "Index";
}
<style type="text/css">
    #dropZone {
        background: gray;
        border: black dashed 3px;
        width: 200px;
        padding: 50px;
        text-align: center;
        color: white;
    }
</style>
<script type="text/javascript">
    $(function () {
        $('#dropZone').filedrop({
            url: '@Url.Action("UploadFiles")',
            paramname: 'files',
            maxFiles: 5,
            dragOver: function () {
                $('#dropZone').css('background', 'blue');
            },
            dragLeave: function () {
                $('#dropZone').css('background', 'gray');
            },
            drop: function () {
                $('#dropZone').css('background', 'gray');
            },
            afterAll: function () {
                $('#dropZone').html('The file(s) have been uploaded successfully!');
            },
            uploadFinished: function (i, file, response, time) {
                $('#uploadResult').append('<li>' + file.name + '</li>');
            }
        });
    });
</script>

<h2>File Drag & Drop Upload Demo</h2>
<div id="dropZone">Drop your files here</div>
<br/>
Uploaded Files:
<ul id="uploadResult">

</ul>

Now that the view is in place, we can add the required method to our HomeController in order to support the file upload. I choose to call this method “UploadFiles”, it must have a parameter named files (the one we choose in the configuration of our drop zone) and the parameter must be of type “IEnumerable<HttpPostedFileBase>”. In order to keep things simple, we will just write the files to some temporary folder. But of course you can do whatever you want with the uploaded files…

public class HomeController : Controller
{
    private const string TempPath = @"C:\Temp";

    public ActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public ActionResult UploadFiles(IEnumerable<HttpPostedFileBase> files)
    {
        foreach (HttpPostedFileBase file in files)
        {
            string filePath = Path.Combine(TempPath, file.FileName);
            System.IO.File.WriteAllBytes(filePath, ReadData(file.InputStream));
        }

        return Json("All files have been successfully stored.");
    }

    private byte[] ReadData(Stream stream)
    {
        byte[] buffer = new byte[16 * 1024];

        using (MemoryStream ms = new MemoryStream())
        {
            int read;
            while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, read);
            }

            return ms.ToArray();
        }
    }
}

Conclusion

Now al what rest us, is pressing F5 and testing the application in a browser that already supports this HTML5 specification. I have tested this in the latest version of Google Chrome and Firefox.

image

As I have shown you, it is very easy to implement file drag and drop behavior into your web application, but of course you will still have to support a legacy mechanism as well for people with older browsers.

You can download my sample from:
https://skydrive.live.com/redir?resid=7A3CD9AFBD57E81E!428

Categories: ASP.NET MVC, C#, jQuery

Developing mobile applications with PhoneGap and JQuery Mobile

March 26, 2012 24 comments

Introduction

For a very long time I have avoided learning HTML and ASP.Net and stayed away of applications using it. I didn’t wanted to end up with messy code and hacks to support multiple browsers (read: Internet Explorer 6.0). But as HTML5 and CSS3 are gaining more momentum, I have changed my mind and I have started learning HTML, CSS and JavaScript. Even if these technologies have their annoyances, they are THE way to develop cross platform software applications.

Recently there was a lot of buzz around PhoneGap. This a software framework that allows wrapping HTML5 applications into native applications for various mobile platforms (including iPhone, Android and Windows Phone). The idea of developing a mobile application for multiple platforms with a single code base is very tempting.

During the article I have still used the old name PhoneGap, but the framework has recently been open-sourced and renamed into Cordova (http://incubator.apache.org/projects/callback.html). Adobe will continue to offer a commercial version using the PhoneGap branding (http://phonegap.com/).

During the past days, I have developed a simple Todo Application using the following technologies:

  • PhoneGap to wrap in into a native application.
  • JQuery Mobile for the UI.
  • HTML5 local storage to store the user data.
    The applications consists of 6 screens and runs completely local; it does not require internet access to function. Theoretical it should be able to run on iPhone, Android and Windows Phone, but I was only able to test on a real Windows Phone device and on the desktop versions of Firefox, Safari and Chrome. So although it was not tested on multiple devices, I don’t expect a lot of issues when running it on IOS or Android.

todo1 todo2

PhoneGap & JQuery Mobile

I selected JQuery Mobile for the user interface because I like using JQuery for JavaScript development and thus it seemed the most natural choice for my screens. JQuery Mobile was pleasant and easy to use; in no time I was able to develop my screens using a simple HTML syntax combined with HTML5 data-* attributes.

I tested the application both on local browsers (Firefox, Chrome, etc.) and on the Windows Phone mobile browser. But the issues started when I started wrapping my HTML code into PhoneGap. Suddenly nothing still worked. After some trial and error, I discovered that it was required to add the PhoneGap JavaScript library to the web pages, even if you don’t need to use any special features of PhoneGap.

A second issue that I discovered was that JQuery Mobile and PhoneGap currently don’t work good together on Windows Phone 7. I was using a single HTML file per page, but the only thing that worked was either disabling the AJAX based loading of pages in JQuery Mobile or reverting to multi page HTML files. I choose for the multi page HTML files.

I hope that the page loading issue on Windows Phone will be fixed by either the PhoneGap or the JQuery Mobile developers as it is annoying.

Debugging

Debugging a HTML application wrapped in PhoneGap is not possible. The only option is to test and debug it with the developer tools of desktop browsers. If something goes wrong on the mobile device in PhoneGap, the only thing you can do is using old school console log statements to figure out what is happening.

I solved this issue this by creating two projects: a web application and the phone gap WP7 project. I develop and test with the web application project on a local browser and when I want to run it on a device, I execute a script to put the HTML  and JavaScript code into the WP7 project. Below you can find the synchronization script that I used:

@ECHO OFF

SET sourceDirectory=Html5TodoApp
SET destinationDirectory=Html5TodoApp.WindowsPhone\www

MKDIR %destinationDirectory%\images\

COPY "%sourceDirectory%\*.js" %destinationDirectory%\
COPY "%sourceDirectory%\*.html" %destinationDirectory%\
COPY "%sourceDirectory%\*.css" %destinationDirectory%\
COPY "%sourceDirectory%\images\*.gif" %destinationDirectory%\images\
COPY "%sourceDirectory%\images\*.png" %destinationDirectory%\images\

patch.exe "%destinationDirectory%\index.html" index.patch

The patch of my index.html file is required because for PhoneGap I have to add a reference to the cordova.js file. Something that is not possible in a regular browser. The “index.patch” is a regular patch file that saves me from having to update my index.html manually when I copy the source code to the PhoneGap project.

*** C:\Users\Pieter\Documents\Visual Studio 2010\Projects\Html5TodoApp\Html5TodoApp\index.html    Thu Mar 22 17:15:13 2012
--- C:\Users\Pieter\Documents\Visual Studio 2010\Projects\Html5TodoApp\Html5TodoApp.WindowsPhone\www\index.html    Thu Mar 22 17:20:51 2012
***************
*** 4,9 ****
--- 4,10 ----
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, maximum-scale=1.0, user-scalable=no;" />
<title>Todo Demo Pieter</title>
<link rel="stylesheet" href="jquery.mobile-1.1.0-rc.1.css" />
+         <script src="cordova-1.5.0.js"></script>
<script src="jquery-1.7.1.js"></script>
<script src="jquery.mobile-1.1.0-rc.1.js"></script>
<script src="todo.logic.js"></script>

Conclusion

Would I recommend developing mobile applications using PhoneGap? It would depend on the application. It was a little bit more difficult to develop and test everything compared to using c# and Silverlight and the performance of the interface was also less “fluent” than a native application on my Nokia Lumia 800. But the big advantage is that my application can run on multiple platforms with almost the same code and this can be important for companies that want to deploy their business application on multiple platforms.

Microsoft patterns & practices: Project Silk

This was already released a few months ago, but I still want to share it with you: Microsoft patterns & practices has released guidance around building modern browser applications. The guidance includes both a written e-book and a sample web application. If you or your company want to rethink your current web application architecture this is a must read.

The guidance describes both the client side code (how to create jQuery UI widgets, how to achieve modularity with JavaScript, how to do client side caching, …) and the server side implementation based on ASP.Net MVC.

You can find the guidance at Codeplex:
http://silk.codeplex.com/

Categories: C#, jQuery