Saturday, July 30, 2005

Ruby On Rails, the Week in Review

Does the title sound pompous enough? ;)

Anyways, I'm going to try and pretend that I'm not just a kid in a candy store with RoR, and that I can be objective. :D

RoR makes developing fun. I think that's the best thing I can say about it. When you're having fun programming, you're more productive. At least that's the way it works with me. It takes so much busy work out of your hands, so much worry about design, it's so easy to pull off impressive demos that yeah, I'd believe the 10X faster development boasts, at least for the coding bits.

So what are the downsides? Well, as far as I can tell it's not going to work under IIS6. That means the administrators have to sink time into learning how to setup and use Apache. Maybe that will be a good thing in the long run, who knows? The simple truth is that they wouldn't have to do that if I'd have stuck with MonoRail though.

What else can I come up with? Well, I haven't found a good, documented RubyIDE for Windows, that includes RubyDoc. I'm not saying FreeRide is bad, because it's not, but it's lacking in some polish, including .RHTML highlighting, and support for Projects. I end up using UltraEdit more often than not, which isn't necessarily a bad thing. I really like not having to have something as piggish as VS.NET open to get my work done. It's like I have a whole new computer without VS.NET slowing it down. :)

ActiveRecord doesn't have great support for legacy databases. No composite keys, and your model will look nasty if your casing/naming strategy in the database isn't laid out "The Ruby Way". Thankfully that's not a concern right now, but it's unfortunately a deal-breaker for most projects I'd imagine.

Thursday, July 28, 2005

SQL Lattice Structures?

Just a quicky, but if anyone can point me to a good resource on creating lattices in SQL Server I'd appreciate it. I'm familiar with Nested Sets, but I'm looking for something that doesn't seem so update intensive, but even more, I need to create lattice like structures, not just trees.

I could do it with parallel trees, but then I have to choose between reporting sacrifices, or n*depth row explosion depending on how parallel I want to get. This one really has me stumped.

I could always go with an adjacency model, but the intensive nature of walking the trees makes that unappealing...

Tuesday, July 26, 2005

Ruby on Rails: Day 1

So we're working on another prototype/demo application at work, and we're at the point we need to have it all start coming together with the database.

As an experiment of sorts I took what we had today and started porting it to RoR. By the end of the day I had most of it converted, and some of the pages were also now wired up to the database.

As much as I like MonoRail, and even though it's probably the platform we eventually deploy with, there was no question in my mind that I was much more productive with RoR.

First off, the templates use a real programming language, which beats NVelocity anyday (though MonoRail will be getting Rook done soon, and has a Boo implementation). Wait a sec, let me frame this better. This isn't a RoR is better than MonoRail post. MonoRail is younger, and has to work within the confines of a much less flexible language. One without transparent AOP, dynamic Types, etc etc. What MonoRail is is really really great.

But what RoR is is simply better than anything .Net has to offer (as far as Website development goes; not everything's a nail after all). It's simple, it's fast, it's fun. What's the hardest thing for most .Net developers to grasp?

Good design.

At least it has been for me. At least it seems to be for most people on the ASP.NET forums asking for help.

One of RoR's biggest advantages is that it presents you with a prepackaged great design structure, and you just need to fill in some implementation details. There's the Views, Models, Controllers, Helpers, Components, Partials... It might not be everything to everyone, but it's a lot more than ASP.NET has to offer, and a lot more than some O/R Mapper vendors try to push which amounts to the same ol' same ol' sphaghetti code, but spread over two pages (an aspx and codebehind) instead of one (asp).

Yes, any good design involves writing helpers, business objects, server controls, perhaps a service layer, and there's nothing about .Net that holds you back there, but it's not the same thing as getting a great design right out of the box either.

Friday, July 22, 2005

Ruby love

Yes, today was a good day. Got to do make some more reports with Ruby, and it's just such a gratifying feeling reusing some helpers, adding 3 new lines of code, doing some WYSIWYG in Excel, and generating an entirely different report. Take this for example:


% queries.execute_query('ListInactiveUsers') { |row|
<tr>
<td><%=row['Name']%></td>
<td><%=row['ExpiredOn']%></td>
</tr>
% }


I mean, that's purty sweet. Anyways, time to boogie...

Wednesday, July 20, 2005

ERB (Ruby)

So I have this templating program that provides support for queries and the like to product MHTML files (for Word, Excel, etc, just so I don't have to mess with Office Interop).

I rewrote it in Ruby today, using the built-in templating engine ERB over the c# version's NVelocity. Cut the amount of code in half, and ERB is a lot more flexible.

Ruby is good stuff. You might hear it called "hype", but there's a reason so many very smart people are excited about it. It's just damn cool, it's elegant in a way few languages are, it's support libraries are amazingly mature for such a "young" language (a lot more complete than .Net's when it comes to templating for just one example), and it just helps you keep what should be simple tasks simple. Why should I worry about great design with this templating program for example? In Ruby I just whip up a couple helpers, instantiate them in the current context, and toss in a couple lines to open and execute the template.

$stdout << %Q{Ruby is fun!}

Tuesday, July 19, 2005

Resharper 2.0!

Resharper 2.0 EAP has started! Go get it!

Thursday, July 14, 2005

Quickie: Calendar.js

The positioning doesn't work in XHTML. You need to add "px" to the units since XHTML (at least in Firefox) doesn't assume pixels as a default unit.

Monday, July 11, 2005

Calendar.js followup

Well, I polished off most of it today at work. Naturally I can't share the code, but here's a few tips to get you on your way:

Logging helps. Even if it's just say, a class you write that manages the dirty work of adding spans to a div for messages, log feedback can be invaluable if you're like me and can't figure out how to use Firefox's Javascript Debugger (though the Javascript Console is priceless!), or you need to test in IE or other browsers.

You can dynamically create a new member field anytime, anywhere. When you need to temporarily store some data or event scoped to a calendar, creating a new member, even from functions that aren't members of the calendar itself, can be helpful. Defining your own attributes is a real nifty way to pass values (though I don't know if it would validated under the XHTML strict DTD).

Don't be afraid of doing too much about performance. When the calendar is active, that means the user has given it focus. So as long as you don't chew up all 5GHz on that quad processor development box you have, you'll be fine as long as it looks fine to you. As a general rule, I'd say that 30% usage on my P4 2.6HT at work is acceptable (since it's HT, 50% would be close to effectively maxing out CPU).

What about date-ranges? Instead of setting the inputBox and hiding everything onclick of a day, couldn't we maybe have a property that tells the "control" it's in Range mode and to stay open for another click? That way instead of the typical two-calendar setup to represent date-ranges, and who knows how many clicks if you need a range outside of the current month (say 8+ as a guess), you could cut the whole thing down to two clicks. Mouse over the year, the month, click the day, mouse over the next year, month, and click the day for the end of the range, and the input box could be set to "1/1/2003 - 7/11/2005" for example. Nifty huh?

Absolutely positioning an element (placeHolder), absolutely positioning a child element of the first (dayBox), and then sticking a table in the second element (dayTable) results in really funky rendering in Firefox. The solution is to not set position = "absolute" on the child element, and instead set position = "relative" on the table. This way we can line up the table with a given month's offsetLeft.

Figuring out how to apply a different style to the table cell containing today's date can be a minor nuiscance. Figuring out how to apply a different style to the currently selected date was a real pain for some reason.

If you need the calendar to overlay select boxes, you can use an iframe and stick the placeholder in that. Just make sure you whip up a blank.html page, and set the src on the iframe to it so you don't get security warnings when running under SSL. Also, you'll need some browser specific shenanigans since IE doesn't support .appendChild() for iframes and both IE and Firefox access the document element of an iframe differently. (I think it's .documentContent or something for Firefox if I remember correctly.)

I know, probably sounds like more work than you want to do, and hey, I wouldn't blame ya. I hate trying to extend other people's code generally. But if you can spare the budget (it took me the better part of the day to finish it), then all bragging aside, this is easily the nicest calendar control I've used personally, and the best part is, it doesn't matter what language you're using. ASP.NET, ASP Classic, RubyOnRails, PHP, JSP, etc... nothing language/platform specific here.

Sunday, July 10, 2005

Code Dump: Calendar.js

This is something a friend and I whipped up in our abundance of spare time. :)

The intent is to use it at work (after much more tweaking of course) for our own "1-click" calendar "control".

This requires the Prototype Library. You can use the calendar like so:


<html>
<head>
<title>Calendar Test</title>
<script type="text/javascript" language="javascript" src="prototype.js"></script>
<script type="text/javascript" language="javascript" src="calendar.js"></script>
</head>
<body>
<p>A calendar control should render here:
<span id="placeholder1" style="background-color: blue;"></span>
</p>
</body>
</html>

<script type="text/javascript" language="javascript">
new Calendar("placeholder1", "calendar1");
</script>


And here's the code:


var _calendars = new Array();

var Calendar = Class.create();
Calendar.prototype = {
initialize: function(placeholderId, inputId) {
this.id = inputId;
this.timeout = 0;
this.isIE = (document.all)? true: false;

_calendars[this.id] = this;

this._createInputbox(placeholderId);
this._createPlaceholder(placeholderId);

this.years = new Array(2003, 2004, 2005);
this.months = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
},

_getNumberOfDays: function(year, month) {
var oneHour = 1000 * 60 * 60;
var oneDay = oneHour * 24;
var thisMonth = new Date(year, month, 1);
var nextMonth = new Date(year, month + 1, 1);
return Math.ceil( (nextMonth.getTime() - thisMonth.getTime() - oneHour) / oneDay);
},

_setInnerText: function(element, text) {
if(this.isIE) {
element.innerText = text;
} else {
element.innerHTML = text;
}
},

_getInnerText: function(element) {
if(this.isIE) {
return element.innerText;
} else {
return element.innerHTML;
}
},

_removeChild: function(id) {
for(var i = 0; i < this.placeholder.childNodes.length; i++) {
var child = this.placeholder.childNodes[i];

if(child.id == id) {
this.placeholder.removeChild(child);
}
}
},

_displayItems: function(immediate) {
window.clearTimeout(this.timeout);

if(immediate) {
if(this.yearbox) {
this.yearbox.style.display = this.yearbox.attributes["over"].value == "true"? "block": "none";
}

if(this.monthbox) {
this.monthbox.style.display = this.monthbox.attributes["over"].value == "true"? "block": "none";
}

if(this.daybox) {
this.daybox.style.display = this.daybox.attributes["over"].value == "true"? "block": "none";
}
} else {
this.timeout = window.setTimeout("_calendars[\"" + this.id + "\"]._displayItems(true);", 500, "javascript");
}
},

hideItems: function(immediate) {
if(this.yearbox) {
this.yearbox.setAttribute("over", false);
}

if(this.monthbox) {
this.monthbox.setAttribute("over", false);
}

if(this.daybox) {
this.daybox.setAttribute("over", false);
}

if(immediate) {
this._displayItems(true);
} else {
this._displayItems();
}
},

show: function(element) {
var items = new Array(this.yearbox, this.monthbox, this.daybox);
var showLevel = true;

for(i = 0; i < items.length; i++) {
var item = items[i];

if(!item) break;

item.setAttribute("over", showLevel);

if(element.id == item.id) {
showLevel = false;
}
}

this._displayItems(true);
},

showYears: function() {
var element = document.createElement("div");
element.style.display = "none";
element.style.margin = "4px 0px";
element.id = this.id + "_yearbox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.yearbox = element;

for(i = 0; i < this.years.length; i++) {
this._addYear(this.years[i]);
}

this.show(element);
},

showMonths: function(year) {
this.selectedYear = this._getInnerText(year);

var element = document.createElement("div");
element.style.display = "none";
element.style.margin = "4px 0px";
//element.style.width = "20em";
element.style.backgroundColor = "lime";
element.id = this.id + "_monthbox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.monthbox = element;

for(i = 0; i < this.months.length; i++) {
this._addMonth(this.months[i], i);
}

this.show(element);
},

showDays: function(month) {
this.selectedMonth = parseInt(month.attributes["number"].value);

var element = document.createElement("span");
element.style.display = "none";
element.style.backgroundColor = "red";
element.id = this.id + "_daybox";
element.setAttribute("calendarId", this.id);

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

element.onmouseover = function() {
window.clearTimeout(_calendars[this.attributes["calendarId"].value].timeout);
}

this._removeChild(element.id);
this.placeholder.appendChild(element);
this.daybox = element;

var table = this._buildCalendar(this.selectedYear, this.selectedMonth);

// element.style.width = table.offsetWidth;

this.show(element);
},

_createPlaceholder: function(id) {
var element = document.createElement("div");
element.style.position = "absolute";
element.style.left = $(id).offsetLeft;
element.style.top = $(id).offsetTop + $(id).offsetHeight;

element.id = this.id + "_placeHolder";
element.setAttribute("calendarId", this.id);

$(id).appendChild(element);
this.placeholder = element;

return element;
},

_createInputbox: function(id) {
var element = document.createElement("input");
element.type = "text";
element.style.border = "solid 1px black";
//element.style.clear = "right";
element.id = this.id;
element.setAttribute("calendarId", this.id);

element.onmouseover = function() {
_calendars[this.attributes["calendarId"].value].showYears();
}

element.onmouseout = function() {
_calendars[this.attributes["calendarId"].value].hideItems();
}

$(id).appendChild(element);
this.inputbox = element;

return element;
},

_addYear: function(year) {
var span = document.createElement("span");
span.style.border = "solid 1px black";
span.style.margin = "0px 2px";
span.style.padding = "1px 2px";

span.onmouseover = function() {
_calendars[this.parentNode.attributes["calendarId"].value].showMonths(this);
}

this._setInnerText(span, year);
this.yearbox.appendChild(span);
},

_addMonth: function(month, number) {
var span = document.createElement("span");
span.style.border = "solid 1px black";
span.style.margin = "0px 2px";
span.style.padding = "1px 2px";
span.setAttribute("number", number);

span.onmouseover = function() {
this.style.backgroundColor = "blue";
var calendar = _calendars[this.parentNode.attributes["calendarId"].value];
calendar.showDays(this);
}

span.onmouseout = function() {
this.style.backgroundColor = "transparent";
}

this._setInnerText(span, month);
this.monthbox.appendChild(span);
},

_buildCalendar: function(year, month) {

var table = document.createElement("table");

var daysRow = document.createElement("tr");
var daysOfWeek = new Array("S", "M", "T", "W", "T", "F", "S");

for(i = 0; i < daysOfWeek.length; i++) {
var cell = document.createElement("td");
cell.style.textAlign = "center";
this._setInnerText(cell, daysOfWeek[i]);
daysRow.appendChild(cell);
}

table.appendChild(daysRow);

var firstDay = new Date(year, month, 1);
var dayOfWeek = firstDay.getDay();

var previousMonth = month == 0? new Array(year - 1, 11): new Array(year, month - 1);

var previousNumberOfDays = this._getNumberOfDays(previousMonth[0], previousMonth[1]);
var currentNumberOfDays = this._getNumberOfDays(year, month);

var counter = previousNumberOfDays - dayOfWeek + 1;
do {
var row = document.createElement("tr");
table.appendChild(row);

for(x = 0; x < 7; x++) {
var cell = document.createElement("td");
cell.style.textAlign = "right";
cell.onclick = function() {
var calendarId = this.parentNode.parentNode.parentNode.attributes["calendarId"].value;
var calendar = _calendars[calendarId];

var selectedDay = parseInt(calendar._getInnerText(this));
var selectedDate = new Date(calendar.selectedYear, calendar.selectedMonth, selectedDay);

calendar.inputbox.value = selectedDate.getMonth() + 1 + "/"
+ selectedDate.getDate() + "/"
+ selectedDate.getFullYear();

calendar.hideItems(true);
};

row.appendChild(cell);

if(counter > previousNumberOfDays + currentNumberOfDays) {
// trailing month...
this._setInnerText(cell, counter - (previousNumberOfDays + currentNumberOfDays) );
} else if(counter > previousNumberOfDays) {
// current month...
this._setInnerText(cell, counter - previousNumberOfDays);
} else {
// leading month...
this._setInnerText(cell, counter);
}

counter++;
}
} while(counter <= previousNumberOfDays + currentNumberOfDays);

this.daybox.appendChild(table);

return table;
}
}


A bit sloppy sure, but should be easy enough to follow hopefully. Of course it'll need some styling tweaks, localization, etc, and I didn't check the latest version in IE (though you'll notice a isIE member to accomodate if it doesn't work).

Still, should get you pretty close to a working javascript calendar control.

This page is powered by Blogger. Isn't yours?