2006-07-21

A peek at Yahoo! UI

It's been a while since I touched blog template code, and this being my place of random hackery for the various libraries and tools I stumble upon, when not in a user script domain, additional more or less useless features were added. (I fully blame Henrik for spurring me to get that pensive mood picture less randomly floating about in the air, and get this hackery started in the first place. :-)

Anyway, I have for the longest time been meaning to take a closer peek at the Yahoo! UI toolkit to see how it measures up against MochiKit, Dojo, jQuery and friends, and today ended up being that day. Long story short -- clicking my blog header above yields the hinted at useless featuritis. The rest of this article is mostly phrased as a shootout between MochiKit and YUI, as the level of maturity of these libraries match one another well, though the communities and overall design makeup do not. Hopefully some of the right people may also read it as constructive criticism on aspects to improve upon, and how.

First, though, before trying out YUI, I had a slight peek at the jQuery 1.0 alpha release, after taking note of its new $( cssSelector ).animate( properties, speed ) method. It seemed neat -- list one up to a handful of camelCased CSS properties and their wanted target values, suggest a speed in seconds, and jQuery does the animation smoothly for you. This worked well enough in the basic case of animating one element and some properties on it, but when I tried to both change the height of one div and the vertical padding of another at the same time via two consecutive calls, they ended up chained after one another in time instead. Ouch.

Figuring I had already used up my query credits on the jQuery list some month ago for getting my zipped-in commenter pictures, I did not ask about how it was supposed to be done to work, but moved on to YUI instead. Where jQuery has more or less the look and feel of bringing Ruby to the world of Javascript, YUI is the library that puts back the Java in Javascript, both in the good and the bad way. Good, in being a (rather consistently) high quality and extensive library, bad, in giving unwieldingly long names for the smallest of tasks. (Yes, this is a purely emotional subject. Bear with me, or skip the next two paragraphs.)

On this latter aspect, YUI is the antithesis of both jQuery and MochiKit, despite the fact that MochiKit too has broken up namespace into dot-separated modules. First and foremost because, while MochiKit too offers the benefit of only reserving one single intrusion into your variable and function namespace (the MochiKit module itself), it also lets you import the whole slew of Pythonesque / functional programming core components of MochiKit.Base into your global namespace for all sorts of really useful constructs (do try them out in the live MochiKit interpreter). While this is in part unfair comparison (as YUI does not extend the base language but rather offers tool modules and widgets) the pragmatic approach to naming and designing APIs in MochiKit is in the memorable and readable camp, whereas YUI always is in the bulky Javaesque carpal tunnel syndrome camp.

The ideal example, which is not unfair, is how both libraries abstract event handler signalling, into connecting events with a callback that gets the event passed as the first parameter, regardless of browser. By MochiKit's design, your callback gets a well documented MochiKit event object with functions like src() to see the node you had registered the handler for, target() to see which node triggered the event (sometimes a child of src) and stop(), for calling stopPropagation() and preventDefault() for the event, whereas in YUI, you get the native event object and abstract away browser differences for everything in it by calling YAHOO.util.Event.stopEvent( e ), passing along the event object.

While you should never need to see the actual native event object in a well wrapped library, MochiKit lets you do it for the rare case where you have to or want to with an event() method on its event object. Needless to say, the MochiKit approach makes it much easy on the developer to use the library's helpful abstraction that protects you from the random differences of browser implementation peculiarities with its approach, where YUI, while probably boasting the same set of abstractions, achieves it with a method that isn't as easy to pick up or use, and will most likely have developers continue to peek through the raw event object unaidedly, making their YUI applications work on their browser of choice while failing mysteriously with other browsers or platforms, even where supported by YUI. Unfortunate.

Next comes documentation. YUI, as MochiKit, has neat and tidy autodocs for everything and both ships with these in the zip file and publishes them online. It's somewhat easier to find your way around the MochiKit docs who offer a shared root node of the library tree, but it's at most an insignificant shortcoming of YUI's compared to the next issue: almost nowhere are there any visual examples at all of how YUI works in practice. The Animation module, for instance, is fairly well documented and walkthrough:ed, but nowhere online do you get any preview of how the code actually performs in reality. Edit: There actually are examples, and on the site too; they are just very well hidden (I still haven't found the actual links myself, but Google has). No easy checking of whether it actually does work across multiple browsers, if you have any, no quick peeks of actual code you can see does what you are looking for, no DIY without first reading through the tutorials or autodocs scanning for dependencies and exact call signatures.

For that, you have to download the library, unzip it, browse through a deep directory structure, find the right index files in the examples directory or scour the web to come up with good hands-on examples. YUI spread would benefit a lot from an online examples section such as MochiKit's.

Another great feature worth borrowing from jQuery is having the latest version (and why not past releases) of the unzipped source code on a permalink online, for instant peeking at the code and playing online -- and even using it from there, for the convenience and instantly up and running code without trafficking your web server or hotel. Given that The YUI libraries already by default access some CSS and images from Akamai for some of its libraries, it's beyond me why not all of the code is permalinked there, too. These are some rather easy ways of chipping off barriers to entry thresholds.

All in all, though, once you are up and running with YUI, have invented your own shortcuts there will probably never be any consensus about for the too long module names, there is very much to be glad about, in how tedium is replaced with really powerful and well thought out structures for how to shape event flows, animation and the many other things you easily end up deeply frustrated about when using Plain Old Javascript; you can smell much developer love and experience in YUI, behind the Javaisms smoke screen.

2006-07-20

Google × Dilbert mashup

Google homepage As I was lazily browsing around userscripts.org this afternoon, I stumbled upon this most amusing Google × Dilbert mashup by Raj Mohan.

Despite being a trade-off between amusement factor and additional clutter, I opted to install it right away, especially as I rarely ever visit the Google homepage these days anyway, with my home browser equipped with the Google toolbar. That fact also made me realize that when I did visit, it was mildly annoying that it was so hard to get strips into context (long arc strips being more the rule than the exception with Dilbert). I figured I would prefer being able to flick back and forth a bit, and as I made a script like that recently for more conveniently browsing Sinfest (userscript.org page), I thought I'd do something similar for this Dilbert hack, but with a bit of ajax for doing it in-place instead of with links, as I did with Sinfest. I'm rather satisfied with the result (direct install link).

Both hacks show the previous / next strip when clicking the left / right portion of the comic. This is, I would deem, the second most intuitive behaviour for any comic browser or image album interface, the most intuitive being just "click anywhere in the image to see the next image". Which is not as useful, though, and as you can see the URLs in your status field as you hover the image and as this script targets the web savvy crowd familiar with user scripts anyway, usability won over discoverability by a comfy margin and I picked the prev/next navigation mode.

I'd like to draw some attention to the fact that while it would have been easier to just add a click event listener to the image, peeking at the coordinates of the click and deciding whether it was in the left or right portion of the image, I went through the trouble of setting up an image map for achieving the effect of "hover to see what will happen on clicking", as we are used to from ages of browser handling experience.

Also, and this is the part where many ajax happy developers foul out, it makes these links behave as links in all respects, allowing those who want to open them in a new tab or window to do so, in the same way they have their browser configured to do it. Shift click, middle click, right-click-and-pick-an-option-in-the-menu, et cetera. You can't do that if you barge on adding event handlers that just do it your way.

Back to the Dilbert hack. As most of the Dilbert strips are three-pane rather than four-pane, as in the case of Sinfest, it struck me it would look neater still having the clickable surface split in three, as that would also allow for a link to the strip itself in the middle. (A bit difficult to share links with others to a strip you liked, if the URL you found it at was http://www.google.com/, see. Most people would probably not understand which particular strip you were referring to, but think you were being obnoxious.) And it seems to work quite well, too.

Enjoy!

2006-07-18

Spam fighting phpBB boards

The FireBug forums were rather heavily spam infested recently. Most of it was fortunately in the rather boring General section, but it had spilled into a few of the other categories too, so I asked Joe Hewitt if I could help out blasting it to bits (for the benefit of the community -- it's a small service to offer for a project you really care about), on those occasions I peer through the forums anyway. I could.

Fighting spam on a phpBB board is painful, as it's probably about as much work getting rid of it as getting it there in the first place (for the spammers); lots of clicking and waiting for page loads and the like. I had anticipated this lack of convenience, though, and was prepared to rewrite the phpBB admin interface to make it more workable.

screen shot Thus the phpBB quick purging (direct install link) user script was born. If you have logged in with admin rights (the script knows, by the presence of the link "you can moderate this forum"), it adds delete links to all posts or threads in view, and a "delete all" link at the bottom. Enjoy!

2006-07-16

Custom keymaps in windows

The more I reflected on it, the more I wanted a ☠ skull key (olde-hat Dungeon Master ex-addict that I am), and while that would have been a breeze under any self respecting unix system, with tools like xmodmap and xkeycaps, I couldn't for the world figure out how to do it under Windows XP. Lots of painstakingly googling around later, I found some pages on how to hack the registry to remap the keyboard (for stuff already in the keymap, but in all the wrong places), then a working freeware KeyTweak and an even neater remapkey.exe in the Windows Server 2003 Reskit Tools, and finally something like the xkeycaps I was looking for, in MSKLC: the Microsoft Keyboard Layout Creator.

If I can only figure out how to avoid getting the "There was a problem building the keyboard file. Would you like to see warning/error information?" prompt and instead get the "The Windows Installer package was built successfully in..." message, things would be extra tremendous. Answering "Yes" at the aforementioned prompt, by the way, yields a "There was no warnings or errors." dialog. [OK]. I suppose I'll have to try it on another computer, reinstall windows or something. Business as usual.
Categories:

2006-07-10

Encoding / decoding UTF8 in javascript

From time to time it has somewhat annoyed me that UTF8 (today's most common Unicode transport encoding, recommended by the IETF) conversion is not readily available in browser javascript. It really is, though, I realized today:
function encode_utf8(s) {
  return unescape(encodeURIComponent(s));
}

function decode_utf8(s) {
  return decodeURIComponent(escape(s));
}
2012 Update: Monsur Hossain took a moment to explain how and why this works. It's a good, in-depth post citing all standards in play so you need not bring a wizard's beard to know why it works. Executive summary: escape and unescape operate solely on octets, encoding/decoding %XXs only, while encodeURIComponent and decodeURIComponent encode/decode to/from UTF-8, and in addition encode/decode %XXs. The hack above combines both tools to cancel out all but the UTF-8 encoding/decoding parts, which happen inside the heavily optimized browser native code, instead of you pulling the weight in javascript.

Tested and working like a charm in these browsers:
Win32
  • Firefox 1.5.0.6
  • Firefox 1.5.0.4
  • Internet Explorer 6.0.2900.2180
  • Opera 9.0.8502
MacOS
  • Camino 2006061318 (1.0.2)
  • Firefox 1.5.0.4
  • Safari 2.0.4 (419.3)
Any modern standards compliant browser should handle this code, though, so don't worry that it's a rather sparse test matrix. But feel free to use my test case: encoding and decoding the word "räksmörgås". That's incidentally Swedish for a shrimp sandwich, by the way -- very good subject matter indeed.

And if you hand me your platform/browser combination and the its success/failure status for the tests, I'll try to update the post accordingly.
Categories:

2006-07-02

Expressive user scripts with XPath and E4X

This is another tricks of the trade kind of post about some base tooling and patterns I mostly always end up using in my user scripts, much like the post I wrote about the event manager class.

First, there is the $x( xpath, root ) method, which takes an XPath expression (see a former article on an XPath bookmarklet, or go pick up the Firefox XPath Checker extension for playing interactively with XPath expressions) and an optional root node (to resolve it from), and returns an array of nodes matching the expression. The name is borrowed from FireBug, though FireBug unfortunately does not accept the second parameter. (Great for limiting node searches to subtrees of a page, and for writing tidy, small functions that parse out data from the document on their own, given a context node.) Mine looks like this:
function $x( xpath, root )
{
var doc = root ? root.evaluate?root:root.ownerDocument : document;
var got = doc.evaluate( xpath, root||doc, null, 0, null ), next;
var result = [];
while( next = got.iterateNext() )
result.push( next );
return result;
}
As it's not a practice I've seen many adopt in user scripts, and as it readily condenses very much of what I do to a few very expressive lines of code, let me share my second largest time saver and node dribbling readability improver. It's marrying map with the $x XPath slicer above to form a function that takes two or three parameters: an XPath expression (cutting out some relevant subset of DOM nodes in a page), a function (to apply to all of them) and again the optional root node (to resolve the expression from). I tend to name mine foreach in the interest of brevity:
function foreach( xpath, cb, root )
{
var nodes = $x( xpath, root ), e = 0;
for( var i=0; i<nodes.length; i++ )
e += cb( nodes[i], i ) || 0;
return e;
}
Usage is simple; here's an example I just tossed up that stops links from opening in new windows (direct install link):
foreach( '//a[@target]', dont_open_new_windows );
foreach( '//base[@target]', dont_open_new_windows );

function dont_open_new_windows( a )
{
if( !has_frame_named( top, a.target ) )
a.removeAttribute( 'target' );
}

function has_frame_named( w, name )
{
if( w.name == name )
return true;
for( var i=0; i<w.frames.length; i++ )
if( has_frame_named( w.frames[i], name ) )
return true;
return false;
}
People from functional language backgrounds take higher order functions like map for granted, the beautiful little swiss army knife that iterates lists performing some function on all their elements, returning a new list with the results. It makes for very neat and tidy code, without lots of looping constructs clogging up the flow, and with properly named functions as above, it even adds a free touch of documentation as to what action these loops performs. Bonus maintainability!

As the observant reader might have noticed, though, I allow the callback passed to foreach return a value that is added up and gets returned after the call. Not necessary for this rather typical case of just performing some action on the matched nodes, but once in a while that operation might be conditional, and it might be of interest to the caller how many times it was performed. By returning 1 from the callback when it did something, though, the foreach will return that information, allowing an early exit or similar, when appropriate.

For improved sanity (and, admittedly, for the hell of it), I tossed up another script that disposes of Dr. Phil references from match.com, making use of this functionality to add a menu command that reincarnates them again (...whyever someone would want to do that).

As merely hiding or removing a subset of nodes in an HTML document typically leaves weird-looking holes or layouts I opted to also add some subtle indicator of what was once there, and which could also serve as click-to-restore links. And while at it, to hint of how many Phils would pop back into view on clicking them using a hover title. As you see, the real action gets rather terse and readable using the tools above:
var phils = foreach( '//*[contains(@id,"Phil")]', hide_node );
if( phils )
{
phils = 'Restore '+ phils +' Dr. Phil reference'+
(phils==1?'':'s');
GM_registerMenuCommand( phils, show_phils );
}
foreach( '//div[@class="ugh"]',
function( div ){ div.title = phils; } );
(The class="ugh" divs being my click-to-restore indicators.) Some people prefer adding icons using images and the data: protocol, which gets a bit messy but works. I opted for a Unicode thumbnail of the original content, post mortem for added symbolism -- U+2620, or "☠".

As a programmer, never underestimate Unicode as the source of useful graphics for this kind of thing; there is plenty on offer. Googling for "unicode" and the artwork you seek often strikes gold. If you don't have a keyboard sporting a ☠ skull key, just have javascript render the proper string for you on its own instead:
var skull = String.fromCharCode( 0x2620 );
And while this post is already getting a bit lengthy, I thought I'd at least mention the script that got me writing this article in the first case, on realizing how quickly it came to be, thanks to all the handy tools introduced above: another scratch-an-itch script reshaping the pages of match.com.

It employs another usability improvement technique I can warmly recommend for pages on web sites that do not use the document title for useful page-relevant content. The document title being the name of tabs in modern browsers, it very useful for bringing order to an unruly browsing experience. Just set document.title to whatever title you would prefer, and if you feel like it, you may also change the tab favicon.

That can be done using this bit of code from Mark My Links, also parts of my standard library of useful assorted goodies:
function override_favicon( url )
{
foreach( '//link[@rel="shortcut icon"]', remove_node );
append_to( <link rel="shortcut icon" href={url}/>,
$x('//head')[0] );
}

function remove_node( node )
{
node.parentNode.removeChild( node );
}

function append_to( e4x, node, doc )
{
doc = doc || (node ? node.ownerDocument : document);
return node.appendChild( import_node( e4x, doc ) );
}
This is made as terse as above much thanks to the expressive power of E4X, and made somewhat verbose again in the library import_node method due to the present lack of the optional domNode() API, which unfortunately didn't make it into Firefox 1.5. Here's hoping it's coming to a browser near you before long so we can drop that mess.

Happy hacking! And don't write more code than you have to.