Tuesday, September 25, 2007

All Code is Business Logic

Worse than Failure really hit it out of the park with The Mythical Business Layer. It's a great article that makes the point that developers don't like solving the boring old business problems, so they transform them into big "enterprise" problems that are fun and challenging to solve ... with disastrous results.

If you want to be successful, stop thinking about yourself and start thinking about your customers. This is not a new concept - Dale Carnegie .

Tuesday, September 18, 2007

Undo isn't easy

Aza Raskin says undo for web apps is easy. *cough bullshit cough* I completely agree with Aza's overriding premise: undo is a lot better than a warning. This makes his current article that much more frustrating - he's using an incorrect argument to champion a correct idea. Kind of like saying performance doesn't matter because CPUs are cheep.

So, to the point. Aza argues that you should use undo instead of warning because undo is, despite the prevailing opinions on the subject, easy to do in web apps. This is just plain wrong - a general solution to undo in AJAX web apps is a hard problem. He's off the tracks quickly:

In this series of blog posts, my goal is to explain just how easy it is to provide Undo functionality


So, you need a series of posts to show how easy something is. Ok, if it's so easy, why does it take a series of posts?

Next, Aza deploys a commonly used (and completely incorrect) approach to prove his point: he offers up one specific example where his conjecture holds. This is about as clever as saying "Everyone drives a Honda. See, I drive a Honda". To prove that undo is easy, you can't just show that in one specific example it is easy, you have to show that it is easy in all situations. The comments are ample proof that there are scenarios where a solution is tricky.

Aza continues, either out of ignorance or religious devotion, to defend his position by pointing out that his example is only valid in a specific, simple scenario. This amounts to defending your position by agreeing you are wrong.

Using a default params hash :: Avoiding defaulting with ||

I saw yesterday. I'm a big fan. One pattern, though, that I have begun generally avoid is using the || operator to specify default values. Consider this code:


function doStuff(params){
params = params || {};
var foo = params.foo || 'fooValue';
var bar = params.bar || 'barValue';
...
}


I love how clean this pattern is. The API is flexible and it reads very easily. Unfortunately, there is a very subtle bug - you can't pass in values that are falsy because || uses type coercion. So this:


function doStuff(foo){
foo = foo || 'foo not specified';
alert( foo );
}

foo( '' );

will give you 'foo not specified' when the intent was to use an empty string. You can only rely on the default operator when falsy values cannot be valid values.

I have begun to use a default hash pattern I first saw in Ruby on Rails code (and is also used extensively in script.aculo.us):


function doStuff(params){
params = params || {};
var default = { foo : 'fooDefault', bar : 'barDefault'};
params = merge( defaults, params );

alert( params.foo );
};


This will give you what you want.

Here here an example merge function:

function merge(ontoObj, fromObj){
for( var i in fromObj ){
if(!fromObj.hasOwnProperty(i))
continue;
ontoObj[i] = fromObj[i];
}
return ontoObj;
}

Thursday, September 13, 2007

replaceHTML - remove, insert, put back is better

** Updated **
Steven put up another speed demo that demonstrates that replaceHtml is in fact faster than the remove-update-replace method, though not by nearly the same gap as innerHTML.
*************

Read an piece on Ajaxian this morning covering Steven Levithan's replaceHTML post on innerHTML being too slow and offering a replaceHtml alternative that is much faster. It works by creating a new element, using innerHTML on it, then replacing the existing node with the new node. Here is the code:


/* This is much faster than using (el.innerHTML = value) when there are many
existing descendants, because in some browsers, innerHTML spends much longer
removing existing elements than it does creating new ones. */
function replaceHtml(el, html) {
var oldEl = (typeof el === "string" ? document.getElementById(el) : el);
/*@cc_on // Pure innerHTML is slightly faster in IE
oldEl.innerHTML = html;
return oldEl;
@*/
var newEl = oldEl.cloneNode(false);
newEl.innerHTML = html;
oldEl.parentNode.replaceChild(newEl, oldEl);
/* Since we just removed the old element from the DOM, return a reference
to the new element, which can be used to restore variable references. */
return newEl;
};


This is a lot faster, but there really isn't any need to create a new node. The reason the method is so much faster is due to using innerHTML on an element that is not in the DOM vs. one that is in the DOM. Instead of creating a new node and deleting the old one, you can just take the node out of the DOM, use innerHTML, and then put it back. This has the added benefit of not invalidating any existing references to your element.

I whipped up an example to compare the differences. I generated an HTML string with 15,000 elements and used three different methods for updating a a div to this content - innerHTML, removing the node before innerHTML, and then the replaceHtml function. I did each four times and profiled with Firebug. Here is the script:


var html = [];
for( var i = 0; i < 15000;i++ )
{
html.push("<span>CONTENT</span>");
}
html = html.join("");
var el = document.getElementById("div");
el.innerHTML = html;
function doit(){
withoutRemove(el, html);
withRemove(el,html);
el = replaceHtml(el, html);
withoutRemove(el, html);
withRemove(el,html);
el = replaceHtml(el, html);
withoutRemove(el, html);
withRemove(el,html);
el = replaceHtml(el, html);
withoutRemove(el, html);
withRemove(el,html);
el = replaceHtml(el, html);
}
function withoutRemove(el,html){
el.innerHTML = html;
}

function withRemove(el,html){
var nextSibling = el.nextSibling;
var parent = el.parentNode;
parent.removeChild(el);
el.innerHTML = html;
if(nextSibling)
parent.insertBefore(el,nextSibling)
else
parent.appendChild(el);
}

function replaceHtml(el, html) {
var oldEl = el;
/*@cc_on // Pure innerHTML is slightly faster in IE
oldEl.innerHTML = html;
return oldEl;
@*/
var newEl = oldEl.cloneNode(false);
newEl.innerHTML = html;
oldEl.parentNode.replaceChild(newEl, oldEl);
/* Since we just removed the old element from the DOM, return a reference
to the new element, which can be used to restore variable references. */
return newEl;
};


And here are the results:

Method Average Time
------------- ------------
withoutRemove 4914.063ms
replaceHtml 656.25ms
withRemove 601.563ms

Wednesday, September 12, 2007

using innerHTML with DOM methods

I tend to prefer to generate content following this pattern:


var html = [];
html.push('<h1>CONTENT!</h1>more content...');
...
el.innerHTML = html.join('');


In most cases in most browsers this is the fastest way to create markup it's a lot easier to grock the markup this way verses using DOM methods.

One issue I have with the approach is that you need a 'hook' element to dump the html into. This leads to a lot of empty nodes sitting around waiting for script to innerHTML them. Eventually, I stumbled on a way to avoid the hook elements and still have my innerHTML.

The key is to create a temp element

var temp = document.createElement('p');

us it to parse an html string

temp.innerHTML = '<h1>content!...</h1>';

then using DOM methods to insert the content

while( temp.childNodes.length > 0 )
el.parentNode.insertBefore(temp.childNodes[0], el);


Example:


var html = "<div>A lot of content...</div>";
var temp = document.createElement('p');
temp.innerHTML = html;
var el = document.getElementById('idOfElementToInsertBefore');
while( temp.childNodes.length > 0 )
el.parentNode.insertBefore(temp.childNodes[0], el);

Wednesday, September 5, 2007

5 truths about web users

1. Users don't care if you have an awesome build script.

Does the gmail development team have a continuous integration process? Does Expedia.com's development team have full unit-test coverage for all their code? Can Linked In deploy the website by pushing one button?

I don't know and am still one of millions of happy customers.

A kick ass environment can really help you build a great product - but it won't build it for you. People tend to focus on problems they are comfortable with to avoid the really hard problem. Like setting up a great environment instead of creating a great product that people love to use.

Worry about having a great product before you worry about your development environment.

2. Users have more than a LAN between their browser and your servers.

I am in the middle of building the latest and greatest. The main page has 15 uncompressed javascript files, 10 background gifs, a couple of extra css files, and sends almost half a meg of data to render the page. It takes about a second for all of this to show up in my browser.

Of course, my browser is sitting on the server, and I've got endless RAM and multiple processors.

When I put this on a test server and hit it from home through DSL, the cloud, and our shared pipe at work - a second becomes over 10 seconds. Long enough that I check ESPN for any new football news.

Having a slow app is like building a car with doors that don't open. People won't buy a car if they have to crawl through the window to get in.

Put your app on an external server and hit it with consumer-grade net access.

3. Masturbatory animation only satisfies the developer.

I need tabs. I know how to use multiple animation frameworks. Hell, I can roll my own animation! So my tabs animate, rotate, morph, slide, and fade. YEAH.

Here is what I think:
Ooh, my tabs rotate, change color, animate, and play a sound when you hover over them. That's SOOO HOT!
Here is what my users think:
What the shit!
Don't do anything just because you can - it will just get in the way.

4. Users don't know flash or html.

Check out .


Here is what the developer thinks:
HTML and Flash - I'm giving my users the best of both worlds!
Here is what the average user thinks:
What the shit!
When your application asks users a question, first ask yourself "do I really need to ask?" If you do, ask yourself "would my mom know how to answer that question?" If they answer is no, rethink your solution.

5. You users will not print out you home page, frame it, and hang it on their wall.

Your application is not art. Users do not care if it "pops" or "looks hot". Users care that it does what they expect when they click a link. Users care that they can use the app without having to constantly think "how do I do that?". Users really care when you app makes their lives easier.

Don't worry how your app looks hanging on your wall, worry about how happy your users are.