You're viewing blogs from JavaScript only. RSS?

View all different categories

13th of October

Going real simple on HTML5 audio

DoneCal users are to 80+% Chrome and Firefox users. Both Firefox and Chrome support the HTML <audio> element without any weird plugins and they both support the Ogg Vorbis (.ogg) file format. change log here

So, I used use the rather enterprisey plugin called SoundManager2 which attempts to abstract away all hacks into one single API. It uses a mix of browser sniffing, HTML5 and Flash. Although very promising, it is quite cumbersome. It doesn't work flawlessly despite their hard efforts. Unfortunately, using it also means a 30kb (optimized) Javascript file and a 3kb .swf file (if needed). So, instead of worrying about my very few Internet Explorer users I decided to go really dumb and simple on this.

The solution basically looks like this:

 // somewhere.js
 var SOUND_URLS = {
   foo: 'path/to/foo.ogg',
   egg: 'path/to/egg.ogg'
 };

 // play-sounds.js

 /* Call to create and partially download the audo element.
  * You can all this as much as you like. */

 function preload_sound(key) {
  var id = 'sound-' + key;
  if (!document.getElementById(id)) {
    if (!SOUND_URLS[key]) {
      throw "Sound for '" + key + "' not defined";
    } else if (SOUND_URLS[key].search(/\.ogg/i) == -1) {
      throw "Sound for '" + key + "' must be .ogg URL";
    }
    var a = document.createElement('audio');
    a.setAttribute('id', id);
    a.setAttribute('src', SOUND_URLS[key]);
    document.body.appendChild(a);
  }
  return id;
 }

 function play_sound(key) {
   document.getElementById(preload_sound(key)).play();
 }

 // elsewhere.js
 $.lightbox.open({
    onComplete: function() {
       preload_sound('foo');
    }
 });
 $('#lightbox button').click(function() {
    play_sound('foo');
 });

Basically, only Firefox, Chrome and Opera support .ogg but it's a good and open source encoding so I don't mind being a bit of an asshole about it. This little script could be slightly extended with some browser sniffing to work with Safari people but right now it doesn't feel like it's worth the effort.

This make me happy and I feel lean and light. A good feeling!

22nd of August

Title - a javascript snippet to control the document title

This is a piece of Javascript code I use on Kwissle to make the document title change temporarily. Other people might find it useful too.

Code looks like this:

 var Title = (function() {
  var current_title = document.title
    , timer;

  return {
     showTemporarily: function (msg, msec) {
       msec = typeof(msec) !== 'undefined' ? msec : 3000;
       if (msec < 100) msec *= 1000;
       if (timer) {
         clearTimeout(timer);
       }
       document.title = msg;
       timer = setTimeout(function() {
         document.title = current_title;
       }, msec);
     }
  }
 })();

Demo here

10th of July

Comparing Google Closure with UglifyJS

On Kwissle I'm using Google Closure Compiler to minify all Javascript files. It's fine but because it's java and because I'm running this on a struggling EC2 micro instance the CPU goes up to 99% for about 10 seconds when it does the closure compilation. Sucks!

So, I threw UglifyJS into the mix and instead of replacing the Closure compiler I added it so it runs alongside but I obviously only keep one of the outputs.

Here is the log output when I run it on my MacbookPro:

 MAKING ./static/js/account.js
 UglifyJS took 0.0866 seconds to compress 3066 bytes into 1304 (42.5%)
 Closure took 1.2365 seconds to compress 3066 bytes into 1225 (40.0%)
 MAKING ./static/js/ext/jquery.cookie.js
 UglifyJS took 0.0843 seconds to compress 3655 bytes into 3009 (82.3%)
 Closure took 1.3472 seconds to compress 3655 bytes into 4086 (111.8%)
 MAKING ./static/js/ext/jquery.tipsy.js
 UglifyJS took 0.1029 seconds to compress 7527 bytes into 3581 (47.6%)
 Closure took 1.3062 seconds to compress 7527 bytes into 3425 (45.5%)
 MAKING ./static/js/maxlength_countdown.js
 UglifyJS took 0.082 seconds to compress 1502 bytes into 1033 (68.8%)
 Closure took 1.2159 seconds to compress 1502 bytes into 853 (56.8%)
 MAKING ./static/js/ext/socket.io-0.6.3.js
 UglifyJS took 0.299 seconds to compress 76870 bytes into 30787 (40.1%)
 Closure took 2.4817 seconds to compress 76870 bytes into 30628 (39.8%)
 MAKING ./static/js/scoreboard.js
 UglifyJS took 0.084 seconds to compress 2768 bytes into 1239 (44.8%)
 Closure took 1.2512 seconds to compress 2768 bytes into 1167 (42.2%)
 MAKING ./static/js/rumbler.js
 UglifyJS took 0.0872 seconds to compress 3087 bytes into 1384 (44.8%)
 Closure took 1.2587 seconds to compress 3087 bytes into 1235 (40.0%)
 MAKING ./static/js/ext/shortcut.js
 UglifyJS took 0.0987 seconds to compress 5796 bytes into 2537 (43.8%)
 Closure took 1.3231 seconds to compress 5796 bytes into 2410 (41.6%)
 MAKING ./static/js/play.js
 UglifyJS took 0.1483 seconds to compress 18473 bytes into 10592 (57.3%)
 Closure took 1.4497 seconds to compress 18473 bytes into 10703 (57.9%)
 MAKING ./static/js/playsound.js
 UglifyJS took 0.0824 seconds to compress 1205 bytes into 869 (72.1%)
 Closure took 1.2335 seconds to compress 1205 bytes into 873 (72.4%)  

(Note here that for the file ./static/js/ext/jquery.cookie.js Closure failed and when it fails it leaves the code as is and prepends it with a copy about the error from the stdout. that's why it's greater than 100% on that file)

Here are the averages of those numbers:

 AVERAGE TIME: (lower is better)
   * UglifyJS: 0.11554 seconds
   * Closure: 1.41037 seconds

 AVERAGE REDUCTION: (higher is better)
   * UglifyJS: 45.6% 
   * Closure: 51.5%

(I'm skipping the file that Closure failed to minify)

So, what does that mean in bytes? These are the source Javascript files for two pages but the total is 123949.0 bytes. With Closure that saves me 63833.7 bytes (62 kbytes) whereas UglifyJS only saves me 57760.2 bytes (56 kbytes) of bandwidth.

Discussion...

The fact that Closure fails on one file is a real bummer. I'm not even using the advanced options here.

UglifyJS doesn't save as many bytes as Closure does. This is potentially important because after all, minification process happens only once per revision of the original file but might be served hundreds or millions of times.

Because I run my minifications on-the-fly it does matter to me that UglifyJS is 1220% faster.

It's rare but I have observed twice that the minified Javascript from Closure has actually broken my code. I default to suspect that's my fault for not making the code "minifyable" enough (e.g. too many global variables).

I've just noticed that I'm relying on files that almost never change (e.g. jquery.tipsy.js). I might as well create a ...min.js versions of them and add them to the repository.

In conclusion...

Because of the convenience of UglifyJS being so much faster and that it doesn't choke on that jquery.cookie.js file I'm going switch to UglifyJS for the moment. The remaining bytes that I don't save become insignificant if you add the gzip effect and compared to images the bandwidth total is quite insignificant.

9th of May

A script tag's type in HTML5

If you look at html5boilerplate.com they never use any type on their <script> tags. Hmm... but is there more to it?

If you don't specify a type, the default becomes "text/javascript" but according to RFC4329 the "text/javascript" MIME type is obsolete in favor of "application/javascript".

If the default MIME type for a <script> tag thus becomes either "text/javascript" or "application/javascript" is there any sensible browser on this green earth that would not translate a piece of inline Javascript code as, exactly that; Javascript? Probably not.

What about <script> tags with a src attribute? Does the type matter?

I read the spec a couple of times and it feels like reading legalese but it ultimately says: the value of the type tag must be that of the body of the script tag.

So, what happens if you embed a javascript file with a mismatching type? Let's see:


Read the whole text (200 more words)

1st of May

maxlength_countdown() - a useful jQuery plugin for showing characters left

If people find this useful I might turn it into a proper jQuery plugin and publish it.

Without further ado; here's the demo

What it does is that for all input fields that have a maxlength="nnn" it shows a counter to the right that increases in opacity as it reaches the maximum. You can generally start it like this:

 $('input[maxlength]').maxlength_countdown();

Since the plugin "hard codes" the count down expression in English you can override it easily like this:

 $('input[name="message"]').maxlength_countdown(function (count, max) {
    return count + " (max: " + max + ")";
 });

What do you think? Is it useful? Does it make sense?

14th of April

Mocking DBRefs in Mongoose and nodeunit

Because this took me a long time to figure out I thought I'd share it with people in case other people get stuck on the same problem.

The problem is that Mongoose doesn't support DBRefs. A DBRef is just a little sub structure with a two keys: $ref and $id where $id is an ObjectId instance. Here's what it might look like on the mongodb shell:

 > db.questions.findOne();
 {
        "_id" : ObjectId("4d64322a6da68156b8000001"),
        "author" : {
                "$ref" : "users",
                "$id" : ObjectId("4d584fb86da681668b000000")
        },
        "text" : "Foo?",
        ...
        "answer" : "Bar"
        "genre" : {
                "$ref" : "question_genres",
                "$id" : ObjectId("4d64322a6da68156b8000000")
        }
 }

DBRefs are very convenient because various wrappers on drivers can do automatic cross-fetching based on this. For example, with MongoKit I can do this:

 for question in db.Question.find():
    print question.author.first_name

If we didn't have DBRefs you'd have to do this:

 for question in db.Question.find():
    author = db.Authors.findOne({'_id': question.author})
    print author.first_name


Read the whole text (420 more words)

 

Older entries