Decorator Factory Aspect Sunday, 11 May 2008

A Decorator Factory Aspect is a Factory method, added as an Aspect to a constructor of a Decorator.

I should first define Decorator (also called a wrapper), Factory and Aspect. I'll explain them together for brevity.

Decorator Pattern
makes it possible to extend (decorate) the functionality of a class by adding a new decorator class that wraps the original class
Factory Pattern
The Factory pattern is a creational design pattern that encapsulates the processes of creating objects
Aspect
introduces separation of concerns, specifically cross-cutting concerns, as an advance in modularization (Wikipedia link)

Decorator Factory

The Decorator Factory gets or creates a decorated element. The id of the wrapper is the same as the id of the element. It's makes it easy to get (or create if none exists) a Decorarated Element from any function that knows about the element. Conversely, the object that decorates the element can always get a reference to the element it decorates. It looks like this:

/**
 * @constructor
 * @param {String} id - the id of the element and widget. 
 */
function GenericWidget(id, x) {
  this.id = id;
  this.x = x;
}

// Factory.
GenericWidget.instances = {};
GenericWidget.getById = function(id, x) {
  if(this.instances.hasOwnProperty(id)} return this.instances[id];
  return this.instances[id] = new this(id, x);
};

GenericWidget.prototype = { 
  show : function() { 
    document.getElementById(this.id).style.visibility = "visible";
  }
};

By calling getElementById, the decorator can avoid some of the problems with changing node references with innerHTML (though state changes must still be managed manually).

Problem: DRY

It is cumbersome and error-prone to write out a Factory each time we want to use a Factory and doing so violates DRY.

Problem Analysis

The id parameter variable does not change. The part of the Factory that varies is the additional zero or more arguments (varargs). In this case, x. So, if I can solve passing varargs to a constructor, it will be possible to create a generic Factory Aspect.

How to get create a new object of a_constructor, based on an id? If the object is being newly created, then it will be necessary to pass in the extra arguments to the constructor.

newApply

A way to call new with variable arguments would solve this problem. A new + apply() would provide the varargs ability apply, but passed to [[Construct]], not [[Call]].

This has been solved in APE core. The source code for APE.newApply:

/** 
 * @param {Function} fun constructor to be invoked.
 * @param {Array} args arguments to pass to the constructor.
 * Instantiates a constructor and uses apply().
 */
newApply : function(fun, args) {
    if(arguments.length === 0) return;
    var f = arguments.callee, i;

    f.prototype = fun.prototype;// Copy prototype.
    f.prototype.constructor = fun;

    i = new f;
    fun.apply(i, args); // Apply the original constructor.
    return i;
}

What's it Good For?

Now I can create a generic getById function. This function can be added as an aspect to any constructor function. Factory Aspect APE.getById is a part of APE core.

getById : function(id) {
    if(!this.hasOwnProperty("instances")) this.instances = {};
    return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));       
},

More Examples

I have used this approach for many parts of APE, including Calendar, Draggable, and Slider. It is most useful for building widgets.

Technorati Tags:

Posted by default at 10:35 PM in JavaScript

The JavaScript Guru Tuesday, 6 May 2008

Programming is not "wizardry." I am not a "ninja" or a "guru."

What, no Sword?

Programming is a scientific discipline. It requires zero mystical powers (although I have been known to "disappear" before long meetings). If you want a guru, go climb a mountain (results not guaranteed).

More Yellow Flags

I sometimes get job descriptions and do interviews that can put me off. "JavaScript Ninja" is one flag.

"We want someone who can Hit the Ground Running and Work Independently," is another more subtle flag.

This usually means they want someone to work alone, not with the team.

They Don't Want what's Good for Them

Part of what I'm finding to be a problem is that employers get the impression that I'm good at DHTML and they think they'll have me work by myself.

The situation where one or more members on the team are unwilling put forth the mental effort to do what needs to get done can lead to hiring a contractor. This is a terrible position for any contractor to be in.

Teamwork and Ownership

The reality is that the more successful projects have better cooperation and communication. Hiring a "guru" or "wizard" contractor is usually carried out for the worst reasons ("we're behind"), and with the worst process ("let's have the contractor own this part"). Hiring new people takes time. Even a contractor. It also takes time integrating the new person. So hiring a contractor to "catch up" isn't generally a good idea.

Having code in the project that is owned by the contractor gets the company in technical debt because it leaves them with code that they do not understand.

Having the contractor sit and work apart from the team often makes it much harder for the contractor to do a proper job.

Change

When [x_new_feature] is introduced, it might require change to the existing code. If it's a new feature, then it must mean that the requirements changed in some way, and sometimes the existing code is not designed to accommodate the new change (normal). If the contractor is asked to build [x_new_feature], and he is working alone, well, then he either has to step outside his role and change existing code (can get him fired) or the new feature will get built in a hackish way around the other non-changing code that is owned by the team.

Hiring a contractor is perfectly fine, but having contractor-owned code makes it hard for the team to handle changes, leads to code rot, and sometimes leads to the company hiring another contractor. This is wasteful (no matter how many millions of investor money is available). It is beneficial for the company employees to understand as much of the company code as possible. A contractor should not be a lone gun, but should be working with the team, trying to understand their problem with them, collaboratively, and helping them to find solutions to the problems.

Hire a Ninja?

I am very cautious of anyone who presents himself as a "ninja" or a "guru". Posting an ad for "JavaScript Guru Wanted," is asking for someone who is both overconfident and underqualified, and that is disastrous combination of characteristics in a potential newhire.

The code belongs to the company and gets written by the team. Having the contractor work apart from the team on a side project or sit in a different area is a bad idea; it just gets the company into debt more.

Pair programming, metaphor/ubiquitous language, daily standups should include the contractor. With complicated applications, TDD and Pair can be practiced in a way that can significantly benefit the successful outcome of the project and collective understanding of the code. Even with a contractor.

Let the contractor know how your team works and how he's expected to fit in. This will help avoid misunderstandings and can help your team find the right contractor for the job.

Technorati Tags:

Posted by default at 8:15 PM in Uncategorized

APE JavaScript Library Monday, 5 May 2008

Frustrated with every other Javascript framework, I have decided to write something better.

Points

  • Minimal Framework, mainly used for AOP and OOP functionality
  • All code is tested using YUI Test (TDD)
  • AOP Event System with asynchronous error handling
  • Namespacing
  • Modularity, Cohesion, Packaging
  • No browser detection
  • JSDoc

Here it is: APE JavaScript Library

Testing

By using a test driven approach, I was able to avoid problems that I found in other libraries.

APE Core

The core of APE is so small that the overhead of adding APE to a project is significantly less than that of other libraries.

I generally don't like a lot of the libraries that use a "GOD" object approach. These objects can be recognized by being undefinable by more than a few words. The approach is quite popular and annoys me to no end. How do you describe the dojo object, for example?

The APE object itself is defined in APE.js, providing only the most basic things that a framework would need: 1) Object creational features, and 2) a core namespace. Although some might argue that a library core needs more than that.

The APE object is only 3k minified and less than 1k gzipped. The size is not the point. The point is that APE is not a kitchen-sink, or God object.

Help Wanted

You can join, too! Here's what APE needs:

New Features

There is much more that can be added. For example, some ADT's: Resizable (croppers, windows), FormSerializer.

SVN and Bugzilla

I still need to add hosted SVN to the directories and install bugzilla.

Performance Tests

I've always been big on performance, and not just for JavaScript but all areas of life, and I am very competitive in nature.

APE needs benchmarks to compare with other libraries. This will provide useful analysis for other library authors who aspire to have code that is as performant as that in APE.

Anyone interested in joining this project contact dhtmlkitchen - at - gmail - dot - com.

Technorati Tags:

Posted by default at 7:26 PM in Uncategorized

Testing JavaScript Saturday, 19 April 2008

Slides from JS Meetup's "Testing JavaScript"

Technorati Tags:

Posted by default at 1:03 AM in JavaScript

Load Time Constants, Part I Thursday, 21 February 2008

One issue that comes up when writing scripts is addressing upper and lower case tag names.

Problem

An XHTML document served with the content type application/xhtml+xml will have all lower case tag names in the DOM. The exact same page served with the content type text/html will have all upper case tag names.

This can affect the way we script our pages by requiring us to clutter up the script with calls to toLowerCase().

Example

function getCoords(el) {
  for( var parent = el.parentNode; parent && parent !== container; 
                                         parent = parent.parentNode) { 
    if( parent.tagName.toLowerCase() == "table" ) {
      var pcs = getComputedStyle(parent, "");
    }
  }
}

It's easy enough to call el.tagName.toLowerCase() but when this is done repeatedly in a loop, it can slow things down.

Solution

One way to avoid that issue is to define a run time constant. This would be done in a closure over the function that was calling toLowerCase()

(function(){ 

var TABLE = /^h/.test(document.documentElement.tagName) ? "table" : "TABLE";

function getCoords(el) {
  for( var parent = el.parentNode; parent && parent !== container; 
                                         parent = parent.parentNode) { 
    if( parent.tagName == TABLE ) {
      var pcs = getComputedStyle(parent, "");
    }
  }
}
})();

Scope

Function getCoords() can see TABLE, but nothing outside of that closure can see getCoords.

Function getCoords must then be exported by assigning it to a property of a globally accessible object (using the Module Pattern or Exporting Pattern).

Conclusion

Having a constant can help to speed up the loop by avoiding the property lookup and an extra function call to toLowerCase().

Having one constant value reduces the number of strings being created and garbage collected. Since an active garbage collector can also hurt performance, a constant value avoids this hit.

Finally, the result can be compressed using a compression tool, resulting in TABLE being converted in to a one letter symbol.

Technorati Tags:

Posted by default at 5:20 PM in JavaScript

Google Code in Safari Friday, 1 February 2008

The JavaScript Gurus at google have developed a neat feature for Googlecode. It verifies the form for you, to make sure you're data is OK. If their script thinks you've correctly filled out the form, the submit button gets enabled.

This has got to be one of the dumbest things I've seen in a long time...

Google Code: Borken in WebKit

See that Create Project button up there?

It's disabled, isn't it? Yes, it is. And if you're using Webkit, it will stay disabled.

User Experience and Usability

There is JavaScript code in Google's "create project" page that enables the button. Since it doesn't work in Webkit, the website is unusable.

Surprised?

It's nothing to be surprised about. The new GMail is just as buggy in Safari as the old one was; sometimes the message gets only half-sent (truncation). Google Groups does the same thing; sends half a message sometimes (embarrassingly horrible code there). Groups' "search this group" feature doesn't work in any browser if you have language pref (link). Groups also has the same problem with the button becoming disabled when it is clicked, which doesn't work so well on a wifi remote connection if you got disconnectet. It really is a piece of garbage.

A Reputation to Live up to

Arrogantly bolstered by aggressive financial growth, the Google Empire churns out one poorly engineered software project after another. Each project gaining massive recognition, as it bears the Google brand name. Like Microsoft, they're accepted as de facto in the industry; (1) Like Microsoft, they aggressively hire engineers, acquire competitors, et c. (2)

(1) Got an iPhone? I bet it came with Google's lame mobile search instead of the better Yahoo Mobile search.

(2) Google docs and spreadsheets, an acquisition, is developed and maintained by recent grads and contractors. No surprise that even after nearly 4 years it's still almost completely unusable.

Source Code

The inline <script> tag for Google Code contains the relevant button-enabling code. No type attribute on the script tag, missing var keyword for most of the variables (which are all top level). The obvious point is the accessibility issue: Users who don't have a browser Google supports are blocked.

<script>
 _exposeExistingLabelFields('edit');
 var submit = document.getElementById('submit');
 submit.disabled='disabled';
 var projectname = document.getElementById('projectname');
 var licensekey = document.getElementById('license_key');
 var summary = document.getElementById('summary');
 var description = document.getElementById('description');
 var cg = document.getElementById('cg');
 projectname.focus();
 var solelyDigits = /^[-0-9]+$/
 var hasUppercase = /[A-Z]/
 var projectRE = /^[a-z0-9][-a-z0-9]*$/
 function checkprojectname() {
 name = projectname.value;
 feedback = document.getElementById('projectnamefeedback');
 submit.disabled='disabled';
 feedback.style.color = 'red';
 if (name == '') {
 feedback.innerHTML = '';
 } else if (hasUppercase.test(name)) {
 feedback.innerHTML = 'Must be all lowercase';
 } else if (solelyDigits.test(name)) {
 feedback.innerHTML = 'Must include a lowercase letter';
 } else if (!projectRE.test(name)) {
 feedback.innerHTML = 'Invalid project name';
 } else if (name.length > 50) {
 feedback.innerHTML = 'Project name is too long';
 } else {
 feedback.innerHTML = '';
 feedback.style.color = '';
 
 checksubmit()
 }
 }
 function checksubmit() {
 submit.disabled='disabled';
 if (projectname.value.length > 0 &&
 licensekey.value.length > 1 && 
 summary.value.length > 3 && 
 description.value.length > 3 && 
 (cg == undefined || cg.value.length > 1)) {
 submit.disabled='';
 }
 }
 checkprojectname();
</script>

Looking at the Mess

Notice the: submit.disabled='disabled';, well that's pretty odd. I can guess what the author was trying to do. The browser might do a boolean conversion on the 'disabled', to get a value of true, but that is not guaranteed. I'm guessing there's code somewhere else that says submit.disabled = "" or submit.removeAttibute("disabled")

I love how they embed all the localized error messages in a series if else of checkprojectname function. A fine example of Google's standard of quality.

What I Think of Google's Code

Garbage. I wouldn't care so much if their god damned page worked.

Technorati Tags:

Posted by default at 2:17 AM in JavaScript

Crashed! Thursday, 31 January 2008

DHTML Kitchen was down.

My web host upgraded CPanel. They did not tell me, but they did. In so doing, the permissions on my database got changed, so my blog software could not access my MySQL database. The blog software threw an error, which crashed Tomcat. Tomcat could not be restarted, because when the app loaded, the blog software would try to read from the database, which would cause another crash, bringing Tomcat right down again.

Glad it's back up and running now.

Posted by default at 2:25 AM in About this site

Event Notification System Friday, 4 January 2008

An Event Notification System is an object that manages notification of events to multiple callbacks. The Event Notification System uses an Event Registry to store the callbacks as bound methods. When the event fires, the callbacks are invoked.

Event Registry

An Event Registry is a store of bound methods. An Event Registry is used by an Event Notification System. The Event Notification System is tightly coupled with the Event Registry. Sometimes it is referred to as the Registry. In reality, the Registry is just a data structure and the Event Notification System is a behavioral object.

Almost Every JavaScript library has an Event Registry, or at least some way of dealing with event notification.

For example:

// YUI:
YAHOO.util.Event.addListener( link, "click", linkClickHandler, thisArg );

// Prototype: (not a registry, but the old 'addEvent' function renamed).
Event.observe( link, "click", linkClickHandler ); 

// Dojo:
dojo.connect( link, "onclick", window, "linkClickHandler" );

They're all different in how they work.

The Event Registry is useful for a few reasons.

  • It allows multiple callbacks to be assigned to a function call.
  • Provides a usable alternative to attachEvent. Internet Explorer 7 and below has attachEvent/detachEvent. The callback function for attachEvent executes in global context (this is window), not the object it was attached to.

A good Event Registry solves these problems. A good Event Registry also allows for context resolution with an optional thisArg. A good Event Registry also allows custom events to be registered using the same interface.

A poorly designed Event Registry concerns itself with things related to native events (DOMContentLoaded, keyPress, et c). A poorly designed Event Registry does not pass an event object to the callback (perhaps trying to use eval to pass varargs).

Error Handling in an Event Notification System

Callback Errors Should not Break the Registry

A good Event Registry does not allow any callback to break the registry.

One common problem in most Event Notification Systems (such as Dojo, Mochikit, YUI, and jQuery) is that they allow the callback to break the System. If a callback fails, it prevents subsequent callbacks from firing. A callback should not be given the ability to break the Registry.

Here's how to break a Registry that doesn't consider errors:

var passed = false;
addCallback( link, "click", function(){ setTimeout(checkTitle, 500); } );
addCallback( link, "click", function(){ throw Error('bad'); } );
addCallback( link, "click", function(){ passed = true; } );

function checkTitle(){ 
    if(!passed) 
        alert("registry broken: second callback did not fire.");
    else 
        alert('passed');
}

Callbacks sometimes throw Errors. It is important for the Event Registry to consider this and take the responsibility to handle these errors properly. If an error occurs in a callback, it should not break the Registry.

It should be guaranteed that all callbacks fire, even when earlier callbacks throw errors. This is a natural expectation; it's exactly how DOM Events work:

DOM Events Test

(function(){
var s = document.getElementById('r-test');

var el = document.getElementById("registry-dom-event-button");
if(!window.hasDocumentListeners) {
    el.addEventListener( "click", setUpCheck, false );
    el.addEventListener("click", throwError, false );
    // setTitle must fire.
    el.addEventListener( "click", setTitle, false ); 

    window.hasDocumentListeners = true;
}
function setUpCheck(){ setTimeout(checkTitle, 500); }
function throwError(){ document.title = ""; throw Error('bad'); }
function setTitle(){ document.title += 'ok'; }
function checkTitle(ev) {
    if(document.title != "ok") {
        alert("DOM Events broken: setTitle did not fire. " + document.title );
    }
    else {
        alert("passed");
    }
}
})();

Result and Analysis

There should be 1 error and an alert passed. This indicates that after the error happened, the setTitle callback successfully fired.

This example assumes:

  • Callbacks fire in the order in which they were registered.
  • The bad error in the first callback does not stop subsequent callbacks from firing.
  • The button supports the EventTarget interface (not in IE7).
  • The button supports onfocus (not in safari bug 16331).

Proper Callback Error-Handling

Throwing the error in a separate thread allows the callstack to continue without breaking. Any errors that are thrown are thrown in the correct order in the callstack. The Event Publisher's fire function would have something like this:

try {
// If an error occurs, continue the event fire,
// but still throw the error.
  callback.call( thisArg, ev );
}
catch( ex ) {
  setTimeout("throw ex;", 1); 
}

The one subtle issue is that setTimeout uses global scope, like the Function constructor, not like eval, which runs in the calling context's scope.

A closure must be used to preserve the ex variable.

try {
// If an error occurs, continue the event fire,
// but still throw the error.
  callback.call( thisArg, ev );
}
catch( ex ) {
  setTimeout(function(){ throw ex; }, 1); 
}

Event Registry Test

The remaining problem with the above code is that the error condition is untestable. Writing a test suite forced me to realize this and I changed the design.

try {
if(csi[0].call(csi[1], e) == false)
  preventDefault = true; // continue main callstack and return false afterwards.
}
catch(ex) {
  APE.deferError(ex);
}

Where APE.deferError is defined:

deferError : function(error) {		
  setTimeout(function deferError(){throw error;},1);
}

I have included the source code for my own Event Registry, along with this test, which shows how I managed to test APE.deferError.

Performance?

Wrapping each callback call in a try catch might seem to be bad for performance. I tried it with mousemove event on my drag code, dragging multiple drag objects at a time (example), and it seemed fast enough; I did not notice performance problems in any browser. There is most likely some performance overhead using this approach, but I did not find a need to write a benchmark.

src should never be a string. Although this may seem obvious, YUI actually allows src to be a string, where the string represents an element's ID. The document is polled regulary until the element with the id matching string is found and then the callback is attached to that element. If the element has been renamed, the document is still polled and silent failure occurs.

This can lead to silent failure or corrupted application state if the element is not found. It is not recommended.

Packaging and API Design

The Event Notification System is a low level component with no external dependencies.

Being a low level component, the Event Notification System should be maximally stable (no efferent couplings), and maximally abstract. In this case, the Event Notification system is maximally abstract because it can't be subclassed or used independently.

Stable Dependencies Principle

Depend in the direction of stability

Stable Abstractions Principle

A package should be as abstract as it is stable.

Reuse Equivalence Principle

The Granule of Reuse is the Granule of Release.

The Event Notification System is a low level component with no external dependencies. It is intentionally packaged as a single, tested unit. It amplifies the essential (event notification) and eliminates the irrelevant.

Creating special cases for handling DOM events (keyCode, et c), would reduce abstraction. These special cases are perfectly valid, but do not belong in the Registry. Special case needs can either be hard-coded into end-implementation code (using feature/capability detection) or, if the special-case logic is complex, programmed into an object that performs a task (such as an Adapter object).

An example of an Adapter object would be a Content Load Adapter or a KeyEvent Adapter (key events are highly inconsistent across platforms). Such objects would be slightly higher-level and, having at least one dependency, would be less stable (though this is not a bad thing).

Department Store JavaScript

[insert_popular_library_name_here] usually include more code than any one application could possibly use in an attempt to cover the needs of every application.

Libraries that add more functionality into one module than is usually needed, or create modules that are not cohesive do so in spite of commonly known software package design concepts. The one-stop library approach is appealing because it allows developers to "stop cobbling bits of javascript."

Performance (Again)

Load Time Performance problems can be acheived by creating custom javascript builds on the server. Hand-rolled "combination" files or utils files are fine for web sites with fewer pages. Sites that don't require 200k+ of additional javascript should not include such functionality.

Technorati Tags:

Posted by default at 9:42 PM in Uncategorized

Browser Detection Saturday, 24 November 2007

There have been many, many, articles and discussions that eschew browser detection, yet it continues.

Despite all of this information, browser detection can be seen in most of the popular JavaScript libraries including Prototype, YUI, Dojo, jQuery, and is present in applications such as the newly refactored GMail:

For a better Gmail experience, use a fully supported browser.

Browser detection is bad for many reasons:

  1. Browsers change.
  2. The User Agent string does not represent the browser reliably.
  3. Even if it did, the browser doesn't represent feature support (See #1).

Browser detection is unrelated to the problem it is trying to solve.

Browser detection makes code hard to maintain. It accomplishes this by requiring that the next version of [insert_browser_name] will also have to be tested and special-cased in the code.

Alternative: Feature Detection

For example, does the browser support opacity? This can easily be determined:

if("opacity"in el.style) { }

Support of opacity has nothing to do with whether that browser actually is an IE version, nor is the reverse true: IE does not imply support (or lack of support) for opacity.

Detection for feature support does not suffer from maintenance problems when Internet Explorer decides to support opacity. Capability detection takes feature detection one step further.

Once the code has been properly designed and tested, it should not be a problem to maintain.

I have learned this the hard way and have tried to remove browser detection from my drag code, though evidence of my mistake is still present. I had to refactor my drag code in specific cases where it checked for browsers identifying with an Opera User Agent string (removed checks to ua.opera). My code still contains one conditional branch that needs to be refactored, As is, the script works in all of the modern browsers (this is subject to change).

With browser detection, the internal quality of the code suffers, even if the code works. This is because it introduces a dynamic aspect that must be maintained as the browsers change. Implementation Matters.

ToBoolean

Be careful when testing for values of properties. Some values may evaluate to false in a boolean context.

// Error-prone, scrollTop may be 0, which would evaluate to false
if (document.body.scrollTop) {
    // statements that work with scrollTop property
}

The new GMail

New code should definitely not rely on browser detection

GMail, which was recently redesigned, still uses browser detection and also punishes users with the performance hit of a misused HTTP redirect (HTTP/1.x 302 Moved Temporarily), or, if GMail finds your browser's User Agent header unsuitable, it two HTTP redirects.

In fact, when developing for mobile phones, I have found Chris Penderick's UserAgent switcher useful. Unfortunately, this confuses GMail, messing up the rendering and even encoding of messages.

GMail seems to be predominantly developed with a windows-centric mentality. This is evidenced by the lack of support for Command + S to save in Mac, and the Safari and Opera bugs witnessed in earlier versions of GMail.

Google Groups

Google Groups also relies on faulty browser detection to block certain features.

Google Groups Browser Error, in a Draggable, Floating DHTML Pane Google Groups Browser Error, in a Draggable, Floating DHTML Pane

This feature is not supported in your browser. Download a copy of Firefox or Internet Explorer to upload your picture.

I find it ironic that my browser can be assumed to support the draggable, floating pane, and not suitable for uploading a picture. The floating pane is draggable from anywhere inside it, so it is impossible for a user who gets the error to select the error message text.

Acknowledgements

Google deserves proper recognition for providing a clear example of why browser detection is a bad practice.

Contrasting Example

For proof that an effective Ajax application can be developed without browser detection, have a look at Google Reader.

Browser Detection is mostly a bad idea.

In the meantime, I'm looking for a decent mail application that runs in the browser. Both Yahoo mail and GMail fall short of my expectations.

Technorati Tags:

Posted by default at 9:40 PM in Browsers

Opera Bug: getComputedStyle Returns Margin for Unset Top/Left Values Thursday, 8 November 2007

In Opera 9.2 getComputedStyle(el, '').getPropertyValue returns the margin value for top/left values when the top/left values aren't set. In Safari the returned values are 'auto' in this case.

Example

testcase showing bug in Opera and Safari

Workaround

The way to avoid the problem is to explicitly add top and left values to the stylesheet:

#Test {
  top: 0;
  left: 0;
}

Then getComputedStyle will return correct values for top/left (0px) in Opera 9 and Safari.

pixelXXX

A convenient alternative would be a currentStyle.pixelLeft. Only Opera and IE support currentStyle and only Opera supports currentStyle.pixelLeft. (IE supports style.pixelLeft; this only reads from the style attribute)

Mozilla does not support pixelXXX properties at all, though Opera, IE, and Safari 3 all do.

Technorati Tags:

Posted by default at 6:25 PM in Browsers

Iteration, Enumeration, Primitives, and Objects Sunday, 21 October 2007

Iteration VS Enumeration

When trying to understand a language, it is necessary to first understand the words.

iteration
the for statement (§12.6.3)
enumeration
the for in statement (§12.6.4)

While writing Enumeration and Object Oriented JavaScript, I read and reasearched (I also grew a beard and lost about 20lbs in the process). I read blogs and libraries to see what other programmers were doing. I came across a peculiar quote by Alex Russel (dojo fame).

The contortions that Ajax toolkit vendors go through to keep iteration over JavaScript objects and primitives coherent is, quite simply, insane. Much of Dojo, in particular, is designed around this problem.

—Alex Russel

Iterate Over a Primitive?

That sounds completely insane! I read through dojo.js (uncompressed), which was pretty heavily commented, but could find no such contortion.

I thought about this some more, and I'm pretty sure it's not possible.

Attempting to enumerate over a primitive value results in an Object being created. That object (including its prototype chain) is then enumerated over.

Boolean.prototype.crap = "useless";
for(var prop in true) { alert(true[prop]); }
if(!delete Boolean.prototype.crap) 
    alert("uh oh :(");

Attempting to enumerate over a primitive will result in the evaluation of the primitive in creation of an object (§9.9), just as is with the property access operators, e.g. true[ prop ] or true.valueOf() === true or 1.1255.toPrecision(4);

The internal ToObject method, in the above case, will return a Boolean object holding the value true. The property named crap is resolved in Boolean.prototype, holding the string value "useless".

How About a String?

A string value might seem to be iterated over (with some practical usefulness). In that case, a String object would be created, holding the original string value; you wouldn't actually be iterating over a string value; it would just seem like it.

The difference between a String object and a string value, is explained in my entry How Property Access Works.

Practical Example of Iteration

Objects which support some form of sequential indexing of properties can be practically iterated over (§12.6.3).

JavaScript provides two types of built-in objects that fit that description.

String
Property values are characters. indexed by a numerical String starting at "0"
Array
Property values are anything. Indexed by a numerical String starting at "0"

Iterate over an Array

An Array is an object that is specialized by its length property, it's [[put]] method, and its literal initializer syntax. Array.prototype is sometimes modified to add features that are not supported in certain browsers (like Array Extras, supported in Webkit and Gecko, but not Opera 9.2 or IE 7).

Because Array.prototype is often modified, it is not safe to enumerate over an Array (unless you have a sparse array and know how hasOwnProperty works).

Non existent properties that need to be added by programmer-defined code do not exist, and therefore, cannot have the DontEnum attribute. For example, Array.prototype.some does not exist on Array.prototype in IE or Opera, but is non-enumerable, native code in other browsers.

To avoid enumerating over a property in the prototype chain, a cautious programmer will avoid using for in on an Array, and use hasOwnProperty when he does use for in.

Library authors might consider adding only the top-level Extras, e.g. Array.some to browsers that don't have Array.some, and leaving the prototype alone.

Iterating Over a String

When the string value is converted to a String Object, the characters in the value are accessible through the object.

String.prototype has a method to access character properties: charAt (§15.5.4.4).

Here's where it gets interesting.

Gecko and Webkit implement another way access to String index properties: The property access operators [ ]. This is not part of the official specification.

String's [[Put]] method §8.6.2.2 is specialized in Webkit. This seems to create some problems, as can be shown in an example.

Example Using [] on a String object

(function(){
    String.prototype['2'] = "prot";
    String.prototype['4'] = "prot4";
    a = new String("012");
    a[1] = "one";
    a[11] = "eleven"
    var result = [];
    for(var i = 0; i < a.length; i++)
        result.push(a.charAt(i) + ", a['"+i+"'] = "+ a[i]);

    result.push(a.charAt(4)  // Should be "".
        + " , a['"+ 4 +"'] = "+ a[4] );
    
    result.push(a.charAt(11)  // Should be "".
        + " , a['"+ 11 +"'] = "+ a[11] );

    return result.join(String.nl);
})();
Result Expected
0, a['0'] = 0 1, a['1'] = one 2, a['2'] = 2 , a['4'] = prot4 , a['11'] = eleven
Browser: Internet Explorer Mozilla Opera 9.2 Safari 3
Result: 0, a['0'] = undefined 1, a['1'] = one 2, a['2'] = prot , a['4'] = prot4 , a['11'] = eleven 0, a['0'] = 0 1, a['1'] = one 2, a['2'] = 2 , a['4'] = prot4 , a['11'] = eleven 0, a['0'] = 0 1, a['1'] = one 2, a['2'] = 2 , a['4'] = prot4 , a['11'] = eleven 0, a['0'] = 0 1, a['1'] = 1 2, a['2'] = 2 , a['4'] = prot4 , a['11'] = eleven

String.prototype.charAt works consistently in all browsers. The property access operator results are more interesting.

In Gecko and Opera, property access ([], §11.2.1) on a String checks the object for a property by the name given. If no property is found, if the string value of the property name is integral, charAt (or its equivalent) is called, passing the property, e.g aString[1.0] is evaluated as aString['1'] (§15.5.4.4).

Webkit has the opposite result. Webkit seems to have broken [[Put]] for String.

Observations on Property Access

Mozilla and Opera
  • a['0'] returns the character at index 0, "0". (goes through charAt)
  • a['1'] is resolved to a property, the value "one" is returned.
  • a['2'] does not find a property on the object itself and does not call [[Get]]. charAt is called.
  • a['11'] is resolved to a property, the value "one" is returned.

Gecko's [[Put]] results in a property being added to the object. The property will not be returned in charAt, but will be returned with [ ] (§11.2.1).

Webkit

Webkit seems to have a special algorithm for [[Put]] on String. A property P seems to be added to a String only when ToNumber(P) is greater than the String's length.

The approach Webkit takes is a bad one. I cannot see any reason why the Webkit team did this. A specialized [[Put]] might be the result of the design under the hood and was a preventative measure to keep the string from being modified (guess).

Unlike an Array, a String's length property is ReadOnly.

So we've covered enumerating over a primitive (useless), iterating over an String and an Array. What about iterating over an object?

Iterate over an Object

Its not usually practical, but it is possible

var nlist = {
  n : 1,
  nn: 2,
  nnnn: 3,
  nnnnnnnn: 3
};

if(! "console" in window) {
    document.title = "log: ";
    window.console = { log : function(s){ document.title += s; };
}
var result = "";
for( var p = "n"; nlist.hasOwnProperty(p); p+=p ) {
    console.log(nlist[p]);
}

Iterating over a BitSet as a String could be useful.

It would be possible (possibly even useful) to iterate over (or use Array or String extras with) a BitSet object. Fortunately, it's not that hard to write a BitSet class.

Enumeration is a Problem

Nearly every JavaScript library attempts to address the language limitations and browser bugs. Enumeration needlessly complicates Object. The libraries painfully show this to be true.

ECMAScript could instead provide Enum, SortedSet<T>, Map<T>, ListIterator<T>. These collections would provide safer, more powerful alternatives to the current problem.

I've provided more analysis on the problem of enumeration in my article, Enumeration and Object Oriented JavaScript

Technorati Tags:

Posted by default at 6:12 PM in JavaScript

Browser Stats Thursday, 18 October 2007

Browser Stats

Firefox is #1. Trumps MSIE.

 BrowsersGrabberHitsPercent
FFFirefoxNo7985734.6 %
MS Internet ExplorerNo7497732.5 %
Unknown?6332827.4 %
SafariNo40061.7 %
OperaNo36071.5 %
MozillaNo31081.3 %
NetNewsWireNo5170.2 %
KonquerorNo4220.1 %
CaminoNo2520.1 %
NetscapeNo910 %
 Others 1230 %

Opera's settings allow surfers to easily spoof the User-Agent header. Opera is probably slightly underrepresented.

Technorati Tags:

Posted by default at 2:05 AM in Browsers

Type Checking in JavaScript Wednesday, 10 October 2007

So how do you tell if an object is a function?

What's wrong with typeof?

typeof o == "function";

Some browsers have unexpected behavior when using the typeof operator with Host objects.

For example, in Safari, typeof aNodeList == "function" bug 14547.

Mozilla returns "function" for typeof on RegExp objects (bug 14547), and object elements (bug 268945, bug 296858).

The instanceof Operator

The more flexible alternative to typeof is instanceof.

    return it instanceof Function;

Unfortunately, instanceof will not return the desired result across frames. Each window has a different global object, so Function in frame 1 is not Function in frame 2.

When working across frames, the constructor property and isPrototypeOf will have the same problems as instanceof.

For example, an iframe with a function f, and the following code in the parentWindow:

var i = document.getElementsByTagName("iframe")[0];
var iframeWindow = i.contentWindow;
iframeWindow.f instanceof Function; // false
typeof iframeWindow.f == "function"; // true;

It should be easy to know what an object's type is. Why does JavaScript make this so hard?

What Do the Libraries Do?

Dojo had the idea of depending on a combination of the userAgent string, the return value of the object's toString method, and implicit type conversion that happens with the Equals Operator §11.9.3.

if(dojo.isBrowser && dojo.isSafari){
 // only slow this down w/ gratuitious casting in Safari since it's what's b0rken
  dojo.isFunction = function(/*anything*/ it){
    if((typeof(it) == "function") 
      && (it == "[object NodeList]")){ return false; }
  return (typeof it == "function" || it instanceof Function); // Boolean
  }
}else{
  dojo.isFunction = function(/*anything*/ it){
    return (typeof it == "function" || it instanceof Function); // Boolean
  }
}

This is to address Safari's unreliable results when using the typeof operator on one particular host object.

The function will fail in Safari 3 when passed an HTMLCollection (try typeof document.links). It will fail for any other object that Safari thinks implements call.

The problem is not that dojo forgot to check for HTMLCollection. Web developers should not need to know such obscurities.

jQuery : isFunction

The jQuery code stumbles on this problem:

// This may seem like some crazy code,
// but trust me when I say that this
// is the only cross-browser way to do this. --John

  isFunction: function( fn ) {
    return !!fn && typeof fn != "string" && !fn.nodeName && 
      fn.constructor != Array && /function/i.test( fn + "" );
  },

Lets break down this crazy code to see what it's doing.

( !!fn ).......................convert to boolean value. null, et c are out
( typeof fn != "string" )......not a string value (String objects pass here) 
( !fn.nodeName )...............Not an object with a truthy nodeName property
( fn.constructor != Array )....Not an Array object constructed in this frame
( /function/i.test( fn + "" )..toString contains "function"

Does it work?

// Case 1: testObj is not a function. 
var testObj = {
    toString: function() {
        return "This object contains only one function."
    }
};

// Case 2: testString is not a function.
var testString = new String("hey! This is a String, not a function!");

// Case 3: WidgetFactory is a function.
function WidgetFactory( id ){ }
WidgetFactory.toString = function() { return "WidgetFactory"; };

// Case 4 : An Array in the frameWindow is not a function.
// var someArray = [ function(){}, "Simplify functional testing." ];

// Here's the test:
var frameWindow = document.getElementsByTagName("iframe")[0].contentWindow;
isFunction( testObj ); // true.
isFunction( testString ); // true.
isFunction( WidgetFactory ); // false.
isFunction( frameWindow.someArray ); // true, contains "function" (twice).

Doesn't work that well, does it?

Running code doesn't lie. All four tests failed.

The jQuery function will give the correct result sometimes, but not in any of the cases above. This is because jQuery is relying on the result from the function's toString.

These cases are quite obvious and likely cases where the above method will fail. Nothing tricky about it.

The value returned by toString should not generally be relied upon, and especially not with a Host object.

JavaScript forces developers to rely on toString with Date or Number objects. This is an API design of the language that goes against convention. Such design should not influence a JavaScript library author's design decisions.

The disadvantage of specifying the format of the toString return value is that once you've specified it, you're stuck with it for life.—Joshua Bloch, quote from Effective Java

Method toString should not, generally speaking, be relied upon. Instead, toString should be used for diagnostic messages.

String Objects are Not string Values

Web developers sometimes get confused with string values and String objects. My tutorial on how property access operators work, clearly explains this fundamental concept.

Here's another example from Dojo.

dojo.isAlien = function(/*anything*/ it){
  // summary: 
  // Returns true if it is a built-in function or some other kind of
  // oddball that *should* report as a function but doesn't
  if(!it){ return false; }
  return !dojo.isFunction(it) 
    && /\{\s*\[native code\]\s*\}/.test(String(it)); // Boolean
}

Besides conjuring up images of little green men, function isAlien returns true for anything that isFunction returned false for and also contains "[native code]" in the object's string value. The intent seems to be to check if a function might be a function even when typeof failed.

I have a very hard time believing that any unmodified built-in function does not return "function" for typeof. The comment needs more detail. It would be interesting to see the dojo comment proven true. At least they put an explanatory comment in.

The comment implies that a built-in function might not return "function" but does not provide a case where this is true. The code is based on that assumption and assumes that such an object's String value will contain "[native code]".

This approach suffers from the same problems that jQuery's isFunction suffered from. If any object's string value contains "[native code]", isAlien will return true. Granted, "[native code]" is less common than "function", but buying vowels isn't the answer.

Function isAlien provides a way for non-callable objects to slip through as functions. It's inclusive, like the jQuery function, and just as dangerous. It does this by relying on [native code] making deductions about the Object based on its toString's return value.

Return values from toString should not, in general, be relied on

If it walks like a duck and quacks like a duck, it must be a duck.

How to Determine if an Object is Really a Function

Functions are unique in a few ways. The return value for toString is not one of them.

It seems safer to introspect the object based on a unique characteristic.

Update - Oct 13

A solution is needed. Here is one more offering, introspecting the constructor property, accompanied with a testcase.

function isFunction(fn) {
  if(typeof fn != "function") return false;
// Now check the constructor property. 
  if(typeof fn.constructor != "function") return false;
// If constructor is Function, then constructor.prototype will have call.
  return fn.constructor.prototype.hasOwnProperty( "call" );
}

That's some ugly looking code, huh? Who has a better one?

Try to provide case where the testcase will fail.

We can make more functions for isArray, et c. What a pain in the ass. This should be easy. JavaScript makes it hard.

Type Checking - the Status Quo

JavaScript needs a way to perform equivalence check. This might be helpful for cross frame issues.

JavaScript provides instanceof and typeof, but neither are sufficient

instanceof fails across frames. It doesn't work with primitives, e.g. "foo" instanceof String is false.

The typeof operator has a limited number of return types. typeof doesn't support all of the built-ins properly (e.g. Array, Error, Date, and null are all "object") and doesn't support any user-defined constructors. Typeof is allowed to return an implementation-dependent value for host objects. Safari conforms, but in an annoying way that causes problems.

Type Result
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Object (native and doesn't implement [[Call]]) "object"
Object (native and implements [[Call]]) "function"
Object (host) Implementation-dependent

Internet explorer includes an additional "unknown".

Typechecking is an area that JavaScript, as a language, needs to improve.

Update...

Brendan just posted on the ES4 List:

The is operator tests universal or Platonic type, which involves shared, immutable type descriptors that do not vary across windows or frames. So

(it is Callable)

That looks very useful. Immutable objects are powerful building blocks for other object types. Looks like there might be the possibility of other callable types (or subclass of Function) in ES4.

Technorati Tags:

Posted by default at 4:57 PM in JavaScript

How Property Access Works Friday, 5 October 2007

It's important for web developers to understand how the property access operators work. Here's a javascript basic lesson that every web developer should know:

Using an XML Reader?

The live JavaScript code won't run in your reader, but can be run on the site

(function(){
var a = "A String?";

a.isString = true;

return a.isString;
})();

What should be returned? Will it be the value true?

undefined

What happened to the isString property?

To answer that question, you need to know how the property access operator works.

The property access operator converts the value of its left hand operand (if it is a primitive) to an object and gets the property off that object.

Effectively, the above example is equivalent to:

    var a = "A String?";
    Object(a).isString = true;
    return Object(a).isString; // undefined

An object is created, assigned the property isString with the value true, and then becomes inaccessible by program code.

How about assigning properties to String objects?

(function(){
var a = "Axel";
a = new String(a);

a.foo = 123;

return a.foo;
})();
123

Variable a is now a String object. Objects can have properties.

The same conversion does not, unfortunately, take place with the instanceof operator.

(function(){
    var a = "Al Gore";
    return a instanceof String;
})();
false

It is still possible to check the constructor property of the object that is created by the property access operator.

(function(){
    var a = "avatar";
    return a.constructor === String;
})();
true

The typeof operator returns "string" for string values and "object" for String objects.

(function(){
    var a = "apple";
    var b = new String("bork");
    return typeof a + "; " + typeof b;
})();
string; object

Primitive values are not objects. Don't let the property access operators fool you.

Core JavaScript 1.5 Reference:Global Objects:String

Technorati Tags:

Posted by default at 3:09 AM in JavaScript

Opera Clobbers Object.prototype with FunctionExpression? Wednesday, 3 October 2007

(function(){

propertyIsEnumerable = function(){ alert(123); };
( {} ).propertyIsEnumerable( "name" );
})();

In opera 9.2, both Mac and Windows, I get the alert.

This happens when adding a FunctionExpression property to the global object. The problem only happens when the property name corresponds to a native method.

Hopefully Futhark, Opera 9.5's new script engine that will replace linear_b, will address this problem.

Technorati Tags:

Posted by default at 8:01 PM in JavaScript

 

*AnimTree
*Tabs
*GlideMenus
*DragLib