Create a Simple Task List Using ASP.NET, WCF, and jQuery, Part 1
Easily make an application that allows users to define, add, edit, delete, and filter tasks
June 8, 2011
Maintaining a task list (or a to-do list) is a common time management technique that helps us keep track of work and agenda items, along with their priority and status. No wonder Windows Task Manager is often an integral part of web and desktop calendaring applications. In this two-part article, we will build our own task-list application using jQuery, ASP.NET, and Windows Communication Foundation (WCF). Though simple, our task-list application will illustrate how jQuery can interact with server data through WCF services. We will also get to apply several jQuery concepts such as selectors, AJAX calls, and plug-ins.
Functional Requirements
First, let’s summarize our expectations for the task-list application that we are going to build:
Users should be able to define tasks. A task definition consists of title, description, priority (e.g., high, normal, low); status (e.g., pending, in-process, completed); and due date.
Users should be able to add, edit, and delete tasks.
Users should be able to filter tasks on the basis of priority and status.
Tasks that have a due date that has already passed should be highlighted when the application starts.
The application should talk with the server only during add, edit, and delete operations to keep post backs or page refreshes to a minimum.
We will use jQuery, ASP.NET 4.0, and SQL Server 2008 to develop the task-list application. The application will illustrate how to:
Use common jQuery selectors.
Use Microsoft's jQuery template plug-in to create a grid layout.
Deal with dates while communicating with WCF.
Make AJAX calls to WCF service through jQuery.
Create your own plug-in using jQuery.
Creating the Web Application
To begin, use Visual Studio to create a new ASP.NET website. Figure 1 shows the New Web Site dialog box in Visual Studio.
Figure 1: Creating a new website in Visual Studio
Once the website is created, add a new folder named Scripts, and place the jQuery files in it, as Figure 2 illustrates.
Figure 2: Placing jQuery script files
Note: For detailed instructions about downloading the jQuery library and the jQuery templates plug-in, see the ReadMe.txt file in the code download that accompanies this article.
All the data that the task-list application requires will be stored in a SQL Server database. To add a new SQL Server database to your website, right-click the App_Data folder and select Add New Item to open a dialog box, which Figure 3 shows.
Figure 3 : Adding a SQL Server database
Give the database a name, such as TaskListDb.mdf. Once the database is ready, create a new table named Tasks with the schema as shown in Figure 4.
Figure 4: Tasks table schema
The Tasks table has six columns: TaskID, Title, Description, Priority, DueDate, and Status.
Creating a WCF Service to Perform Data Access
The data from the Tasks table needs to be accessed from the client side using jQuery code. Hence, we need a callable endpoint at the server end that can be consumed from the client. A WCF service can provide such an endpoint. We will use a WCF service for adding, updating, deleting, and retrieving data from the Tasks table. Later, we will call this WCF service from the jQuery code.
To create a WCF service, open the Add New Item dialog box again and select WCF Service, as shown in Figure 5.
Figure 5: Adding a new WCF service
Adding a new WCF service will create a .svc file under the root folder and a .cs file under the App_Code folder. The code file contains an interface (IService) that defines the service contract. We will modify the IService interface to include Insert, Update, Delete, and SelectAll methods, as shownin Figure 6.
[ServiceContract]public interface IService { [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] int Insert(TaskData t); [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] int Update(TaskData t); [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] int Delete(Guid id); [OperationContract] [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] TaskData[] SelectAll();}
Notice that the IService interface is decorated with the ServiceContract attribute, indicating that it is a WCF service contract. All the methods of the IService interface are marked with two attributes, namely OperationContract and WebInvoke. The former attribute indicates that the method is a WCF callable operation, and the latter indicates that the method can be called via the Representational State Transfer (REST) programming model. The WebInvoke attribute also specifies three properties. The Method property specifies the HTTP method (GET or POST) that will invoke the method under consideration. The RequestFormat and ResponseFormat properties govern the format in which the request data and response data are serialized. The value of Json indicates that the data transfer between client and server will happen using the JavaScript Object Notation (JSON) format. Another possibility is XML, but since we are using jQuery on the client side, we will stick to the JSON format.
The methods as defined by the following IService interface will be implemented by the Service class, and these are discussed later.
public class Service : IService
{
…
}
Implementing the Service Contract
Before we go into the details of implementing the IService interface, let's create LINQ to SQL classes that will help us perform database operations, such as INSERT, UPDATE, DELETE and SELECT. Open the Add New Item dialog box again, as shown in Figure 7. This time, select the LINQ to SQL Classes option.
Figure 7: Adding LINQ to SQL classes
This will add a .dbml file under App_Code folder. Drag and drop the Tasks table from the Server Explorer onto the design surface of the .dbml file to create a LINQ to SQL class, as shown in Figure 8.
Figure 8: LINQ to SQL class for Task table
In addition to creating LINQ to SQL classes, we also need a class (TaskData) that will carry data between the client and the server. The TaskData class acts as a data contract and is shown in Figure 9. Note: The TaskData class can be found in the Service.cs file in the code download.
[DataContract]public class TaskData{ public TaskData() { } public TaskData(Guid id,string title,string description,int priority,int status,DateTime duedate) { this.TaskID = id; this.Title = title; this.Description = description; this.Priority = priority; this.Status = status; this.DueDate = duedate; } [DataMember] public Guid TaskID { get; set; } [DataMember] public string Title { get; set; } [DataMember] public string Description { get; set; } [DataMember] public int Priority { get; set; } [DataMember] public DateTime DueDate { get; set; } [DataMember] public int Status { get; set; }}
The TaskData class is marked with the DataContract attribute, indicating that it is a WCF data contract. The TaskData class defines six properties that correspond to the Tasks table columns. All the properties are marked with the DataMember attribute, indicating that they will be serialized between the client and the server.
We are now ready to implement the methods of the IService interface. Let's discuss them one by one.
The Insert() method, shown in Figure 10, adds a new task to the Tasks table. The method accepts task details as an object of the TaskData type and returns an integer indicating the success or failure of the operation (though we will not implement the error handling part here). The complete code of the Insert() method is shown in Figure 10.
public int Insert(TaskData t){ Task newTask = new Task(); newTask.TaskID = Guid.NewGuid(); newTask.Title = t.Title; newTask.Description = t.Description; newTask.Priority = t.Priority; newTask.Status = t.Status; newTask.DueDate = t.DueDate; dataContext.Tasks.InsertOnSubmit(newTask); dataContext.SubmitChanges(); return 0;}
The Insert() method creates an instance of LINQ to SQL class named Task and sets its properties. It then inserts the new object using the InsertOnSubmit() method. The changes are committed to the database when the SubmitChanges() method is called.
The Update() method, shown in Figure 11, accepts a task to be modified and saves the modified task into the database using LINQ to SQL. The Update() method first filters the task to be updated based on its TaskID. It then assigns the modified values of the task. Finally, the SubmitChanges() method saves the modified task to the database.
public int Update(TaskData t){ IQueryable<Task> existingTask = from tasks in dataContext.Tasks where tasks.TaskID==t.TaskID select tasks; Task obj = existingTask.SingleOrDefault(); if (obj != null) { obj.Title = t.Title; obj.Description = t.Description; obj.Priority = t.Priority; obj.Status = t.Status; obj.DueDate = t.DueDate; dataContext.SubmitChanges(); } dataContext.SubmitChanges(); return 0;}
The Delete() method, shownin Figure 12, accepts the TaskID of the task to be deleted and deletes the corresponding task record.
public int Delete(Guid id){ IQueryable<Task> existingTask = from tasks in dataContext.Tasks where tasks.TaskID == id select tasks; Task obj = existingTask.SingleOrDefault(); if (obj != null) { dataContext.Tasks.DeleteOnSubmit(obj); dataContext.SubmitChanges(); } return 0; }
The SelectAll method, shownin Figure 13, returns all the available tasks as an array of TaskData objects.
public TaskData[] SelectAll() { var allTasks = from tasks in dataContext.Tasks orderby tasks.DueDate select tasks; List<TaskData> items = new List<TaskData>(); foreach (var obj in allTasks) { items.Add(new TaskData(obj.TaskID,obj.Title, obj.Description,(int)obj.Priority, (int)obj.Status,(DateTime)obj.DueDate)); } return items.ToArray();}
Notice that since Task and TaskData are different classes (one is a LINQ to SQL class whereas the other is a data contract), we need to transfer all the data from Task objects into TaskData objects, then call the ToArray() method of the generic List class to get an array of TaskData objects.
Configuring the WCF Service and Creating a Web Form
Before putting the WCF service in use, we have to make sure that it’s configured correctly. The <system.serviceModel> section of the web.config file, shown in Figure 14, contains all the configuration needed by our WCF service. The lines at callout A are especially important to us. The name attribute of the <service> tag specifies the fully qualified service class name (Service, in our case). A WCF endpoint allows the client to access the functionality exposed by a service. The contract attribute specifies the fully qualified name of the service contract interface (IService).
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="ServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> <endpointBehaviors> <behavior name="ServiceBehavior"> <webHttp/> </behavior> </endpointBehaviors></behaviors><services>BEGIN CALLOUT A <service behaviorConfiguration="ServiceBehavior" name="Service"> <endpoint address="" binding="webHttpBinding" contract="IService" behaviorConfiguration="ServiceBehavior"/> </service>END CALLOUT A </services></system.serviceModel>
Now that we have completed the WCF service, let's move on to the client-side development using jQuery. The task-list application consists of a single web form logically divided into two parts. The top part of the web form, shown in Figure 15, allows a task to be added or edited.
Figure 15: Area for adding or editing a task
The task entry region consists of common ASP.NET server controls, such as TextBox, DropDownList, and Button. Although we will not be using any server-side features of these controls in our code, you can make use of such features if, for example, you need server-side validations. Notice the Due Date entry field. The date picker displayed therein is actually a jQuery plug-in we will build later on.
The bottom part of the web form consists of a table that lists all the previously entered task records in order of oldest to newest due date. The task list table also has check boxes to select one or more tasks for deletion. Just above the task list table there is a drop-down list that allows us to filter tasks on the basis of priority or status. Figure 16 shows the task list table with few sample tasks.
Figure 16: Task list table with sample tasks
jQuery Template Behind the Task List Table
We will be rendering the task list table as shown in Figure 16 using the jQuery Templates plug-in. So we need to ensure that our web form has the following <script> sections:
<script src="Scripts/jquery-1.4.3.js" type="text/javascript"></script><script src="Scripts/jquery.tmpl.js" type="text/javascript"></script>
After these sections are added, we can use jQuery templates plug-in markup. Figure 17 shows this markup.
Have a look at the <script> block shown in Figure 17. This is how you define a jQuery template. Notice that the type attribute is set to text/x-jquery-tmpl. indicating that this <script> block is a jQuery template. Conceptually, jQuery templates are similar to ASP.NET template-based data controls in that they define the layout of the resultant HTML elements when data is bound with them. Our template contains seven columns.
<script id="taskTableContainer" type="text/x-jquery-tmpl"> <tr> <td><input type="checkbox" value=""></td> <td style="width:200px">${Title}</td> <td style="width:200px">${Description}</td> <td style="width:60px">{{if Priority == 1}} High {{else Priority == 2}} Normal {{else Priority == 3}} Low {{/if}} </td> <td style="width:60px">{{if Status == 1}} Pending {{else Status == 2}} In-process {{else Status == 3}} Completed {{/if}} </td> <td style="width:100px">${ToJSDate(DueDate)}</td> <td> <input type="button" value="Show" id="${TaskID}"> </td> </tr></script><table id="taskTable" border="0" class="TaskTable"><tr><th> </th><th>Title</th><th>Description</th><th>Priority</th><th>Status</th><th>Due Date</th><th> </th></tr></table>
The first column renders check boxes so that users can mark one or more tasks for deletion. Columns 2 through 5 display property values of the TaskData object. Recall that the SelectAll() method of our WCF service returns an array of TaskData objects. This array is to be displayed in the table row as defined by the jQuery template. To access individual properties of a TaskData object, we use the following syntax:
${<property_name>}
Thus, ${Title} will return the value of the Title property for a specific TaskData instance.
jQuery templates allow you to render HTML conditionally. For example, we are storing the priority and status information of a task as integer values in the database, but while displaying them, we would like them to be string literals (e.g., "High", "Low"). The if-else construct of jQuery templates allows us to test for some conditions and output HTML accordingly.
The DueDate value returned by the WCF service is first converted into JavaScript-understandable form using the ToJSDate() function. We will discuss this more in later sections.
The last column of the template contains an HTML button. Clicking the Show button will display that task record for editing. Notice how we have assigned the TaskID value to the ID attribute of the button. We did this because we need to know which task is being selected for display.
Although our template is ready, it will not display anything on its own unless we render its contents somewhere in the web form. In our example, we will use a table (taskTable) for that purpose, as shown in the latter part of Figure 17.
Creating the DatePicker Plug-in
We will develop a simple yet convenient date picker plug-in to select the due dates for our tasks (see Figure 15). In order to create our date picker plug-in, we need to add a separate JavaScript (.js) file and place the plug-in code in it. The skeleton of plug-in code is shown in Figure 18.
(function( $ ){ $.fn.DatePicker = function(method) { //plugin code goes here };})( jQuery );
What we are doing here is defining a jQuery plug-in named DatePicker. The DatePicker plug-in is going to have a couple of methods. We will pass the method name as a parameter and invoke certain code accordingly. The skeleton we use ensures that our plug-in won't collide with other libraries that might also use the dollar sign.
Note: The official jQuery website (jquery.com) has documented a set of recommended practices for writing custom plug-ins. If you are not familiar with writing jQuery plug-ins, you should read that documentation as well.
The complete definition of the DatePicker plug-in is shownin Figure 19.
$.fn.DatePicker = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.init.apply(this, arguments); } else { $.error('Method ' + method + ' does not exist on jQuery.DatePicker'); }};
The code in Figure 19 checks the method name that is passed and calls that method from the methods object literal. The methods object literal basically holds the individual methods as a series of name-value pairs so that they can be conveniently called. We need three methods for the DatePicker plug-in: init(), setDate(), and getDate(). If the method parameter is missing, we call the init() method. A skeleton object literal declaration will look like this:
var methods = { init: function (options) {…}, setDate: function (value) {…}, getDate: function () {…}};
Let's look at each of the methods. The init() method renders the DatePicker user interface, consisting of three <select> elements. Figure 20 shows the complete code of the init() method.
init: function (options) { return this.each(function (index) { var dateHTML = "<select>"; var monthHTML = "<select>"; var yearHTML; for (var i = 1; i <= 31; i++) { dateHTML += "<option>" + i + "</option>"; } dateHTML += "</select>"; var monthNames = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"); for (var i = 0; i < 12; i++) { monthHTML += "<option value='" + (i + 1) + "'>" + monthNames[i] + "</option>"; } monthHTML += "</select>"; var defaults = { prev: 3, next: 3 }; var options = $.extend(defaults, options); var dt = new Date(); yearHTML = "<select>"; for (var i = dt.getFullYear() - options.prev; i <= dt.getFullYear() + options.next; i++) { yearHTML += "<option>" + i + "</option>"; } yearHTML += "</select>"; $(this).append(dateHTML); $(this).append(monthHTML); $(this).append(yearHTML);});},
The init() method programmatically creates three <select> elements: day, month, and year. The three for loops essentially fill these three <select> elements. The <select> elements are then appended to the element to which our DatePicker is attached (indicated by this keyword) using the append() method. In our example, we will be using a DIV, so the newly created <select> elements will be added to the DIV element that we specify.
Notice one useful configuration feature, the $.extend() function. We may need to control the days, months, and years being filled in the DatePicker. For example, we may want to fill the year drop-down list with the next three years only. Such a configuration can be accomplished by using the $.extend() function. First we define some defaults. For example, { prev: 3, next: 3 } will display the three previous years and three future years. If no configuration is supplied via the init() method parameter, this default configuration will be used. If, however, some different configuration is provided, we will use the $.extend() method to merged this different configuration with the default configuration. This way, we may configure just the previous number of years or just the next number of years displayed, keeping the other value to its default.
Notice that the init() method returns this to maintain the chainability of jQuery code.
The setDate() method sets the current selection of the three <select> elements to a specified value. Since our DatePicker displays dates in day/month/year format, we will use the same format for passing date values throughout the task-list application. Figure 21 shows the complete code of the setDate() method.
setDate: function (value) { return this.each(function (index) { var dt = value.split("/"); var i = 0; $(this).children().each(function () { $(this).val(dt[i]); i++; }) })},
The setDate() method accepts a date value, splits it to get an array, and then sets the current selections of day, month, and year drop-down lists. Notice how we iterate through all the select elements using the each() method. The each() method is called for each element of the matched set of elements. In our case, there are three child elements to the parent (this). Therefore, code within the each() method will be called three times. To set the current selection of the drop-down lists, we use the val() method and pass the appropriate array element (day, month, or year) as its parameter.
The getDate() method returns the date selected in the DatePicker plug-in to the calling code. Figure 22 shows the complete code of the getDate() method. The getDate() method picks up the current selection from the day, month, and year drop-down lists and stores it in an array. A date in dd/mm/yyyy format is then formed by joining the array elements.
getDate: function () { var dt = new Array(); this.each(function (index) { var i = 0; $(this).children().each(function () { dt[i] = $(this).val(); i++; }) }) return dt.join("/");}};
This completes our DatePicker plug-in. In the web form, make sure to refer our DatePicker plug-ins, as shown here:
<script src="Scripts/DatePicker.js" type="text/javascript"></script>
The Stage Is Set for a Functional App
In this first part of our two-part series, we’ve started developing a simple task-list application using ASP.NET, WCF, and jQuery. We’ve developed a WCF service that exposes methods for adding, modifying, and deleting tasks from the database. We’ve also used the jQuery Templates plug-in to display tasks in a grid fashion. Finally, we’ve developed our own plug-in, DatePicker, which allows us to pick due dates. In Part 2, we’ll complete the task-list application by adding jQuery code that calls the WCF service and makes the application functional.
Bipin Joshi ([email protected]) has been programming since 1995 and has worked with .NET since its inception. He has authored or co-authored several books and numerous articles about .NET technologies. Bipin blogs about yoga and technology at bipinjoshi.com.
About the Author
You May Also Like