Table of Contents
It has been long due I was planning to write an article on creating some useful ASP.NET MVC application. I have codenamed it "DailyJournal". It is a simple application which allows creation of multiple activities and assigning tasks to these activities. It's kind of a "Yet another Task/Todo application".
The application makes use of jQuery, so some knowledge is assumed of it. The application also makes of the the jQuery UI plug-in.
For the basics of jQuery, you can refer any of the numerous resources, or my article here at CodeProject: Getting Friendly With jQuery.
The credentials which you can use with the attached demo application are shown below:
User Name : admin
Password : admin123
- ASP.NET MVC
- jQuery + jQuery UI (for AJAX and UI)
- ELMAH for error logging
This is just a rough draft, and so I am putting down some of the known limitations.
Some points of warning before we move further with this application. This is just an early prototype. As such, many of the design principles have been ignored. But, I will try to cover that up in the next update once I get my head around this.
The application, in its current state, supports the following features:
- Create users
- Assign activities to users
- Assign tasks to activities
- Assign a status to a task
The user creation/authentication is being done by the default Membership Provider. Most of the activities are highly visual, i.e., you can drag-drop tasks to different areas, in-place edition of task details, and so on.
The following are the current issues with the design, which I promise to refactor in the second version:
- No validations.
- Fat Controller.
- XSS/CSS vulnerable.
- No service model/abstraction yet. For the demo, LINQ to SQL is implemented.
- No separation of layers.
- UI Design.
- et el...
Note: The second iteration will cover the following points:
- Major refactoring
- All the above issues
- Unit tests
- Add extensible validation
- IOC
- Theme support
Once you log in to the system, you are presented with the following screen which lists the current activities:
You can add a new activity by clicking on the "New Activity" button. It brings up a dialog box.
The dialog is created using the "jQuery UI Dialog" plug-in. To create a dialog using the jQuery UI, use the following two steps:
- Define a
div
which should be shown as the dialog and give it an ID (you can also give it a CSS class).
<div id="dlg-work-area">
<% Html.RenderAction("AddWorkArea", "Work"); %>
</div>
- To invoke the dialog, add the following code in the jQuery ready function:
$(function() {
$("#dlg-work-area").dialog( {
autoOpen: false,
title: "Add Activity",
height: 250,
width: 350,
modal:true
});
}
- Hook the click event handler on the "Add New Activity" button.
$("#btnNewActivity").click(function()
{
$("#dlg-work-area").dialog('open');
});
Note: We have set autoOpen
to false
as we want the dialog to open when the "Add New Activity" button is clicked.
Once you select an activity, you are taken to the task screen. Here you can create a new task, and change the task status by dragging and dropping within the available columns. There is a minor issue with drag-drop with this version as the column headers are also draggable. This will be fixed.
You can edit the task inline. Double click on the task description to do so. It will bring up the following screen. You can save or cancel the changes.
In place editing is achieved by the code snippet outlined below. Refer to the widget.js file under the /scripts/widget folder.
Set up the events and in-place editing in the onready
function of jQuery:
$(function() {
setEditable();
});
The setEditable()
function is defined below:
function setEditable() {
$('.edit').click(function() {
var textarea = '<div><textarea>'+$(this).html()+'</textarea>';
var button = '<div><input type="button" value="SAVE" ' +
'class="smallButton saveButton" /> <input ' +
'type="button" value="CANCEL" ' +
'class="smallButton cancelButton" /></div></div>';
var revert = $(this).html();
$(this).after(textarea+button).remove();
$('.saveButton').click(function(){saveChanges(this, false);});
$('.cancelButton').click(function(){saveChanges(this, revert);});
})
.mouseover(function() {
$(this).addClass("editable");
})
.mouseout(function() {
$(this).removeClass("editable");
});
};
The above code does the following things:
- Find all elements with the class "
edit
".
- Build up the text area and button.
- Hook up the events for the Save and Cancel buttons.
For details about this, refer to the widget.js file.
The below is the bare bone model diagram:
The application in this prototype uses the routes that is defined in the global.asax.cs, which is of the format:
routes.MapRoute(
"Default", "{controller}/{action}/{id}", new { controller = "Dashboard", action = "Index",
id = UrlParameter.Optional } );
The default "Controller" is the "Dashboard" and the default action is "Index". The routes follow the basic convention of controller/action/{optional ID} pattern.
The dashboard drag-drop uses the jQuery UI Sortable plug-in. This can be found in the widget.js file.
function reBind(element) {
$(element).sortable({
connectWith: '.column',
receive: function(event, ui)
{
var target = event.target.id;
if (null != target)
target = target.substring(target.lastIndexOf("_")+1);
var taskId = ui.item.attr('id')
if (null != taskId)
taskId = taskId.substring(taskId.lastIndexOf("_")+1);
var postdata = "taskStatusId=" + target + "&taskId="+taskId ;
$.post("/work/savewidget?" + postdata, function(data){
});
}
});
}
What it does is find all the elements with a class of "column" and apply sortable behaviour on it. The code to save the widget location is in the "receive" event handler. The data is collected and sent to the "Work" Controller's "SaveWidget
" action.
The controller code to render the task widgets is shown below. If there are no task status (as tasks are grouped by status), the user is taken to the "AddTaskStatus" screen.
[HttpGet]
public ActionResult Board(Guid workAreaId)
{
var allStatus = db.TaskStatus.Where(ts => ts.WorkAreaId == workAreaId).ToList();
if (allStatus.Count == 0)
{
return RedirectToAction("AddTaskStatus", new { workAreaId = workAreaId });
}
ViewData["AREA_ID"] = workAreaId.ToString();
ViewData["AllStatus"] = allStatus;
return View(allStatus);
}
The inline task editing is passed to the following controller action:
[HttpPost]
public void SaveTaskDesc(Guid TaskId, string desc)
{
var task = db.Tasks.Where(t => t.Id == TaskId).SingleOrDefault();
task.Description = desc;
db.SubmitChanges();
}
<sectionGroup name="elmah">
<section name="errorLog" requirePermission="false"
type="Elmah.ErrorLogSectionHandler, Elmah" />
</sectionGroup>
<elmah>
<errorLog type="Elmah.XmlFileErrorLog, Elmah"
logPath="~/App_Data" />
</elmah>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd"
type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
First, we define a section for "elmah". Then we set the log to an XML log file. And then the most important thing is to setup the HTTPHandler. By setting up ELMAH, the error logging functionality is taken care of. The URL to access the errors is: http://serverurl/elmah.axd.
I know there are lots of limitations, but I hope that with further iterations, it will get better and better.
The second version of this article is almost ready. I am doing some refactoring and cleanup and putting some final design thoughts.
- June 08, 2010 - Updates and corrections.
- May 20, 2010 - Early prototype.