31.7.06

Fundamentals of Confusion

(Tricky, Tricky JavaScript - part three)

To those who have hurt their heads scratching them.

1. Functions are Objects. This one is easy. You can pass them around just like a Boolean or a String. It can be returned by another function:

function x(){
return function (){ return []; }
}

x()(); // returns []

x(); // returns a function

setTimeout(function (){
doStuff();
}, 500);


Anonymous functions are very useful in situations like the one above. Why create another function in the global scope? You have the added benefit of being able to use closures.



2. Not all Comparisons are Equal. This one can be confusing. If you're not using strict comparison, code coercion will take place. However, if you're directly testing an object, the results can be counter-intuitive:

var A = !![];


Double negation is a boolean coercion. This bit says should be read as "does the object exist?". A is true. The same can be said about [] ? true : false; or if ([]){ }

if ([] == 0){ }


This is where it can get you. The comparison evaluates to true, but only because the array is equivalent to zero. The coercion could be interpreted as looking at the array's length. Note that if (0) {} is false. Also, be on the lookout for this:

if ([0] == 0) { }
if (['0'] == 0) { }


These evaluate to true too! The lesson is, forget coercion. If you compare to zero, null or underfined, use strict comparison. If you want to see if an array is empty, use if ([].length) { }. Enough of arrays, though.

var B = (NaN == NaN);
var C = (undefined == undefined);


Something that is not a number can be anything but a number. So B is false. Something undefined is just that: undefined. They're all the same. So C is true



3. Closures define environments, not "snapshots". When a closure is created, it keeps a reference to the variables in scope when the closure was created, not necessarily the values!

  var c = {a:true, b:false};
var m;
for (var n in c){
m[n] = function () {return c[n];};
}
var D = m['a']();


Did you think a was true? It's not. The closure keeps a reference to c and n, not their values. When m['a'] is declared, it only declares a function. There is no declaration of what the function contains. However, when the function is called, it looks for the value of n, which is false because it stays at the last state of its iteration. The same can be said if a ifelse loop is used instead

In case you missed it, see part one and part two of this "confusion" series, aimed at the ones who jumped more recently on the JavaScript wagon.

A word about geeks

Joel is pretty pissed in his latest post: Travelers Insurance: Drop Dead.

It's funny, though. I understand that we're the "geeks", but when I think of it, the people I work with are not just intellectually smarter, but also emotionally smarter than the guys that used to be the "jocks" (am I making fun of them now?). Sure, engineers in general are more practical and tend to choose stain-and-wrinkle-free clothing that's probably not Armani, but last time I looked they were wearing Lucky Jeans or Abercrombies. (maybe that's geeky and now I'm being made fun of)

Stereotypes are ok when you know that only a small percentage will kind of fit. It's funny. It's not real. But when you see that someone seriously thinks that a group of people are stereotypical... then they're nothing but a bunch of idiots with no friends.

28.7.06

Eloquent coding

A simple function I wrote 2 years ago looked like this:
function booleanToggle(bool, id1, id2) { 
// getObject is a super-old-school function kinda like $
var style1 = (typeof id1 == 'string') ?
getObject(id1, true) : id1.style;
var style2 = (typeof id2 == 'string') ?
getObject(id2, true) : id2.style;
if (eval(bool)){
style1.display = '';
style2.display = 'none';
} else {
style1.display = 'none';
style2.display = '';
}
}
I ran across it today and couldn't bear looking at it, so now it looks like this:
function booleanToggle(bool, id1, id2) {
$(id1).style.display = eval(bool) ? '' : 'none';
if (id2) { $(id2).style.display = eval(bool) ? 'none' : ''; }
}
I wanted to keep the same signature for backwards compatibility.

There's something about the change that I can't describe. I'm happy because there's less code and I don't think I sacrificed readability. I even made it more flexible, making the last argument optional.

If my prose were eloquent, I'd be able to explain this idea to junior engineers. But it's not.

JSON is not just literal notation

I never thought about it, but you better believe it! Jonathan Snook explains it well in his latest post.

technorati tags:, ,

25.7.06

Quote


"How terrible to watch a man who has the incomprehensible within his grasp, doesn't know what to do, and sits down playing with a toy called God"

-Feodor Dostoyevsky

technorati tags:,

24.7.06

Tricky, Tricky JavaScript - part two: setup

It's easier to help each other if we're all on the same page. This is because before asking for help you've already done the minimum search, and because when we use your environment it sucks to not have the tools we need.
  1. Bookmark Devguru. It is by far the fastest way to get JS reference. Bookmark it. Do it.
  2. Disable caching
    • In IE: Tools > Internet Options... > "Temporary Internet files" Settings... > Check for newer versions every visit to the page. Amount of disk space to use: 1MB
    • In FX: Tools > Options > Privacy > Cache [tab] > Use up to 0 MB of disk space
    In IE, this does not guarantee that it won't cache a file. Since the minimum amount to use is 1MB, it's possible that it can try to use that space and cache what you don't want. Don't fret, you can always use Ctrl-F5 to force a full refresh in both browsers.
    Also, I'd rather leave FX alone since that's what I use for browsing. When I use it for testing, I always force-refresh if needed.
  3. Install the debugging tools. These include debuggers and other diagnostic tools. This list is the absolute must-have.
    MSDE
    Microsoft Development Environment. The JS debugger for IE. Comes bundled with MS Office, you can't download it separately. Make sure it's enabled by going to Tools > Internet Options > Advanced [tab] and uncheck disable script debugging. To open it, there's a few ways:
    • View > Script Debugger > Open (or alt-v,e,o)
    • Insert the debugger keyword in your code. IE will stop execution there and prompt to open a debugger. Venkman also stops at this keyword, but it won't open the debugger for you. Firebug also stops at this keyword.
    Fiddler
    a proxy that will allow you to see exactly what the server is sending before the browser mangles it. You can check headers (caching, mime-types, compression) and tamper with the request and response.
    DevToolbar
    Some fool at MS made this awesome toolbar. You can see the current document's structure, tamper with nodes (including styles!) and a bunch of other crappola.
    Firebug
    An excellent FX extension with a console, debugger and DOM inspector. Lightweight, but a bit... lightweight. Not quite a replacement for Venkman or the DOM inspector, but very close.
    Venkman
    The JS debugger for FX. It's a bit clunkier than MSDE, but you'll have to use it one day.
  4. Using scripts. There are a few ways to include your scripts in a page, and each is used in a particular situation.
    Inline <script> tag.
    The simplest and dirtiest way, you can insert this anywhere in your page by writing your code as the tag's body. Make sure you have type="text/javascript". Use inline scripts when you expect your scripts to initialze your page. Scripts within a page must be kept to a minimum. Pass your JSP variables to functions that you are storing in another file, which can be cached.
    <script type="text/javascript">
    // assume a function:
    initializeRules(ruleId, context);
    initializeRules(<c:out value="${ruleId}" />,
    "<c:out value="${context}" />");
    </script>
    Linked <script> tag.
    This goes in the <head> tag and you set the src attribute to point to your JS file. The main thing to consider here is that the browser can and will cache JavaScript imported this way, which saves you time and bandwidth for subsequent page loads. Note that you can only do this when loading a fresh page.
    <script type="text/javascript" src="/path/to/linkedScript.js"></script>
  5. Get Documentation. There are a few tools out there that will extract documentation from source files and generate an API similar to JavaDoc. You can also write one yourself using regular expressions, of course. It is important that you fully document your code and check the generated documentation for it. Ideally, this documentation is built automatically by the people or department responsible for it. In reality, documentation tends to get lost somewhere.
  6. Syntax check-up. There's always some nasty habit that will eventually kill us. Same with code. Make sure you run your code through JSLint to catch all syntax errors. There's a version online or you can also set it up locally to work with your favorite text editor.

technorati tags:

Tricky, Tricky JavaScript - part one

This part one of a series of posts intended to clarify some confusing aspects of the language. This is not another tutorial.

So, what exactly is the purpose of this? What's with the fuss? Isn't JavaScript just another language? Well, yes but there are a few things which make it different from other languages. It has to run on the client's machine and yet not be an application means that it's important that we cover the possible variations (browser versions and types), while keeping an eye on size.

Size matters: smaller scripts mean faster loads, lower costs (less use of bandwidth) and ease of maintenance. But be careful: the smallest script is definitely not the easiest to maintain When writing JavaScript (and I'd say this about anything you write), keep in mind not only that it has to work. It's almost as important to consider that one day your code will have to be fixed or extended.

Aside from the obvious purpose of making it work, you should code for humans. The purpose of the syntax you choose should be so that it can be understood and maintained. Try to make it as eloquent as possible: short and clear. For example, what's best:

  1. if (array) { ... }
  2. if (array.length != 0) { ... }
  3. if (array.length !== 0) { ... }
  4. if (array.length) { ... }

While you think, enjoy this rant: A scripting language is all about abstractions. The further you go from, say, compiler code, the less lines of code it should take you to do something. With that in mind, a language like JavaScript should allow you to do what you want to do with very few lines of code. But what if your code is lengthy? Well, if it's not a framework, component or library, then your code is in dire need of one.

Time's up. Tally your results:

  1. No way! This is actually wrong! an empty array will evaluate to true, which is not what is intended here. More details on a later post.
  2. You're wasting precious characters.
  3. Nice try, but strict comparison doesn't change anything
  4. Right. In this case, we want to execute some code if the array is not empty. This is the best way to write it, because the length attribute will always return a number, and zero will be the same as false here.

technorati tags:

3.7.06

Arrays and Objects

A while ago I was mentioning how Arrays can be accessed as Objects (since they are Objects). Following on the same thought, here's a curious observation.

Assume we have an array and an object:
var a = [];
var o = {};


We will set a property using named property notation on each of them:
a['test'] = 'hi';
o['test'] = 'hi';


So far, both the array and the object are the same: The array is being used as an Object, thus they will both behave the same. The "test" property will be returned when we use a for in loop.

Things get interesting when we use numbers:
a["5"] = 123;
o["5"] = 123;


Intuition would tell us that since we're using named properties, we should still be using the array as an object, but it turns out that this is not true. What we just did is the same as:
a[5] = 123;
o[5] = 123;


Which results in :
a.toSource() = [undefined undefined undefined undefined undefined 123];
o.toSource() = ({5:123});


Iteration on each of the objects would have to be done in a completely different way, one being a for loop, another being a for in loop, thus demonstrating that care should be exercised when choosing an array or an object for collections.

Parenthesis "scope" in FX

It turns out that wrapping an object in parenthesis not only allows you to manipulate it. Quite literally, the parenthesis provides a wrapper so one can access properties and methods of an object, sometimes in a non-intuitive way:

Number.between = function () {...};
(3).between(2, 5);


A more common application is the use of anonymous functions to provide a scope for variables. This method allows a developer to keep memory clean, by effectively providing an encapsulating block:

(function(){
var x = 12;
alert(x);
})();


Now, straight to the point: Wrapping a function makes it invisible to Firefox, as if the parenthesis themselves provided a "scope". If you try the following code in Firefox and IE, it will fail in Firefox but it will work in IE:

(function testme(){
alert('success!');
})();

testme();
// this will break in Firefox

In my uneducated opinion, the parenthesis should only provide a wrapper --a "handle", perhaps-- so that the function can be called immediately after being declared. Since the function was given a name, this name should be available in global scope, but we see this is not the case for Firefox.

Update: As a more versed reader points below, this is due to a different ECMAScript implementation. FX has it right, IE has it wrong. I have to say it's more handy the way IE did it.

Micromanaging

"In business management, micromanagement is an example of poor management where the manager over-manages people unnecessarily. Instead of giving people general instructions and then allowing them to do their job, the micromanager monitors and assesses every step. The manager may be motivated by concern for details. The effect, however, may be to de-motivate employees and create resentment."

Let's follow an example:
1. Your supervisor wants to know what you're doing. Fair enough. Visibility is crucial for management. A manager needs to understand how long something takes and what the risk of it is.

2. Your supervisor wants to understand why you're doing what you're doing. This is where it can get hairy. It's a slippery slope: First of all, if his skillset does not include yours, you'll spend a lot of time explaining him the basics, and everything else. Second, if he tends to form his own opinions and lacks openmindedness, he'll try to tell you how to do it better and why his way is better. It can get much worse than that, going to extremes like him trying to tell you how you should think, or not "believing" that you don't think the way he thinks you do.


Other aggravating factors: Starting sentences with: "As a [your position here], you should/are supposed to/are expected to...".
This situation can get very ugly very quickly, making you want to quit or start fights. It's not easy to know exactly what to do: should you talk to your manager? What if he won't listen (maybe he is a bit stubborn)? By going to his/her supervisor, will it make the relationship worse? Will he get you in trouble saying how much you've disappointed him to save his skin?

The easy way out: get another job.

The right way: fix it. Find a way. If you're not the only person unhappy with this supervisor, it's unfair for everyone to have to put up with it.

Don't talk about work outside of work

Was Plato ever told that? Was the Buddha ever told that? Where their jobs not to philosophize? Where would humanity be if people like them didn't hang out with their pals ("disciples") and talked about work?

It just dawned on me. While I do agree that people outside of the subject might not want to hear about it while in a social situation, this popular "rule" is a hint of what turns people into drones: people that go to work thinking "it's just work" so they can go home and "live" and spend the money they've earned.

I've always said, before getting hired for the first time, that I wouldn't work. I'd get paid for doing what I like. So far, so good. In fact, I like it so much, that I won't shut up about the projects I have in mind.

Good work is characterized by quality. Quality is exuded by people who care about what they do. You can't care just nine-to-five, you either care or you don't. This is why creativity and all the cognitive process behind good ideas can't be bound to a particular time. Good ideas suddenly hit you, and they have to mature and be thought through.

Now I'm going to appeal to my vast audience:
People (person?): talk about work outside of work. Think important things. Talk about things that serve a purpose, that sparks creative thought on other people.

Unscathed

This is one of those aha's that I didn't have to find out myself. While minding my own business I came across this dude's blog article on IE, createElement and the name attribute.

In a nutshell, and reusing a quote from MSDN:

The NAME attribute cannot be set at run time on elements dynamically created with the createElement method. To create an element with a name attribute, include the attribute and value when using the createElement method.

Do read the article, because it's pointless for me to paraphrase it. However, I do have a comment and this goes in general: instead of using document.all, I'd rather test the issue itself. Do whatever works in all other browsers, then test if it worked, and execute the exception if it didn't. In this case, it obviously means you'll have to use more lines of code.

How long your code will stick around and what other functions or libraries you use will dictate which method is preferred. If there is a chance that a script enables document.all for other browsers, or your "fix" will be hard to find among thousands of lines of code when upgrades give you trouble, the option will be evident.

Sir, you've got bizzarre widths

If you have one text input, and one password input and you define no widths for them, you expect them to be the same size. Why, yes they are indeed EXCEPT in WinIE/XP. That's right, if you're running XP, your password fields will be slightly shorter by default.

Greeeat.

More properties and attributes fun

Back to the wonderful world of cross-browser surrealism. A few posts ago, I was describing how the properties of a node are automatically declared as attributes for the tag and viceversa in IE (tsk, tsk). So I wrote a function that takes an object and transfers its properties into a node element's properties and attributes --explicitly for FF.

AH! the catch! If you redeclare an event listener attribute as a function, it won't work. It makes sense: onclick="function(){ doStuff()}" doesn't work. What do you do? not declare the event listeners as attributes.

Quirky bastards.

Checked = false = true... DUH!

See if this is in the HTML 4.01 spec. It's stoopid:

A checkbox with an attribute checked=false, will actually get checked by the browser. This means that the attribute does not map well to the property, as the property only checks if the attribute is present, not what its value is.

Go ahead. Try this:
<input type="checkbox" checked="false" />

Stoopid.

Commenting code

I have found extremely valuable to follow basic documenting standards like the ones described in this article

I've written some code that can retrieve the complete JS source file from the server and generate the API documentation, which allows us to keep our served files lean (the comments are removed before serving them to the client, hence "complete") and to enforce good documentation (since people reusing the code have only access to the API).

This level of separation keeps people reusing our code happy because it's easy to figure out how to use and at the same time it ensures that the documentation is complete and accurate -- no one likes being interrupted because the documentation is incomplete.

Associative arrays

If you still believe in Santa, stop reading.

Are you familiar with associative Arrays in JavaScript? Well, they're also referred to as hashtables. Guess what? They don't exist. You have been fooled.

The Array object, like all other JavaScript objects, inherits from the Object object --I can't find a way to not make this redundant... hmmm-- so when you think you're making an associative array, you really are only defining properties to the Array. The array itself can still have other elements (always retrieved by index)

var hashtable = new Array();
hashtable['uno'] = "The one";
hashtable['dos'] = "Two";

So far, so good. How do you iterate? you use a for in loop.

hashtable.push('foo');
hashtable[1] = 'gnarl';

How do you iterate? well... you could still use a for in loop, which would return you "The one" and "Two". However, what if you use a regular for i loop? Well, you'd get "foo" and "glarl". Is this odd? Can an array be both an associative array AND a regular array? Not at all! The reason is, THERE IS NO SUCH THING AS AN ASSOCIATIVE ARRAY.

Hey, I have a brilliant idea. Let's make an associative Boolean:

var isSupahCool = new Boolean();
isSupahCool['uno'] = true;
isSupahCool['dos'] = false;

NOT! --see, the boolean is still a boolean. I'm only defining properties for the Object it inherits from.

Don't get me wrong, this IS actually a supah cool way to bestow functionality to our objects, say, by making small beans:

var array = [];
array.contains = function (){...};
array.isLocked = false;

The array is still an array, but with other methods and properties that give it more pizazz. Does it mean that it's WRONG if you use an "associative array"? Not at all. It's just not the array you're using. You might as well use a slice of tuna.

var sushi = new SliceOfTuna();
sushi['california'] = 'yum';

You get the picture.

Dude, you're getting old and cranky

Today I found myself opposing an initiative to have a refactoring day. I'm getting old and cranky. I really don't think I'm growing up

* When it comes to refactoring, you can't just go and change the
code without a good reason to do so. I agree that we need to address
defects from the root, but there are several problems:


Problem: Refactoring has considerable risk You may
think you're fixing bugs, but you may be creating more. Figuring out if
the time that you will spend is worth it or not is not an easy
managment decision. I have the impression that in general, no one will
just let you refactor code because the code sucks. Look at what
happened at Netscape when they decided to refactor: not only did they
open a whole new can of bugs, but they also lost the browser war.
Granted, we're not spending a year in rewriting our product from scratch, but
the point is, the code works and changing it may do more harm than
good. From the management perspective, that just makes no sense.

Problem: Refactoring drains resources from the front line
Hard fact: there are defects that make our product less-than-perfect
and they need to be fixed ASAP. Fix the defects, crank features for a
mind-boggling lightning release of OurProduct 2.1. That's what sells. A company
doesn't make a product. It makes money. We need to concentrate on good
designs, from good usability to good code. As our code base grows, it
will slowly push out legacy code. Feature by feature we target areas
that will have to get rewritten anyways, and we won't waste precious release time.

Problem: Refactoring is not the root solution
Refactoring only changes bad code that was written before. What about
bad code that will be written tomorrow? Emphasis needs to be put on
good practices and education. As we edit files, we clean them up. It's
an ongoing practice. The cleanest code is not the one that you clean up
the most, but the one that you mess up the least.

Problem: You may be refactoring the wrong thing
Relates to draining resources, but I'll compare to performance tuning.
You just don't do performance tuning wherever you think you need to do
it. It's done at the bottleneck and only when you need it. How are you going to know where your worst code is? How do you know what
refactoring effort will squash the most future defects? What will
prevent you from refactoring your now refactored code in the future
(when you decide you know better or when a new technology arises)?
Tough questions not worth the time.

* Proposed alternatives (I propose all, not some):

Alternative: Fit refactoring into your design schedule If your feature involves changing any code, make sure you take the time to change it right (not just build on top of scum).
Alternative: Educate Educate yourself on best
practices, to improve your designs and implementations: reduce defect
propensity. Educate those around you: expose more of your knowledge
through complete documentation, wiki pages, blogs. Talk to your peers,
discuss ideas like best practices. Organize training sessions. We are
in serious need of education.

Alternative: Do it on your spare time Yeah, it may
suck and I know you do have a goldfish and friends and Halo buddies to frag. It
really depends on how passionate you feel about doing things right.
Note that separating church from state is perfectly fine, the point
here is that if you want something done, just DOO EET.

The magic word that opens new worlds

Closure.

I'm not a CS major. I've been doing JS for a while and found very few things that I didn't know. One of them, was why some variables remain in scope and some not. I tried searching for answers, but Google is of little help if you don't know what you're looking for.

Closure. I read somewhere the word and BAM! a whole new world openened, of people that know what they're doing in JavaScript (not form validation scripts, not drag-and-drop out-of-the-box script crap). I pored through [web] pages and pages and learned a lot more than I thought I could. Not only I learned about fancy stuff (real programming) in JS, but I also learned some humility: if I don't find more info, it's NOT because I already know everything there is to know.

This word immediately answered what I wanted to know, and the pages linked to even more pages with even more in-depth knowledge not only about JS, but also usability and some cool browser hacks. I had tapped into a higher plane of knowledge.

Forget "open sesame", as "closure" --ironically-- opened more doors for me.

Not hacked at all

So, everybody knows that form input controls are under the OS control, not the browser control. IE does this "funny" thing where if you put an absolutely positioned container over select boxes, the select boxes will still render on top.
The hack is, of course, to use an IFRAME. For convenience and added hackability, we use filter:alpha(opacity=0) and z-index=-1 on an iframe positioned inside the container we want to cure. Ah, but since this is only for IE, we add some proprietary IE browser logic (--[if IE]--).
"But hold on!", you say. That's right. You were wondering why if i put a border around the container, the select boxes will still butcher the border. That, of course has to do with the crappy box model implementation, and I have yet another hack (er, workaround) for the IFRAME style to lighten your day:

border: 1px solid transparent;

That's just money.

Attributes, properties and other appendages

When it comes to talking code, you gotta be careful with your terms. There are already unclear concepts, so if on top of that you use the wrong term, well, don't blame people for doing the wrong thing.
I'm talking about "attribute" and "property". IE manages to fudge the difference by turning all attributes into properties, so this should make it clear once and for all:
Attributes are for tags:
Properties are for objects: Object.property;
I've also heard the use of "parameter" instead of "attribute" or "property". Come on, seriously.

Pixels, elephants and unreasonable units

When adding units to your properties, zero is zero. 0px is the same as 0blobs. If you're going to add a unit to zero, at least make it a funny one, so I don't get pissed off when I come across it (hey, I'm the code nazi, remember?).

Ah, this one, I love:

.5px;

Cross browser tag attributes

Granted, it's not simple to do it right to start with. In order to do it right (validate and blah blah), you'll need custom DTDs . Let's assume we don't care for now >:)

Long story short: IE will be friendly enough to expose the tag attributes as properties of the object. Code speaks louder than english, so:

<input name="test" id="test" custom1="true" />
<script type
="text/javascript"
>
var tag = document.getElementById('test');
alert(tag.custom1);
// undefined for moz
alert(tag.getAttribute('custom1')); // true

tag.setAttribute('custom2', 'true');
alert(tag.custom2);
//undefined for moz
alert(tag.getAttribute('custom2')); // true

tag.custom3 = true;
alert(tag.custom3);
// true
alert(tag.getAttribute('custom3')); // null for moz

</script>

Moral: Stick to getAttribute() for attributes. Use the property for properties. Simple huh?

Iterating through form elements

Iterating through form elements to add or remove elements of the form is tricky at best.

I'm looking at code I wrote about a year ago. I was so proud of its complexity. Now, it has come to bite me in the ass.

Long story short: the form.elements array will hold the same references to the elements in Firefox, even after they get removed from the form (via DOM). Not sure what the spec says it should be, but it's certainly confusing. If the element is no longer attached to the form node, it's not part of it. On the other hand, IE will update the array and always give you the elements that belong to the form, so if you remove an element, your iterator will be screwed.

Which one is it? If an element is removed from the form.elements array, should the reference to it still be in the array? Beats me.

Back to writing cross-bullshit code.