javascript

When Image Browser loses all its styling

The Image Browser project for Drupal 6.x provides a nice Media-esque modal dialogue for inserting your uploaded images into a rich-text field. However, the transition between the two panes of the modal workflow - image selection, to entering extra details like alignment classes - is handled by Javascript.

This means that if you have an entirely different module, whose Drupal Javascript behavior throws an error, then your whole cycle of behavior calls fall over. The callback between the two panes fails to bind to the form onsubmit, and so submitting one pane leads to an unstyled version of the second one and a change in the URL of the modal iframe.

This happened recently with the Colorbox module. The only solution is to configure its Javascript not to appear on that page: if you’re dealing with a module that has no such configuration options, then good luck….

Blog category:

"Trailing Comma" by Barcamp Weekend

I love Barcamp Weekend, and their own particular flavour of what the genre's aficionados call "world code."

Here's a sneak peak at some of the lyrics from Barcamp Weekend's forthcoming single release, Trailing Comma. It's about the perils of writing Javascript without keeping an eye on the behaviour of a certain class of browsers.

Who gives a fuck about a trailing comma?
I've seen IE alarm a
User,
Confuse her

Though it might not be ECMA,
Most other browser check the
Syntax
Then relax

Why'd you fail to parse the file?
So much coding, just one little flaw,
Goes to show: should always read the spec!

We've all been there, right?

Trailing commas and unfeasibly high line numbers

Bursting IE's Javascript parser, or: generating bizarre error messages through subprocess apoptosis

In Javascript, trailing commas are to be considered harmful. Strictly speaking, they're not allowed by the syntax, but this wouldn't be such a problem were it not for the fact that some browsers (including Firefox) will quietly ignore them, pretending briefly that Javascript's syntax is Pythonic or, um, Rubric. The safest route to take is to avoid trailing commas wherever possible. I've mentioned the general problem before, and the hard core among you would probably go so far as to change their formatting to highlight trailing commas.

But what makes it all far, far worse is that IE6 and IE7 can sometimes produce error messages which, as is usual for those browsers, contain no useful information whatsoever.

Here's a recent example:

Line: 64432871
Char: 2
Error: Expected identifier, string or number

See that impossibly high line number? That's a result of the parser being struck soundly on the head by the syntax error, and consequently dribbling its way past the end of the Javascript file. IE's incomprehensible "English" error message conspires with circumstance to make the whole report of no help at all.

Something like JSLint, on the other hand, is of tremendous help, and we'll be running as much Javascript through it as possible in future. JSLint is just as unforgiving, but as it's reliably unforgiving and incredibly communicative with it then that's a bonus rather than a detriment.

It's sometimes a little too strict to be useful, complaining about implicit global variables (even when that variable is called window). My suggestion is to ignore those reports, though; but watch those commas!

Blog category:

Compiling languages down to Javascript

A hundred years from now, all code will look both similar and different.

If it’s really the case that browsers, virtual machines and IDEs will one day converge, then the first steps would be to run Java, Ruby and other languages in a browser using Javascript. (Hat tip to Nick for the timely links.)

[Edit: run Python using Javascript too.]

Emacs as an anagram of "ECMA-S"

Your editor will become your browser will become your IDE. The process has already begun. Please wait.

Steve Yegge on *Emacs, pointing also to the possible future direction of the *browser:

“IDEs are draining users away, but it’s not the classic fat-client IDEs that are ultimately going to kill Emacs. It’s the browsers. They have all the power of a fat-client platform and all the flexibility of a dynamic system. I said earlier that Firefox wants to be Emacs. It should be obvious that Emacs also wants to be Firefox…

“… [N]ow the browsers are starting to sprout desktop-quality apps and productivity tools. It won’t be long, I think, before the best Java development environment on the planet is written in JavaScript.”

I’m more of a vi user these days—it behaves much more consistently over emergency ssh sessions—but as a general advocate of Emacs over IDEs I can see his point. Browsers should want to be like Emacs, or at any rate more like the VM of your choice.

Between browser-as-VM, Firefox, ECMAScript and compatibility frameworks there’s the seeds of an RIA revolution. After all, to what extent will industries gladly rest their financial weight on RIAs, so long as they’re all written in Flash-on-the-browser, and so long as browsers remain so unpredictable and Flash remains… well, Flash?

How to see Last.fm from the other side of the room with LastJS

Proof of concept, more than anything else: how to hook up to a recalcitrant Flash-based interface.

Back from a rather physical near-week of team building with work, I’ve been listening to Last.fm today to recuperate and nurse my tired muscles. But with the music playing on our crusty, trusty old purple iMac in one corner, and me in the other reading, drinking tea or otherwise recuperating, how can I tell at a glance what the current artist is?

Last.fm’s web interface isn’t optimized for the sort of “media station” use we put it to, and their downloadable widget won’t work on the antiquated version of OSX that I’ve just about managed to squeeze onto dear old Purpurea. So today I’ve spent some time writing a bookmarklet to allow me (and you, dear reader) to hook into Last.fm and its Flash player’s behaviour, to update a larger-view popup window with the necessary information.

Here’s the bookmarklet—which with attention to catchy names I’ve called LastJS—with spaces added for legibility:

javascript:void(function(){
  var s1=document.createElement(’script’);
  var s2=document.createElement(’script’);
  s1.src=’http://www.jpstacey.info/applications/lastjs/jquery.js’;
  s2.src=’http://www.jpstacey.info/applications/lastjs/last.js’;

  var h=document.getElementsByTagName(’head’)[0];
  h.appendChild(s1);
  h.appendChild(s2);
}())

(As the above suggests, the bulk of the code is at http://www.jpstacey.info/applications/lastjs/last.js.)

And here’s what you need to do to use it:

  1. Add this link to your browser favourites (IE7) or bookmarks (everyone else): LastJS bookmarklet.
  2. While on a Last.FM “now playing” page e.g. for The Hold Steady, click the bookmarklet.
  3. This will pull an instance of jQuery and LastJS off my own server, and add a “click for big” link under the title.
  4. Click on the link. A new window will open, containing the artist information (track information is a bit obfuscated until the Flash player loads the next track).
  5. Wait. As the track changes, the new window will update with the new track information.
  6. The text is purposefully large yet not too large. If you’ve got a big monitor you can make it even bigger by increasing the base font size in your browser. To do this in Firefox press CTRL-+ (or Apple-+); I think it’s something similar in IE7.

Feel free to poke around at the code, and distribute it (under GPL, for what it’s worth: I tried looking into precisely which variant I wanted but my knees started to ache). Here are some of the problems it has to solve:

  1. Last.fm uses Prototype, and so needs jQuery to restore the $ variable using jQuery.noConflict().
  2. Events in the Flash player trigger functions in the window scope: by putting a new method in for a given event handler you can hook into Flash events. Care has to be taken, though, not to wrap and re-wrap too often—the bookmarklet might after all be re-clicked—or you might provoke a recursion warning.
  3. Popup blockers mean that you can’t always guarantee the window will exist, and the code has to check accordingly.
  4. Firefox seems to blank the contents of the popup window shortly after it’s opened, so any writing of text to it has to be delayed, lest it be lost by this clear-out.

All probably needs work, but feel free to have a play. It doesn’t circumvent any DRM or anything, of course, but I’ve no idea whether it invalidates terms of use, so caveat fructor.

Pretending that Javascript is XSL, part 3: hCard to vCard

In previous posts (part 1, part 2) I established the possibility that there were advantages to making Javascript more functional, to bring it in line with CSS and XSL. I didn’t say what these were, particularly, but I then provided a few bits and pieces on top of jQuery to make Javascript just that: functional and quasi-XSL in its behaviour.

Now I’d like to start exploiting that behaviour, and I’m going to use the hCard microformat to illuminate its use. Briefly: a microformat is a set of agreed HTML classes used to invisibly encode structured semantic data in HTML; hCard is the implementation in HTML of the vCard specification for “virtual business card” files, using microformat classes. If you mark up people’s addresses using the hCard classes, then it’s possible to automate the conversion from hCard-enabled HTML to vCards, meaning you can click on buttons on webpages and have a vCard served up to you containing the contact information present in the webpage, verbatim, in a format you can put into your address book of choice.

One of the most-used conversion methods—Brian Suda’s X2V, a web service which converts XHTML with hCard markup into vCards and then presents them to the site visitor—uses XSL. In fact, that was what got me thinking about this whole system. Brian’s work is neat, although his own server takes a hit every time someone uses the web service (and it only works on XHTML, not non-XML HTML. What if, I thought, we could get the browser to do it instead; if we could implement template-like functional Javascript?

Anyway, below we find a couple of hCards, culled more or less directly from the Microformats examples page.

Frank Dawson
Lotus Development Corporation
work address (mail and packages):
6544 Battleford Drive

Raleigh NC 27613-3502

U.S.A.
+1-919-676-9515 (w, vm)
+1-919-676-9564 (wf)

Netscape Communications Corp.
work address:
501 E. Middlefield Rd.

Mountain View, CA 94043

U.S.A.
+1-415-937-3419 (w, vm)
+1-415-528-4164 (wf)

They look like slightly unstructured HTML, don’t they? That’s sort of the point. But hidden in the HTML are vCard classes. How do we tease them out with Javascript?

Well, there’s a question to be asked before that, I suppose, which is: why would we follow your method, and not someone else’s? What’s so good about functional Javascript? Good question. Well, if every hCard looked like the above, then you could write some completely procedural Javascript to turn it into a vCard. No problem.

But what if the order of the content was changed? The hCard—indeed, microformats in general—has quite a malleable structure, with some classes sometimes appearing on elements inside other elements, and sometimes not. What if there were more telephone numbers and email addresses, and what if they turned up in all sorts of different orders? These are just HTML classes, after all. With procedural Javascript you could start writing switch/case statements to cover every opportunity, and essentially come up with one big unavoidably recursive function. It’ll be hard to structure, hard to maintain and completely unmodular. A document-driven method of extracting the vCard, on the other hand, doesn’t need to worry about all the various different combinations of nested elements: it would just keep one eye on context and process whatever it found. Also, the development cycle could be faster, because templates could be overridden without breaking existing behaviour: just use the template() command to override existing behaviour.

Let’s instead assume you’re following my every word. For this next bit, you’ll need Firefox and Firebug, or to stuff all these instructions into a single file. Otherwise, you’ll have to take my word for it. Firstly, I’ve included jQuery on every page of my blog, so if you’ve got the ‘bug then you don’t have to resort to my insert-JS bookmarklet to squirt it in.

So: first, create the treewalker() and template() functions from part 2. Next, assign treewalker() to body and everything below it:

template("body, body *", treewalker);
template("body, body *", treewalker, "default");

You could restrict this assignment to everything within the .vcard elements, by giving the relevant CSS specifier instead, if there were a lot of content outside the hCards. It would speed up the initial setup phase, but it does complicate the demonstration so I’ve left that refinement out.

Remember we ran the treewalking before? Do that now:

var result = document.body.treewalk();

All being well, you should get a blank string back. Now it’s time to start adding some alternative rules with template(). Try this:

template(".vcard", function() { return "BEGIN:VCARDn" + this.default() + "END:VCARDn" });

Now run the treewalker again. Oh, each hCard has just given you a vCard! An… empty vCard. Isn’t that great? Um. We can add to that, though:

template(".vcard .fn", function() { return "FN:" + $(this).text() + "n" + this.default(); });
template(".vcard .org", function() { return "ORG:" + $(this).text() + "n" + this.default(); });

Now document.body.treewalk() doesn’t just return a vCard for every hCard, but it knows about names and organisations. Also, because we keep including the call to this.default() in our overrides, we still treewalk into any element inside the FN or ORG containers.

What about emails? Well, in the source we can spot an a.email element up there, so let’s give the following a whirl:

template(".vcard .email", function() { return "EMAIL;TYPE=internet:" + this.href.replace(/mailto:/, "") + "n" + this.default(); });

Try running document.body.treewalk() again. Hm. I don’t know about you, but I’m getting an error from that. Ah, wait: sometimes we have span.email rather than a.email. Spans don’t have @href attributes. Well, we could change the above rule and immediately reapply it using template() with no ill effects. But instead let’s keep it in place, and use a more specific specifier to override it just on spans:

template(".vcard span.email", function() { return "EMAIL;TYPE=internet:" + $(this).find(".value").text() + "n" + this.default(); });

Re-run the treewalker. It now finds all email hCard elements and brings them out into the vCards!

I’ll leave you with one more demonstration, for the slightly more complex TELephone field. As you can see above, there are lots of “types” for this field (Work, VoiceMail, etc.) and these sit in child elements of the telephone element. So we need to assign overrides to both the telephone element and its children.

Here’s a rule for the TELephone container:

template(".vcard .tel", function() {
  var t = "TEL";
  // Run defaults to get types where appropriate
  t += this.default().replace(/,/, ";") + ":";
  // See if we’ve got a “value” child
  var val = $(this).find(".value");
  return t + (val.length ? val.text() : $(this).text()) + "n";
});

This method is a bit more complex because we need default() to just get the .type children, and then we reach down to get. Maybe if we could give specifier argument to the default behaviour e.g. default('.type') first, then default('.value')… But that’s a project for another day, I think. Right now, let’s assign a rule to the types children and then run our treewalker:

template(".vcard .tel .type", function() {
  var jQ = $(this);
  return "," + (jQ.attr("title") ? jQ.attr("title") : jQ.text());
});

Result? You should now have Javascript which can produce vCards (currently without geographical address support, as I don’t have time and you might get bored) from the hCard microformat. It’s easy to extend, easy to maintain and, in my opinion, fairly concise. Here’s the whole shebang, less the two framework functions from my previous posts:

// Start with body
template("body, body *", treewalker);
template("body, body *", treewalker, "default");
// vCard wrapper
template(".vcard", function() { return "BEGIN:VCARDn" + this.default() + "END:VCARDn" });
// FN and ORG
template(".vcard .fn", function() { return "FN:" + $(this).text() + "n" + this.default(); });
template(".vcard .org", function() { return "ORG:" + $(this).text() + "n" + this.default(); });
// Email - A and SPAN tags
template(".vcard .email", function() { return "EMAIL;TYPE=internet:" + this.href.replace(/mailto:/, "") + "n" + this.default(); });
template(".vcard span.email", function() { return "EMAIL;TYPE=internet:" + $(this).find(".value").text() + "n" + this.default(); });
// TEL
template(".vcard .tel", function() {
  var t = "TEL";
  // Run defaults to get types where appropriate
  t += this.default().replace(/,/, ";") + ":";
  // See if we’ve got a “value” child
  var val = $(this).find(".value");
  return t + (val.length ? val.text() : $(this).text()) + "n";
});
// TEL types
template(".vcard .tel .type", function() {
  var jQ = $(this);
  return "," + (jQ.attr("title") ? jQ.attr("title") : jQ.text());
});

And that’s it. I hope the approach comes in useful. By next year, you’ll have hCard-enabled pages, with vCard conversion in the browser. Happy Christmas!

Pretending that Javascript is XSL, part 2: jQuery++

If you’re here, then you probably came from here, and you want to make Javascript more functional. If you didn’t come from there—and this is getting a bit like a Choose-Your-Own-Adventure book, isn’t it?—then you might want to go there first, to see if you want to be here.

So: functional Javascript. Not just functional, but with all the automation of XSL transformations and CSS applications, where you can set it all running and it’ll produce something and hopefully throw no errors. Let’s start with jQuery.

jQuery provides Javascript with a functional framework. Here’s the equivalent of the examples in XSL and CSS, supported by the inclusion of jquery.js:

jQuery("p.intro").each(
  function() { this.style.color = green; }
);

I hope the similarities are clear, and not too strained. Now all three languages do implicit looping over sets of element nodes, and no longer require checks for missing elements; that’s evidence that it’s starting to behave functionally. There’s still a few pieces missing, though. We’d like to be able to iterate over the tree with a set of default rules, and also replace the default rules with our own where necessary.

What would the default rule look like? Well, we can pass around all sorts of objects—this being an object-oriented language—but for now let’s play it safe and follow XSL’s lead, and have each node return the concatenated text returned by all its child nodes. That means that, by default, the whole HTML document would return an empty string. It might be nice to return an array of equivalent objects, or even some transformed tree, but let’s remain old-skool. Anyway, we can always serialize any HTML elements we want to include as text, and then stick them back into the DOM later. There’s probably a way of doing some of these tasks with core jQuery, but as we’re also passing result data around as well as input data, I’m going to step outside the framework (its extension model typically takes a jQuery object in, and returns a modified jQuery object, which isn’t quite what we’re after).

Here’s a default rule: it says “call the default rule (i.e. me) on all my children”. We’ll call this rule treewalker, because that’s what it does. We’ll also assume that we’re going to assign this function as the .treewalk method on each element:

var treewalker = function(i) {
  var t = "";
  $(this).children().each(function(i) { t += this.treewalk(); } );
  return t;
}

And here’s a way of assigning rules to elements. It’ll assign the rule as the .treewalk method unless we specify otherwise.

var template = function(specifier, fn, property) {
  if (typeof property == "undefined") property = "treewalk";
  $(specifier).each(function(i) { this[property] = fn; });
}

It looks a bit clunky, because falling back on the default property means we have to have an if-exists check. That’s to be avoided where possible in functional programming, but bear in mind that we’re still looking under the bonnet (or “hood” if you like), not at the actual functional code. We’ll get to the fully-functional bit shortly.

We’ve got one last bit and we’re done. We want to put the default rule on every element within a certain scope: we’ll assume for now that the whole HTML document body is to be treated; that might be computationally heavy for big documents, but we could change that. We’ve already defined a way of putting rules onto things, so let’s use that to put the treewalker function in as both .treewalk and .default. That way, we have a copy of the method hanging around, that we can fall back on if we overwrite it:

template("body, body *", treewalker);
template("body, body *", treewalker, "default");

That’s it. We’re now ready to pretend our Javascript is XSL. Here’s how we run it:

var result = document.body.treewalk();

Try it. “But that’s just an empty string!” you might, if you were feeling ungrateful, complain. Are you never satisfied? More on this later.

Pretending that Javascript is XSL, part 1: XSL, CSS and JS side by side

There are three main technologies that your browser employs to present HTML for you: XSL, CSS and Javascript. The first two of these are functional: that is, they turn your incoming (X)HTML documents into a set of functions, or behaviours if you like. Because CSS isn’t generally considered a language, let alone a functional one, then it’s worth seeing an example in both languages. Here’s the CSS:

p.intro { color: green; }

And here’s a sort-of XSL equivalent:

<xsl:template match="p[@class='intro']">
  <p color="green"><xsl:apply-templates /></p>
</xsl:template>

They both take place in the context of some generic processor, which rattles through the document executing default rules (XSL: strip out all but text nodes; CSS: apply the plain styles of your browser) unless your program—a list of disconnected rules, really—tells it differently. The combination of (XSL/CSS)+(X)HTML+defaults is thus turned into an explicit script for the browser to run.

So far, so reasonable. But what about the third technology, Javascript? Well, plain Javascript is an object-oriented procedural language. It orders the browser around for a bit, and then when you want to do something to the current page, Javascript manipulates the (X)HTML tree by grasping hold of it with both hands and giving it a tug, using DOM methods like .getElementById(id) and attributes like .parentNode. This procedural approach expects the tree to have a certain structure, or at the very least has to keep checking if the structure has changed and coping with that. This means that the programmer generally has to construct a lot of loops over, say, child elements, and also check for existence a lot. There’s a slight anomaly, in that you could think of the event-driven aspect of Javascript as being functional—it turns the user’s input through clicks, mouse movement and keypresses into browser behaviour, remaining otherwise dormant—but by and large Javascript’s meat is procedural.

There’s two routes you can take at this point. You can either say that, because Javascript is meant to be object-oriented, then the best way to work with it is to augment its functionality and simplify object construction, but ultimately leave it as that: if it weren’t functional, then it wouldn’t be Javascript. Or you can say that, given the advantages that XSL and CSS gain by being functional—a kind of “safety”, some scaleability, and document-driven processing—Javascript might want to have a piece of that too, while sacrificing some of its object orientation.

The first route is entirely laudable, because some problems are object-shaped and some are function-shaped. But, in the spirit of adventure, let’s investigate the second route for a while: pack some sandwiches and get some stout shoes on, and I’ll meet you in my next blog post.

Insert any Javascript bookmarklet

As the natural extension of Gareth Rushgrove’s bookmarklets for inserting the Dojo or YUI Javascript toolkits mentioned by Simon, here’s a tidying-up of a bookmarklet I’ve been using to bring in any Javascript using user input via a popup prompt:

Insert-JS bookmarklet

javascript:void(function(){
  var%20s=document.createElement("script");
  s.src=prompt("Full%20URL:");
  document.getElementsByTagName("head")[0].appendChild(s);
}())

I was originally appending it to the body as an invisible div, making use of d.innerHTML to add both src and type attributes so as to make sure the browser won’t ignore it. The above based on Gareth’s seems to work fine, though.

Blog category:

Pages

Subscribe to RSS - javascript