I was still a relative amateur fresh from a recent success and itching for more. Despite still being an intern they pulled no punches and dealt me a project that dwarfed the previous one. I knew immediately it was going to be far more involved, but even then I had no idea how much more so.
The premise was deceptively simple (aren’t they always?): a system to aid the logging of radio traffic. Years ago someone had cobbled together something out of VBA and Access but it was barely better than the pen and paper they still occasionally had to use. Did I mention its background color was set to cyan?
That wouldn’t have been so bad if they hadn’t wanted to use this project as a guinea pig for trying out some new technologies. Despite being early 2007, this would be their first serious attempt at using .NET 2.0 (really, .NET at all) with SQL Server 2005 (previous stuff was mostly using SQL Sever 2000 or DATACOM/DB via ODBC). Several machines had recently be outfitted with Visual Studio 2005. The servers had been recently set up. Why not have the intern give it a shot? I wouldn’t be the only one trying the new technology, but it would be the most involved project using it by far. So much so that a couple other projects started later and were finished well before mine.
Did I mention how much .NET experience I had at that point? Oh, right — effectively none. Sure, I had used it briefly in one class on one project as part of a group, but I also watched my cousin replace a car radiator one afternoon but that doesn’t make me ready to be an auto mechanic. Other than being vaguely aware of the technology (and recognizing the .aspx page extension!), I was effectively fresh out of med school handed a scalpel and told to get busy with the surgerying.
And did I mention that, as usual, I would shoot for the stars to both impress others and to make it more interesting for me (my latent masochism again)? Let me break this down now:
- Intern (me) to be primary developer with full-time mentor to help with some things off-and-on
- Intern and mentor (and everyone else in the room) have a combined effective .NET experience of zero
- First time we’d be seriously attempting a .NET application
- First time we’d be seriously attempting an ASP.NET application
- First time we’d be seriously using SQL Server 2005
- First time we’d be seriously using stored procedures instead of dynamic SQL
- Intern, in his infinite wisdom, decides to also try out this neat AJAX thing he’d heard buzz about
I cannot stress how stupid this was. I had no idea what I was doing with ASP.NET nor could anyone but Google help me. And I thought this fancy-shmancy sounding household cleaner technology sounded like something cool to throw into the mix.
Not enough time was spent on design. Well, that is to say, we spent plenty of time figuring out what they wanted, all the details for the most part, and gathering materials for reference, even securing a pretty good design of the page and how the interface would more or less work. But the implementation details, well… it’s kinda hard to come up with those in any sense of concreteness when you are barely familiar with the tools and technologies you’ll be implementing them with.
The lack of knowing any best practices or having any sort of good reference for how an ASP.NET project should be structured, coupled with coming from a brief (two months, tops) stint learning and doing Classic ASP and VBScript, led to pages looking more-or-less like this:
<%@ Page Language="C#" Inherits="RadioRoom.activeunits" Codebehind="activeunits.aspx.cs" %>
<html>
<head>
<title></title>
</head>
<body>
<form id="units_table_form" action="">
<input type="hidden" id="inactive_officer_messages_notification"
value="<%=HttpUtility.HtmlEncode(InactiveStatus)%>" />
<input type="hidden" id="safety_alert_interval"
value="<%=HttpUtility.HtmlEncode(UserOptions.SafetyAlertInterval.ToString())%>" />
<input type="hidden" id="fyi_notification"
value="<%=HttpUtility.HtmlEncode(FYINotify)%>" />
<input type="hidden" id="fyi_notification_count"
value="<%=HttpUtility.HtmlEncode(FYICount)%>" />
</form><%
clearParams();
addParam("@sort", System.Data.SqlDbType.TinyInt, System.Data.ParameterDirection.Input, sort);
if (SessionUser.Level == AccessLevel.Captain)
addParam("@captain", System.Data.SqlDbType.VarChar, System.Data.ParameterDirection.Input, SessionUser.district);
if (initDataReader(Procedure))
{
db_i = 0;%>
<table id="table_unit_grid_header" class="tableActiveUnits" cellspacing="0">
<tr class="label">
<td class="unit clickable<%if (sort == 1) {%> sortup<%} else if (sort == 2) {%> sortdown<%}%>"
onclick="changeSort('activeunits', '<%if (sort == 1) {%>2<%} else {%>1<%}%>')">Unit</td>
<td class="lastCall clickable<%if (sort == 3) {%> sortup<%} else if (sort == 4) {%> sortdown<%}%>"
onclick="changeSort('activeunits', '<%if (sort == 3) {%>4<%} else {%>3<%}%>')">
Last Call
<input type="hidden" id="activeunits_sort_hidden" value="<%=sort%>" />
</td>
<td class="msg clickable<%if (sort == 5) {%> sortup<%} else if (sort == 6) {%> sortdown<%}%>"
onclick="changeSort('activeunits', '<%if (sort == 6) {%>5<%} else {%>6<%}%>')" style="WIDTH: 50px;">Msg</td>
</tr>
</table>
<div id="active_units_container" class="scrollable" style="HEIGHT: <%=RadioRoom.calculateAUHeight(SessionOptions.getUnitListNumRowsShown())%>px;">
<table id="activeunits_table" class="tableActiveUnits" cellspacing="0">
<tbody id="activeunits_tbody"><%
while (read())
{
db_i++; %>
<tr id="activeunits_row<%=db_i%>"
class="<%=RowClass%>"
onmouseover="AUhighlight(<%=db_i%>)"
onmouseout="AUunhighlight(<%=db_i%>)"
onclick="AUselect(<%=db_i%>)"
ondblclick="AUdblclick('<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%>')">
<td class="unit clickable" id="activeunits_unit_row<%=db_i%>">
<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%>
<input type="hidden" id="activeunits_unitid_row<%=db_i%>_hidden" value="<%=HttpUtility.HtmlEncode(db_dr["unitid"].ToString())%>" />
<input type="hidden" id="activeunits_unit_row<%=db_i%>_hidden" value="<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%>" />
<input type="hidden" id="activeunits_idletime_row<%=db_i%>_hidden" value="<%=HttpUtility.HtmlEncode(IdleTime.ToString())%>" />
</td>
<td class="lastCall clickable" id="activeunits_lastcall_row<%=db_i%>">
<%=LastCall%>
</td><%
if (pending)
{ %>
<td class="msg clickable" id="activeunits_extra_row<%=db_i%>"
onclick="createTab({label: '<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%> messages', page: 'messages', id: '<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%>'})">
<img id="unit_details_message_icon<%=HttpUtility.HtmlEncode(db_dr["unit"].ToString())%>"
title="Click to view this unit's pending messages."
class="iconSilk"<%
if (priority)
{ %>
src="../images/icons/email_urgent.png"
alt="Urgent Message Pending Icon" /><%
}
else
{ %>
src="../images/icons/email.png"
alt="Message Pending Icon" />
</td><%
}
}
else
{ %>
<td class="msg" id="activeunits_extra_row<%=db_i%>"></td><%
} %>
</tr><%
}%>
</tbody>
</table><%
// Check if anything was displayed...
if (db_i == 0)
{ %> <div class="centerAlign">No Units<br />Currently On Duty</div><%
} %> </div><%%></body>
</html>
I won’t bother showcasing the corresponding code-behind file. Truly a mess of server tags. Incomprehensible, headache-inducing, and nightmare only begin to describe the kind of pages this approach produced. And this is one of the simpler ones. As you can no doubt tell, I was still very much thinking like Classic ASP or maybe [bad] PHP (I had done some of that in the past, too). The stored procedures are even more ridiculous (I’m sure we had over a hundred) and several of them are hundreds of lines long.
The mess got messier yet because I ended up utilizing three (three!) JavaScript projects: Prototype Framework, script.aculo.us library, and the Dojo Toolkit. Mind you, this was when Dojo was still version 0.4.x and while 1.0, Dijix, etc. came out later on by then it was too late to re-do everything since Dojo 1.0 was backwards incompatible. I used Dojo extensively in place of what really should have been ASP.NET controls and so the system is a giant exercise/showcase of what Dojo can do. It is impressive, but convoluted and confusing and, in hindsight, not worth the trouble at all. Prototype was used to ease doing a myriad of different interface actions and script.aculo.us was used only a few times for some fancy effects. Because of the JavaScript heavy help most of the interface and actions and code is actually written in JavaScript. In addition to utilizing the three aforementioned projects, my hand-written JavaScript code alone totals roughly 7500 lines across six .js files and several thousand more for inline script blocks. I also did some significant customizing of Dojo to get several new widgets and some other fixed/modified behavior.
The list of features, many of them unnecessary, includes:
- Extensive use of on-demand lazy-loading via AJAX injecting individual .aspx pages as needed/called
- Modular page structure broken into three main columns with the center having a modular tab layout
- Each section and tab can be dynamically refreshed, reloaded, removed, changed, you name it
- AJAX auto-polling to maintain up-to-date information roughly every half-minute (Comet was deemed to be overkill, natch)
- Timers keep track of user activity to perform idle timeouts and to pause the auto-polling routines
- Highly multi-task friendly, enabling the user to switch quickly between tasks as demanded by the real-time radio traffic
- Radio logging, filtering, searching, sorting
- Debug mode with debug messages, Dojo console output, and timer indicators to aid debugging
- Aids input with extensive auto-complete and suggestions
- Messaging, pending/history, and notification
- Complete and extensive support for viewing/modifying ancillary data such as places, people, contact info
- Full support for six different user roles and thus versions of the system/interface
- Tooltips, status messages, help file, and other extras
- Many user customizable options for behavior and appearance
- Support for multiple themes, released with two normal ones to pick from (a default and a high-contrast) and an additional semi-secret seasonal one
- Logos, favicons, icons, and all sorts of graphic design goodness courtesy of my long-standing relationship with Photoshop/GIMP
And so much more that I won’t mention here. Bloated with feature creep? You bet. Extensive mistakes made in all areas of design, implementation, etc.? Oh, definitely. The most learning-from-mistakes experience I’ve ever had thus far? Probably.
The project as a whole would end up spanning about a year before going productional, though that was simply the first phase release. After another project, in which I decided to try my hand at Windows Forms despite never really having gotten the hang of ASP.NET, I would revisit the land of radios once more about 8 months later for phase two.
No comments:
Post a Comment