Projects
April 11th, 2008
Dromaeo is the name that I've given to the JavaScript performance test suite that I've been working on over the past couple months.
I was hoping to hold off on this release for another week or two, while I finished up some final details, but since it's been discovered, and about to hit the Digg front page, there isn't a whole lot that I can do to stop it.
There's a ton of details concerning how it works, and how to use it, on the Dromaeo wiki page. I won't go through too much of it here, but it should clarify most question there.
Probably the most pressing question that'll be encountered (outside of what is answered on the wiki page) is "What is the relation of Dromaeo to SunSpider?" (SunSpider being the WebKit team's JavaScript testing suite).
Right now I'm working very closely with all the browser vendors to make sure that we have a common-ground test suite that is both highly usable and statistically sound (not to mention providing results that are universally interesting). There are a number of outstanding concerns that've been raised by users of the suite, along with a number of concerns that've already been rectified - again, all of this is clarified on the Dromaeo wiki page. It's of the utmost concern that this suite be as applicable as possible. It's very likely that the core suite will be moving to a common working ground where all browser vendors can work on it.
I especially want to thank Allan Branch of LessEverything who provided the awesome design for the site. It's like he tapped into my brain and produced exactly what I wanted - without knowing even it. I highly recommend them, if you have design work that needs to be done.
Tags: testing, performance, javascript, browsers, mozilla
22 Comments on 'Dromaeo: JavaScript Performance Testing'
January 29th, 2008
One method that I've been wanting for quite a while now was a simple way to format old JavaScript dates in a "pretty" way. For example "2008-01-28T20:24:17Z" becomes "2 hours ago". Here's some more examples:
prettyDate("2008-01-28T20:24:17Z") // => "2 hours ago"
prettyDate("2008-01-27T22:24:17Z") // => "Yesterday"
prettyDate("2008-01-26T22:24:17Z") // => "2 days ago"
prettyDate("2008-01-14T22:24:17Z") // => "2 weeks ago"
prettyDate("2007-12-15T22:24:17Z") // => undefined
Note that I only care about dates in the past (by far the most common use case) and only dates within the past month (anything beyond a month becomes fuzzy and impractical).
JavaScript Pretty Date
- pretty.js (Also include a .prettyDate() jQuery plugin, for convenience.)
- Demo (Some examples of date conversion using basic DOM manipulation.)
Example Usage
In the following examples I make all the anchors on the site, that have a title with a date in it, have a pretty date as their inner text. Additionally, I continue to update the links every 5 seconds after the page has loaded.
With plain DOM:
function prettyLinks(){
var links = document.getElementsByTagName("a");
for ( var i = 0; i < links.length; i++ )
if ( links[i].title ) {
var date = prettyDate(links[i].title);
if ( date )
links[i].innerHTML = date;
}
}
prettyLinks();
setInterval(prettyLinks, 5000);
With jQuery:
$("a").prettyDate();
setInterval(function(){ $("a").prettyDate(); }, 5000);
About
There's a variety of these functions around, in various languages, but I wanted one in JavaScript for a couple reasons:
Microformats - Microformats specify a scheme for passing ISO dates to the client (precise date/time value) while providing the user with a "casual" date representation. I wanted to be able to generate a pretty version of the date, from a Microformat, easily and painlessly.
Auto-update - Secondly, I wanted to have these Microformat dates update automatically, as the page remained loaded. This way, if a user left a page open for 15 minutes, and came back, the pretty dates would be silently updated to their current times. This may seem nitpick-y but Twitter fails to do this and it drives me absolutely insane.
Write once, use everywhere - If I wanted to have these dates be constantly updated I would have these options: Only generate these dates on the server - re-querying new results via an Ajax request, generate the results on the server and on the client, or generate the results exclusively on the client. Client makes the most sense as it would result in the least amount of code duplication and general overhead.
I hope this will be useful to some, I know it's helped me out a bunch.
By the way - let's say this piece of code was from a - theoretical - larger project that was attempting to create an easy-to-use, Open Source, Twitter clone - would anyone be interested in using it?
Tags: javascript, jquery, time, dates
54 Comments on 'JavaScript Pretty Date'
September 17th, 2007
Recently, I've been spending a lot of time analyzing the speed of pure JavaScript engines, looking at how well they perform and what their particular strengths and weaknesses are. To start with, I analyzed the bleeding-edge code from:
Right now I'm only looking at pure, JavaScript-only, tests (no tests of DOM or other APIs) and am NOT looking at the speed of the browsers' native JavaScript engine implementations. (So, even though you may see a speed for a particular engine, that does not directly correlate to the speed of the JavaScript running within the browser itself. There's always a significant amount of overhead required to run JavaScript code seurely within a browser, thus the efficiency of that security layer will frequently become a deciding factor in the results.
The four engines that I picked all had complete JavaScript implementations and usable JavaScript shells (that way I could feed my tests in and have them cleanly run).
To browse the results I've pulled together a simple application that can be used to view a representation of the data from all the major JavaScript engines paired with the code from the tests which run them.
Right now the browser works fine in Firefox, is quirky in Opera and Safari, and explodes in IE (it requires canvas support). I'll finesse it into shape when I have a little more time this week.

Note: This demo uses a bunch of functionality from the new jQuery UI library, including themes, tabs, accordion, and resizables.
Tags: analysis, speed, data, javascript, ecmascript
25 Comments on 'JavaScript Engine Speeds'
July 9th, 2007
This weekend I took a big step in upping the ante for JavaScript as a Language. At some point last Friday evening I started coding and didn't stop until sometime mid-Monday. The result is a good-enough browser/DOM environment, written in JavaScript, that runs on top of Rhino; capable of running jQuery, Prototype, and MochiKit (at the very least).
The implications of this are phenomenal, and I'm not the only one who's interested in it what this could mean for server-side JS development. More on that in a minute, but first here's some sample results from running jQuery:
jQuery
$ java -jar build/js.jar
Rhino 1.6 release 6 2007 06 28
js> load('build/runtest/env.js');
js> window.location = 'test/index.html';
test/index.html
js> load('dist/jquery.js');
// Add pretty printing to jQuery objects:
js> jQuery.fn.toString = DOMNodeList.prototype.toString;
js> $('span').remove();
[ <span#å°åŒ—Taibei>, <span#å°åŒ—>, <span#utf8class1>,
<span#utf8class2>, <span#foo:bar>, <span#test.foo[5]bar> ]
// Yes - UTF-8 is support in DOM documents!
js> $('span')
[ ]
js> $('div').append('<span><b>hello!</b> world</span>');
[ <div#main>, <div#foo> ]
js> $('span')
[ <span>, <span> ]
js> $('span').text()
hello! worldhello! world
On a whim, I then plugged in Prototype and MochiKit, both of which appeared to work OK (I haven't done any significant testing with them - so there's probably gaps). Here's some sample results:
Prototype
$ java -jar build/js.jar
Rhino 1.6 release 6 2007 06 28
js> load('build/runtest/env.js');
js> window.location = 'test/index.html';
test/index.html
js> load('prototype.js');
js> $$('div p')
<p#firstp>,<p#ap>,<p#sndp>,<p#en>,<p#sap>,<p#first>
js> Object.toJSON({foo:'bar',baz:true});
{'baz': true, 'foo': 'bar'}
js> var fn = (function(name,msg){
print(name + ' ' + msg); }).curry('John');
js> fn('hello!');
John hello!
MochiKit
$ java -jar build/js.jar
Rhino 1.6 release 6 2007 06 28
js> load('build/runtest/env.js');
js> window.location = 'test/index.html';
test/index.html
js> load('Mochikit.js');
js> $$('div')
<div#main>,<div#foo>
js> document.body.innerHTML = '';
js> document.body.appendChild( P( 'test',
A({href:'http://google.com/'}, 'link')) );
js> document.body.innerHTML
<p>test<a href='http://google.com/'>link</a></p>
js> $$('a')
<a>
I just want to emphasize that these are un-modified copies of jQuery, Prototype, and MochiKit - all running perfectly in this un-natural environment.
When I came up with this idea for an environment, I was mulling over a couple ideas: Namely, better ways of automating tests and ways to bring JS-style DOM/HTML interaction to the server-side. Having a way to bring this popular idiom to established problem sets seemed like a lot of fun.
In short, the following (at the very least) can all get a big dose of JavaScript:
- Automated Testing
- Screen Scraping
- Web Application Development
Now, if you think I'm crazy, I'd like to show you a couple quick examples:
Automated Testing
$ java -jar build/js.jar
Rhino 1.6 release 6 2007 06 28
js> load('build/runtest/env.js');
js> window.location = 'test/index.html';
test/index.html
js> load('dist/jquery.js');
js> load('build/runtest/testrunner.js');
js> load('src/jquery/coreTest.js');
PASS (1) [core] Array.push()
PASS (2) [core] Function.apply()
PASS (3) [core] getElementById
PASS (4) [core] getElementsByTagName
PASS (5) [core] RegExp
PASS (6) [core] jQuery
...
Oh yes, that's right - the full jQuery test suite is now automated and capable of running in Rhino (passing all tests). jQuery served as my initial testbed for development, making sure that I was getting all of my code right. So if you import a copy of jQuery into this environment, it should work "just fine".
By the way, you can try out the automated test suite by getting a copy of trunk/jquery out of SVN, then running make runtest - the results are just awesome.
Screen Scraping
This is one part that works pretty well right now - with the huge caveat that it only works on well-formed XML documents (oops!). I'll be integrating an HTML parser into the code base so that we can make this functionality a little more resilient. In the meantime, here's an example of the sort of scraping that you can do currently:
load("env.js");
window.location = "http://alistapart.com/";
window.onload = function(){
load("dist/jquery.js");
print("Newest A List Apart Posts:");
$("h4.title").each(function(){
print(" - " + this.textContent);
});
};
And here's another one that writes the results out to a file:
load("env.js");
window.location = "http://alistapart.com/";
window.onload = function(){
load("dist/jquery.js");
var str = "Newest A List Apart Posts:\n";
$("h4.title").each(function(){
str += " - " + this.textContent + "\n";
});
var out = new XMLHttpRequest();
out.open("PUT", "file:/tmp/alist.txt");
out.send( str );
};
Oh yeah, I went there - I made PUT and DELETE requests to local files perform the expected actions. I think the result is hilarious.
Web Application Development
This is still a work in progress, but some of the initial ideas are already at play here in this environment. When I have some time I plan on making a JavaScript-based web app framework out of this - which should be pretty cool.
Here's some psuedo-code for how I think it could work:
window.onload = function(){
print("Content-type: text/html\n");
if ( location.href == "/" )
show_home();
print( document.innerHTML );
};
function show_home(){
document.load("index.html");
document.getElementById("time").innerHTML = (new Date()).toString();
}
Download!
Check out the code - there's still huuuge gaps of functionality missing - I only implemented the bare minimum to get this environment working (and passing the jQuery test suite). So your mileage may vary.
Download: http://jqueryjs.googlecode.com/svn/trunk/jquery/build/runtest/env.js (Formatted)
How to Use
To start with, you'll need to have, at least, Rhino 1.6R6. You can download it from Mozilla FTP.
Now download the env.js script and put it in the same directory as the Rhino js.jar.
In order to use it from the command-line, you'll wanna do something like this:
$ java -jar js.jar
js> load('env.js');
js> window.location = 'some.html';
some.html
js> // Your code here!
It's important that you do window.location = "some file" before loading any DOM-dependent code (as the 'document' object doesn't exist before the location request).
A full list of Rhino-shell-specific commands can be found in the Rhino Shell docs.
If you want to write executable scripts, the contents will look something like this:
load('env.js');
window.location = 'some.html';
window.onload = function(){
// Your code here
};
Which can then run like so: java -jar js.jar myscript.js.
Feedback is very much welcome - I've only thought of a couple use-cases thus far, but I'm sure that the surface is just being scratched.
Tags: rhino, java, ecmascript, firefox, mozilla, javascript
61 Comments on 'Bringing the Browser to the Server'
December 17th, 2005
This script is capable of taking an RSS (or Atom) feed and converting it into a valid JSON object. I currently cache feed requests once per hour, to save on bandwidth - so keep that in mind when developing an application.
You can use the interface that I have setup, below, or you can download the script itself (written in Perl) and run it on your own server (which is preferable).
Interface
This script currently has a REST interface, accessible via a GET request. The full request would look something like this:
GET http://ejohn.org/apps/rss2json/?url=URL&callback=CALLBACK
the URL parameter would contain the URL of the RSS/Atom feed which you are attempting to convert. The optional Callback parameter would reference a callback function that you wish to have called, with the new data.
You can test this out by visiting the following URL:
http://ejohn.org/apps/rss2json/?url=http://ejohn.org/index.rdf
Sample Code and Demo
A simple, sample, program would look something like this:
getRSS
("http://digg.com/rss/index.xml", handleRSS
);
function handleRSS(rss) {
alert( "Dowloaded: " + rss.title );
}
function getRSS(url, callback) {
feedLoaded = callback;
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "http://ejohn.org/apps/rss2json/?url=" + url
+ "&callback=feedLoaded&t=" + (new Date()).getTime();
document.getElementsByTagName("head")[0].appendChild(script);
}
I've also setup a more advanced demo, which uses some tweaked code from the del.icio.us JSON page.
Also, the del.icio.us popular widget that I made using the Google Homepage API also uses the RSS to JSON convertor. Taking a quick peak at the source code shows it in use.
Download
If you wish to run your own copy of the RSS to JSON script, you can download it from here. It's a Perl-based CGI script which requires that the following modules be installed: LWP and XML::Feed.
This work is licensed under a Creative Commons Attribution 2.5 License.
Tags: javascript, json, perl, rss, convert
16 Comments on 'RSS to JSON Convertor'
November 12th, 2005
This is an unobtrusive implementation of Sparklines, done in Javascript using the new Canvas element. It has been confirmed to work in the new Firefox betas, at the very least. Until the Canvas element becomes more widely accepted, this is more of a 'fun demo' at best.
How To Use:
To use, place your data points within your HTML, like so:
<span class="sparkline">10,8,20,5...
</span>
(Note: Any HTML element with a class of 'sparkline' is checked)
Then, in your CSS, you might want to have the rule:
.sparkline { display: none; }
so that non-compatible browsers don't see a huge pile of numbers.
Finally, include the library in your header, like so:
<script language="javascript" src="jspark.js"></script>
Demo
An example of this code, in action, can be found here: Demo.
Download
The JSpark library: jspark.js
This work is licensed under a Creative Commons Attribution 2.5 License.
Tags: canvas, javascript, firefox, cool
8 Comments on 'Javascript Sparklines Library'
September 8th, 2005
This is the project page for my entry into the addEvent() recoding contest. It works in all of the modern browsers: Windows IE 5+, Mozilla, Opera, and Safari. The code meets every item outlined in the guideline - attempting to be as short and simple as possible. You can view a demo of it in action.
This library consists of two functions: addEvent and removeEvent. To use them, simply copy the code from below, paste it into your Javascript code, and call it using these methods:
addEvent( object, eventType, function );
The 'object' parameter should be the object upon which you want the event to be called.
The 'eventType' parameter should hold the name of the event, for example: 'click', 'mouseover', 'load', 'submit', etc. More can be found here.
The 'function' parameter should be a reference to a function that you wish to have called whenever the event fires. One parameter will be passed to 'function' - the event object.
Some examples of addEvent in use:
addEvent( document.getElementById('foo'), 'click', doSomething );
addEvent( obj, 'mouseover', function(){ alert('hello!'); } );
removeEvent( object, eventType, function );
removeEvent is virtually identical to addEvent, with the obvious difference: Instead of the function being added to the event handler, it is removed instead. All of the above code and parameters applies to this function.
The code, itself, is very short and simple - only 15 lines long:
function addEvent( obj, type, fn ) {
if ( obj.attachEvent ) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
obj.attachEvent( 'on'+type, obj[type+fn] );
} else
obj.addEventListener( type, fn, false );
}
function removeEvent( obj, type, fn ) {
if ( obj.detachEvent ) {
obj.detachEvent( 'on'+type, obj[type+fn] );
obj[type+fn] = null;
} else
obj.removeEventListener( type, fn, false );
}
Much of the above code is trying to fix a serious problem with Internet Explorer. The code has to be this troublesome due to the fact that when your attached function gets fired, the 'this' reference refers to the worthless 'window', when, in fact, it should refer to the parent object. An explanation of the key points:
obj['e'+type+fn] = fn;
This makes the function a child of the specified object. The key, which is placed in the object hash, is (hopefully) unique and won't collide with any other function additions.
obj[type+fn] = function(){ obj['e'+type+fn]( window.event ); }
This line creates an anonymous function who, once executed, will fire the previously attached function - passing it the global event object. This whole function is being attached to the object so that it can be removed later, using the removeEvent() function.
Finally, for more information on the attachEvent, detachEvent, addEventListener, and removeEventListener functions - please refer to the excellent resource at Quirksmode.
If you have any additional questions concerning the above code, please feel free to ask.
Tags: programming, event, javascript, popular
68 Comments on 'Flexible Javascript Events'
August 30th, 2005
Lazy Sheep is a del.icio.us bookmarklet that auto-tags and auto-describes your bookmarks.
Using the tags and descriptions shared by other del.icio.us users, Lazy Sheep makes tagging a page a one-click operation. In order to best suit any user, Lazy Sheep also includes a comprehensive set of options that can be configured to your exact specifications.
Visit
Tags: javascript, bookmarklet, delicious, cool, popular
38 Comments on 'Lazy Sheep Bookmarklet'
·
« Previous entries