Archive

Archive for the ‘javascript’ Category

Updates from all around - Dec 2009

December 19th, 2009

A lot of the work I'm doing doesn't turn into new posts so here's a summary of what's been going on:

yayQuery

Since it was announced here, we've come a long way. We just put out our 7th episode (now in HD!). The show format has tightened up and we're putting a lot of time into making it even better. Follow us on twitter or subscribe to catch all the goodness. We also put out an entire episode devoted to what's coming in jQuery 1.4 (aka 1.MOAR), so peep it if you're curious.

jQuery Singalong plugin

I was a finalist at Boston's Music Hack Day with the jQuery Singalong plugin. It essentially allows you to add timed annotations to the HTML5 audio and video elements. (Also it was the first time I legitimately used javascript's Infinity :) In addition, I put out the (very!) alpha jQuery Bouncing Ball plugin. Check out the demos to get a taste

Modernizr updates

Faruk and I pushed out Modernizr 1.1, which can now detect what audio/video formats your browser supports, all sorts of HTML5 forms goodness, and some other ones like localStorage and applicationCache. We're delighted to be part of Mark Pilgrim's Dive into HTML5 book and pumped to help push the edge of progressive enhancement.

@font-face and webfonts

It's a rapidly changing landscape, so I've been keeping all my font-y posts up to date:

@font-face feature detection

I've updated the existing feature detection script to use a smaller file. (Thanks to the Font Squirrel for the help). You would use this script to detect if support is present or not; perhaps implement a Cufon fallback. These new improvements will make their way into Modernizr 1.2.

I've also added on a isFontFaceSupported browser sniffing alternative. The main benefit here is that you're guaranteed accurate results synchronously. (The true feature detection can't do that, though the new small size is much faster in Firefox.)

Other bits and bobs

The nice guys at that other jQuery Podcast had me on the show in November. I talked about, well.. basically all of the stuff above. :) It's a good show.

I tweeted about a new quick approach to a for loop:

for (var i = -1, len = arr.length; ++i < len; ) // the cool guy loop

The fun part is that the counting expression (the third part) is empty. It's faster than your standard loops, but reverse while() is still quicker. @jdalton beat me to this one, for the record.

Lastly, along with my writers, I've been keeping my mp3 blog Aurgasm fresh with new music you'll love. I've got a lot more things in the pipeline, so right after my Christmas and New Years jaunt around Germany, Denmark, and Scotland, you'll see them soon. Happy December ya'll.

Paul Irish javascript, jquery, typography

Avoiding the FOUC v3.0

September 23rd, 2009

FOUC is an unwelcome guest to your soirée of intertube funtimes. He comes in and distracts users eyes with things they shouldn't be seeing, and then departs ever so quickly. We don't like him.

So you've likely seen code like this:

<body>
  <script>document.body.className += 'js';</script>

No good. Scripts in the body block rendering so we can do better[1]:

<head>
  <script>document.documentElement.className += 'js';</script>

Here we'll be adding the class to the HTML element instead of the body, which we have access while we're in the HEAD. (Naturally CSS works just fine with a html.js selector, though this doesn't validate in HTML4)

But here's my big hang-up:
I prefer to write unique css for the no-javascript user. I don't want to be writing .js in front of every selector for my basic accordion/carousel/etc widgets. It's terribly tedious. I really just want a .no-js hook.

My solution:

<html class="no-js">
<head>
  <script>(function(H){H.className=H.className.replace(/\bno-js\b/,'js')})(document.documentElement)</script>

The markup has a no-js class on HTML by default but we'll very safely change that to 'js' inside the head. That compressed line of javascript is basically:

document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/,'js');

But I make it small as it's one of the only scripts that should run in your head. Because everyone has their scripts near their </body> tag, right?

We use the same approach in Modernizr, because we want the classes to be all set on the HTML element right when the BODY content starts loading in. Fight the FOUC!

Paul Irish front-end development, javascript

@font-face feature detection

August 20th, 2009

As I'm piecing together a very comprehensive solution for using custom typefaces online, one of the crucial aspects is determining what browsers support @font-face.

My requirements for this detection were:

  • No browser userAgent sniffing
  • No extra HTTP request required
  • The test must be synchronous, no race conditions or HTTP requests
  • Must be performant with a small footprint, natch
  • Results should match the latest research on compatibility

I quickly tried:

!!window.CSSFontFaceRule

This test works great in FF2+, Safari and Opera. But it fails in IE (bug surprise) and Chrome gives a false positive.

The code

What follows is the best test for @font-face support I have found:

 
/*!
 * isFontFaceSupported - v0.9 - 12/19/2009
 * http://paulirish.com/2009/font-face-feature-detection/
 * 
 * Copyright (c) 2009 Paul Irish
 * MIT license
 */
 
var isFontFaceSupported = (function(){
 
 
    var fontret,
        fontfaceCheckDelay = 100;
 
		// IE supports EOT and has had EOT support since IE 5.
		// This is a proprietary standard (ATOW) and thus this off-spec,
		// proprietary test for it is acceptable. 
    if (!(!/*@cc_on@if(@_jscript_version>=5)!@end@*/0)) fontret = true;
 
    else {
 
    // Create variables for dedicated @font-face test
      var doc = document, docElement = doc.documentElement, 
          st  = doc.createElement('style'),
          spn = doc.createElement('span'),
          wid, nwid, body = doc.body,
          callback, isCallbackCalled;
 
      // The following is a font, only containing the - character. Thanks Ethan Dunham.
      st.textContent = "@font-face{font-family:testfont;src:url(data:font/opentype;base64,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)}";
      doc.getElementsByTagName('head')[0].appendChild(st);
 
 
      spn.setAttribute('style','font:99px _,serif;position:absolute;visibility:hidden'); 
 
      if  (!body){
        body = docElement.appendChild(doc.createElement('fontface'));
      } 
 
      // the data-uri'd font only has the - character
      spn.innerHTML = '-------';
      spn.id        = 'fonttest';
 
      body.appendChild(spn);
      wid = spn.offsetWidth;
 
      spn.style.font = '99px testfont,_,serif';
 
      // needed for the CSSFontFaceRule false positives (ff3, chrome, op9)
      fontret = wid !== spn.offsetWidth;
 
      var delayedCheck = function(){
        if (isCallbackCalled) return;
        fontret = wid !== spn.offsetWidth;
        callback && (isCallbackCalled = true) && callback(fontret);
      }
 
      addEventListener('load',delayedCheck,false);
      setTimeout(delayedCheck,fontfaceCheckDelay);
    }
 
    function ret(){  return fontret || wid !== spn.offsetWidth; };
 
    // allow for a callback
    ret.ready = function(fn){
      (isCallbackCalled || fontret) ? fn(fontret) : (callback = fn);
    }  
 
    return ret;
})();

Download from github

The latest is always at: http://github.com/paulirish/font-face-detect
isFontFaceSupported.js Uncompressed - 4.3k
isFontFaceSupported.min.js Compressed - 3.1k

Usage

isFontFaceSupported() // will return a boolean indicating support
 
// you can also use with a callback,
//  it will be called 100ms later which is adaquate for Gecko and Webkit to properly use the data-uri'd font.
isFontFaceSupprted.ready(function(bool){
  // bool is a boolean that indicates support
});

Sorry. :(

You'll spot the IE conditional compilation in there. I don't like it either, but I'm unaware of any other workable approach (that doesn't pull in an .eot) to test for @font-face support. If you have an idea, please share it!

On the approach

I first use the Web Font Optimizer to subset a truetype font to only contain the period (.) character (2.2k file!), then send it through a data URI converter, then chuck it into a style tag. I test the width of a span of text without the custom font, and then again with the custom font. If the values are different, we can assume @font-face is supported and works.

With a trip through the YUI Compressor, the script is 3.5k3.1k. If you have any ideas on bringing that figure down, I'd love to hear 'em.

Great! Any disadvantages?

Yeah there's one big one. Both Gecko and Webkit load in a data-uri font asynchronously, so the test may give a false negative if you call isFontFaceSupported() immediately afterwards:

<script src="isFontFaceSupported.min.js"></script>
<script>
  if (isFontFaceSupported()) ... // this may report a false negative.
 // that's why we have the isFontFaceSupported.ready() callback mechanism

I'm not terribly happy with this asynchronous delay, so I've written an alternative that uses browser userAgent sniffing. This practice is not recommended and is not terribly future-proof, but it's the only synchronous solution available.

isFontFaceSupported() - sniffing variant
/*!
 * isFontFaceSupported - Sniff variant - v0.9 - 12/19/2009
 * http://paulirish.com/2009/font-face-feature-detection/
 * 
 * Copyright (c) 2009 Paul Irish
 * MIT license
 */
 
/* Browser sniffing is bad. You should use feature detection.
   Sadly the only feature detect for @font-face is 
   asynchronous. So for those that *need* a synchronous solution,
   here is a sniff-based result:
*/
 
var isFontFaceSupported = function(){
 
  var ua = navigator.userAgent, parsed;
 
  if (/*@cc_on@if(@_jscript_version>=5)!@end@*/0) 
      return true;
  if (parsed = ua.match(/Chrome\/(\d+\.\d+\.\d+\.\d+)/))
      return parsed[1] >= '4.0.249.4';
  if ((parsed = ua.match(/Safari\/(\d+\.\d+)/)) && !/iPhone/.test(ua))
      return parsed[1] >= '525.13';
  if (/Opera/.test({}.toString.call(window.opera)))
      return opera.version() >= '10.00';
  if (parsed = ua.match(/rv:(\d+\.\d+\.\d+)[^b].*Gecko\//))
      return parsed[1] >= '1.9.1';    
 
  return false;
 
}

This guy is also on github

2009.09.24: updated the code, and threw it all on github. callback style makes its debut here. This code matches the exact same implementation that's in Modernizr 1.0

2009.12.18: Added a useragent sniffing alternative for those who want reliable synchronous detection.

2009.12.19: New, smaller font file (Thanks Ethan Dunham). The file is now 15% smaller. Script does not remove the extra element it adds to the DOM now, as to assure more accurate results.

Paul Irish front-end development, javascript, typography

Random hex color code generator in JavaScript

June 19th, 2009

For fun I asked a few friends for ideas on a random color generator in a single line of javascript. You know, these guys: #0afec0, #c9f2d0, #9b923e.

Here's what we came up in about two minutes (in chronological order)…

'#' + (function co(lor){   return (lor += 
  [0,1,2,3,4,5,6,7,8,9,'a','b','c','d','e','f'][Math.floor(Math.random()*16)]) 
  && (lor.length == 6) ?  lor : co(lor); })('');

Similar recursive technique, but using a string instead of an array and aliasing the Math object:

(function(m,s,c){return (c ? arguments.callee(m,s,c-1) : '#') + 
  s[m.floor(m.random() * s.length)]})(Math,'0123456789ABCDEF',5)

Using a named function expression instead of arguments.callee.

'#'+(function lol(m,s,c){return s[m.floor(m.random() * s.length)] +
  (c && lol(m,s,c-1));})(Math,'0123456789ABCDEF',4)

If we assume JavaScript 1.6, then we could just use Array.map():

'#'+'0123456789abcdef'.split('').map(function(v,i,a){
  return i>5 ? null : a[Math.floor(Math.random()*16)] }).join('');

But then, the magic of Math struck (16777215 == ffffff in decimal):

'#'+Math.floor(Math.random()*16777215).toString(16);
Thx to ben alman, nlogax, and temp01 for their smarts.

Paul Irish javascript

log() - A lightweight wrapper for console.log

June 9th, 2009

There are a few things that a console.log wrapper can and should do:

  • Prevent errors if a console isn't around (i.e. IE)
  • Maintain a history of logs, so you can look in the past if your console is added afterwards (e.g. firebug lite)
  • Normalize the browser differences in console integration (e.g. when passing multiple arguments into console.log())
  • For something you type regularly, make it quicker to type for the lazy among us.

But there are a few considerations…

Console.log.apply doesn't handle multiple arguments in Safari 3 or Chrome 1.1

Firebug, Chrome, and Safari have a clearer presentation for strings when inside an array:

However while the improved array-wrapped presentation is nice, nested arrays get truncated:

Truncation is no good, so here we avoid array-wrapping when we can.

Extra features

  • A reverse-chronological history, accessible as an array at log.history
  • I have also included a shorthand logargs() function that is useful when you're inside a function and want to know the context and arguments passed in. I use it a lot in ajax callbacks. Worth noting that it uses arguments.callee.caller, which will be deprecated in ECMAScript 5 Strict mode. :(

The code:

 
window.log = function(){
 
  // store logs to an array for reference
  log.history = log.history || [];
  log.history.push(arguments);
 
  window.console && console.log[console.firebug ? 
    'apply' : 'call'](console,Array.prototype.slice.call(arguments));
}
 
// logargs(this); == console.log(this,arguments);
window.logargs = function(context){
  // grab the calling functions arguments
  log(context,arguments.callee.caller.arguments); 
}

And if you'd like it minified:

window.log=function(){var a="history";log[a]=log[a]||[];log[a].push(arguments);window.console&&console.log[console.firebug?"apply":"call"](console,Array.prototype.slice.call(arguments))};window.logargs=function(a){log(a,arguments.callee.caller.arguments)};

Interestingly, the minified version of this script is smaller (262 bytes), and arguably more useful, than the minified firebugx.js, which I've covered before. Plus it has quite a few more features.

Demo?

log() and logargs() demo

Plus Firebug lite?

You got it. This bookmarklet will add firebug lite, and then output the logged history when it's ready:
>>> Fbug Lite+log <<<

Want more power?

After writing this, I worked with Ben Alman on a more comprehensive and robust logging script. It's excellent if you take full advantage of the console API. And you should be aware that Safari 4 and Chrome 2 have most of that API supported. Make full use of it and don't you dare type another alert()!

Paul Irish javascript

Markup-based unobtrusive comprehensive DOM-ready execution

March 11th, 2009

On a recent project I took my previous approach to automating firing of onload events to a new level.

For instance if your code was architected in an object literal such as:

 
FOO = {
  common : {
    init     : function(){ ... },
    finalize : function(){ ... }
  },
  shopping : {
    init     : function(){ ... },
    cart     : function(){ ... },
    category : function(){ ... }
  }
}

A page with this body tag:

<body id="cart" class="shopping">

would load these functions sequentially:

UTIL.fire is calling: FOO.common.init()
UTIL.fire is calling: FOO.shopping.init()
UTIL.fire is calling: FOO.shopping.cart()
UTIL.fire is calling: FOO.common.finalize()

In addition, using these classes and IDs on the body tag provides some excellent specific hooks for your CSS.

The javascript:

UTIL = {
 
  fire : function(func,funcname, args){
 
    var namespace = FOO;  // indicate your obj literal namespace here
 
    funcname = (funcname === undefined) ? 'init' : funcname;
    if (func !== '' && namespace[func] && typeof namespace[func][funcname] == 'function'){
      namespace[func][funcname](args);
    } 
 
  }, 
 
  loadEvents : function(){
 
    var bodyId = document.body.id;
 
    // hit up common first.
    UTIL.fire('common');
 
    // do all the classes too.
    $.each(document.body.className.split(/\s+/),function(i,classnm){
      UTIL.fire(classnm);
      UTIL.fire(classnm,bodyId);
    });
 
    UTIL.fire('common','finalize');
 
  } 
 
}; 
 
// kick it all off here 
$(document).ready(UTIL.loadEvents);

This system worked very well and keeps you in serious control of the execution order.

In the end, I used this plus a custom event to bind super low priority script.
For example:

$(document).bind('finalized',function(){ ... }); // placed within a FOO.shopping.category()

And I'd trigger that

$(document).trigger('finalized');

at the very end of UTIL.loadEvents(). This allows you to keep similar code together, but delay portions responsibly without any setTimeout ugliness.

Paul Irish javascript, jquery

Release: infinite-scroll.com! (w/ jquery and wordpress plugins)

September 25th, 2008

Today I'm releasing www.infinite-scroll.com. Essentially infinite scroll is using ajax to pre-fetch content from a subsequent page and add it directly to the user’s current page. If you have no idea what I'm talking about, you should scroll down to the bottom of aurgasm.us, roflcon.org, or blog.molecular.com.

There is a full design pattern explanation describing when to use it, and considerations to think about when implementing. I also wrote a history of infinite scroll, to cover the history of this somewhat new ajax-enabled interaction technique.

But most importantly, there are two software releases in here:

The Infinite Scroll Wordpress plugin

Enable your wordpress blog with infinite scroll functionality without knowing any javascript. You just need to know css selectors. And no theme php changes should be required to use.

The Infinite Scroll jQuery plugin

Enable anything! Obviously this is for javascript developers only, but it should give you enough flexibility to utilize in your own application.

Check it all out at www.infinite-scroll.com.





Since this is a development-focused blog, you may ask…

How is it done?

There is a little known feature in the .load() method that lets you specify the CSS selector of the html you want to include. jQuery will load in any local URL, then parse the html and grab only the elements you've defined with your selector. This allows for some pretty fun shit: client-side transclusions (a la purple include) ; and some really kickass shit when you combo it with a local php proxy.

This is really the meat of the code:

// load all post divs from page 2 into an off-DOM div
$('<div/>').load('/page/2/ #content div.post',function(){ 
    $(this).appendTo('#content');    // once they're loaded, append them to our content area
});

So the infinite scroll plugin basically leverages that load() method at its core. It's basically scraping your existing page structure, which means you don't need to code any custom backend stuff to enable this functionality! Booyah, right?

The rest of the code maintains the status of the ajax call, dies when it finishes going through your pages, and show/hides the loading notification. Please take a look and let me know what you think!

Paul Irish front-end development, javascript, jquery

Sequentially chain your callbacks in jQuery - Three ways

August 22nd, 2008

I had a challenge to get four divs to fade in sequentially.

Writing this out the long way is not really my favorite:

$("#vehicle1").fadeIn("slow", function(){
  $("#vehicle2").fadeIn("slow", function(){
    $("#vehicle3").fadeIn("slow", function(){
      $("#vehicle4").fadeIn("slow");
    });
  });
});

Ewwww. right?

After some conversation in the #jQuery IRC channel, I present twothree classier ways of accomplishing the same thing:

Self-executing callback chaining

(function showVehicle(elem){
  elem.fadeIn('slow',function(){
    $(this).next().length && showVehicle($(this).next()); 
  });
})( $("div.vehicle:first") );

Custom event triggering (via: ajpiano)

$('div.vehicle')
  .bind('showVehicle',function(e) {$(this).fadeIn('slow',function(){ 
    $(this).next().length && $(this).next().trigger("showVehicle"); 
  })})
  .eq(0)
    .trigger('showVehicle');
Update 2009.06.15: I'm incredibly impressed with temp01's solution in the comments. So that's my recommendation going forward:

Self-executing callback chain on an arbitrary jQuery object (via: temp01)

(function hidenext(jq){
    jq.eq(0).fadeOut("fast", function(){
        (jq=jq.slice(1)).length && hidenext(jq);
    });
})($('div#bodyContent a'))

Paul Irish javascript, jquery

Best practice: Poll instead of a setTimeout hack

May 13th, 2008

Very often you'll have events happening asynchronously, but you need to wait until one has completed before you fire the second.
And you may not have the ability of attaching a callback function to the first.

In my less wise days I'd say "Lets just setTimeout it for a couple seconds…" but always felt really dirty about it.

A classier approach I've used lately is to poll for a change. Here I'm using the gmail greasemonkey API and waiting for it to load in before I start using it:

gmonkey.load("1.0");
 
// this is a self-executing anonymous function that uses setTimeout to call itself 
// at 50ms intervals until the isLoaded variable resolves as true.
 
(function(){
  if (gmonkey.isLoaded){
    // do stuff i want to do with the API
    gmonkey.get('1.0').addNavModule('notepad', '<iframe src="http://aaronboodman.com/halfnote/"></iframe>');
  } else {
    setTimeout(arguments.callee,50);
  }
})();

I found myself doing this a lot when loading in multiple external resources and playing with them.. So to generalize the code I wrote executeWhenLoaded():

// executeWhenLoaded() will be overloaded with as many arguments as i want to check for presence.
function executeWhenLoaded(func){  
 
  for (var i = 1; i<arguments.length; i++){ // for loop starts at 1 to skip the function argument.
    if (! window[ arguments[i] ]) {
      setTimeout(arguments.callee,50);      
      return;
    } 
  }
 
  func(); // only reaches here when for loop is satisfied.
}
 
// and in use:
 
executeWhenLoaded(function(){ 
    console.log(session.data);
},'session');   // session will return a value when the whatever preceding functionality is done.

executeWhenLoaded's first argument is the function to call, it can an unlimited number of arguments, which are all strings that reflect objects in the global namespace that have to be present in order to execute that function.

Update: In the comments, ProggerPete notes that this is not cross-browser compatible.. yet! In IE6, at least, the browser loses the original reference to the arguments object when it cycles through on the arguments.callee call. He offers a fix below.

Paul Irish front-end development, javascript

Quick internationalized sort in javascript

April 21st, 2008
Comments Off

Let's take a list of countries that was originally alphabetized in English, but is now translated to French.

var arr = ["Argentine", "Australie", "Autriche", "Belgique", "Brésil", "Canada", "Chili", 
"Chine", "Costa Rica ", "République Tchèque", "Danemark", "Équateur", "El Salvador ", 
"Finlande", "France", "Allemagne", "Guatemala", "Hong Kong", "Hongrie", "Inde", "Irlande", 
"Italie", "Japon", "Corée du Sud", "Luxembourg", "Mexique", "Pays-Bas", "Nouvelle-Zélande", 
"Norvège", "Panama", "Pologne", "Portugal", "Russie", "Slovaquie", "Espagne", 
"la Suède", "Suisse", "Turquie", "Royaume-Uni", "Uruguay", "États-Unis"]

You can see the incorrect sort order for Germany ("Allemagne") and the US ("États-Unis").
Running the standard javascript Array.sort() will sort it according to the American English language:

arr.sort();
/*==>
["Allemagne", "Argentine", "Australie", "Autriche", "Belgique", "Brésil", "Canada", "Chili", 
"Chine", "Corée du Sud", "Costa Rica ", "Danemark", "El Salvador ", "Espagne", "Finlande", 
"France", "Guatemala", "Hong Kong", "Hongrie", "Inde", "Irlande", "Italie", "Japon", 
"Luxembourg", "Mexique", "Norvège", "Nouvelle-Zélande", "Panama", "Pays-Bas", "Pologne", 
"Portugal", "Royaume-Uni", "Russie", "République Tchèque", "Slovaquie", "Suisse", "Turquie",
 "Uruguay", "la Suède", "Équateur", "États-Unis"] */

Note the misplacement of the last three entries. A real internationalized sort of this would be a huge motherbitch to implement, but here is a quick and hacky way to get your ducks in order:

  arr.sort(function(a,b){
 
    function normalize(str){
       return str
               .toLowerCase()
               .replace(/è|é|ê|ë/,'e').replace(/ò|ó|ô|õ|ö/,'o').replace(/ì|í|î|ï/,'i')
               .replace(/à|á|â|ã|ä|å|æ/,'a').replace(/ù|ú|û|ü/,'u');
    }
 
    a = normalize(a);
    b = normalize(b);
 
    return ((a < b) ? -1 : ((a > b) ? 1 : 0));
  });
/*==>
["Allemagne", "Argentine", "Australie", "Autriche", "Belgique", "Brésil", "Canada", "Chili", 
"Chine", "Corée du Sud", "Costa Rica ", "Danemark", "El Salvador ", "Équateur", "Espagne",
 "États-Unis", "Finlande", "France", "Guatemala", "Hong Kong", "Hongrie", "Inde", "Irlande", 
"Italie", "Japon", "la Suède", "Luxembourg", "Mexique", "Norvège", "Nouvelle-Zélande", 
"Panama", "Pays-Bas", "Pologne", "Portugal", "République Tchèque", "Royaume-Uni", 
"Russie", "Slovaquie", "Suisse", "Turquie", "Uruguay"] */

It's not perfect (I bet that "la Suède" should actually be in the S's), but it'll get you a bit closer without too much effort.

2009.10.29: A much better method:
  arr.sort(function(a, b) {
    if (typeof a === 'string' && typeof b === 'string') {
      return a.toLowerCase().localeCompare(b.toLowerCase());
    });

Paul Irish hacks, javascript