Creating Advanced ASP.NET MVC Controls (Part 3, A Scheduler)

by Seth Juarez 18. August 2009 11:16

Purpose

This is part 3 of a series going through the process of creating an advanced control for the ASP.NET MVC system. I've decided to create a schedule control that allows a user to schedule and item on a calendar control as well as add some meta-data information to the scheduled date. Together with the debugger we have built, this should not be too difficult

Getting Started

Whenever I start building a new control, I simply go commando-style and write the html, css, and javascript first to make sure everything looks good on that end. This helps me with debugging.

The Markup

First things first: the html. I like to do calendars this way:

<table class="scheduler_month">
	<tr class="scheduler_month_header">
		<th colspan="7">September 2009</th>
	</tr>
	<tr class="scheduler_days_header">
		<td>Sun</td>
		<td>Mon</td>
		<td>Tue</td>
		<td>Wed</td>
		<td>Thu</td>
		<td>Fri</td>
		<td>Sat</td>
	</tr>
	<tr class="scheduler_month_days">
		<td class="scheduler_month_invalid_day"></td>
		<td class="scheduler_month_invalid_day"></td>
		<td class="scheduler_month_day">1</td>
		<td class="scheduler_month_day">2</td>
		<td class="scheduler_month_day">3</td>
		<td class="scheduler_month_day">4</td>
		<td class="scheduler_month_day">5</td>
	</tr>
	<tr class="scheduler_month_days">
		<td class="scheduler_month_day">6</td>
		<td class="scheduler_month_day">7</td>
		<td class="scheduler_month_day">8</td>
		<td class="scheduler_month_day">9</td>
		<td class="scheduler_month_day">10</td>
		<td class="scheduler_month_day">11</td>
		<td class="scheduler_month_day">12</td>
	</tr>
	<tr class="scheduler_month_days">
		<td class="scheduler_month_day">13</td>
		<td class="scheduler_month_day">14</td>
		<td class="scheduler_month_day">15</td>
		<td class="scheduler_month_day">16</td>
		<td class="scheduler_month_day">17</td>
		<td class="scheduler_month_day">18</td>
		<td class="scheduler_month_day">19</td>
	</tr>
	<tr class="scheduler_month_days">
		<td class="scheduler_month_day">20</td>
		<td class="scheduler_month_day">21</td>
		<td class="scheduler_month_day">22</td>
		<td class="scheduler_month_day">23</td>
		<td class="scheduler_month_day">24</td>
		<td class="scheduler_month_day">25</td>
		<td class="scheduler_month_day">26</td>
	</tr>
	<tr class="scheduler_month_days">
		<td class="scheduler_month_day">27</td>
		<td class="scheduler_month_day">28</td>
		<td class="scheduler_month_day">29</td>
		<td class="scheduler_month_day">30</td>
		<td class="scheduler_month_invalid_day"></td>
		<td class="scheduler_month_invalid_day"></td>
		<td class="scheduler_month_invalid_day"></td>
	</tr>
</table>
This comes out looking like:

1stPassCalendar

Styles...

It looks pretty good as a starting point. With a little css we can actually make it look good (Disclaimer: I write programs and thus cannot be trusted with what "looks good"):

.scheduler_month
{
	border: solid 1px #C0C0C0;
	border-collapse: collapse;
}

.scheduler_month_header th
{
	background: #714546;
	color: white;
	height: 20px;
	width: 210px;
	font-size: 12px;
}

.scheduler_days_header td
{
	background: #FFA54C;
	color: white;
	height: 20px;
	width: 30px;
	font-size: 12px;
	text-align: center;
	font-weight: bold;
}

.scheduler_month_invalid_day, .scheduler_month_day
{
	height: 20px;
	width: 30px;
	font-size: 12px;
	text-align: center;
	border: solid 1px #C0C0C0;
}

.scheduler_month_invalid_day
{
	border: none;
	background: #E2E2E2;
}

Here is the outcome:

2stPassCalendar

Functionality?

The goal of the control is to save/edit data for each day and mark the calendar if there is any associated data with the day. In order to do this, we need to have a mini-form to take and display data:

<div id="DayData">
	<div class="label">Date:</div>
	<div id="DayCurrent" class="input"></div>
	<div class="label">Title:</div>
	<div class="input"><input type="text" id="DayTitle" name="DayTitle"/></div>
	<div class="label">Description:</div>
	<div class="input"><textarea id="DayDescription" name="DayDescription"></textarea></div>
	<div class="button">
		<input type="button" value="Cancel" id="DayCancel" name="DayCancel" />
		<input type="button" value="Save" id="DaySave" name="DaySave" />
		<input type="hidden" id="DayId" name="DayId" />
	</div>
</div>

This little number has the visible textboxes where the interaction takes place as well as a hidden field that will allow us to maintain state (DayId). Adding some more styles we end up with:

3rdPassCalendar

Do some work already!

Now for some jQuery magic! We want to display the mini-form, gather data, and persist it (at least on the client side for now). Some JavaScript first:

$('.scheduler_month_day').click(function(event) {
	var id = this.id;

	// proper date object
	var d = convertDate(id);
	
	// put in value (if exists)
	var index = window.Changes.find(function(x) { return x.Id == id; });
	if(index > -1) {
		$('#DayTitle').val(window.Changes[index].Title);
		$('#DayDescription').val(window.Changes[index].Description);
	} else {
		$('#DayTitle').val('');
		$('#DayDescription').val('');
	}

	// set the id to proper cell reference
	$('#DayId').val(this.id);
	$('#DayCurrent').text(d.toDateString());

	// make it look nice when we show it
	if(!$('#DayData').is(':hidden'))
		$('#DayData').fadeOut('fast').hide();

	$('#DayData')
		.css({left: event.clientX + 10, top: event.clientY + 10})
		.fadeIn('slow').show();
});


window.Changes = new Array();
$('#DaySave').click(function() {
	// get values
	var id = $('#DayId').val();
	var title = $('#DayTitle').val();
	var desc = $('#DayDescription').val();
	
	// already in there?
	var index = window.Changes.find(function(x) { return x.Id == id; });

	// do appropriate thing if it already exists
	if(index == -1)
		window.Changes.push({ Id: id, Title: title, Description: desc });
	else
		window.Changes[index] = { Id: id, Title: title, Description: desc };

	$('#' + id).addClass('scheduler_month_day_data');

	// close win
	$('#DayData').fadeOut('fast').hide();

	// make sure everything is ok
	if(window.isDebug)
		_(window.Changes).clear()
			.write('Current Change Set');
});

Explanation

Note that the crux of the code does the following:

  1. Fill in form data (if it exists)
  2. Save or update data (depending on whether or not it exists in the first place)
The magic is on line 31 where there is a global array that maintains all of the changes. This is where the debugger comes in handy. It allows us to visualize what data has been persisted on the client side. On line 8 and 39 there is an interesting function worthy of mentioning. What I have done is "extend" the functionality of the Array object by adding:

Array.prototype.find = function(x) {
    for (var i = 0; i < this.length; i++) {
        if (typeof (x) == 'function' && x(this[i]))
            return i;
        if (this[i] == x)
            return i;
    }
    return -1;
};

The primary job is to figure out if the array has an element in it or not. Either a value or a function can be passed in. Naturally I chose the function parameter since we are dealing with an array of objects. What it does is tell me if the save is an update or an addition. Looking the the control code again, we can see this happening on lines 9 and 42. It decides what to do if the item does not exist by using the DayId (line 18) that we save and later retrieve. Here is a picture of the whole thing (debugger and all):

finalPassCalendar

Where to from here?

So now what? This control is all nice and all, but here are some things left to do:

  1. Make the thing more generic (i.e. it needs to be able to represent any month or number of months)
  2. Get initial data from persistent storage (i.e. auto populate values)
  3. Save changes to persistent storage

A word about the debugger...

I have made some minor changes to the debugger. They revolve mainly around usability. It is also important to note that if you try to visualize deep objects using the debugger, you will stall your browser. It is designed for small-ish things (remember it was like a 2-4 hour thing I made) so if it hangs on you, you've been warned

Code Already

I would love feedback on the code. Are there any ommissions/improvements/rants/raves/etc.? Hope this has been helpful!

Tags: , , , ,

Ajax | ASP.NET | JQuery | MVC

Creating Advanced ASP.NET MVC Controls (Part 2, Finished Debugger)

by Seth Juarez 17. August 2009 16:14

Purpose

As mentioned in the previous post, in order to create good client side controls that interact well with the ASP.NET MVC system, we need to have a way to visualize data that the control either generates, or passes to the controllers. I found this difficult to acheive in IE as well as Firefox. I did not need/want all of the complexities of Firebug or IE's Developer tools (which are great btw). I just wanted to see my data! The problem with the previous version of the debugger was that it was too dang simple! If I were to use it like this:

_('Test').clear()
	.write('(Simple)')
	.set(complexArray)
	.write('(Complex Array)')
	.set({ one: { a: 'Hospital', b: 4, c: {x:3,y:5}}, two: 'test'})
	.write('(Complex Object)')
	.set(function(arg) { alert('HI!' + arg); })
	.write('(function)')
	.set({ one: new Array('Happy', 'debugger', 'array'), two: 'test'})
	.write('(Complex Object)')
	.set(
		new Array(
			new Array(
				{a: 1, b: 2, c: 3},
				{a: 4, b: 5, c: 6},
				{a: 7, b: 8, c: 9}), 
			new Array(
				{a: 'one', b: 'two', c: 'three'}, 
				{a: 'four', b: 'five', c: 'six'}, 
				{a: 'seven', b: 'eight', c: 'nine'})))
	.write('(Complex Array)');

});

the debugger would simply not be good enough!

On to version 2!

In order to actually print something useful given the complexity of the potential objects, we need to use some recursion. The bottom level of the recursion would simply be printing out simple data types:

  1. either a function (yes this is a data type),
  2. a string, or
  3. a number

The debugger would then simply recurse over arrays and objects down to the base types. This turned out to be harder because of a simple mistake I made. What is the difference bewteen these two snippets:

Snippet 1

writeArray: function(li, arg) {
	for(var i = 0; i < arg.length; i++) {
		$(li).append('[' + i + ']:')
		this.dispatcher(li, arg[i], true);
		$(li).append('<br/>');
	}
}

Snippet 2

writeArray: function(li, arg) {
	for(i = 0; i < arg.length; i++) {
		$(li).append('[' + i + ']:')
		this.dispatcher(li, arg[i], true);
		$(li).append('<br/>');
	}
}

Nothing you say? Nay dear reader, it turns out that snippet 2 is VERY wrong! During recursive calls, the variable i if not re-declared using the var keyword gets reassigned in each recursive call to itself: this leads to a short-circuiting of the for loop.

Final Product

In this version I removed the jQuery UI references because the draggable got annoying. Here are some screenshots:

screen1

and

screen2

Final Words

In the next post, we will actually start to build a control. I am thinking some kind of advanced date-picker thing-y (that is a technical term) that allows users to toggle multiple dates and add meta information to said dates. Here is the code

.

Tags: , , , ,

Ajax | ASP.NET | JQuery | MVC

Creating Advanced ASP.NET MVC Controls (Part 1, A Debugger)

by Seth Juarez 13. August 2009 01:38

Purpose

In previous posts, I started creating an html grid helper that was really just an experiment to see how one would accomplish such a thing. Here, I wanted to delve into a more generic approach to creating html controls for the ASP.NET MVC paradigm that would accomplish the following:

  1. be highly interactive on the client side, while
  2. minimizing the client-server interaction

With tools like jQuery among others, #1 is quite easy to accomplish. But how do we go about accomplishing #2? I have seen custom controls that generate an AJAX call to the server every time something happens in the control. Imagine a setting like a gradebook (we are doing this currently where I work) where the user clicks on a cell in a table to denote whether or not a student was absent on a particular day. If I was to generate an AJAX request for each click, the control would not be very useful (at best it would be slow). What if the control were smart enough to keep track of itself and then send back only changes? This would certainly accomplish #2. In the next few posts, I will go through some of the steps that led me to a solution that seems to work. I am hoping that you, dear reader, will assist me along the way with anything I might have missed, omitted, or simply screwed up.

Debugging

When I first started, I found out quickly that I needed a way to see what was happening when the user was interacting with my controls. I know such things exist! Firebug is God's gift to every web developer! In our setting, however, we are required to make it work in IE (along with every other browser that has not been bestowed with the excellence known as Firebug). I also know that in IE 8 there are some pretty spiffy developer tools. But... These tools were way more than I needed/wanted. I just wanted a simple way to print out things as events were fired off. Something like:

$(document).ready(function(){
	var clickNo = 0;
	$('#Main td').click(function(){
		if(window.isDebug)
			_(this.id).write('Click # ' + (++clickNo));
	});
});

Notice the simplicity of lines 4 and 5. If JavaScript is in debug mode, put an object in a jQuery like constructor and write out the value along with a title (to keep me sane). I made a debugger like that at work, but as any self-respecting developer, I wanted to make it better. The JavaScript above, simply captures a user's click on a table cell and if the browser is in debug mode, writes out the id of the td element that was clicked along with a special message. Here is what it does:

debug_view

Having a huge box like that could get annoying, so I made it draggable and changed the opacity of the console when the mouse moved off of the debug console:

debug_view2

This proved extremely helpful when debugging! As you look at the code, please keep in mind that I followed the jQuery way of doing things. I love the way jQuery works so I decided to mimic its general constructs in my debugger. As you can see, it certainly is no Firebug, but it is powerful enough to provide the kind of information needed when building complex client-side html controls.

Where Next?

In the next installment, I will beef up the debugger so that it does more than print JavaScript strings. If this is going to be of any use, it will need to print out complex JavaScript objects (like arrays) in a meaningful way. This should not be difficult to accomplish since the for(x in obj) construction allows us to actually perform some introspection (or reflection in C#) on any JavaScript object. This along with the typeof operator should be sufficient to meet our goal.

Tags: , ,

ASP.NET | JQuery | MVC

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

Pages

RecentComments

Comment RSS