Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

C# and .NET Tips and Tricks

Quests in programming in .NET

Δεκέμβριος 2011 - Δημοσιεύσεις

ASP.NET MVC, HTML5, CSS3, JQuery templates, IEnumerable binding, knockoutjs and offline cache in a demo. A brave new world

In one of my previous posts(appox. a year ago) I have demonstrated how to bind to IEnumerables in ASP.NET MVC. I have shown several ways of dynamically adding/removing elements from the list through the use of Javascript and then jQuery-templates. In that postI have used the traditional ASP.NET MVC literals <% %> and of course the whole site operated in HTML4 and jQuery.

 

Today a lot of things have changed. From HTML4 we are moving to HTML5, we start using CSS3 for our styles, we use Razor for our views in ASP.NET MVC and we can take advantage of the knockoutjslibrary that has managed to bring the world of MVVM patterns closer to the world of Javascript and it did it with some impressive results. Therefore, in this post we are going to re-implement the same project adopting all those new (or not so new) great technologies.

 

We need to implement a simple site where we have a list of clients with a first and last name and we need to give to our users the ability to manage that list (insert/remove/edit elements). The initial list and all changes are communicated to the server using AJAX calls. We need to make the site friendly to computer and mobile browsers and provide the maximum possible quality in terms of user experience in both environments. Finally we need our site to know when it is online, in order to inform the user and prevent any changes.

 

(Device awareness) CSS3 Styling with media-queries and other goodies

The first change is the fact that we want to differentiate the view depending on whether we look the demo from a mobile device or from our pc. Therefore we will use CSS3 media queries to change the .css file used depending on the situation detected. In the _Layout.cshtml file we add the following:

 

<link href="@Url.Content("~/Content/mobileDevice.css")" 
      rel="stylesheet" type="text/css" media="screen and (max-width:480px)" />
<link href="@Url.Content("~/Content/computer.css")" 
      rel="stylesheet" type="text/css" media="screen and (min-width:481px)" />

 

Where the important attribute is the “media” attribute which differentiates the stylesheet based on the width of the device.

 

Another element that enables us to define specifically how things will be presented on a mobile device is the viewport meta tag as follows:

 

<meta name="viewport" content="user-scalable=no,width=device-width" />

 

This says to the mobile browser that we do not want to allow the user to be able to scale the webpage with his/her fingers and also that the viewport of the browser (the width of the mobile browser) is the same as the width of the device (as opposed to the more traditional approach where the mobile browser allows us to pan and zoom). Just adding this line makes our page look more like a “mobile app” to the device’s browser.

 

We also want to use a specific kind of fonts (Segoe) that are not the “standard” and therefore need to be embedded to the page. We can do that with CSS3 starting by finding the .ttf file we need from our computer and submitting that to the fontsquillerpage in order to get the font files in various formats recognized by most browsers and the .css definitions of our new font. We get the files, we put them in a special “Fonts” folder in our page and include a reference to the .css file holding their definitions (newFonts.css file in our project). Below is a screenshot of the website’s home page as seen in a browser more than 480px wide and in a browser less that 480px wide with the embedded fonts:


 

Capture2    Capture1


Note the new fonts achieved as previously mentioned and the rounded corners of the links in the mobile version. Those are achieved by the new elements in css as follows combined with the advanced selectors in CSS3 (in specific those ones apply the border rounded corner rules only to the first and last li elements of the unordered list (ul) that holds the navigation:

 

<nav>
    <ul>
        <li>@Html.ActionLink("jump to the demo","Simple","Clients")</li>
        <li>@Html.ActionLink("about","About","Home")</li>
    </ul>
</nav>

 

and the CSS applying the rounded borders:

 

nav ul li:first-child a 
{
    -webkit-border-top-left-radius: 8px; 
    -webkit-border-top-right-radius: 8px; 
    border-top-right-radius: 8px; 
    border-top-left-radius: 8px; 
    -moz-border-top-right-radius: 8px; 
    -moz-border-top-left-radius: 8px;
}
nav ul li:last-child a 
{
    -webkit-border-bottom-left-radius: 8px; 
    -webkit-border-bottom-right-radius: 8px; 
    border-bottom-right-radius: 8px; 
    border-bottom-left-radius: 8px; 
    -moz-border-bottom-right-radius: 8px; 
    -moz-border-bottom-left-radius: 8px;
}

 

We have also applied some shadow to the header of the mobile version with: text-shadow:1px 1px #fff; Also note the new semantic markup for HTML5. The “nav” element indicates that there is a site navigation set of links in this part of the webpage. The same difference in styling is applied also to the “demo” page as shown in the images below":


image     image


Again the two different stylesheets have definitions that achieve the differentiation.

 

(Easier unobtrusive Javascript) Data binding with knockoutjs and jQuery templates

And now we move on to the UI in terms of the client’s list. The methods shown in the previous postare all nice but they rely on a page “POST” to save the values and a page “GET” to get the values from the controller’s actions. We want to turn the page fully AJAX based and provide the maximum quality of user experience. For starters, let us imagine that we have “magically” received the list of clients and simulate this by adding in the page’s JavaScript the following list:

 

var clientsList = [{ id :1, FirstName: 'Ioannis', LastName: 'Panagopoulos' },                   
                   { id: 2, FirstName: 'Kostas', LastName: 'Papadopoulos' },                   
                   { id: 3, FirstName: 'Petros', LastName: 'Georgiadis' },                   
                   { id: 4, FirstName: 'Maria', LastName: 'Apostolou' }];

 

This is hardwired and will be later substituted with an AJAX call. To use knockoutjsand jQuery templateswe need to download and include in the _Layout.cshtml file the following:

 

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.tmpl.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout-1.2.1.js")" type="text/javascript"></script>

 

The first step is to define an element in the page where the list will be displayed:

 

<section id="clientsList" data-bind='template: "clientTemplate"'></section>

 

The data-bind attribute is called a custom data attribute in HTML5. Custom data attributes are all attributes prefixed with “data-“. After the “-“ you can put whatever you like. HTML5 defines that custom data attributes are to be used to assist the programmer in any way (meaning that the word after the – may be anything) in developing the page. We know that knockoutjs searches for the data-bind custom data attribute to do its magic. In the previous declaration we tell through the custom data attribute that within the “clientsList” section (section is another HTNL5 element, it could easily be a div in this case) we want to render a jQuery template named “clientTemplate”. This template is as follows:

 

<script type="text/html" id="clientTemplate">    
    {{each(i,client) clients}}
    <div id="Clientidx">
        <input class="clientNameField" data-bind="value: FirstName" />
        <input class="clientNameField" data-bind="value: LastName"  />
        <span class="removeButton" data-bind="click: function(){ removeClient(client) }">x</span>
    </div>
    {{/each}}
</script>

 

This template says that we need to iterate a list named “clients” consisting of objects which we will call “client” (the name is just a way to have a reference to the object in each iteration). For each object, we need an input element to present the value of its FirstName property and an input element for the value of its LastName property. Finally for each object an “x” will appear that has a handler to be executed on its click event. The handler says that we need to call the function removeClient passing as argument the object of “x”’s line.

 

In another place of our code we need to be showing the total number of clients in the list:

 

<h1>clients list (<span data-bind="text: clients().length">&nbsp;</span>)</h1>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

Where we bind to the total number of elements in the clients list. And in another place we have the following:

 

<button class="defaultButton" data-bind="click: addClient">add a client</button>
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

So the template finds the template definition, FirstName and LastName find their object within the clients list but who supplies the clients list and the removeClient and addClient methods? Well those are supplied by an object we call ViewModel and is defined as follows:

 

var ViewModel = {
            clients: ko.observableArray(clientsList),
            removeClient: function (clientToRemove) {
                ViewModel.clients.remove(clientToRemove);
            },
            addClient: function () {
                ViewModel.clients.push({id:-1, FirstName: "", LastName: ""});             
            }
        };
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

In other words, the ViewModel holds everything our UI has in terms of functionality and data. The ko.observableArray that is used makes the array be able to notify about its changes to every element attached to its properties (including the one we defined showing the total number of clients in the list). This makes it possible for the dynamic change of the number of clients reported whenever we press “add client”. This is the equivalent to INotifyPropertyChanged! The final step is to attach the ViewModel to the UI:

 

$(document).ready(function () {ko.applyBindings(ViewModel);});
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

and all the bindings find their way within the ViewModel. To complete our example we need to be able to get this information from an ASP.NET MVC Controller and submit the results to one through AJAX calls. Provided that we implement a controller to get all the clients as follows:

 

public JsonResult GetClients()
{
    List<Client> Clients = new List<Client>();
    _populateClientsList(Clients);
    return Json(Clients,JsonRequestBehavior.AllowGet);
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

The JavaScript is changed to get the client’s list as follows:

 

$(document).ready(function () {
    $.getJSON("@Url.Action("GetClients","Clients")", null, clientsReceived);
});
function clientsReceived(res) {
    ViewModel.clients = ko.observableArray(res);
    ko.applyBindings(ViewModel);
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

(there is no need anymore for the clientsList)

 

The same applies for posting the results back to the server when the user has finished editing the list we call:

 

saveClientsList: function () {
    $("#ajaxCallWait").toggle();
        $.post("@Url.Action("SaveClients","Clients")", $.toDictionary({ Clients: ko.toJS(ViewModel.clients) }), 
                function (data) {
                   $("#ajaxCallWait").toggle();
                   alert(data.message);
                })
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

A few things to note here. First we use the ko.toJS function to get the “raw” list without the ko.ObservableArray embelishments. Second we use a wonderful plugin from here(toDictionary) to enable the correct generation of the elements required for binding with the ASP.NET MVC controller. The controller now is as follows:

 

[HttpPost]
public JsonResult SaveClients(IEnumerable<Client> Clients)
{
    //Your saving logic here
    return Json(new { result = "success",
                      message = String.Format("{0} clients received",Clients.Count()) });
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

 

So this is it! We have managed to bind everything with considerable few lines of code compared to what we have achieved.

 

Knowing when we are offline and reacting accordingly

When the user is offline he will not be able to receive any list elements but he will be still be able to add clients and press the save button. We want to prevent this by indicating with a message that the user is offline.

 

We include a cache.manifest file in our project as follows:

 

CACHE MANIFEST
CACHE:
/demo/Clients/Simple
/demo/Scripts/knockout-1.2.1.js
/demo/Scripts/jquery-1.5.1.min.js
/demo/Scripts/jquery-toDictionary.js
/demo/Scripts/jquery.tmpl.js
/demo/Content/computer.css
/demo/Content/mobileDevice.css
/demo/Content/newFonts.css
FALLBACK:
/demo/Scripts/online.js /demo/Scripts/offline.js
NETWORK:
*
#version 1

 

This says that whatever is below the CACHE: should be stored locally, everything else should be fetched from the network (NETWORK: *) and if the browser cannot reach the online.js file (meaning that there is no internet connection), the browser should use the offline.js file that is automatically cached for offline use.

 

The online.js file has only: var online=true; inside while the offline file has only: var online=false; inside. Our webpage includes only online.js as follows (we DO NOT include the offline.js):

 

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

 

and before performing the initial get request for the clients we add:

 

$(document).ready(function () {
        if (!online) {
                alert("Sorry but you are offline!");
                return;
        }
        $.getJSON("@Url.Action("GetClients","Clients")", null, clientsReceived);
});

 

The trick is that when online online.js will set the online variable to true while when offline the online.js file will be substituted with the offline.js file and the online variable will be false. Note that when you use the cache manifest you need to have the complete path for your files. In my case I have deployed the site in a subfolder named demo. Also note that if you have a single mistake or a missing file or a wrong path in the cache.manifest, then the whole file is not parsed. You generally need fiddlerto debug this correctly where you see what is wrong.

 

Well this is it! The complete makeover of an HTML4 page to HTML5. Hope you have enjoyed it!

 

You may download the project

Posted: Παρασκευή, 16 Δεκεμβρίου 2011 6:08 μμ από iwannis | 2 σχόλια
Δημοσίευση στην κατηγορία: ,