RSS
 

Posts Tagged ‘javascript’

JavaScript masked inputs, do we need them?

02 Nov

I recently learned that jQuery UI was to get its own input mask plugin.

jQuery UI input masks
jQuery UI’s upcoming masks

If you’re not sure what an input mask is, it’s where a defined set of rules govern what text can be input into a field. As you type, the text in the input mask may be altered to a specific format, like a telephone number or date, or text may be blocked completely, when entering a number where the rules specify a letter should be, for instance.

Thankfully, we don’t see much input masking on the web. Most web authors stick to validation and proper labelling to ensure their visitors enter values correctly. I suspect this is due to the difficulty involved in making masked inputs work well in a cross browser manner. The jQuery UI team must at least see a demand for input masks to be considering implementing them, but I’ve yet to see one in action on a large production website.

I chose the word ‘thankfully’ because I’ve never been a big fan of masked inputs. Aside from the cross-browser issues, you also have to deal with accessibility issues. Here’s a short list of some of the problems a developer will run into when creating a masked input:

  • Ranges are awkward to work with, and you’ll struggle to do this right without using them.
  • Messing around with the value of an input whilst the user is typing can (and will, in most cases) break the browser’s undo function.
  • If you’re masking the input with text, it’s hard to get it to look good, especially with a variable-width font.
  • Complex masks can be confusing for the user. For example, the British post code has several variations, messing around with the formatting whilst the user is typing could confuse them because what they typed doesn’t look like their post code.

Some examples I’ve seen try and mitigate these problems by using a different approach, like using multiple inputs and moving the focus from one to the next as the user is typing. These come with their own host of accessibility problems, like preventing copy/paste on the whole text.

My short journey to the dark side

I decided that, rather than just complain about current implementations of masked inputs, I’d see if I could come up with anything better. So, while the SO was watching the telly, I set about putting an idea into practice. And it was tough! You can see the fruits of my labour here:



[iframe http://jsfiddle.net/AndyE/hnJhY/show/light 100% 60px]

I know, it’s very basic. It looks and works best in IE, though that’s purely accidental. I ran into several annoying cross browser issues along the way and didn’t get much time to polish it up. As a proof-of-concept, though, I think it gets the point across. It works by overlaying a <span> element above the text field, and that element contains the mask text with each letter of text being wrapped in its own span element. This allowed me to vary the colour of the static / characters. pointer-events: none is used so that WebKit and Gecko allow you to click through the span to focus the input text. In IE, I cleverly — if I may say so myself — set contentEditable = 'true' on the <span> so that, when it is focussed, the caret position is obtained and seamlessly passed to the <input> at the correct position.

Despite its shortcomings, I’m rather pleased with the result. It allows undo to continue working in WebKit/Trident, and this functionality could be extended to Gecko, at least. However, there are several serious issues with the approach, the biggest being that text positioning in replaced elements is not the same as in non-replaced elements. In IE, the mask text and the input text are perfectly aligned. In most other browsers, the input text is a pixel or so off. Someone with more time on their hands may be able to come up with a way to feature detect this and adjust the input padding values accordingly.

If you’re thinking about using a masked input, I’d advise you to really weigh up whether it will really give you more than good validation and proper labelling your input fields. If you need guidelines for what your users should enter, the HTML5 placeholder attribute will serve you well enough, and there are plenty of shims to get the attribute working in unsupported browsers. If you decide to use a masked input anyway, then at least use a nice looking fixed width font.

 

ES6 – a quick look at Weak Maps

31 Oct

One of my favourite proposals in ECMAScript Harmony is Weak Maps. Simply put, ‘weak maps are unordered tables mapping objects to values’ with references to those objects and values being weak. A weak reference means that the item won’t be protected from garbage collection if no other references are held. For example:

// Using the proposed API (currently implemented in Firefox)
var wm = WeakMap();

(function () {
    var obj = {};
    wm.set(obj, 1);
})();

console.log(wm); //-> "[object WeakMap]"

By now a very obvious use case might have occurred to you, but I’ll get to that in a minute. When the above function finishes execution, obj is no longer reachable, so it can be garbage collected even though our WeakMap object is still reachable. This addition to the language is a useful tool for an age-old concern in JavaScript and the DOM; memory leaks. That aforementioned use case is becoming more obvious now, eh?

Where do Weak Maps fit in with current code?

OK, if you never had to implement this kind of thing before, you might not have thought of jQuery’s data() method. jQuery.data() allows us to attach an arbitrary value to a DOM object. Whilst you can do this with expando properties, ordinarily you run the risk of circular references and memory leaks. jQuery tackles this by creating a unique ID for itself (only once) and each element it is attaching data to. It tacks the element’s unique ID, which is really just an incremented counter, onto an expando property whose name is the unique ID. So it looks something like this:

     element[jQuery.expando] = elementId;

Any data you attach to the element is actually stored in an array at index elementId. jQuery is able to avoid memory leaks because there are no circular references, and it can delete all stored data when the page is unloaded (or the element is removed using jQuery’s own methods). However, it does have some limitations. For example — according to the source code — object/embed/applet cannot accept expando properties and, therefore, jQuery.data() cannot work with them.

Weak maps come in here because they can do the job much better. They cut out the need for the expando property entirely, along with the requirement of handling JS objects differently to DOM objects. They also expand on jQuery’s ability to allow garbage collection when DOM elements are removed by its own methods, by automatically allowing garbage collection when DOM elements no longer reachable after they’ve been removed by any method. Had WeakMaps been implemented in ECMAScript 3 (if only), jQuery’s workload with data() could have been significantly reduced. Perhaps jQuery wouldn’t have bothered implementing data if we could create weak maps, though.

Another use case to consider is secretly extending objects without the danger of naming collisions. Using someGloballyAvailableObject.someProperty = "some arbitrary value"; as an example, what if someGloballyAvailableObject already has someProperty? We’ve just written a potentially script-breaking piece of code. However, with a weak map we can eliminate this danger entirely:

(function () { 
    var wm = new WeakMap();
    wm.set(someGloballyAvailableObject, {someProperty:"some arbitrary value"});
})();

There’s also a hidden bonus here. I used the word secretly before because no other piece of our script can access this mapped value if we never expose the wm variable.

You might recall (if you read it) my article on creating lookup lists. If you do, you might be thinking that weak maps are a good use for creating lists, but in reality this isn’t the case because weak maps — in the current proposal, at least — will only map a value to an object.

Weak map compatibility shims

A true weak map is technically impossible in ECMAScript 3. We can create a map of objects to values by simply using two arrays:

function WeakMap() {
    this.keys = [/* key01, key02, key03 */];
    //                ↓      ↓      ↓
    this.vals = [/* val01, val02, val03 */];
}

Unfortunately, there’s 2 very important caveats here. Firstly, these references are not (and cannot be) weak, so they will not be garbage collected as long as the mapping exists. Secondly, for our get(key) method to work, we need to search our keys array. This significantly impacts performance, particularly on large maps.

As for ES5, I was pointed in the direction of this implementation recently. It’s very clever; It eliminates the need for a keys array and uses a custom valueOf function to keep track of the mapped value. I profiled the method in Chrome and it does indeed appear to prevent leaks. However, it does still have its limitations like not working on frozen objects or objects whose valueOf function is not configurable.

 
 

Update: HTML5 oninput event plugin for jQuery

24 Oct

About a year ago, I wrote my first special events plugin for jQuery — a cross-browser implementation of the HTML5 oninput event. A while afterwards, though, it became apparent that there were a multitude of issues with browsers that already implemented the event.

  • Opera 10.x doesn’t fire the event for cut, paste, undo & drop operations on <input> elements. They fixed this in Opera 11, though.
  • Safari 4.x and lower don’t support the event for <textarea> elements. The textInput event is supported on them for 3.1+, however.
  • Internet Explorer 9 forgets to fire the event when the backspace key is used to delete text. Other than that majorly horrendous oversight, they almost got it right.
  • Firefox has issues that make feature detection very difficult. In fact, detection is only possible via another Firefox proprietary feature, which I suppose is rather lucky.

With these problems stacking up, my code was stacking up too — how could I detect and work around each of these problems? On top of that, I couldn’t easily make the event work with delegate() or live(). Soon, it became apparent that I was going to have to completely rethink the approach I made in the original plugin.

Feature detection

The original code used feature detection to discover whether browsers supported the event. "oninput" in document.documentElement is enough for this in most browsers, but the aforementioned Firefox issues required an excellent little snippet from Daniel Friesen, which I slightly modified. It was a shame though, as this extra work bulked up the plugin a little more than I would have liked. Safari didn’t like our feature detection either, because even though our snippet told us the browser implemented support for the event, it doesn’t tell us that <textarea> won’t fire it.

I eventually decided that feature detection wasn’t going to work — graceful degradation, on the other hand, might. This involves binding to all known events that fire when an input element is modified, and then setting a flag to tell the event handlers whether or not they need to execute. For events that fire pre-DOM modification, we set a timer with timeout value of 0ms (this will be adjusted to browser’s minimum allowable value). The events that fire post-DOM modification cancel this timer. So, let’s say our browser is Internet Explorer 6, we have some text in our clipboard and we just pressed Ctrl+V in a <textarea> that has our events bound. For the Ctrl key, the onkeydown event sets the timer but when it executes the value hasn’t changed, so the handler does nothing. For the V key, the plugin will handle the events in the following sequence:

Pre-DOM modification
    onkeydown -> timer is set
    onpaste   -> previous timer is cleared, new timer is set

Post-DOM modification - DOM is modified, textarea's value is updated but element hasn't repainted yet
    onpropertychange -> timer is cleared, the handler is triggered

Opera 9, however, the best possible compatibility we can provide is via the onkeydown event, so that sets the timer which executes almost instantly, triggering our special event handler. In Safari 4, the textInput event fires post-DOM modification, so that clears the timer and triggers the handler, giving seamless `oninput` compatibility to the developer.

Late binding

No, not that kind of late binding. In order to mitigate the lack of event bubbling for our Internet Explorer polyfill, I opted to have the plugin not bind the input events to the element right away if it wasn’t an <input> or <textarea>. Instead, it binds several bubbling events — onfocusin, onmouseover and ondragstart — to the element. When one of those events fires, event.target.tagName is checked and if it is “INPUT” or “TEXTAREA” then we bind our input events to it. This allows delegate() to work really well in IE and I suspect, but haven’t verified, this is how jQuery is able to offer event delegation for non-propagating events such as onresize.

Problems with the new approach

Undoubtedly, the biggest problem is naming the special event. I can’t think of a way to have a special event named `input` that binds to the native `input` event and completely overrides it because, when we come to trigger the event later, we can’t. In my oh-so finite wisdom, I decided that the best solution I could provide was a differently named special event. So, when using the plugin now, you need to bind to "txtinput". $(selector).input(function () {}); will still work, though.

Another problem was teardown. Suppose we have a DOM structure like body -> div -> input — now, what happens if we bind to the <body> and <div> elements? Actually, this works completely as expected but, what if we unbind from one of those elements? Our teardown function needs to make sure that no other handlers are relying on our bound events before we remove them. The solution makes use of data and, every time an event is bound, increases an internal count to keep track. This counter is decreased for each of those elements in the teardown.

You can see the updated plugin at the original project page.

 

Building and checking lists in JavaScript

16 Sep

Recently, I was building a white-list of strings that I needed to check an input string against. It reminded me to write about something I wanted to write about for a while now.

First, lets take a look at the different ways we can define a list of strings:

Using arrays as lists

Normally, this is the first thing you would think of when building a list of strings. It might look something like this:

var list = [ "item1", "item2", "item3", "item4", "item5" ];

if (list.indexOf("item3") > -1) {
}

Fairly straightforward, right? Array lists are useful if you’re iterating over them and using each value for something, but when it comes to white-listing or black-listing something, it gets a little more complicated.

Advantages

  • Easy to define and maintain.
  • Can iterate over and perform a task on each item
  • Simple method to add values — list.push("item6");.
  • ES5 makes it easy to search for value existence — list.indexOf("item3"); //-> 2.

Disadvantages

  • Can be awkward to avoid adding duplicate values at runtime.
  • Non-ES5 implementations require a shim or a for loop to search for value existence.
  • The larger the array, the more performance significantly degrades.

Using a regular expression

I see this method used quite a lot, it’s a simple regular expression with all the words joined by the pipe symbol | for alternation:

var list = /^(?:item1|item2|item3|item4|item5)$/;

if (list.test("item1")) {
}

This is a decent method for small lists that are intended to be constant and never modified at runtime.

Advantages

  • More widely supported than Array.prototype.indexOf.
  • Modern regex implementations appear to be able to cope well with even large lists.
  • Case-insensitive searches are easier, just add the /i modifier.

Disadvantages

  • Regular expressions require further understanding than basic JavaScript, it’s easy for naive users to make a mistake (such as missing ^ and $).
  • Difficult to modify at runtime, creating a new regexp may require string escaping.
  • Less readable and maintainable when the list is large.
  • No best of both worlds, you can’t use the regex to iterate over and perform a function on each list item.

Using an object

This is my preferred method of defining a list, offering the most flexibility and the fastest lookup methods.

var t = true, list = {item1:t, item2:t, item3:t, item4:t, item5:t};

if ("item3" in list) {
}
if (list.hasOwnProperty("item3")) {
}
if (list.item3) { // "truthy" values only
}

I defined the list with true as the value for each item, but you could assign null or undefined if you want to use the in operator or hasOwnProperty.

Advantages

  • Easy to iterate over and do something with each item/property.
  • Custom values can be used for even more flexibility.
  • Very easy to add/remove items at run time, with no chance of duplicate items.
  • Code is easily readable, writeable and maintainable. Omitting string delimiters for property names makes for the same amount of typing as an Array (if assigning a single character variable/value).

Disadvantages

  • Lots of seemingly redundant code when not actually using values. It’s more typing than should be needed!
  • Potential naming collisions with Object.prototype properties if using the in operator or boolean checks.

Some slight changes to Object literal grammar would make this approach even more perfect. If we could just write { item1, item2, item3, item4, item5 } to have an object initialized with those properties (and undefined values) or { item1: item2: item3: item4: item5: true } to have 5 properties defined with the same value, our syndrome-less carpal tunnels would be thanking us in years to come. I know something like that has been proposed for ECMAScript Harmony, so let’s hope that works out.

Performance

Mostly for myself, I set up a JSperf test to compare the performance between the different lookup methods. Here’s what I learned:

  • Array.prototype.indexOf is, as expected, the slowest. If you’re using a shim for older browsers, expect this to be sickeningly slow.
  • The straight boolean tests are the fastest by a long way, but remember they’re not the strictest test.
  • Opera is able to optimize the regular expression so that it is faster than the in operator and hasOwnProperty.
  • Chrome’s hasOwnProperty function appears to be well optimized, since in all the other browsers, in was the faster of the two.
  • Firefox is pretty slow compared to the other browsers when it comes to boolean checks.

So, there you have it. Don’t use arrays for lists and lookups because Array.prototype.indexOf isn’t as fast as other methods you can use. In fact, if you’re bothered about speed at all, use one of the object methods. Some methods aren’t always appropriate, for instance if you want to assign custom values to your lists they might not always be truthy. You’re always safe with hasOwnProperty, but if you control where the values are coming from (e.g. list[element.tagName]) go with the truthy values for best performance.

 

Global `eval` with a result in Internet Explorer

24 Aug

A while ago, I was charged with the task of writing a JavaScript console window for certain environments that didn’t have access to the Internet Explorer developer tools. Such environments include Windows Desktop Gadgets and applications that make use of the .NET or COM WebBrowser controls, that run on the Internet Explorer engine (MSHTML).

The first non-trivial task was figuring out how to make Internet Explorer execute code in the global scope and then get the result. Indirect eval doesn’t help me because I need to support document modes where IE’s eval implementation is broken. It was clear that I would have to use Internet Explorer’s proprietary window.execScript function, but I wasn’t sure how I would get the result to display in the console window. Looking around the web didn’t help. As usual, kangax had an article and even a solution for providing a somewhat cross-browser global eval. However for Internet Explorer, the solution didn’t return a result so I was back to square one and it was time to get my hands dirty.

To begin with, I tried what was a rather naive attempt at a quick solution:

function executeConsoleInput(code) {
    var result;
    window.execScript("console.___lastInputResult___ = " + code);
    result = console.___lastInputResult___;
    delete console.___lastInputResult___;
    return result;
}

Before I tested it I already knew what the big problems would be:

  • All input becomes an expression, no statements or declarations allowed. This means any of var, try, catch... finally, if...else, do... while, etc will throw a syntax error. Also, function foo() { } is treated as a function expression and not a declaration.

  • Aside from that major spanner in the works, the first semi-colon in our code finalizes the assignment to executeConsoleInput.result, so anything after that is evaluated but not part of the result.


So this really wasn’t a viable option unless I wanted to limit the console user to single expressions. However, I felt I was on the right track and it didn’t take me long to realize I could use both execScript and eval together:

function executeConsoleInput(code) {
    var result;
    window.execScript("console.___lastInputResult___ = eval('" + code + "')");
    result = console.___lastInputResult___;
    delete console.___lastInputResult___;
    return result;
}

Perfect! …well, almost. Although this worked perfectly on a few short statements it quickly became clear I’d still been a little naive. Certain characters in the input would, quite obviously, break eval. Line-breaks were the most troublesome. Eventually, I came up with this and added it to the solution:

code = code.replace(/["'\\]/g, "\\$&").replace(/\r\n/g, "\\n");

After re-reading kangax’s article, I decided to post a comment with my solution for a global eval that returns a result, but since I’ve been working a little more on the console recently I decided to upgrade the comment to a fully fledged article of my own.

What’s even more intriguing about this is that it could be a viable polyfill for global eval in IE 8 and lower and it definitely is viable for IE 9/10 running in compatibility modes. Why can I make this assertion? I set up a jsperf test that compares the two functions and in IE 9 compatibility modes the withEval function is faster! I could only test IE 7 in a virtual machine, where withEval was between 15-25% slower. Whether or not this is an acceptable loss compared to the result and compatibility mode gains is up for debate. Personally, I prefer the same behaviour across browsers and, generally, thousands of execScript(eval()) operations per second is enough for anyone. I would be very interested in an explanation for the IE 9 eval optimization, either way.

Update: It turns out I didn’t need to escape the input — as John-David Dalton points out in the comments, you can assign the expression to a global variable and give that to eval instead of a string value. D’oh, I’m not sure why that didn’t occur to me! This also works around a problem with unicode escape sequences in the string that I hadn’t gotten around to addressing yet.

If we add this to kangax’s cross-browser globalEval code, we can make it behave to consistently return a value across Internet Explorer versions less than 9 and other browsers:

var globalEval = (function() {
  var isIndirectEvalGlobal = (function(original, Object) {
    try {
      // Does `Object` resolve to a local variable, or to a global, built-in `Object`,
      // reference to which we passed as a first argument?
      return window.eval('Object') === original;
    }
    catch(err) {
      // if indirect eval errors out (as allowed per ES3), then just bail out with `false`
      return false;
    }
  })(Object, 123);

  if (isIndirectEvalGlobal) {
    // if indirect eval executes code globally, use it
    return function(expression) {
      return window.eval(expression);
    };
  }
  else if ("execScript" in window) {
    // if `window.execScript` exists, use it along with `eval` to obtain a result
    return function(expression) {
      globalEval.___inputExpression___ = expression;
      window.execScript("globalEval.___lastInputResult___ = eval(globalEval.___inputExpression___)");
      return globalEval.___lastInputResult___;    
    };
  }

  // otherwise, globalEval is `undefined` since nothing is returned
})();

// and a simple test, inspired by kangax's detection method:
alert((function (Object) {
    return globalEval("Object === 123"); // result is false
})(123));

There is a slight difference in implementation requirements here, though – this requires globalEval to be defined as a global variable (or a property of the window object).

 

Internet Explorer 9′s problematic console object

04 Apr

I sent out a quick tweet the other day after answering this question on Stack Overflow. Apparently, Internet Explorer 9 had caused the asker some confusion by not giving the console object permanent residency as part of the DOM – you have to open the developer tools for any tab you want the object to be made available to first. As an aside, the asker also wondered why when he tried to use console.log.apply, he got an error. After some quick investigations, it appeared obvious that the console object being injected into the window by the developer tools hadn’t received an update since Internet Explorer 8 and so the methods it contains aren’t instances of Function like all the most other DOM methods. The first thing that popped into my head was to try Function.prototype.bind.apply and see if it worked, although I wasn’t optimistic.

var log = Function.prototype.bind.call(console.log, console);
log.apply(console, ["this", "is", "a", "test"]);

To my surprise, it worked great. I posted it as my answer and sent out the tweet to let the world know the crisis was over. Then, today a similar question was posted, again asking why console.log.apply doesn’t work. I had a little more time, so decided to rework the solution to apply to all the console methods that IE supports. Whilst writing the function, I discovered that IE still reports console methods as objects when using the typeof operator:

typeof console.log;
//-> "object"

This actually violates the ECMAScript 5th edition’s rules for the typeof operator, which specifies “function” should be returned for both native and host objects that implement [[Call]]. I headed over to Microsoft Connect to see if they were still accepting bug reports, but decided to search first and found that kangax had beaten me to it (he reported it pre beta and got a generic “we’re investigating” response). Instead, I 1-upped his bug report and added my workaround, which I’ll also post here in a slightly more concise form:

if (Function.prototype.bind && console && typeof console.log == "object") {
  [ 
    "log","info","warn","error","assert","dir","clear","profile","profileEnd" 
  ].forEach(function (method) {
    console[method] = this.call(console[method], console);
  }, Function.prototype.bind);
}

Now if we take another look at these methods, we should get (more or less) the same result in each browser:

typeof console.log;
//-> "function"
    
Object.prototype.toString.call(console.log);
//-> "[object Function]"

console.log.apply(console, ["this", "is", "a", "test"]);
//-> "thisisatest"

The usefulness of Function.prototype.bind here got me thinking — if we can make native copies of host objects, perhaps we can make native copies of ActiveX objects and methods too? nope.

Update

You can achieve the same result with IE 8′s console too, using a compatibility implementation of bind, but you must bind to Function.prototype.call, rather than console.log:

var log = Function.prototype.call.bind(console.log, console);
typeof log;
//-> "function"
 

Helpful error messages aren’t just a JavaScript’s throw away

17 Jan

This is one that’s been bugging me for years, but today something happened that finally made me decide to rant about it. ECMAScript’s throw statement has it’s uses in try... catch... finally blocks but it’s also used often outside, on its own, to halt script execution. The most notable example I can think of is on line 480 of Crockford’s json2.js:

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');

Now, most browsers don’t have a problem with this and not just because they support native JSON.parse. They will happily throw this exception and output the error message appropriately. Except for our old buddy, Internet Explorer. Internet Explorer does something amazingly stupid. It successfully throws an error, but the message accompanying it is “Exception thrown and not caught”. Wow, that’s so ridiculously unhelpful I could cry (but I promised myself I wouldn’t). I’m forced to hunt down the source of the error in my JavaScript code, something that was no easy task in IE 7 due to inaccurate reporting of line numbers and file names. You could argue that “JSON.parse” isn’t a very valuable error message either, but that’s beside the point.

The strange thing is that Internet Explorer will behave properly if the throw statement is throwing an Error object. The error message will be the one supplied to the Error constructor. So why doesn't this happen for Error's brethren; EvalError, TypeError, SyntaxError, RangeError, URIError and ReferenceError? Other browsers will display the error message even if you just throw a string.

When Internet Explorer 9 hit beta, I decided to report this as an "interoperability issue". The Internet Explorer team are always banging on about how they're trying to make Internet Explorer work like other browsers by following standards and non-standard, but popular, implementations of various functionality. I thought to myself, "How hard can it be to fix this?". Surely, there will be one code path for if you throw an Error object and another code path for everything else, so the first one could be quite easily expanded to accommodate everything else instead? As it happens, today my issue was changed to "Resolved", then "Closed" as "By design".

So there you have it, straight from the horse's mouth; Internet Explorer hinders your development in as many ways as possible "by design".

For some reason, the link to the bug report is telling people they don't have the required permissions to view. It is set to public, however, so I'm not sure what's going on there (perhaps they want to hide their shame!).

 

Scoping with HTML and inline event attributes

12 Dec

Just the other day on Stack Overflow, I saw a seemingly uninteresting question with the following code:




The user’s problem was that the call to click() wasn’t executing correctly, but if he changed the name of the function it did. I’ll admit that I wasn’t really in the mood for this type of question, so I brushed it off and moved on. The subject arose again in the JavaScript chat room where Pointy, a frequent flyer in the JS tags, was attempting to get to the root of the problem. My initial thought was that the user had made a mistake, but at the same time I thought it couldn’t hurt to check it out. So I fired up jsFiddle and pasted in the code. It failed silently, so I checked a couple of other browsers with the same result.

It was obvious what was going on at this point – the <button> element has a method on it named click() and the browsers were forcing the lexical scope of the function to include properties of the current object. I made a quick and dirty change to verify this:

    <button onclick="console.log(tagName)">try it</button><br />

Sure enough, the console output was BUTTON. I’m sure many experienced developers will be unsurprised at this, knowing this is the expected behaviour all along but, honestly, I’ve never encountered this before (probably due to how I like to attach my events via JS in the first place) and I don’t remember reading it in any specification. Pointy was able to find this portion of the HTML5 spec, however:

Lexical Environment Scope
  1. Let Scope be the result of NewObjectEnvironment(the element’s Document, the global environment).
  2. If the element has a form owner, let Scope be the result of NewObjectEnvironment(the element’s form ownerScope).
  3. Let Scope be the result of NewObjectEnvironment(the element’s object, Scope).


This indicates that the JavaScript equivalent for an element contained within a form would be something like this:

element.onclick = new Function(
    "event", 
    "with (document) { with (this.form) { with (this) { /* function code */ }}}"
);

I verified this by quickly testing getElementById without referencing the document object first. It worked just fine.

Now, after seeing all this, I’m left with several questions in my head all revolving around one important question — why? — why does anyone feel like it is necessary to do all this? I can’t think of any positive reasons for it to be this way, only reasons for it not to be this way:

  • The document object and the host element for the event are both very easily accessible from within the function’s scope.
  • NewObjectEnvironment() has taken a lot of flak over the years, the only thing that uses it is the with statement and Crockford likes to remind us that with statements are harmful, like in this example, we might not know if click() belongs to the element, the form containing it, the document or the window. It’s also very inefficient.


The only reason I can think of that this is in the HTML 5 specification is for backwards compatibility. This might be the way the coding dinosaurs wrote their HTML and JavaScript 15 years ago and this feature has survived since Netscape Navigator 2.0. In my opinion, this isn’t an excuse and it should have been deprecated and removed long ago. I don’t feel so badly about it for myself, though, I’ve got by for years without even encountering this behaviour and I’m sure it will never affect me in the future, unless I’m working with someone else’s code. Just one more reason to attach your event handlers using script, I suppose.

 
 

Utilizing the awesome power of jQuery to access properties of an element

28 Oct

I’m about 35% of the way through a 100gb backup at the moment, so I thought I’d throw in a quick blog post. I’m sure this has been brought up a million (or so) times but, in my opinion, you can’t reiterate this point enough. I occasionally get my hands dirty in the jQuery tags on Stack Overflow, and all too often I see code like this:

$(".myClass").click(function () {
    if ($(this).attr("id") == "myId") {
        runMyFunction($(this).val());
    }
    else if ($(this).attr("id") == "myOtherId")) {
        runMyOtherFunction($(this).val());
    }
});

The worst part is that this type of code is commonly given as an answer to a question. While I often feel tempted to vote down these solutions, they’re not incorrect per se, so I generally tend to post my own answer. “Why is this bad?”, you might be thinking. Well, lets simplify and break down the following code:

    if ($(this).attr("id") == "myId") {

We can separate $(this).attr("id") into 2 expressions; $(this) and attr. When you pass an argument to jQuery, it runs through the following process:

  1. Create a new instance of jQuery.fn.init(), passing this to the constructor which,
  2. initializes 4 variables,
  3. checks that the 1st argument passed isn’t a “falsy” value like null, undefined, “”, etc,
  4. looks for a nodeType property on the 1st argument passed, which is true in our case and,
  5. sets the resulting jQuery object’s context, 0 and length properties before returning the newly created instance.

Now .attr("id"):

  1. Pass the context (the collection of elements – only 1 in our case), our attribute name (“id”), value (undefined in this case), another argument named pass (true) and the jQuery.attr function to jQuery.access which,
  2. stores the length of the collection passed in as an optimisation,
  3. checks to see if the second argument is an object (a collection of property and value pairs to set on each element in the collection),
  4. checks to see if a property is being set, in our case it isn’t so it instead,
  5. checks the stored length of the collection isn’t 0 and calls the passed in jQuery.attr, passing the collection’s first element and the attribute name we tried to access. This function then,
  6. checks that the element argument passed in isn’t a “falsy” value, its nodeType property isn’t 3 or 8 (text and comment nodes respectively),
  7. checks that the pass argument was passed in (it wasn’t in this case),
  8. sets a variable that identifies whether the element passed in is an XML element (it’s not) and another that checks whether a value has been passed to assign to the property,
  9. tries to normalize attribute names into property names by looking it up in a map keeping the original name if it isn’t found,
  10. checks for a nodeType of 1 (element node), which is true in our case so then it,
  11. performs a regular expression test on the string to see if it matches /^(?:href|src|style)$/ and stores the result in a variable,
  12. checks to see if the string is “selected” to work around a bug in Safari. It isn’t, so it moves on and,
  13. checks to see if the string is the name of a property on the element, which is true in our case so it,
  14. checks to see if the set variable is “truthy”, it’s not so it skips that block and,
  15. checks to see if the element is a form element, it’s not so it,
  16. checks to see if the string is “tabIndex” to apply another workaround if true. It’s not so, finally, it feels justified to,
  17. return element[attribute].

Wow. Talk about taking the scenic route, we went through those 17 steps for jQuery to finally get to what we could have done in the first place: this.id. Of course, I’m not trying to put a downer on jQuery (it’s great and does all things!). jQuery *has* to do all this stuff for you so that you don’t run into the usual unexpected issues caused by certain implementations of getAttribute/setAttribute. Knowing which attributes are safe to access as property names means that you don’t need jQuery to do this leg work for you.

The real point of this post is that, when answering questions or helping people out with source code, try and set a good example. For the majority of attribute names you don’t need to use jQuery’s attr() to get the value of an attribute. If you’re unsure, look it up and find out if there are any specific quirks you need to know about. It might even be more efficient to work around those quirks without jQuery’s help.

I’ll leave you with an example that shows the difference over many iterations:

[iframe http://jsfiddle.net/AndyE/YPUTp/embedded/result,js height=100px width=500px]

 

Cross-context isArray and Internet Explorer

26 Oct

Update, 10th July 2011 — It appears IE9 regressed this bug, so I updated the conditional compilation fix I wrote to work for all IE versions. I prefer to use feature detection where available, but I don’t see this being feasible. The bug is still present as of IE 10 PP2, but the code could easily be adjusted to check for IE less than 10 if the problem is fixed again before the final release.

I’ve been working on something lately that needs to check if a variable is an array. It’s a well known fact among JavaScript developers that, unfortunately, this is one of those situations where the typeof operator just doesn’t cut it. For a long time, many developers and libraries – jQuery, for one, used the following method to test if a variable is an array:

function isArray(v) {
    return v instanceof Array;
}

This checks to see if the passed in variable inherits from the native Array constructor function. The problem with this is that when we reference Array, we know that it’s a property of the current global object – in the case of a browser, it’s a member of window. Each browser window is a separate context with its own set of properties and methods, so the Array constructor isn’t the same as that of another construct. What this means for the above piece of code is, if the function is passed an array from a different context, that array isn’t an instance of the current context’s Array constructor.

Early last year, kangax (I mentioned him in the Object.keys post) came to the rescue with a robust solution that worked across frames:

function isArray(v) {
    return Object.prototype.toString.call(v) == "[object Array]";
}

This is knowledge of the specification for you. Array.prototype.toString loops through the array items and calls their toString method, returning the result as a combined string joined with a comma. Object.prototype.toString, on the other hand, will always return a string in the form of "[object [[Class]]]", where [[Class]] is the name of the internal class of the object. Because the internal class of an array is Array, no matter what context it comes from, we can be sure that this code will always work. Popular libraries like Prototype and jQuery are using this method right now. For those of you thinking, “all’s well that ends well”, I’m going to point you in the direction of the master of raining on parades; Internet Explorer.

Of course, this isn’t the case in Internet Explorer, which behaves rather strangely when passing variables between contexts. When running anything passed between contexts through Object.prototype.toString, the result becomes "[object Object]", including primitives. I’m going to go out on a limb here and speculate that this is due to JScript being heavily COM based – variables must become “host objects” when being passed (via COM) to another context.

In Internet Explorer 9, ECMA-262 5th edition comes to our rescue once again with the long overdue Array.isArray() function. Pass in a variable and it will spit out a boolean, true for arrays and false for anything else. Most other browsers also have this function at the time of writing. But we’re still left with a problem in IEs less than 9.

Potential Solution the first

I played with a lot of solutions, mostly involving duck-typing (which kangax goes into in more detail in his post), but nothing comes close to the robustness of kangax’s solution. I feel like I may have stooped a little too low in this solution, but I think that I may be justified in doing so:

(function () {
    var toString = Object.prototype.toString,
         strArray = Array.toString();

    Array.isArray = Array.isArray || function (obj) {
        return typeof obj == "object" && (toString.call(obj) == "[object Array]" || ("constructor" in obj && String(obj.constructor) == strArray));
    }
})();

This runs through several checks for performance reasons; if the object’s internal [[Class]] is Array, then the extra checks aren’t necessary. The reason I feel like I’m stooping low here is that it relies on Function.prototype.toString(), a method which is defined in the spec as follows:

An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.

Currently, all known implementations return a string in the format "function Array() { [native code] }", although the white-space within this returned string varies between those implementations, notably Internet Explorer’s has line breaks and indentation whereas others are inline. The real problem is that the string [native code] appears nowhere in the spec, so this could be changed at any given time to anything else at all. However, I can rest easy knowing that this code is safe and robust for the following reasons; a) we know that Array.toString() can’t be spoofed, because function Array () { [native code] } will throw a syntax error and, most importantly, b) we know that all current implementations of Function.prototype.toString() behave in this manner and if a future implementation changes it doesn’t matter because Array.isArray() will be present and used instead.

Potential Solution #2

Really, this is just the same solution, but with a bit of conditional compilation magic that means all other browsers can comfortably use Object.prototype.toString() while only IE will be targeted by our Function.prototype.toString(). With this, we can put to rest any fears that our code will have any unexpected repercussions in other browsers, with the added benefit that it should run faster because less checks are necessary:

(function () {
    var toString = Object.prototype.toString,
        strArray = Array.toString(),
        jscript  = /*@cc_on @_jscript_version @*/ +0;

    // jscript will be 0 for browsers other than IE
    if (!jscript) {
        Array.isArray = Array.isArray || function (obj) {
            return toString.call(obj) == "[object Array]";
        }
    }
    else {
        Array.isArray = function (obj) {
            return "constructor" in obj && String(obj.constructor) == strArray;
        }
    }
})();

Potential Solution #3

Let’s face it, people aren’t keen on using conditional compilation. But the only other solution I could think of would be to create a new context (window or iframe), create an array in it and pass it back to the current context, then test if Object.prototype.toString() gives us "[object Object]". If it does, then we can flag that we need to use our workaround. The major downside here is that creating a new context is a pain – it would be slow and, if you’re using a frame, you’d have to add it to the document before being able to get its context. The fastest solution I could think of is to use IE’s proprietary createPopup(), but I’m only speculating that this would be faster and the solution uses a form of… eval.

(function () {
    var popup,
        toString = Object.prototype.toString,
        strArray = Array.toString(),
        hasNoClass = false;

    if (typeof window.createPopup == "object" && Function.prototype.toString(createPopup).slice(0,8) == "function") {
        popup = window.createPopup();
        popup.document.parentWindow.execScript("window.test = [];");
        hasNoClass = toString(popup.document.parentWindow.test) == "[object Object]";
    }

    if (hasNoClass) {
        Array.isArray = function (obj) {
            return "constructor" in obj && String(obj.constructor) == strArray;
        }
    }
    else {
        Array.isArray = Array.isArray || function (obj) {
            return toString.call(obj) == "[object Array]";
        }
    }
})();

As usual, there’s a corresponding answer on Stack Overflow. Any thoughts? Please use the comments below.