August 27, 2007

Screen scraping with jQuery

A test case in my work requires a complete list of HTML elements and a list of self-closing elements (e.g. <br/>).

The W3C Index of HTML 4 Elements lists all defined elements in a table. For each row with an "E" in the Empty column, the corresponding element doesn't need a closing tag (and thus is self-closing).

With two lines of jQuery code in the Firebug console, I got the lists I wanted. Here is how:

To get all elements

$.map($('table tr:gt(0) a'), function(e) {return $.trim($(e).text());})

To get all self-closing elements (formatted for readability)

$.map(
$('table tr:gt(0)').filter(function() {
return $(this).find('td:nth-child(4)').text() == 'E';
}),
function(e){return $.trim($(e).find('td:first-child').text());});
$.trim() is needed because the HTML source contains \n in the Name column.
This demonstrates a handy usage of jQuery as a hacking tool. Another excellent demonstration can be found here.
You can add jQuery to the current page using the jQuerify bookmarklet.
Happy jQuerifying!

July 26, 2007

Ruby code: Finding the closest point

We want to fetch an avatar of specific size. How to find the closest size from all available sizes? Suppose available sizes are 16, 48 and 96.

An obvious version is

  def closest_size(size)
case
when size < 32
16
when size < 72
48
else
96
end
end

The value used in comparison tests is the average of two neighboring candidate sizes. The problem is that when our available sizes change, we need to add or remove when clauses and recalculate average values.


Here is a smarter way

def closest_size(size)
points = [16, 48, 96]
distances = points.map {|a| (size - a).abs}
points[distances.index(distances.min)]
end

It finds the point with shortest distance to the given size. Now if we want to change candidate sizes, we only need to change the array literal. Further, we can even pass the candidate array as an argument.

Rails: Error calling Dispatcher.dispatch...

I ran into a strange issue with one of my rails projects today - the browser displayed html source code in plain text instead of rendering. Nothing is listed in Firebug's net tab. Strange! Later I found the server was always sending back HTTP 404 with Content-type: text/plain. In the log, there were many lines of "Error calling Dispatcher.dispatch #<NameError: cannot remove Object::Handler>".

Finally, I found out that in a controller, someone has put include xxx at file level and in that module he defined class Handler. So that was it. After moving include xxx into the controller class definition, everything went well.

File level include (include outside class/module definition) actually affects Object - the mother of all ruby elves. So it's something we should definitely avoid in real world projects.

Never include outside class/module definition.

BTW, Rails is notoriously good at giving error messages unrelated to the cause of the problem. This is largely due to the dynamic nature of Ruby.

May 6, 2007

Ubuntu 7.04/8.04 and Mouse Wheel

I've just installed unbuntu 7.04 in VMWare. Everything is cool except that the mouse wheel stops working.

sudo gedit /etc/X11/xorg.conf

Find the mouse section which may look like

Section "InputDevice"
Identifier "Configured Mouse"
Driver "mouse"
Option "CorePointer"
Option "Device" "/dev/input/mice"
Option "Protocol" "ps/2"
Option "ZAxisMapping" "4 5"
Option "Emulate3Buttons" "true"
EndSection

Change Options "Protocol" "ps/2" to Option "Protocol" "IMPS/2"

Save the file and restart X (ctrl + alt + backspace).

It works for me. Happy scrolling!

UPDATE: 2008-8-18

Now with Ubuntu 8.04 running in VMWare Workstation 6.04 on Windows Server 2008...

The working config for me is as below:

Section "InputDevice"
Identifier "Configured Mouse"
Driver "vmmouse"
Option "CorePointer"
Option "Device" "/dev/input/mice"
Option "Protocol" "ImPS/2"
Option "ZAxisMapping" "4 5"
EndSection

The mouse cursor moves very smoothly crosses hosting and virtual machine seamlessly.

April 30, 2007

eval() bug in IE

eval('function(){}') evaluates to undefined in IE, the same for eval('(function(){})').

How to get the Function?

eval('[function(){}][0]')

IE, you always let me down!

February 10, 2007

Tricky alternatives to toString() and parseInt()

JavaScript is Loosely typed

JavaScript is dynamically typed, making things extremely flexible. Suppose you have a text field for inputting birth year and want to greet to people born after 1984, simply write

var birthYear = document.getElementById('txtBirthYear').value;
if (birthYear > 1984) {
alert('Greetings!');
}


When JavaScript sees you compare a String with a Number, it automatically converts the String to Number and the perform comparison.



But sometimes the ambiguity of type causes problems. 1 + '1' evaluates to '11' instead of 2. This may cause hard to find bugs. Douglas Crockford categorizes "the overloading of + to mean both addition and concatenation with type coercion" as a design error of JavaScript in his famous article on The World's Most Misunderstood Programming Language.



Still we often need to convert data type between String and Number. To convert variable i to String, simply call i.toString(). To convert s to a Number, we use Number(s). This is nice and clear.



The empty string ('') concatenation trick, the plus sign (+) trick and the minus zero (- 0) trick



But for guys who want to squeeze every byte. There are tricky alternatives.



  • To convert x to String: x + ''
  • To convert x to Number: +x
  • To convert x to Number: x - 0




For examples,

1 + 2 + 3 //produces 6
//while
'1' + 2 + 3 //produces '123'
'1' - 0 + 2 + 3 //produces 6
'1' + '2' //produces '12'
//while
+'1' + +'2' //produces 3

Notice that +x and x-0 doesn't mean parseInt(x) or parseFloat(x), it doesn't do any further parsing.

parseInt('2007 is promising') //produces 2007
//while
+'2007 is promising' //produces NaN
Let's call them the empty string concatenation conversion trick, the plus sign and the minus zero trick. Both of the tricks sacrifice clarity and make code harder to understand.

February 6, 2007

Closure, eval and Function

eval() evaluates a string of JavaScript code. The Function constructor can be used to create a function from string. Someone says that the Function constructor is another form of eval(). However, one significant difference between eval() and the Function constructor is that while eval() keeps the lexical scope, the Function constructor always creates top-level functions.

function f1() {
var bbb = 2;
eval('alert(bbb);');
}
f1(); //alerts 2

function f2() {
var bbb = 2;
new Function('alert(bbb)')();
}
f2(); //bbb undefined error

function f3() {
var bbb = 2;
eval('function() {alert(bbb);}')();
}
f3(); //alerts 2

eval() inside a function body creates a closure while new Function() doesn't. This difference may not bother you for the whole lifetime. However, it happens to bother me once. It's about jQuery - a new type of JavaScript library. I'm using jQuery in my bookmarklet application. In order to make the code as unobtrusive as possible, I decided to put all my code including the jQuery code inside an anonymous function. It looks like this:

(function() {
//jQuery code
//my code
})();

In this way, even the jQuery object is just a local variable. The outside environment is completely unaffected. But $().parents(), $().children, $().prev(), $().next() and $().siblings() always fail in my code. These functions are created by the Function constructor in $.grep() and $.map().

// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( typeof fn == "string" )
fn = new Function("a","i","return " + fn);

So they are all top-level and the identifier "jQuery" inside is resolved as window.jQuery which is undefined and the code fails.


We can implement an alternative to the Function constructor and use it within the lexical scope:

var createFunc = (function () {
var args = [].slice.call(arguments);
var body = args.pop();
return eval('function(' + args.join(',') + ') {' + body + '}');
}).toString();

function f4() {
var bbb = 2;
eval(createFunc)('alert(bbb);')();
}
f4(); //alerts 2
You can use eval(createFunc) just like new Function(), but you get the bonus lexical scope binding.
function f6() {
var add = function(a, b) {return a + b;};
return eval(createFunc)('x', 'y', 'return add(x, y);');
}
f6()(3, 5); //8

At last, I quote Douglas Crockford's words on eval() and the Function constructor


"eval is Evil


The eval function is the most misused feature of JavaScript. Avoid it.


eval has aliases. Do not use the Function constructor. Do not pass strings to setTimeout or setInterval. "