Archive for the 'JavaScript' Category

C3PO: Common 3rd-party objects

Monday, February 18th, 2013

Problem: too much JavaScript in your page to handle 3rd party widgets (e.g. Like buttons)
Possible solution: a common piece of JavaScript to handle all third parties' needs

3t1jsi

What JavaScript?

If you've read the previous post, you see that the most features in a third party widget are possible only if you inject JavaScript from the third party provider into your page. Having "a secret agent" on your page, the provider can take care of problems such as appropriately resizing the widget.

Why is this a problem?

Third party scripts can be a SPOF (an outage), unless you load them asynchronously. They can block onload, unless the provider lets you load it in an iframe (and most don't). There can be security implications because you're hosting the script in your page with all permissions associated with that. And in any case, it's just too much JavaScript for the browser to parse and execute (think of mobile devices)

If you include the most common Like, Tweet and +1 buttons and throw in Disqus comments, you're looking at well over 100K (minified, gzipped) worth of JavaScript (wpt for this jsbin)

This is more than the whole of jQuery, which previous experiments show can take the noticeable 200ms just to parse and evaluate (assuming it's cached) on an iPhone or Android.

What does all of this JS do?

The JavaScript used by third parties is not always all about social widgets. The JS also provides API call utilities, other dialogs and so on. But the tasks related to social widgets are:

  1. Find html tags that say "there be widget here!" and insert an iframe at that location, pointing to a URL hosted by the third party
  2. Listen to requests from the new iframes fulfill these requests. The most common request is "resize me, please"

Now, creating an iframe and resizing it doesn't sound like much, right? But every provider has to do it over and over again. It's just a wasted code duplication that the browser has to deal with.

Can't we just not duplicate this JavaScript? Can we have a common library that can take care of all widgets there are?

C3PO draft

Here's a demo page of what I have in mind. The page is loading third party widgets: like, tweet, +1 and another one I created just for illustration of the messaging part.

It has a possible solution I drafted as the c3po object. View source, the JS is inline.

What does c3po do?

The idea is that the developer should not have to make any changes to existing sites, other than remove FB, G, Tw, etc JS files and replace with the single c3po library. In other words, only the JS loading part should be changed, not the individual widgets code.

c3po is a small utility which can be packaged together with the rest of your application code, so there will be no additional HTTP requests.

Parsing and inserting iframes

The first task for c3po is to insert iframes. It looks for HTML tags such as

<div class="fb-like" data-href="http://phpied.com"></div>

Similar tags are generated by each provider's "wizard" configuration tools.

In place of this tag, there should be an iframe, so the result (generated html) after c3po's parsing should roughly be like:

<div class="fb-like" data-href="http://phpied.com">
  <iframe 
    src="http://facebook.com/plugins/like.php?href=http://phpied.com">
  </iframe>
</div>

The way to do this across providers is to just have every data- attribute passed as a parameter to the 3rd party URL.

Third parties can be setup using a register() method:

// FB
c3po.register({
  'fb-like': 
    'https://www.facebook.com/plugins/like.php?',
  'fb-send':
    'https://www.facebook.com/plugins/send.php?',
});
 
// Tw
c3po.register({
  'twitter-share-button':
    'https://platform.twitter.com/widgets/tweet_button.html#'
});
 
// ...

The only additional parameter passed to the third party URL is cpo-guid=..., a unique ID so that the iframe can identify itself when requesting services.

The parsing and inserting frames works today, as the demo shows. The only problem is you don't know how big the iframes should be. You can guess, but you'll be wrong, given i18n labels and different layouts for the widgets. It's best if the widget tells you (tells c3po) how big it should be by sending a message to it.

X-domain messaging

What we need here is the iframe hosted on the provider's domain to communicate with the page (and c3po script) hosted on your page. X-domain messaging is hard, it requires different methods for browsers and I'm not even going to pretend I know how it works. But, if the browser supports postMessage, it becomes pretty easy. At the time of writing 94.42% of the browsers support it. Should we let the other 5% drag us down? I'd say No!

c3po is meant to only work in the browsers that support postMessage, which means for IE7 and below, the implementers can resort to the old way of including all providers' JS. Or just have less-than-ideally-resized widgets with reasonable defaults.

When the widget wants something, it should send a message, e.g.

var msg = JSON.stringify({
  type: 'resize',
  guid: '2c23263549d648000',
  width: 200, 
  height: 300
});
parent && parent.postMessage(msg, '*');

See the example widget for some working code.

The c3po code that handles the message will check the GUID and the origin of the message and if all checks out it will do something with the iframe, e.g. resize it.

Again, take a look at the demo code to see how it all clicks together

Next?

As you see in the demo, only the example widget is resized properly. This is because it's the only one that sends messages that make sense to c3po.

Next step will be to have all widget providers agree on the messages and we're good to go! The ultimate benefit: one JS for all your widget-y needs. One JS you can package with your own code and have virtually 0 cost during initial load. And when you're ready: c3po.parse() and voila! - widgets appear.

Of course, this is just a draft for c3po, I'm surely missing a lot of things, but the idea is to have soemthing to start the dialogue and have this developed in the open. Here's the github repo for your forking pleasure.

Make sense? Let's talk.

 

Speed geek’s guide to Facebook buttons

Thursday, February 14th, 2013

or "How to help your users share your content on Facebook and not hurt performance"

Facebook's like button is much much faster now than it used to be. It also uses much fewer resources. And lazy-evaluates JavaScript on demand. And so on. But it's still not the only option when it comes to putting a "share this article on Facebook" widgety thing on your site.

The list of options is roughly listed in order of faster (and least features) to slowest (and most features).

#1: A share link

Note that this feature has been deprecated but it still does work. And you see it all over the place.

A simple link to sharer.php endpoint is all it takes. The u parameter is your URL. E.g.:

<a 
  href="https://www.facebook.com/sharer/sharer.php?u=phpied.com" 
  target="_blank">
  Share on Facebook
</a>

The above is a hardcoded URL. You can, of course, spit the current URL on the server side. A JS-only client-side solution could be to take the document.location. You can also pop a window. And use a button, or an image. Say something like:

<button id="sharer">Share</button>
<script>
document.getElementById('sharer').onclick = function () {
  var url = 'https://www.facebook.com/sharer/sharer.php?u=';
  url += encodeURIComponent(location.href);
  window.open(url, 'fbshare', 'width=640,height=320');
};
</script>

Try it:


Method #1's performance price: none

This is just a link you host in your HTML or bit of JavaScript you can inline or package with your own JavaScript (it is, after all, your own JavaScript)

#2: Feed dialog

The feed dialog a next incarnation of the share popup.

It can also be as simple as a link, like so

https://www.facebook.com/dialog/feed
  ?link=jspatterns.com
  &app_id=179150165472010
  &redirect_uri=http://phpied.com

Try it:

Share

You need a redirect_uri which can be something like a thank you page. But instead of "thank you", you can simply go back to the article by making redirect_uri and link point to the same URL

Again, a client-only solution could be something like:

  var feed = 'https://www.facebook.com/dialog/feed?app_id=179150165472010';
  var url = encodeURIComponent(location.href);
  feed += '&link=' + url + '&redirect_uri=' + url;
  window.open(feed, 'fbshare', 'width=640,height=480');

The result is a dialog that looks like:

feed

But this feed dialog can also be a popup. You do this by adding &display=popup. This hides the FB chrome. And you can also make the "thank you" page just a simple page that closes the window.

Try it:

The result:

feedpopup

The other required thing is the app id. You need one. But that's actually cool because it has side benefits. For example better error messages for you (the app admin) that the users don't see. It also gives you a little "via phpied.com" attribution linked to the App URI which is a nice traffic boost hopefully as your sharer's friends see the story in their newsfeed or timeline and click the "via".

story

So, App ID is good, you can get one here.

Additionally there's a bunch of other params you can pass to the feed dialog to control how the story is displayed. You can provide title, description, image, etc. Full list here.

Method #2's performance price: none

Feed dialog has the same (non-existing) performance requirements as the share links. It's all inline. Any content coming from Facebook is only on user interaction.

BTW, this is the method youtube currently uses.

#3: Feed dialog via JS SDK

Now we move on from simple links and popups to using the JavaScript SDK.

First things first, you absolutely must load the SDK asynchronously. Or non-onload-blocking-asynchronously in an iframe. More on these two later.

After you load the SDK like so:

(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/en_US/all.js";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Then, whenever you're ready, you can make a call to get the feed dialog:

FB.ui({
  method: 'feed',
  redirect_uri: 'http://phpied.com/files/fb/window.close.html',
  link: 'http://phpied.com',
  // picture: 'http...jpg',
  caption: 'Awesomesauce',
  // description: 'Must read daily!'
});

For a working example, check this example in jsbin

The result:

jsbin-feed

As you can see, this is now a real properly resized popup. No FB chrome, nice and clean. In general the JS SDK makes everything better. But you need to load it first - the performance price you pay for all the magic.

Method #3's performance price: an async JS

Opening the feed dialog this way requires you to load the Facebook JavaScript SDK. It's one JS file with a short expiration time (20 mins). When it loads, it also makes two additional requests required for cross-domain communication. These requests are small though and with long-expiration caching headers. Since the JS SDK is loaded many times during regular user's surfing throughout the web, these two additional requests have a very high probability of being cached. So is the JSSDK itself. If not cached, at least it's a conditional requests with likely a 304 Not Modified response.

Here's the waterfall of loading the jsbin test page where you can see the JS SDK loading (all.js) and the two x-domain thingies (xd_arbiter.php)

Note that by default the JS SDK sends an additional request checking whether the user is logged in. If you don't need that, make sure you set the login status init property to false, as shown in the test page, like:

FB.init({appId: 179150165472010, status: false});

When loading the JS SDK you must absolutely make sure it's loaded asynchronously, and even better - in an iframe, so the onload of your page is never blocked.

#4: Like button in an iframe

We're coming to the Like button. There are two ways to load it: either you create an iframe and point it to /plugins/like.php or you include the JS SDK and let the SDK create the iframe. Let's take a look at the you-create-iframe option first.

The integration is straightforward: You go to the help page, use the "wizard" configurator found there and end up with something like:

<iframe 
  src="//www.facebook.com/plugins/like.php?href=phpied.com&amp;width=450&amp;show_faces=true&amp;height=80" 
  scrolling="no" 
  frameborder="0" 
  style="border:none; overflow:hidden; width:450px; height:80px;" 
  allowTransparency="true"></iframe>

You're done!

The button comes in three layouts: standard (biggest), box_count and button_count

Try it:

Standard

Box count

Button count

As you can see, you get quite a bit more features here, e.g. number of likes and social context (who else has liked) in the standard layout. Also in the standard layout you get a little comment input. You don't get one in the other layouts because there's no space in the little iframe. You define the iframe and the code inside the iframe cannot break out of it and do something wild (or useful), e.g. open a big commenting dialog. Or make the iframe bigger because the word "Like" may be significantly longer in some languages. When you "trap" the iframe in your dimensions, it stays there.

Method #4's performance price: iframe content

In this method every time someone loads your page, they also visit a page (like.php) hosted by facebook.com. Now, this page is highly optimized: it only has html, sprite and async lazy-executed JS (which doesn't block onload). 3 requests in total. Maybe some faces (profile photos), depending on the layout and whether the user's friends have liked the URL.

As you probably know, every iframe's onload blocks the parent window's onload. So, if you feel so inclined you can always do any old lazy-load trick in the book. E.g. create the iframe after window.onload, or "double-frame" it, or (for the webkits out there) write the iframe src with a setTimeout of 0.

Another thing to consider is to always load the iframe via https, so there's no http-https redirect if the user has opted to always use facebook via https.

#5: Like button via SDK

This is building on what you already know about #3 and #4: You load the SDK. You sprinkle <fb:like> (or <div class="fb-like">) where you want buttons to appear. The SDK finds these and replaces them with iframes.

<!-- all defaults -->
<fb:like></fb:like>
 
<!-- layout, send button -->
<div class="fb-like" data-send="true"></div>

If you don't need to specify the URL to like, it's the current page.

Try it:

Standard

box count

button count

This is the most full-featured button implementation. It will resize the button as required by content and i18n. It will always present a comment dialog. (When people share with their own comment, these stories do better, because it's always nice to see a friend's comment attached to a URL, right?)

The good thing about this method is that you can load any other FB plugin (e.g. follow button by just adding an fb:follow in the HTML) without re-loading the SDK, it's already there and can handle all the plugins, dialogs and API requests.

Method #5's performance price: JSSDK + iframe content

Combining the features of methods #3 and #4 also combines their perfromance impact. Again, the like.php iframe is heavily optimized and tiny. Also the SDK has a chance of being cached from the users visit on another page. And, of course, you always load the SDK asynchronously so it's impact on your initial page loading is minimal. Or load the SDK in an iframe so the impact is virtually 0.

So the total cost in terms of number of requests in empty cache view is 6. 3 from the iframe + 3 from the SDK. Full cache view should be 1 request - just the like.php frame with the current count, faces and so on.

But again, to minimize the impact, you just load the SDK in an iframe (so the whole widget doesn't block onload and doesn't SPOF) or asynchronously (so it doesn't SPOF and doesn't block onload in IEs)

Summary

# Method Features Cost
1 Share link link opens popup, no like count, no social context none
2 Feed dialog link opens page, no like count or context. You can pass customized description, image, etc for the story. Up to you to do a "thank you" page. none
3 Feed via SDK properly resized popup, JS control over the flow. No like count or context Loading JS SDK
4 Like button in your frame like count, social context, but no i18n resizing, comment option only sometimes like.php iframe (3 requests)
5 Like button via SDK All features plus proper resizing, comment dialog, easier to implement via fb:like tags in HTML like.php + SDK

I mentioned a few times in the article but let me repeat once again for the TL;DR folks. If you're loading the JS SDK, it's absolutely mandatory that you make sure it's either loaded asynchronously to avoid SPOF, or even better - in an iframe to avoid blocking onload.

 

Run jsperf tests in a bunch of WebPagetest browsers

Monday, February 11th, 2013

Motivation

1. You write a new test to confirm a JavaScript-related performance speculation
2. You click
3. Your test runs in a bunch of browsers

Glossary

JSperf.com is the site where all you JavaScript performance guesswork should go to die or be confirmed. You know how the old wise people say "JSperf URL or it didn't happen! Now off my lawn!". Yup, that jsperf.com

WebPagetest.org (WPT) is the site where you get answers to the ol' question: "Why do people say my oowsome site is slow? And what should I do about it?"

Bookmarklet is a little piece of JavaScript you conveniently access from your browser bookmarks and inject into other non-suspecting sites.

Github is where you host code.

Bookmaker tool makes a bookmarklet from a .js file URL (probably hosted on github)

Trouble in paradise

These days we're so happy and spoiled with all these amazing tools around us. And yet, when you create a JSPerf test, you have to open all these browsers and run the test everywhere. Even IE. And, when on Mac, IE is usually not readily available. Plus it comes in a bunch versions - from almost-but-not-quite-forgotten IE6, all the way to IE10 The Greatest - and they have different, sometimes contradicting, performance characterics.

To the rescue: WPT

WebPagetest has: a/ ability to run in a bunch of browsers and b/ an API

The bookmarklet

The bookmarklet. It's here, on github

It starts by inquiring about your WPT API key. I know, you have to get one. You can read the API docs on how to get one, but let me save you the trip: you just need to ask pmeenan@[the tool's domain].org for a key. Politely. Tell him I sent you. Promise not to abuse.

  var key = localStorage.wpt_key;
  if (!key) {
    var prompt = window.__proto__.prompt;
    key = prompt('Your WebPageTest API key, please?');
    if (!key) {
      return gameOver();
    }
    localStorage.wpt_key = key;
  }

The key is stored in your localStorage so you don't have to paste it all the time.

Oh, you may wonder what's up with that:

var prompt = window.__proto__.prompt;
prompt('Message...');

Looks like something somewhere on jsperf is doing window.prompt = function(){}, same for window.open and probably others. Makes sense, you don't want popup-y stuff (by the thousands) while running a test a gazilion times. So the bookmarklet has to go the window.__proto__ for the original prompt

Moving on.

Setting up the constant params of the API call. The variable param will be the location which will tell what browser to use. We also give the (undocumented) time a value of 60s, so that the test has time to run. We also want only one run and just the first run (no full cache run).

The URL to test will be the current page loaded in jsperf.com which is where you run the bookmarklet. And we'll append #run for autorun.

  // base params
  var wpt = 'http://www.webpagetest.org/runtest.php?';
  var params = {
    k: key,
    time: 60,
    runs: 1,
    fvonly: 1,
    url: 'http://jsperf.com' + location.pathname + '#run'
  };
  Object
    .keys(params)
    .forEach(function(key) {
      wpt += key + '=' + encodeURIComponent(params[key]) + '&';
    });

Finally, setup the locations with browsers IE6,7,8,9,10 and open all these browser windows:

  var locations = localStorage.wpt_locations;
  if (!locations) {
    locations = ['Dulles_IE6', 'Dulles_IE7', 'Dulles_IE8', 'Dulles_IE9', 'Dulles_IE10'];
  }
  
  // pop some windows up
  var open = window.__proto__.open;
  locations.forEach(function(loco){
    open(wpt + 'location=' + encodeURIComponent(loco));
  });

Again, the full source is here on github

Github has a "raw" version, e.g. this so we take this url, paste it in the bookmaker tool and we get a nice installable bookmarklet link.

Install

Drag this link to you bookmarks:

jsperf -> wpt

Run

1. Go to any jsperf test, e.g. http://jsperf.com/array-proto-vs/3
2. Click the bookmarklet
3. Observe 5 new tabs with 5 IE versions running your test!

jsperf

More browsers

In addition to the browsers (locations) I've defined you can always add more, like Chrome and Firefox. However you probably have these already handy so no need to kill WPT's servers. But the option is there, just edit your localStorage.wpt_locations

Thanks for reading! Comments welcome!

 

WebAudio: live input

Sunday, October 28th, 2012

Live input, aka getUserMedia: it exists in Chrome Canary for audio too. Great times to be a web developer, right?

Let's check it out.

Here's the demo, but first a prerequisite: go chrome://flags, search for "Web Audio Input" and enable it. Restart Chrome Canary.

With a guitar

I wanted to have a little trickier setup and capture guitar sound not just voice with a microphone.

As always, it was bigger hurdle to get guitar sound to the computer, than anything else JavaScript-wise.

I have a guitar amp that has a mini-USB out. This goes to the USB of the computer. Wrestle, system settings, garage band to the rescue.... eventually the computer makes sound.

Capturing

I was assuming the stream you get from getuserMedia can go directly to an HTML <audio> src. No such luck. Works for video but not yet for audio.

So... WebAudio API saves the day.

Setting up audio context (like in the previous post), shimming getUserMedia and setting up callbacks for it:

  // for logging
  function fire(e, data) {    
    log.innerHTML += "\n" + e + " " + (data || '');
  }
 
  // globals
  var audio_context;
  var volume;
 
  // one-off initialization
  (function init(g){
    try {
      audio_context = new (g.AudioContext || g.webkitAudioContext);
      fire('Audio context OK');
      // shim
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
      fire('navigator.getUserMedia ' + (navigator.getUserMedia ? 'OK' : 'fail'));
      // use
      navigator.getUserMedia(
        {audio:true},
        iCanHazUserMedia, 
        function(e){fire('No live audio input ' + e);}
      );
    } catch (e) {
      alert('No web audio support in this browser');
    }
  }(window));

When the user loads the page, here's what they see:

In my case I select the guitar amp and click "Allow" button.

This little window informs me the page is using the audio input:

Playing back

Now that the user has allowed audio access, let's play back the audio we receive, but pass it through a volume control.

All this work happens in the iCanhazUserMedia(), the success callback to getUserMedia.

  function iCanHazUserMedia(stream) {
    
    fire('I haz live stream');
    
    var input = audio_context.createMediaStreamSource(stream);
    volume = audio_context.createGainNode();
    volume.gain.value = 0.8;
    input.connect(volume);
    volume.connect(audio_context.destination);
    
    fire('input connected to destination');
  }

What we have here (ignoring fire()):

  1. setup an input stream from the user stream, this is the first node in the audio chain
  2. setup a volume (Gain) node with initial volume 0.8 out of 1
  3. connect input to volume to output/speakers

And this is it!

Additionally an input type=range max=1 step=0.1 can change the volume via volume.gain.value = value;

Go play! Isn't it amazing that you can now grab microphone or any other audio input and play around with it? All in JavaScript, all in the browser without any plugins.

Moar!

This was a very basic exploratory/primer example. For more:

 

WebAudio: oscillator in JS

Saturday, October 27th, 2012

How about generating some noise in JavaScript?

Demo is here: oscillator.

How does this work?

Using HTML Web Audio you can synthesize audio with a given frequency. E.g. 440Hz is A ("la" in solfège)

This means you don't need an <audio> element or any mp3, ogg, wav, etc, no external files.

Let's see how.

Capable browser?

You need a browser that supports AudioContext. No such (major) browser at the time of writing, afaik. But there's webkitAudioContext supported in stable iOS Safari, Safari, Chrome. Also there could be browsers that support AudioContext but not the oscillator part. So starting off:

  // globals
  var audio_context, oscillator;
 
  // hello audio world
  (function init(g){
    try {
      audio_context = new (g.AudioContext || g.webkitAudioContext);
      oscillator = audio_context.createOscillator();
    } catch (e) {
      alert('No web audio oscillator support in this browser');
    }
  }(window));

Start/stop playing

Alright, next is a play(frequency /*number*/) function which makes noise with a given frequency.

  function play(freq) {
    oscillator = audio_context.createOscillator();
    oscillator.frequency.value = freq;
    oscillator.connect(audio_context.destination);
    oscillator.noteOn(0);
    fire('play', oscillator.frequency.value);
  }

(Don't mind fire(), it's just a poor man's event utility for logging what's going on)

The audio context provides a createOscillator(). You assign the frequency you need and connect this oscillator node to the audio destination (speaker).

There is a nice analogy going on in the Web Audio: you start with some input noise, say coming from microphone or an audio file, or, in this case, you generate the noise yourself. Then you connect that initial input to the output (destination) which is the system speaker/phones. In between though you can pass the noise through a bunch of nodes that can modify the noise.

In this simple example I only have an oscillator node which is connected directly to the audio destination.

noteOn(0) starts playing the noise we just generated.

Implementing stop() to silence the noise is just a question of calling noteOff(0) on the same oscillator node.

  function stop() {
    oscillator.noteOff(0);
    fire('stop');
  }

That's it, go play with the demo.

The demo plays 440Hz (A on 4th octave of the piano) and 880Hz (A on 5th octave) and also lets you punch in a number and see what happens. Probably nice to play with your dog and with sounds at frequencies you cannot hear.

A chord

Finally, an attempt to play a chord: three frequencies at the same time. C major is C, E and G tones. We have an array of the three frequencies, so loop over the array and create and noteOn three oscillator nodes.

  var cmajor = {};
  cmajor.yo = function () {
    var oscs = [], o, i, freqs = [261.63, 329.63, 392];
    freqs.forEach(function(freq) {
      o = audio_context.createOscillator();
      o.frequency.value = freq;
      o.connect(audio_context.destination);
      o.noteOn(0);
      oscs.push(o);
    });
    this.oscs = oscs;
    fire('play', '\n - ' + freqs.join('Hz\n - '));
  };
  
  cmajor.no = function () {
    this.oscs.forEach(function(o) {
      o.noteOff(0);
    });
    fire('stop');
  };

Thanks

Some links for learning more

Once again the demo is here: oscillator.

Intro: html5rocks.com

Educational demos: webaudiodemos.appspot.com/

 

Non-onload-blocking async JS

Thursday, June 28th, 2012

Asynchronous JS is cool but it still blocks window.onload event (except in IE before 10). That's rarely a problem, because window.onload is increasingly less important, but still...

At my Velocity conference talk today Philip "Log Normal" Tellis asked if there was a way to load async JS without blocking onload. I said I don't know, which in retrospect was duh! because I spoke about Meebo's non-onload-blocking frames (without providing details) earlier in the talk.

Stage fright I guess.

Minutes later in a moment of clarity I figured Meebo's way should help. Unfortunately all Meebo docs are gone from their site, but we still have their Velocity talk from earlier years (PPT). There are missing pieces there but I was able to reconstruct a snippet that should load a JavaScript asynchronously without blocking onload.

Here it goes:

(function(url){
  var iframe = document.createElement('iframe');
  (iframe.frameElement || iframe).style.cssText = "width: 0; height: 0; border: 0";
  var where = document.getElementsByTagName('script');
  where = where[where.length - 1];
  where.parentNode.insertBefore(iframe, where);
  var doc = iframe.contentWindow.document;
  doc.open().write('<body onload="'+
    'var js = document.createElement(\'script\');'+
    'js.src = \''+ url +'\';'+
    'document.body.appendChild(js);">');
  doc.close();
})('http://www.jspatterns.com/files/meebo/asyncjs1.php');

The demo page is right here. It loads a script (asyncjs1.php) that is intentionally delayed for 5 seconds.

Features

  • loads a javascript file asynchronously
  • doesn't block window.onload nor DOMContentLoaded
  • works in Safari, Chrome, Firefox, IE6789 *
  • works even when the script is hosted on a different domain (third party, CDN, etc), so no x-domain issues.
  • no loading indicators, the page looks done and whenever the script arrives, it arrives and does its thing silently in the background. Good boy!

* The script works fine in Opera too, but blocks onload. Opera is weird here. Even regular async scripts block DOMContentLoaded which is a shame.

Drawback

The script (asyncjs1.php) runs is in an iframe, so all document and window references point to the iframe, not the host page.

There's an easy solution for that without changing the whole script. Just wrap it in an immediate function and pass the document object the script expects:

(function(document){
 
  document.getElementById('r')... // all fine
 
})(parent.document);

How does it work

  1. create an iframe without setting src to a new URL. This fires onload of the iframe immediately and the whole thing is completely out of the way
  2. style the iframe to make it invisible
  3. get the last script tag so far, which is the snippet itself. This is in order to glue the iframe to the snippet that includes it.
  4. insert the iframe into the page
  5. get a handle to the document object of the iframe
  6. write some HTML into that iframe document
  7. this HTML includes the desired script
 

YSlow development: getting started

Sunday, June 17th, 2012

Since version 2.0, YSlow is no longer just a tool, it's a platform. You can create your own rules (performance or otherwise), combine them into rulesets, tweak scores to your liking and so on.

Once Marcel took over and did version 3.0. YSlow can now run in many many environments: as a Firebug extension (like version 1.0 did), as a Firefox extension, Chrome extension, command-line and so on... including running as a bookmarklet in any browser (including mobile browsers). Funny aside is that YSlow version 0.XYZ was originally just a bookmarklet. Now it's a bookmarklet among everything else.

Now, setting up for browser extension development can be intimidating if you've never done it. But worry not, I want to show you how you can create YSlow extensions and customizations knowing nothing but JavaScript.

We'll be using the bookmarklet version for development.

What's even lovelier is that YSlow is open source now on Github.

Stay tuned

I wish I could tell you more, but it's father's day and the backyard BBQ party (including a rare live appearance from Anaconda Limousine) starts in an hour. And something tells me I won't feel very bloggy after the party. So YSlow would have to wait.

If you cannot wait though here are some pointers:

So welcome to the exciting world of YSlow development, it's fun and games and new rules and new integrations and pure webperf joy!

 

Anaconda Limousine: the guitar parts

Sunday, June 17th, 2012

I'm part of a band that has an album out now. I know, right? (links: excuse-for-a-site, amazon, itunes).

I wanted to put up all the songs on the site, but seems like there's a little dissonance in the band whether this is a good idea. Plan B: 30s samples. Like the bigwigs do on Amazon and iTunes.

But while their samples are random, a band can do a better job in picking parts that are representative of the overall sound. I though - let me pick my solo stuff only as an exercise. So there: Anaconda Limousine: the guitar parts.

I wanted to use command-line ffmpeg, of course, because all music software is like Photoshop to me, just can't figure out what's going on with so much UI. Turned out I needed sox too.

And then I want to use HTML5 Audio to play the samples.

I thought: an audio sprite would be a good idea, put all samples in one file, then JS can update the UI depending on which sample is playing. And I thought might be neat to have the JS turn the volume up and down to fade in/out the samples, like iTunes does. Turns out sox is doing this so nicely, that I let it do it.

Samples

I started by listening to the songs and taking notes with song #, start and end.

var slices = [
  {song: 1,  start:   8, end:  21},
  {song: 1,  start: 301, end: 323}, // from 3:01 to 3:23
  {song: 1,  start: 405, end:   0}, // 0 means till the end
  {song: 2,  start:   0, end:  30},
  {song: 2,  start: 305, end: 318},
  {song: 2,  start: 330, end:   0},
  {song: 3,  start:   0, end:  20},
  {song: 3,  start: 333, end:   0},
  {song: 4,  start: 303, end:   0},
  {song: 5,  start:   0, end:  20},
  {song: 5,  start: 300, end: 333},
  {song: 7,  start:   0, end:  20},
  {song: 7,  start: 340, end:   0},
  {song: 8,  start:   0, end:  25},
  {song: 8,  start: 313, end:   0},
  {song: 9,  start: 155, end: 239},
  {song: 9,  start: 350, end:   0}
];

Start 0 means start from the beginning of the song, end 0 means go to the end.

The time format is optimized for easy typing (I was walking, typing in Notes app on the iPhone). Turned out I need to convert the times to seconds:

function secs(num) {
  if (num <= 60) {
    return 1 * num
  }
  num += '';
  return num[0] * 60 + num[1] * 10 + num[2] * 1;
}

And I need album meta data too, with name of the song, filename and duration:

 
var songs = [
  {name: "Virus",     fname: "01-virus",     duration: 436},
  {name: "Yesterday", fname: "02-yesterday", duration: 346},
  {name: "All for you", fname: "03-all4u",   duration: 404},
  {name: "Damage",    fname: "04-damage",    duration: 333},
  {name: "Everyday",  fname: "05-everyday",  duration: 444},
  {name: "Girl of mine", fname: "06-girlomine", duration: 338},
  {name: "Fool on the hill", fname: "07-fool",  duration: 413},
  {name: "Faultline", fname: "08-faultline", duration: 347},
  {name: "Parting is such sweet sorrow", 
                      fname: "09-parting",   duration: 420}
];

Ripping the album

In the interest of quality I wanted to work with WAV and then encode in m4a, ogg and mp3.

On TuneCore.com there's a nice step-by-step instruction how to rip a CD to WAV in iTunes, because it uses mp3 by default.

So then I had the files:

01-virus.wav
02-yesterday.wav
03-all4u.wav
04-damage.wav
05-everyday.wav
06-girl.wav
07-fool.wav
08-faultline.wav
09-parting.wav

Slicing and fading

I used ffmpeg to do the slicing, like for example the first sample:

$ ffmpeg -i 01-virus.wav -ss 5 -t 20 ff-0.wav

-ss is start time and -t is duration. As you see instead of starting at 8 seconds (as described in the samples array) I start earlier. This is to give some more music for fade in/out.

For fade in/out I used sox. I didn't know this awesome command line tool existed, but found out while searching how to combine wav files (I know for mp3 you can just `cat`)

I don't want to fade in when the sample starts at the beginning. Or fade out then the sample happens to end at the end of the song. sox takes fade-in duration (or 0 for no fade in), stop time (0 for till the end of the file/sample) and fade out duration (again 0 is no fade out)

I used 3 secods fade in, 4 fade out. The sox commands are one of:

$ sox in.wav out.wav fade 3 0 0
$ sox in.wav out.wav fade 3 0 4
$ sox in.wav out.wav fade 0 0 4

And since I have all the configs in JS, JS makes perfect sense to generate the commands. Hope you can make sense of the comments:

var fadein = 3;
var fadeout = 4;
var merge = ['sox'];
slices.forEach(function(s, index){
  var ff = ['ffmpeg -i'];
  ff.push(songs[s.song - 1].fname  + '.wav'); // in file
  ff.push('-ss');
  ff.push(s.start ? secs(s.start) - fadein : 0); // start of the slice
  ff.push('-t');
  ff.push(!s.end ? 
      1000 : 
      secs(s.end) - secs(s.start) + fadein + fadeout); // end slice
  ff.push('ff-' + index  + '.wav'); // out file
  
  var sox = ['sox'];
  sox.push('ff-' + index  + '.wav'); // in file
  sox.push('s-' + index  + '.wav'); // out file
  sox.push('fade');
  sox.push(s.start ? fadein : 0); // fade in, unless it;s the beginning of the song
  sox.push(0); // till the end of the slice
  sox.push(s.end ? fadeout : 0); // fade out unless it's The End
    
  console.log(ff.join(' '));
  console.log(sox.join(' '));
  
  merge.push('s-' + index  + '.wav');
});
 
merge.push('_.wav');
console.log(merge.join(' '));

Running this gives me a bunch of commands:

ffmpeg -i 01-virus.wav -ss 5 -t 20 ff-0.wav
sox ff-0.wav s-0.wav fade 3 0 4
ffmpeg -i 01-virus.wav -ss 178 -t 29 ff-1.wav
sox ff-1.wav s-1.wav fade 3 0 4
[....]

sox s-0.wav s-1.wav s-2.wav s-3.wav [...] s-16.wav _.wav

2 for each sample (slice and fade) and one last one to merge all faded results. Nothing left to do but run the generated commands.

Save for Web

Final step is to convert the result _.wav to a web-ready format: m4a, ogg or mp3:

$ ffmpeg -i _.wav _.m4a
$ ffmpeg -i _.wav _.mp3
$ ffmpeg -i _.wav -acodec vorbis -aq 60 -strict experimental _.ogg

Turn it up!

Enjoy Anaconda Limousine: The Guitar Parts (ogg, m4a or mp3) with all samples in one file.

And come back later for the JS player part.

See you!

 

3PO#fail

Saturday, June 16th, 2012

So I was flipping through recent slides from Steve Souders and came across a reference to a nice post from Pat Meenan explaining how he setup blackhole.webpagetest.org and how you can edit your hosts file to send third party scripts to the black hole simulating a firewall-blocked or down third party and the effect on your site. (whew, long sentence)

I thought to would be nice to make that easier and have people see (and demonstrate to bosses and clients) how damaging frontend SPOF (Single Point Of Failure) can be. A browser extension maybe. A Chrome extension, because I've never made one. The idea marinated undisturbed for a few days and last night all of a sudden I got to work.

May I present you...

Now available at the Chrome web store.

3PO?

3PO = 3rd Party Optimization

I find it amusing, hope you do too

#fail?

Well, yeah. What happens to your site when a 3rd party goes down? Does it still work?

Is it true that your site is only down when it's down? Or it's down when:

It's down
or
Facbeook is down
or
Google is down
or
Twitter is blocked in your office
or
code.jquery.com is down
...and so on and so on.

This extension helps you check what happens with a click of button.

What 3PO#fail does

Very simple: it's looking for scripts from a list of suspects (api.google.com, platform.twitter.com, etc) and redirects them to blackhole.webpagetest.com

The current list of 3rd parties:

var urls = [
  '*://ajax.googleapis.com/*',
  '*://apis.google.com/*',
  '*://*.google-analytics.com/*',
  '*://connect.facebook.net/*',
  '*://platform.twitter.com/*',
  '*://code.jquery.com/*',
  '*://platform.linkedin.com/*',
  '*://*.disqus.com/*'
];

How?

Install the extension. Load your page. Or mashable.com for example. Then this happens:

It's a button with # on it. Click it. It turns red.

The extension now listens to script requests made to one of the suspect domains.

Now shift-reload the page. If a 3rd party script is found, it's redirected to the black hole and then a counter appears.

Observe whether or not the page is usable when a third party is down. Enjoy and demo to your boss. Tell them: sites do go down, companies ban social networking sites, and btw what do you think will happen when you visit China and load our site?

If you're looking for a page to try, go to mashable or business insider or, ironically, test the extension's page in Chrome web store. Turns out they include Google+'s button synchronously.

Dupe

Here come the LOLz. I blasted this extension out to Steve Souders and back he came with: doh, Pat Meenan also did a Chrome extension to do just this.

Bwahaha. What? You snooze, you miss a whole new tool by Pat Meenan himself.

Here's Pat's extension: SPOF-O-Matic. Try it, use it. It looks more thought out than mine definitely. And there's more code. Maybe Pat spend more time than a night on it. Or maybe he didn't, he's an amazing hacker and half! I mean, uh, webpagetest, hello!

I'll definitely "borrow" his list of 3rd parties which has more entries than mine.

Oh well, you live, you learn (to write Chrome extensions)

Chrome extensions

Creating a Chrome extension was a first for me and was mostly frictionless. Well documented, plenty of samples (try to browse the samples in the repository, because downloading ZIP files is too many clicks). Debugging the extension in the same web inspector is a big plus! Overall I think it's easier to write a Chrome extension than a FF one. Although the last I checked, FF has improved a lot.

Now for the nitpicks.

The API is sometimes irritating. I mean things like

setTitle({title: "My title"});

or

setBadgeText({text: "My text"});

Doplicating title, title, title is annoying. Sometimes it's title, sometimes text, or path or name. Method name appears short but in fact you have to remember one more thing - a property name in a config object. Sounds more like setTitleWithTitle(title) which is just as ridiculous (and popular in Obj-C it seems). Anyway.

The web store asks you for 5 bucks to register and submit an extension. Credit card and all. I didn't like that.

My extension was held for a review which doesn't always happen. The help section says 2-3 business days, but it turned out to be only hours for me. Got a nice email saying the extension is approved and also an explanation why it was held for review. Nice touch.

Code

The code is here: https://github.com/stoyan/3PO-fail. There's not a lot of it. A manifest file and an script that listens to specific URLs and request types in a onBeforeRequest event.

Stripping away UI stuff here's all there is to it.

Callback function which redirects the request:

function failer(info) {
  console.log(info.url); // test
  return {
    redirectUrl: 'https://blackhole.webpagetest.org'
  };
}

There's no logic here because the API allows you to let the browser do request inspection and filtering for you. Here all you do is return an object with a redirectUrl property.

And how do you setup your callback to be invoked?

chrome.webRequest.onBeforeRequest.addListener(
  failer,
  {
    urls: urls,
    types: ['script']
  },
  ["blocking"]
);

You specify your callback to be invoked only for script requests and only those that match a URL in the url array (see above)

The end to the SPOF

All you have to do is load third party scripts synchronously. See here the BFF function for an example. Yet, so many sites are not doing it. There's a need for people to understand this problem. Let's call it demand for advocacy. And now there's supply of 2 brand new tools that make it in-you-face obvious what the damaging effects are.

Random

I went over some of the pages that Steve has listed in his calendar blog post: Business Insider and O'Reilly. O'Reilly is better now and it uses my BFF script (nice, 'scuse me there's something in my eye). Business Insider is almost there. The social stuff is async now, but code.jquery.com is still a SPOF. Funny enough they have a blocking script tag pointing to twitter, but it has a class "post-load". So a script kicks in before this tag and replaces it with async loading. I wonder: why the trouble and not just go async to begin with?

 

Canvas pixels #3: getUserMedia

Wednesday, June 13th, 2012

getUserMedia() is a proposal for one of the most desired device APIs that can give HTML and JS access to the user's camera and microphone. It's already available in Chrome Canary in the form of navigator.webkitGetUserMedia(). It's also available in Opera without a prefix.

In part #1 of this miniseries I talked about manipulating pixels in canvas, let's do the same but this time using video data from your own webcam instead of a static image.

Demo

I've only tested in Chrome so if you want to see the demo, you need to:
1. install Canary
2. go to chrome://flags and Enable PeerConnection

If you don't want to go through the trouble, here's a snapshot of what you're missing: a little video element showing your video camera stream and 4 canvas elements where the image data is manipulated in some way.

And the demo

Hooking up the cam

Getting a video stream is pretty straightforward:

navigator.webkitGetUserMedia(
  {video: true},
  iCanHazStream,
  miserableFailure
);

You declare what type of media you want (video in this case) and provide success and failure callbacks. The browser then prompts the user to allow access:

If the user allows, your success callback is called.

Here's mine:

function iCanHazStream(stream) {
  var url = webkitURL.createObjectURL(stream);
  $('video').src = url;
  webkitRequestAnimationFrame(paintOnCanvas);
}

I have a <video id="video"> element on the page and I set its src to be the stream URL. (In Opera you assign the stream directly, not some made up URL. In webkit the URL ends up being something like http://www.phpied.com/files/canvas/blob:http%3A//www.phpied.com/c0d155b9-f4f8-4c4f-b2bc-694de68d74f2. Anyway, not terribly important)

So this is all you need to do in order to display the camera stream in a VIDEO element. Easy right?

Then I have another function paintOnCanvas() which I schedule with the new requestAnimationFrame hotness instead of old school setInterval()

Setting up the canvas

For the image manipulation I'm using the same CanvasImage() constructor from part #1.

During page load I initialize 4 canvas elements with a placeholder image.

var transformadores = [
  new CanvasImage($('canvas1'), 'color-bars.png'),
  new CanvasImage($('canvas2'), 'color-bars.png'),
  new CanvasImage($('canvas3'), 'color-bars.png'),
  new CanvasImage($('canvas4'), 'color-bars.png')
];

And I have 4 simple pixel manipulators like you saw already:

var manipuladors = [
  {
    name: 'negative',
    cb: function(r, g, b) {
      return [255 - r, 255 - g, 255 - b, 255];
    }
  },
  {
    name: 'max blue',
    cb: function(r, g, b) {
      return [r, g, 255, 255];
    }
  },
  {
    name: 'max red',
    cb: function(r, g, b) {
      return [255, g, b, 255];
    }
  },
  {
    name: 'noise',
    cb: function(r, g, b) {
      var rand =  (0.5 - Math.random()) * 50;
      return [r + rand, g + rand, b + rand, 255];
    },
    factor: '(0 - 500+)'
  }
];

Painting on the canvas

Finally, the paintOnCanvas() function. Here's what happens:

function paintOnCanvas() {
  var transformador = transformadores[0];
  transformador.context.drawImage(
    $('video'), 0, 0, 
    transformador.image.width, transformador.image.height
  );
  var data = transformador.getData();
  for (var i = 0; i < 4; i++) {
    transformador = transformadores[i];
    transformador.original = data;
    transformador.transform(manipuladors[i].cb);
  }
  webkitRequestAnimationFrame(paintOnCanvas);
}

First we need to take the image data from the video element and draw it on a canvas. Then read the image data from the canvas, play with it and paint it back. This seems like a hassle, there might be an easier way to get image data from the video or the stream without going stream-video-canvas, but I don't know it. In any event I only do it once for the first canvas, then remember this data and use it for all the 4 canvases.

It's surprisingly easy to draw a video data in canvas, just using context.drawImage(video_dom_element, ...). From there I read the image data into data and loop through the 4 canvas instances, transforming the image using one of the manipulators I have set up.

Once again, for your entertainment, the demo is right here.

 

Canvas pixels #2: convolution matrix

Monday, June 11th, 2012

In the previous post I talked about manipulating and changing pixels in an image (using JavaScript and canvas) one at a time. We took a single pixel and messed around with its R, G, B or A values.

This time let's look into taking account not only the single pixel but the pixels around it. This allows you to do all kinds of effects, the most popular being emboss, edge detection, blur and sharpen.

The demo page is here

Theory

The type of manipulation we'll consider is called image convolution using a 3x3 matrix. You take 9 pixels from the image: the current pixel you're changing and the 8 immediately around it.

In other words you want to change the RGB values for the pixel in the middle based on its own value and those around it.

Let's say we have some sample values (given in red for R, blue for B and green for G in this figure):

Remember this manipulation was called convolution matrix. So you need a matrix. Below is an example of one such matrix (used in the blur effect)

1,2,1,2,4,2,1,2,

Now you take one of the channels, say R for example. You take each of the 9 R values you have and multiply it by the corresponding number in the matrix. Then sum the nine numbers.

1,2,1,2,4,2,1,2,

1*1 + 2*2 + 5*1 + 11*2 + 10*4 + 20*2 + 1*1 + 10*2 + 1*1 =
 1  +  4  + 5   +   22 +  40  +  40  +  1  +  20  +  1  =
                      134 

In addition to the matrix we also have a divisor and an offset, both optional. If there's no divisor (meaning it's 1, not 0), the result for Red we're looking for is 134. As you can see 134 is pretty far off from the original value of 10. But the blur effect has a divisor of 16. So the new value for red is 8.375

If the convolution asked for an offset, you add it to the end result.

Then you repeat the same for Green and Blue. You can do alpha if you want but for regular images it has constant 255 value so you'll do a lot of math and end up with 255.

You may have noticed that the divisor 16 is also the sum of the numbers in the matrix;

1 + 2 + 1 + 2 + 4 + 2 + 1 + 2 + 1 = 16

This way the result image is as bright as the original. If you have an unbalanced matrix you'll get a darker or a lighter image.

The offset is 0 most of the time, but not always. The emboss effect has offset 127 for example.

Demo matrices

My demo uses the most popular matrices out there. You can search the web for other matrices and play with them. None of them define a divisor because it's the sum of their elements, but the API I'll show you lets you use your custom divisor.

Without further ado, here are the matrices I used defined as an array of JavaScript objects:

var matrices = [
  {
    name: 'mean removal (sharpen)',
    data:
     [[-1, -1, -1],
      [-1,  9, -1],
      [-1, -1, -1]]
  },
  {
    name: 'sharpen',
    data:
     [[ 0, -2,  0],
      [-2, 11, -2],
      [ 0, -2,  0]]
  },
  {
    name: 'blur',
    data:
     [[ 1,  2,  1],
      [ 2,  4,  2],
      [ 1,  2,  1]]
  },
  {
    name: 'emboss',
    data:
     [[ 2,  0,  0],
      [ 0, -1,  0],
      [ 0,  0, -1]],
    offset: 127,
  },
  {
    name: 'emboss subtle',
    data:
     [[ 1,  1, -1],
      [ 1,  3, -1],
      [ 1, -1, -1]],
  },
  {
    name: 'edge detect',
    data:
     [[ 1,  1,  1],
      [ 1, -7,  1],
      [ 1,  1,  1]],
  },
  {
    name: 'edge detect 2',
    data:
     [[-5,  0,  0],
      [ 0,  0,  0],
      [ 0,  0,  5]],
  }
];

Results

Original

Blur

Sharpen

Edge detect

Edge 2

Emboss

Emboss (subtle)

Mean removal (sharpen a lot)

The API

The API is the same as in the previous post, same constructor and all, just adding a new method called convolve(). This is where the magic happens.

You use this method like so:

transformador.convolve([
  [1,2,1],
  [2,4,2],
  [1,2,1]
], 16, 0);

Again, 16 is optional as the method will figure it out if you omit and offset is optional too. Actually you can go to the demo and play in the console to see what happens with a different divisor, e.g.

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 10);

or

transformador.convolve([[1,2,1],[2,4,2],[1,2,1]], 20);

convolve()

Some comments on how convolve() was implemented in this demo.

The big picture:

CanvasImage.prototype.convolve = function(matrix, divisor, offset) {
  // ...
};

Handle arguments: flat matrix is easier to work with and figure out the divisor if missing. How 'bout that array reduce, eh? ES5 ftw.

  var m = [].concat(matrix[0], matrix[1], matrix[2]); // flatten
  if (!divisor) {
    divisor = m.reduce(function(a, b) {return a + b;}) || 1; // sum
  }

Some vars more or less the same as the last time in the transform() method:

  var olddata = this.original;
  var oldpx = olddata.data;
  var newdata = this.context.createImageData(olddata);
  var newpx = newdata.data
  var len = newpx.length;
  var res = 0;
  var w = this.image.width;

Then a loop through all the image data, filter out every 4th element (because we ignore Alpha channel) and write the new image data to the canvas.

  for (var i = 0; i < len; i++) {
    if ((i + 1) % 4 === 0) {
      newpx[i] = oldpx[i];
      continue;
    }
 
    // 
    // magic...
    //
  }
  this.setData(newdata);

Remember that canvas image data is one long array where 0 is R for pixel #1, 1 is B, 2 is G, 3 is Alpha, 4 is R for pixel #2 and so on. This is different than more other code examples you'll in different languages where there are two loops in order to touch every pixel: one from 0 to width and an inner one from 0 to height.

And finally, the "magic" part:

    res = 0;
    var these = [
      oldpx[i - w * 4 - 4] || oldpx[i],
      oldpx[i - w * 4]     || oldpx[i],
      oldpx[i - w * 4 + 4] || oldpx[i],
      oldpx[i - 4]         || oldpx[i],
      oldpx[i],
      oldpx[i + 4]         || oldpx[i],
      oldpx[i + w * 4 - 4] || oldpx[i],
      oldpx[i + w * 4]     || oldpx[i],
      oldpx[i + w * 4 + 4] || oldpx[i]
    ];
    for (var j = 0; j < 9; j++) {
      res += these[j] * m[j];
    }
    res /= divisor;
    if (offset) {
      res += offset;
    }
    newpx[i] = res;

these are the pixels we want to inspect. oldpx[i] is the one in the middle which we're changing to newpx[i]. Also note how we default all pixels to oldpx[i]. This is to deal with the boundary pixels: tho top and bottom rows of pixels and the left and right columns. Because the pixel in position 0x0 has no pixels above it or to the left. Then we loop through these and multiply by the corresponding value in the matrix. Finally divide and offset, if required.

Thanks!

Thanks for reading, and now go play with the demo in the console. An easy template to start is:

transformador.convolve([[1,0,0],[0,0,0],[0,0,-1]], 1, 127); 

If you want to apply convolutions on top of each other, you can reset the original image data to the current.

transformador.original = transformador.getData();
 

Async JavaScript callbacks

Sunday, June 3rd, 2012

Ah, asynchronous JavaScripts. Love 'em, hate 'em, but you gotta use them!

I have quite a few posts here on this blog about this stuff. Starting with something I considered an interesting hack to emulate PHP's require(). This was posted in 2005. (2005! That's ancient. That's only a year after gmail was introduced, and you know gmail has been around, like, always). Then there was this and this and this and this. These days dynamic SCRIPT tags are something common, mainly because of their non-blocking behavior that helps performance. This is all good.

I wanted to highlight the part where you load a script file and then execute a function once the file is done loading. A common, but kinda wrong pattern I've posted back at the time, and tweaked a little since, is like so:

var first_js = document.getElementsByTagName('script')[0];
var js = document.createElement('script');
js.src = file;
 
// normal browsers
js.onload = function () {
  alert('loaded!!');
};
 
// IE
js.onreadystatechange = function () {
  if (js.readyState in {complete: 1, loaded: 1}) {
    alert('loaded!!');
  }
};
 
 
first_js.parentNode.insertBefore(js, first_js);

Back at the time (2006) this was fine. The problem is now that since version 9, IE supports onload handler in script elements. But it also supports onreadystatechange for backwards compatibility.

In other words in IE9+ your callbacks will be executed twice. Not good.

Single callback

There are various ways to deal with this situation.

1. You can delete the onload callback in readystatechange, beacuse readystatechange is called first.

js.onreadystatechange = function () {
  if (js.readyState in {complete: 1, loaded: 1}) {
    callback();
    js.onload = null;
  }
};

2. You can use a single assignment to both

js.onload = js.onreadystatechange = function () {
  // stuff...
  js.onload = js.onreadystatechange = null;
 
};

The problem with both of these is that readystatechange is involved even in browsers that are modern (IE9+) and support onload. Feels a bit ugh.

3. You can sniff onload support

if (typeof js.onload !== 'undefined') {
  // good stuff..
} else {
  // onreadystatechange jazz
}

This works because old IEs will not have any onload property (hence undefined) while supporting browsers will have this property initially set to null.

Hmm, making a distinction between two falsy values null and undefined seems a little fragile. The next developer will come and say: "meh, what's with the typeof verbosity, let's just say if (js.onload)"... And the whole thing will fail.

4. (And this is my preferred method) is to sniff support using addEventListener.

It just happens so that IE9, which supports onload, is also the first IE browser that supports addEventListener.

The whole thing looks like:

var first_js = document.getElementsByTagName('script')[0];
var js = document.createElement('script');
js.src = file;
 
if (js.addEventListener) { // normal browsers
  js.addEventListener('load', function(){
    alert('done!!');
  }, false);
} else {
  js.onreadystatechange = function() { // old IEs
    if (js.readyState in {loaded: 1, complete: 1}) {
      js.onreadystatechange = null;
      alert('done!!');
    }
  };
}
 
first_js.parentNode.insertBefore(js, first_js);

Drawback is that you decide on a feature (script onload support) based on a different feature (addEventListener support). I can live with this. We're talking here about an exception for known legacy browsers and shouldn't be an issue going forward in this brave new world where everyone lives in piece and love and brotherhood and sisterhood and motherhood and all browsers support onload and addEventListener.

So anyway, choose your poison :)

Here's a test page that listens to everything so you can play in different browsers:
http://www.phpied.com/files/jsasync/loaded.html

BTW, notice that IE is the only browser that fires window.onload before the onload of the (slow) script. This is another thing to keep in mind and look out for.

 

Ajax with images

Saturday, June 2nd, 2012

So you can do Ajaxy stuff with XMLHttpRequest or or iframes or dynamic JavaScript tags or... how about simple images. This is best used for simple stuff where you can have a limited number of predefined responses, such as "success" and "oops".

All you do is create an image and set its source and this makes a request:

new Image().src = "mystuff.php";

This is if you don't care about the response. If you want to inspect the response though you can attach onload and onerror handlers:

var i = new Image();
i.onload = function() {
  // inspect the response
};
i.src = "mystuff.php";

If you can assume you'll have "OK" response most of the time, you can have the .php return a 204 No Response which is the smallest response (no body). If the .php determines there's something wrong, it then can return an image.

When you send a 204 response, the onerror handler will be called because the response is not really an image. It looks backwards to have your success handler be called onerror, but if you expect more successes than errors, then it's probably worth it.

var i = new Image();
i.onload = function() {
  // an error occurred
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

And the final thing - if you want to have coded responses, in other words be able to differentiate between different errors (each with its error code), you can have the .php return different image sizes. Say with constant height but varying width. E.g. 1x1 image, 2x1, 3x1 and so on. In the onload you inspect the size of the image and determine the type of response.

var i = new Image();
i.onload = function() {
  // an error occurred
  if (i.width === 1) {
    // error #1
  } 
  if (i.width === 7) {
    // error #7
  } 
// etc...
 
};
i.onerror = function() {
  // success!
};
i.src = "mystuff.php";

I'm using a different errors as an example, but you can always have it the other way around: you consider onload a suceess and there are different types of successes.

Email address validation example

Let's take a look at a little more practical example. Let's validate email addresses on the server.

We'll return 7 different image sizes if the supplied email address is invalid or a 204 response is the email looks fine.

The OK response:

function ok() {
  header("HTTP/1.0 204 No Content");
}

The error response that generates an image with a desired width and height of 1 px:

function image($width) {
  header("Content-Type: image/png");
  $im = imagecreate($width, 1);
  imagecolorallocate($im, 0, 0, 0);
  imagepng($im);
  imagedestroy($im);
  die();
}

The error codes:

// 1x1 = empty input
// 2x1 = missing @
// 3x1 = too many @s
// 4x1 = missing username
// 5x1 = missing host
// 6x1 = blocked
// 7x1 = no dns record for host
// 204 = OK

And, finally, the "business" logic:

$email = (string)@$_GET['email'];
 
if (!$email) {
  image(1);
}
 
// split to username and domain
$splits = explode('@', $email);
 
if (count($splits) === 1) {
  image(2);
}
 
if (count($splits) !== 2) {
  image(3);
}
 
list($user, $host) = $splits;
 
if (!$user) {
  image(4);
}
 
if (!$host) {
  image(5);
}
 
$blocked = array('yahoo.com', 'gmail.com', 'hotmail.com');
 
if (in_array($host, $blocked)) {
  image(6);
}
 
 
if (!checkdnsrr($host)) {
  image(7);
}
 
// all fine, 204
ok();

Test page

You can play with the test page here:
http://www.phpied.com/files/imaje/test.html

The markup:

<input id="i"><button id="b">check email</button><pre id="r">enter an email</pre>

And the JS that makes a requests and checks for ok/error:

 
var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = function() {
    $('r').innerHTML = "thanks!";
  };
  i.onload = function() {
    $('r').innerHTML = "invalid email address!";
  };
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};

All there is to it!

Verbose page

A more verbose test can inspect the width of the returned image and display an appropriate message to the user.

Play with it here:
http://www.phpied.com/files/imaje/verbose.html

var i;
$('b').onclick = function() {
  i = new Image();
  i.onerror = ok;
  i.onload = function() {
    err(i.width);
  }
  i.src = "imaje.php?email=" + encodeURIComponent($('i').value);
  $('r').innerHTML = "checking...";
};
 
function ok() {
  $('r').innerHTML = "this is one valid email address, good for you!";
}
 
function err(num) {
  var errs = [
    '',
    'Seriously, I need an email',
    "Where's the @?",
    "Too many @s",
    "Missing username",
    "Missing mail host",
    "BLOCKED! Go away!",
    "Not a valid mail server",
 
  ];
  $('r').innerHTML = errs[num];
}

A good side effect of using a global i is that async responses don't mess up the result. E.g. you send requests #1 and #2, #2 arrives first and is OK, #1 arrives later and is an error. At this point you've overwritten i and the handlers for #1 are no longer available. Dunno is it's possible but it would be cool to be able to further abort a previous request if you've made one after it.

Thanks

Thanks for reading! I know it's hardly new for you, my two faithful readers, but these responses with varying image size came up in a conversation recently and as it turns out there are rumors that there might be about 3 developers in Chibougamau, Quebec, that are not aware of this technique.

 

Simple music player

Wednesday, April 18th, 2012

I put up a few MP3s on http://anacondalimousine.com, in simple a hrefs. While modern browsers will let you click and display some sort of player in a new page, why not play them in-page without a refresh? Simple enough. Plus we have HTML5 audio. Problem is, old IEs don't support HTML5 audio and you need flash or, god forbid, a Java applet to play them.

What's a webmaster to do? All I need is a simple PLAY button next to each link

While I was aware of SoundManager, I accidentally stumbled across sound.js yesterday and thought I should give it a chance. Tiny (3K), it should give me an easy way to do x-browser playing with the help of some SWF for IE, I assume. Dropped the JS from their CDN (nice), but then what? "Online documentation" points to API docs which I don't feel like reading. Demos don't give you the code. Next.

Sound manager has a nice "basic template". Awesome. Replace the sound.js js with SoundManager's. Load in FF. It's looking for an swf. In FF. Why? Because FF doesn't play mp3. Well, I'd rather convert the MP3 than have the user load SWF. But anyway, I drop the swf. For whatever reason (like you need a reason!) I've disabled flash in FF and forgot about it. So no sound in FF. And maybe I'm not the one. Requiring SWF in FF feels unnecessary, although I'm sure Sound Manager probably has way around it. I'm sure. I've met the author, he's brilliant. But... I still need to write some JS to initialize the playing. And if I'm to write code, might as well go solo.

Here's what I came up with, hope it helps.

Disclaimer: I haven't tested in IE, and I mean at all. Should probably work in newer IEs

Demo

http://anacondalimousine.com

Codez, how they work

Everything in an immediate function. Unobtrusive. Bail if (IE) browser doesn't know about Audio.

(function () {
  if (!Audio) return;
  
  // ...
 
}());

Codes for play and stop:

var playsym = "&#9654;", stopsym = "&#9632;";

Some browsers play mp3, some OGG, so let's just change the extension and have both .mp3 and .ogg on the server

var extension = new Audio().canPlayType('audio/mpeg') === '' ? 'ogg' : 'mp3';

Loop though all links on the page, find the ones that point to .mp3

var dl = document.links;
for (var i = 0; i < dl.length; i++) {
  if (dl[i].href.split('.').slice(-1)[0] !== "mp3") {
    continue;
  }
  var p = document.createElement('button');
  p.innerHTML = playsym;
  p.onclick = play;
  dl[i].parentNode.insertBefore(p, dl[i]);
}

Forgot to mention, my markup is as simple as it gets:

  <p><a href="virus.mp3">Virus</a></p>
  <p><a href="yesterday.mp3">Yesterday</a></p>
  <p><a href="parting.mp3">Parting</a></p>
  <p><a href="faultline.mp3">Faultline</a></p>

So all I'm doing is insert a PLAY button right before the link. Onclick it should play.

The play() function initializes Audio with the appropriate extension, based on what the browser supports:

  function play(e) {
    var button = e ? e.target : window.event.srcElement;
    button.innerHTML = stopsym;
    var a = new Audio(button.nextSibling.href.replace('mp3', extension));
    a.play();

And if you click the same button as the music plays, you stop it and reset the button click handler:

    button.onclick = function() {
      a.pause();
      button.innerHTML = playsym;
      button.onclick = play;
    };
  }

That's it

So simple and tiny, it's all inline, no extra requests, not a SWF in sight. Support for 72.3% of all browsers. The other 30% can still download the file and play it with a separate program.

SoundManager and, I assume, sound.js have more features, events and other shiny, but for my simple purpose I'm so happy with the result I'll even blog about it :)

For full source - view source at http://anacondalimousine.com

Moarr info:

p.s. about the mp3 to ogg conversion... ffmpeg ftw:

$ ffmpeg -i in.mp3 -acodec vorbis -aq 60 -strict experimental out.ogg
 

The Truth about semicolons in JavaScript

Monday, April 16th, 2012

jk

jsdrama.com is live though. Enjoy and feel free to add more next time.

(image from wikipedia)

 

Social button BFFs

Tuesday, September 27th, 2011

TL;DR: Loading JavaScript asynchronously is critical for the performance of your web app. Below is an idea how to do it for the most common social buttons out there so you can make sure these don't interfere with the loading of the rest of your content. After all people need to see your content first, then decide if it's share-worthy.

Japanese translation by Koji Ishimoto is here

Facebook now offers a new asynchronous snippet to load the JavaScript SDK, which lets you load social plugins (e.g. Like button) among doing other more powerful things.

It has always been possible to load the JS SDK asynchronously but since recently it's the default. The code looks pretty damn nice (I know, right!), here's how it looks like (taken from here):

(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) {return;}
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

Some nice steal-me JS patterns here:

  • immediate (self-invoking) function so not to bleed vars into global namespace
  • pass oft-used objects (document) and strings ("script", "facebook-jssdk") to the immediate function. Sort of rudimentary manual minification, while keeping the code readable
  • append script node by using the first available script element. That's 99.99% guaranteed to work unless all your code is in body onload="..." or img onload or something similar (insanity, I know, but let's allow generous 0.01% for it)
  • assign an ID to the node you append so you don't append it twice by mistake (e.g. like button in the header, footer and article)

All buttons' JS files

Other buttons exist, most notably the Twitter and Google+1 buttons. Both of these can be loaded with async JavaScript whether or not this is the default in their respective configurators.

So why not make them all get along and shelter them under the same facebook immediate function? We'll save some bytes and extra script tags in the HTML. For G+/T buttons all we need is a new script node. Google+'s snippet has some additional attribs such as type and async, but these are not really needed. Because type is always text/javascript and async is always true. Plus we kinda take care of the async part anyways.

The end result:

  <div id="fb-root"></div>
  <script>(function(d, s, id) {
    // fb + common
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
    fjs.parentNode.insertBefore(js, fjs);
    // +1
    js = d.createElement(s); 
    js.src = 'https://apis.google.com/js/plusone.js';
    fjs.parentNode.insertBefore(js, fjs);
    // tweet
    js = d.createElement(s); 
    js.src = '//platform.twitter.com/widgets.js';
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));</script>

So this thing loads all three JS files required by the three buttons/plugins.

Additionally we can wrap the node creation/appending part into a function. So all the code is tighter. Here's the final snippet:

<div id="fb-root"></div><!-- fb needs this -->
<script>(function(d, s) {
  var js, fjs = d.getElementsByTagName(s)[0], load = function(url, id) {
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.src = url; js.id = id;
    fjs.parentNode.insertBefore(js, fjs);
  };
  load('//connect.facebook.net/en_US/all.js#xfbml=1', 'fbjssdk');
  load('https://apis.google.com/js/plusone.js', 'gplus1js');
  load('//platform.twitter.com/widgets.js', 'tweetjs');
}(document, 'script'));</script>

All buttons' markup

Next is actually advising the scripts where the widgets should be rendered. Facebook offers XFBML syntax, with tags such as <fb:like>, but it also offers pure HTML(5) with data-* attributes. Luckily, so do all others.

Here's an example:

<!-- facebook like -->
<div class="fb-like" data-send="false" data-width="280"></div>
<!-- twitter -->
<a class="twitter-share-button" data-count="horizontal">Tweet</a>
<!-- g+ -->
<div class="g-plusone" data-size="medium"></div>

G+ requires a div element (with g-plusone class name), Twitter requires an a (with a twitter-share-button class name). Facebook will take any element you like with a fb-like class name (or fb-comments or fb-recommendations or any other social plugin you may need)

Also very important to note that you can (and should) load the JS files once and then render as many different buttons as you need. In Facebook's case these can be any type of plugin, not just like buttons. Economy of scale - on JS file, many plugins.

All together now

So here's the overall strategy for loading all those buttons.

  1. Copy the JS above at the bottom of the page right before /body just to be safe (G+ failed to load when the markup is after the JS). This will also help you make sure there should be only one place to load the JS files, although the snippet takes cares of dedupe-ing.
  2. sprinkle plugins and buttons any way you like anywhere on your pages using the appropriate configurator to help you deal with the data-* attributes (FB, G+, Tw)
  3. Enjoy all the social traffic you deserve!

To see it all in action - go to my abandoned phonydev.com blog. Yep, those buttons play nice in mobile too.

 

Lazy HTML evaluation

Wednesday, June 8th, 2011

#7 This post is part of the Velocity countdown series. Stay tuned for the articles to come.

Some time ago Google talked about using a sort of lazy JavaScript evaluation which especially helps mobile devices. The idea was to comment out a chunk of JavaScript you don't need right away and serve it this way. Later, when you need it, you get the content of the commented code and eval() it. More here and here.

At the last Fronteers conference I had the pleasure of chatting with Sergey Chikuyonok, who is so great and (among other things) is responsible for coming up with zen coding and writing a bunch of deep articles on image optimization for Smashing Magazine. So he told me he experimented with similar lazy HTML evaluation and it proved to be incredibly helpful for mobile devices. Not only the overall experience is faster but the initial rendering happens sooner and we all know how important that is.

Sergey is a busy person and chances of him writing about his experiment in English seemed pretty low at the time, so I decided to do an experiment on my own and see what happens. Meanwhile he did write about it so I forgot all about my findings, but here they are now.

Long document

I took one big HTML document - The adventures of Sherlock Holmes, which is half a megabyte or about 200K gzipped. Page A is the document as-is, plus some JS for measurements.

Page B (lazy) is the same page but with about 95% of its content commented out. The remaining 5% is a whole chapter so there's plenty of time to deal with the rest while the user is reading. After onload and a 0-timeout I take the commented markup (conveniently placed in <div id="lazy-daze">) and strip the comments. Then take the "unwrapped" time after another 0-timeout to let the browser repaint the DOM and regain control.

The overall skeleton of the lazy page is like so:

<!doctype html>
 
<html>
<body>
  <h1>THE ADVENTURES OF<br/>
  SHERLOCK HOLMES</h1>
  ...
  ... to chat this little matter over with you.</p>
 
  <div id="lazy-daze">
  <!--
    <p>II.</p>
    <p>
    At three o’clock precisely ... 
    ... she has met with considerable success.</p>
  -->
  </div>
 
<script>
 
 
window.onload = function () {
 
    setTimeout(function(){
 
        var daze = document.getElementById('lazy-daze'),
            inner = daze.innerHTML;
 
        daze.innerHTML = inner.substring(4, inner.length - 4);
    
        setTimeout(function(){
            // take end time... 
        }, 0);
                
    }, 0);
};
 
</script>
</body></html>

Experiment

All the files are here:
http://www.phpied.com/files/lazyhtml/

We have the plain normal document - http://www.phpied.com/files/lazyhtml/sherlock-plain.html
And the lazy one - http://www.phpied.com/files/lazyhtml/sherlock-lazy.html

In order to run the experiment you just go to
http://www.phpied.com/files/lazyhtml/start.html
And click "Go nuts". This will load each of the two documents 20 times and take a few time measurements. "Go nuts" again and you'll get 20 more data points.

The time measurements I take are:

  • "plain" - unload to onload of the base version
  • "lazy" - unload to onload of the lazy version NOT including unwrapping it. This should be quicker than the plain version
  • "unwrapped" - unload to onload plus time to unwrap and rerender - this is expected to be bigger than "plain" because the browser has to render twice and is therefore doing more work
  • DOM loaded "plain" - unload to DOMContentLoaded instead of onload
  • DOM loaded "lazy"

Then I take the same 5 measurements but instead of starting at unload of the previous page, it starts at the top of the documents, as soon as a timestamp can be taken with JavaScript. This will exclude DNS, establishing connection, time to first byte...

Results

Here are the results from back when I did the experiment originally last year, using iPhone 2 (with iOS 3.2 or thereabouts)

I ran this experiment over Wifi and again over 3G.

First striking thing - it takes the about the same time to load the plain old page over Wifi and over 3G. For the smaller, "lazy" document, there is a difference, but there's virtually none for the plain base page. The guess here is that the rendering and its cost in terms of memory and CPU is far greater than the actual download time. In other words it takes longer to render than it does to download an HTML. At least in this class of phones. This guess is confirmed when you look at the time from the top of the documents, when the request overhead is removed:

With or without the request time - it's all pretty much the same.

The next striking thing - and how about that lazy document! It renders 3-4 times faster than the whole plain document. Not bad.

And one more surprise - lazy+unwrap time is less than the plain old document. Now that's interesting. It appears faster to split the task into two and do the whole double-rendering, which should've been slower because it's extra work. I guess that poor phone really chokes on the long document.

The same I found is true in Firefox, but almost the difference is negligible.

iPhone 4

I repeated the experiment tonight on iPhone 4 and wifi. And boy, is there a difference. What used to take 13 seconds is now under 3s.

The lazy + unwrap time is more than the plain time, which was to be expected.

Rendering that initial lazy document is still 2-3 times faster that waiting for the whole document.

The numbers:

  • 2765 plain (2014 DOM)
  • 1268 lazy
  • 2995 lazy+unwrap

Ignoring the request overhead:

  • 2200 plain (1421 DOM)
  • 715 lazy
  • 2423 lazy+unwrap

And one last run/observation - on the 3G and iPhone 4 there isn't much benefit of lazy-evaluation and empty cache. The request seems much more expensive. unload to onload 4.9s where document top to onload is 2.5. When the request overhead is out of the picture than lazy eval wins again - 1.7s compared to 2.5s

Parting words

  • Lazy HTML FTW?
  • Who the heck loads an entire book in a page?! Well it may happen. It may not be a whole book, but just a lot of markup. The entire book gzipped was 219K. A hefty document, but have you seen some of those news sites?
  • Possible use case - blog comments. Lots and lots of blog comments. Or posts.
  • If you're going to lazy-load something and get it with an ajax request, why not save yourself the request and ship with another chunk of html
  • This was a simple layout task. Just a bunch of text. I'm guessing there could be much more complicated pages and layouts to render. And rendering is what takes the time it seems.
  • Drawbacks a plenty because of the hidden content - accessibility, SEO.

Thoughts? Anyone want to run the test on Android or any other phone/device/tab/pad/whathaveyou? The guess is that the newer/powerful the device the smaller the difference. But it will be nice to know.

 

HTTPWatch automation with JavaScript

Sunday, April 10th, 2011

Background

I gave this short presentation at the recent Yahoo FE summit's open mic, here are the slides and some notes.

Slides and screencast vid

Screencast to see the thing in motion:

Notes

Here's the transcript of the slides as produced by slideshare.net. I've added some notes here and there to make it more readable when the presenter is missing.

JavaScript shell scripting - Presentation Transcript

  1. JavaScript is everywhere #42:
          C:> WSH
  2. Stoyan

    I do programming.

  3. Programming

    There are many options to choose from when you decide top practice the
    art and craft of programming.

  4. JavaScript

    ... is a very good one. Simply because JavaScript...

  5. ... is everywhere
  6. On the server

    node.js, asp.net

  7. Mobile

    html5, phonegap, titanium

  8. Desktop

    XULRunner lets you create cross-OS GUI apps

  9. Browser extensions

    FF, Chrome, bookmarklets...

  10. Photoshop

    yep, that too
    Several Adobe products actually let you script common/uncommon/programmable tasks

  11. Form validation too!

    (this was supposed to be funny)

  12. Shell scripting

    let's talk about shell scripting with JavaScript

  13. In Windows
  14. WSH: Windows Scripting Host

    All reasonable Windows machines (at least as old as Win2000) have this Windows Scripting Host in there.
    You can write VBScript or JavaScript (OK, JScript) to ... well, script.
    How does it work?

  15. C:>edit hello.js

    You create a file.

  16. var a = "Hello",
        b = " WSH!",
        c = a + b;
            
    WScript.Echo(c);

    Put any old JavaScript in there and print out some results

  17. C:>cscript hello.js
    Hello WSH!

    And this is how you run it.

    Or this:

    C:>wscript hello.js
  18. Open apps

    In addition to regular sysadmin tasks (copy, write files, move) you can open and script applications too.

  19. var ie = new ActiveXObject("InternetExplorer.Application");
    ie.Visible = true;
    ie.navigate(yahoo.com);

    This is how you open IE and point it to a page.

    Notice something familiar? ActiveXObject - the thing we used in IE before it got native XMLHttpRequest

  20. Firefox?

    Can you also open FF?

    Not really, as it doesn't have COM interface (whatever that is).

    But there's an easy solution

  21. HTTPWatch

    Finally we come to the topic of the talk.

  22. Speed

    Performance is critical for the success of any web app.

    Really, it is.

    When talking about improving speed there are two main steps:

  23. 1. Fix with YSlow

    Take a slow page, run YSlow, do what it says.

    Voila - a fast(er) page.

  24. 2. No regressions

    The second step is to not allow regressions.

    Whatever you fix in step 1 will be slow in the next few months.

    Even less than months the bigger the team or the rate of changes.

    So to prevent regressions, you need to constantly...

  25. Monitor
  26. Set limits

    The simplest way to prevent regressions is to set some limits.

    If you go over the limits, an email is sent, an alarm sounds, panic instills and you've got to fix whatever cause it was.

  27. e.g.
    max 2 scripts
    max 2 styles
    max 9 images
    max 0 redirects
  28. Scripting HTTPWatch

    Watching for violations of the limits manually every day is not a job anyone would want.

    So automating it will help a great way towards employee satisfaction :)

  29. var http = new ActiveXObject("HTTPWatch.Controller"),
        ie = http.IE.New(),
        ff = http.Firefox.New();

    This is how you open IE and FF with HTTPWatch's help.

    FF - yey!

  30. // browser cache
    ie.clearCache();
    
    // show HTTPWatch
    ie.OpenWindow(false);

    Examples of stuff you can do with the HTTPWatch API.

    You can for example hit the page with empty cache and then again with full cache.

    Best of all - these are the real browsers with their sometimes kinky behaviors.
    Actually if you setup several machines for the monitoring (or somehow do multiple IEs)
    you can test with different versions of the browsers. Nothing synthetic!

  31. ie.Record();
    ie.GotoUrl("yahoo.com");
    http.Wait(ie, -1);
    ie.Stop();

    Start monitoring, go to a page, stop monitoring after the page "settles" meaning some time after onload.

    ie.CloseBrowser();
  32. new HTTPWatch()
          http://github.com/stoyan/etc/

    I did this JavaScript thingie to make everything a little easier.

  33. var http = new HTTPWatch(ff);
    http.go(search.yahoo.com);
    http.done();

    Example usage.

  34. [video]
  35. var har = http.toHAR();
    har = eval(( + har + ));
    
    print(har.log.browser.name);
    print(har.log.browser.version);
    print(# requests: );
    print(har.log.entries.length);

    Opening and navigating browsers is cool. But we need some data back.

    HTTPWatch can export a HAR (HTTP Archive) file. I have this toHAR() method.
    It writes the file, than reads and returns it.
    You can than eval() it because it's just a JSON string.
    And you get the data back in convenient JS objects and arrays.

  36. Internet Explorer 6.0.29...
    # requests: 10
    
    Firefox 3.5.6
    # requests: 15

    Result of running the above.

  37. [video]
  38. var comps = http.getComponentsByType();
          
    for (var i in comps) {
      print(i);
      print(comps[i].length);
    }

    Another method I thought would be useful is getComponentsByType()

  39. redirect: 1
    text/html: 3
    image/gif: 4
    image/png: 3
    text/javascript: 1

    Results of the code above.

  40. But wait...

    There's more :)

  41. What about DOM?

    So far we only talked about HTTP traffic inspection - headers and such.

    Good news is that you can also inspect the DOM (in IE only) for any potential red flags.

    For example having the number of DOM elements sharply increase.

  42. [video]
  43. var http = new HTTPWatch();
    http.go(search.yahoo.com);
    
    var d = http.watch.container.document;
    
    print(d.getElementsByTagName(*).length);
    print(d.documentElement.innerHTML);

    That works!

    All your DOM voodoo skillz are suddenly reusable.

  44. require(statz.js);
          
    var doc = http.watch.container.document;
    var html = http.har.log.entries[0].response.content.text;
    
    var out = statz(document, html);
    print(out.join("\n"));

    This is me repurposing two old bookmarklets that gather some interesting stats (one of them was even featured on Ajaxian, remember?).

    It was pretty easy to repurpose the bookmarklets, because it's just JavaScript.

    The stats thingie can inspect both raw HTML that went over the wire, as well as innerHTML that was the result of any additional DOM manipulations.

  45. JS attributes (e.g. onclick): 1207 bytes
    CSS style attributes: 883
    Inline JS: 5243
    Inline CSS: 5015
    All innerHTML: 17283
    # DOM elements: 134
    
    Total size: 14124 bytes
    Content size: 401 bytes
    Content-to-markup ratio: 0.03

    Sample results.

  46. To summarize...
  47. JavaScript
    WSH
    HTTPWatch
    Monitor
    DOM and HTTP
    IE and Firefox
  48. Thanks
          
    phpied.com
 

JavaScript-style object literals in PHP

Sunday, March 20th, 2011

The object literal notation in JavaScript looks like:

var fido = {name: "Fido", barks: true};

or

var fido = {};
fido.name = "Fido";
fido.barks = true;

From assoc arrays to objects

In PHP you would call that an associative array.

$fido = array(
  'name' => "Fido",
  'barks' => true
);

And you can easily make it an object too:

$fido = (object)$fido;
echo gettype($fido); // "object"

Or if you want to start with a blank object and add stuff to it:

$fido = (object)array();

or

$fido = new StdClass();

and then

$fido->name = "Fido";
$fido->barks = true;

A little explanation maybe: objects in JavaScript are hashes, maps, whatever you decide to call them. Objects in PHP were an afterthought in the language and (at least initially) were not much more than "fancy arrays". Fancy associative arrays (hashes, maps, whatever you call them).

Objects in PHP need a class, but the new stdClass() lets you start quickly without the class {...} jazz. Same for casting an array (upgrading it in its fanciness) to an object with (object)array().

So far - so good. What about methods?

Methods anyone?

JavaScript doesn't care about properties versus methods. It's all members of an object (like elements of an assoc array). Only if a member happens to be a function, it's invokable.

fido.say = function () {
  if (this.barks) {
    return "Woof!";
  }
};
 
fido.say(); // "Woof!"

Turns out, since PHP 5.3 there are closures in PHP too. So you can do:

$fido->say = function() {
  if ($this->barks) {
    return "Woof";
  }
};

The difference is that $fido->say() won't work. Two reasons for that:

  1. say is not a method. It's a property. For PHP it matters. You can however assign the property say to a new variable $callme. This variable is now a closure object. As such you can invoke it:
    $callme = $fido->say;
    echo $callme();

    Note the $ in $callme().

  2. the above will also fail because $this is an weird context and doesn't point to the object $fido. But you can use $self and point it to the global object $fido.

So that's a little .... unpretty, but it works:

$fido = (object)array();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function() {
  $self =& $GLOBALS['fido'];
  if ($self->barks) {
    return "Woof";
  }
};
 
$callme = $fido->say;
echo $callme(); // "Woff!"

And a sprinkle of magic

We can make this prettier with the help of a little PHP magic. PHP has some magic methods going on and one of these is the __call() method. If you implement it in a class, then it will be invoked whenever someone tries to call a method that doesn't exist.

In our case $fido->say is not a method. So __call can intercept $fido->say() calls and invoke the $fido->say property as a closure object. Closures are callable and call_user_func() and call_user_func_array() work fine with them. So all in all we should make this work:

$fido = new JSObject();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function($self) {
  if ($self->barks) {
    return "Woof";
  }
};
 
echo $fido->say();

As you can see, very JavaScript-esque. Except that $this is $self and will always be the first argument passed to every method. The secret sauce to make this happen is the JSObject() class.

class JSObject {
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

Nice and easy. Namely:

  1. __call takes the name of the missing method and any arguments.
  2. It checks whether there's a callable property with the same name (a closure object property).
  3. It adds $this to the arguments list and calls the closure.

Yupee! Now you can haz moar class-less JS-like PHP objects :)

(Note that $this->$name is not a typo and should not be $this->name because it's a dynamic property name.)

And one more thing

If we add a constructor to JSObject, it can accept any properties at creation time. So you can be even closer to JavaScript and allow both creating an "empty" object and adding to it later, or creating an object and adding properties simultaneously.

The slightly modified JSObject:

class JSObject {
  function __construct($members = array()) {
    foreach ($members as $name => $value) {
      $this->$name = $value;
    }
  }
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

And example use:

$fido = new JSObject(array(
  'name' => "Fido",
  'barks'=> true,
  'say'  => function($self) {
    if ($self->barks) {
      return "Woof";
    }
  }
));
 
echo $fido->say(); // "Woff"

This is pretty close to what you can have in JavaScript (adding $ and ' even though we can do without them), only changing a few things like -> to . and => to :

$fido = {
  'name' : "Fido",
  'barks': true,
  'say'  : function() {
    if (this.barks) {
      return "Woof";
    }
  }
};
$fido.say(); // Woof

JS and PHP look like twins now don't they.

JS for PHP devs at confoo.ca

This was extracted from a talk I gave at the confoo.ca conference a week or so ago. Below are the slides:

JavaScript for PHP developers
View more presentations from Stoyan Stefanov

 

cssmin.js in windows shell

Friday, October 29th, 2010

JavaScript can run virtually anywhere, including as a windows exe and the windows command line.

Say you have a JavaScript function foo()

function foo(input) {
  var output = input;
  // .. unicorns
  return output;
}

In order to make this a windows shell script you add at the and a way to read standard input and then write to the standard output:

(function () {
 
    var input  = WScript.StdIn.ReadAll(),
        output = foo(input);
 
    WScript.StdOut.Write(output);
 
}());

Then you run this script, say foo.js, like:

$ cscript foo.js < input.txt

And it prints the output to the console.

If you want to read and print the code of foo.js itself you go:

$ cscript foo.js < foo.js
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.

function foo(input) {
  var output = input;
...

You can remove this "Microsoft (R) Windows..." stuff with //NoLogo parameter:

$ cscript //NoLogo foo.js < foo.js
function foo(input) {
  var output = input;
...

CSSMin.JS

Alrighty, going back to the title of the post.

CSSMin.js is a port of YUICompressor's CSS minifier (source, hosted tool). Now adding a few lines at the end makes a windows shell script:

(function () {
 
    var src = WScript.StdIn.ReadAll();
 
    if (!src) {
        // help!
        WScript.StdOut.WriteLine("cscript //NoLogo cssmin.js < in.css > out.css");
        return;
    }
 
    WScript.StdOut.Write(YAHOO.compressor.cssmin(src));
 
}());

Download it here.

Use it like:

$ cscript //NoLogo cssmin.js < in.css > out.css

Don't forget the //NoLogo or you'll end up with "Microsoft..." in your minified files

Random observation: "dude"[0] === "d" in most JS environments but is undefined in WSH (Windows Scripting Host). So "dude".substring(0, 1)

 

Preload, then execute

Saturday, October 23rd, 2010

I talked before about using an object element to load scripts and styles without executing/applying them to the current document. And this is nice for preloading assets - faster and less error-prone than simple inclusion if the document or iframe.

But what about preloading assets (scripts) and then executing them on the current page? Turns out it works just fine, like so:

  • create an object element and point its data to the JavaScript file to be loaded
  • listen to the load event of the object
  • once loaded, create a script element and point its src to the file, which would be already cached
  • optionaly, execute a callback function once the script element is properly inserted into the DOM

Something like this:

function yield(file, callback) {
    var o = document.createElement('object');
    o.data = file;
        
    o.width  = 0;
    o.height = 0;
    
    o.onload = function() {
        var s = document.createElement('script');
        s.src = file;
        s.onload = function() {
            callback(o, file);
        };
        document.getElementsByTagName('head')[0].appendChild(s);
        
    };
    
    document.body.appendChild(o);
}

But, but.. why?

Well, first off it's yet another way to load JavaScript files asynchronously, in a non-blocking fashion. And that's a good thing. Async = win. This method sure beats others such as document.write (eek) or iframe. It has advantage over XHR because there's no same-domain restriction. DOM include is probably the only simpler and more robust method, but there are cases where the object element might be preferable.

DOM-included scripts in FF and Opera always execute in order. That's cool, but sometimes you may prefer out of order async execution. Whenever a script is ready - go! Or sometimes you may prefer if the script is not executed at all! That may sound odd (why would I load it if I don't want to execute it?) but I have a use case - auto-complete. When your data source is on another domain and you can't use XHR, then JSON-P would be the way to go. But the network is a jungle and anything can happen.

Say you make requests for auto-complete suggestions with queries "h", "he", "hel", "hell", "hello". For some reason "hel" is really slow and you already have the results for "hell" and even "hello". What you want to do is just kill and cancel the "hel" response, who cares - it's outdated already. But if you use JSONP in FF and Opera "hel" will block the others. If order is not preserved it might be even weirder - you update the page with the "hello" suggestion and then "hel" finally arrives and updates the suggestion once again. Fail.

Using the object element approach you can keep a list of requests that went out and simply choose to ignore previous requests if newer responses are already there.

I have a test case even.

Small print

This technique assumes that the scripts you load don't have any headers preventing caching, otherwise they'll be requested twice.

My example code above ignored IE completely, but you can easily make it work, either using new Image() instead of object or tweaking the object with height and width of 1 as shown in the old article. Or you can use the trick that LABjs uses (I think) which is to DOM-insert a script element with invalid type such as cache/javascript. Also you need to listen to onreadystatechange instead of load event when inserting a DOM element.

 

The European Tour 2010

Saturday, October 2nd, 2010

So it's been pretty quiet around here. I'm still alive and very much so. Just awfully busy.

Bulgaria

I took a long trip in Bulgaria. About 1 month and 20 days. Including the traditional around-Bulgaria let's-get-drunk-in-different-cities-every-day tour de force with the gang. With unusual amount of guitar playing this time.

Not all fun and roses, though, I had to work most of the time for a superimportant gamechanging ultrasecret project for Yahoo! Search. And work on the finishing touches for a book.

France, Italy, UK

While spending time in Bulgaria I did some quick jumps to other European countries - France, Italy, UK. Everything is so close in Europe, it's a shame not to wander around once you cross the big pond.

In Paris I had the pleasure to practice some French, but most importantly to meet with the local speed freaks at the Web Perf Meetup organized by Éric Daspet. Great experience to interrupt sightseeing and soufflé-eating just to be among fellow geeks in a small and cozy group.

web perf meetup paris

photo: @tbassetto

Second tour leg

Back in California to the normal life and kids' school year start end of August. But a second European tour leg was just around the corner.

JSConfEU in Berlin was last week, this is hands-down the best conference. I cannot speak high enough about it, the organizers, the people, speakers, everyone, the parties (ouch, the parties). I met so many people I have been in contact with (or wanted to be) for quite a while, including the new book's tech reviewer Andrea aka @WebReflection. It's surprising, for the regular introvert geek I am, the pleasure of meeting and talking to people. Oh, the miracles of the little, or not so little, quantities of Berliner Pilsner.

Back from Berlin, a copy of the new book was waiting, how adorable! (I really need to put some marketing effort into it... at least put up a page what's it about, since people are asking). It's been rating very well in Amazon's "JavaScript" category. Saw it at #3 a few times, watch out "good parts"!

Next week it's Fronteers conference in Amsterdam. Really looking forward to this one. After all, the one and only PPK started it all. I'll be speaking about progressive downloads and progressive rendering. Progress is important, progress is critical! And I've never been to Amsterdam.

Then Moscow and highload++ conference. How cool that would be. I always wanted to see Moscow or any part of Russia really.

So... busy, busy, busy. And another book baby on the road. And one last conference but here in LA's "backyard" Las Vegas this time.

 

JavaScript JS Array Documentation (#jsonf, #promotejs)

Sunday, September 26th, 2010

JavaScript JS Documentation: JS Array unshift, JavaScript Array unshift, JS Array .unshift, JavaScript Array .unshift

Join me in the initiative to promote proper JavaScript documentation from MDC. It's embarrassing the quality of links that pop up in search engines when searching JavaScript general questions. Let's help connect newcomers to JavaScript with better reading. PromoteJS.com will give you a unique code.

 

Preload CSS/JavaScript without execution

Wednesday, April 21st, 2010

Preloading components in advance is good for performance. There are several ways to do it. But even the cleanest solution (open up an iframe and go crazy there) comes at a price - the price of the iframe and the price of parsing and executing the preloaded CSS and JavaScript. There's also a relatively high risk of potential JavaScript errors if the script you preload assumes it's loaded in a page different than the one that preloads.

After a bit of trial and lot of error I think I came up with something that could work cross-browser:

  • in IE use new Image().src to preload all component types
  • in all other browsers use a dynamic <object> tag

Code and demo

Here's the final solution, below are some details.

In this example I assume the page prefetches after onload some components that will be needed by the next page. The components are a CSS, a JS and a PNG (sprite).

window.onload = function () {
 
    var i = 0,
        max = 0,
        o = null,
 
        // list of stuff to preload
        preload = [
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.png',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.js',
            'http://tools.w3clubs.com/pagr2/<?php echo $id; ?>.sleep.expires.css'
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;
 
    for (i = 0, max = preload.length; i < max; i += 1) {
        
        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];
        
        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;
        
        
        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }
    
};

A demo is here:
http://phpied.com/files/object-prefetch/page1.php?id=1
In the demo the components are delayed with 1 second each and sent with Expries header. Feel free to increment the ID for a new test with uncached components.

Tested in FF3.6, O10, Safari 4, Chrome 5, IE 6,7,8.

Comments

  • new Image().src doesn't do the job in FF because it has a separate cache for images. Didn't seem to work in Safari either where CSS and JS were requested on the second page where they sould've been cached
  • the dynamic object element has to be outside the head in most browsers in order to fire off the downloads
  • dynamic object works also in IE7,8 with a few tweaks (commented out in the code above) but not in IE6. In a separate tests I've also found the object element to be expensive in IE in general.

That's about it. Below are some unsuccessful attempts I tried which failed for various reasons in different browsers.

Other unsuccessful attempts

1.
I was actually inspired by this post by Ben Cherry where he loads CSS and JS in a print stylesheet. Clever hack, unfortunately didn't work in Chrome which caches the JS but doesn't execute it on the next page.

2.
One of the comments on Ben's post suggested (Philip and Dejan said the same) using invalid type attribute to prevent execution, e.g. text/cache.

var s = document.createElement('script');
s.src = preload[1];
s.type = "text/cache";
document.getElementsByTagName('head')[0].appendChild(s);

That worked for the most parts but not in FF3.6 where the JavaScript was never requested.

3.
A dynamic link prefetch didn't do anything, not even in FF which is probably the only browser that supports this.

for (i = 0, max = preload.length; i < max; i += 1) {
    var link = document.createElement('link');
    link.href = preload[i];
    link.rel = "prefetch";
    document.getElementsByTagName('head')[0].appendChild(link);
}

Then it took a bit of trial/error to make IE7,8 work with an object tag, before I stumbled into IE6 and gave up in favor of image src.

In conclusion

I believe this is a solution I could be comfortable with, although it involves user agent sniffing. It certainly looks less hacky than loading JS as CSS anyways. And object elements are meant to load any type of component so no semantic conflict here I don't believe. Feel free to test and report any edge cases or browser/OS combos. (JS errors in IE on the second page are ok, because I'm using console.log in the preloaded javascript)

Thanks for reading!

 

Publishing 5 books this year

Thursday, April 1st, 2010

So I'll be publishing 5 books this year. Isn't that incredible? Is it even possible? And good quality books at that? It's a nice challenge (my last year's challenge failed, I didn't even bother to count how bad it failed). I think it's possible, especially if you bend a little bit the meaning of "5", "year", "publishing" and "me" :)

Book #1 - High-Performance JavaScript

hpjs

Let's start bending - this is a book where I wrote just one chapter. It's a book by Nicholas Zakas with contributions from:

And I wrote my chapter mainly the last year. My chapter is about the DOM. But the book became available just now, few days ago, so it's published this year (bending, bending...)

Book #2 - JavaScript Patterns

I am hard at work on this one currently (explains the low activity on this blog). I started last year but only finished two chapters in '09. The bending part here is that I've already given presentations on the topic and have been writing a "patterns" column for JSMag for a while, so I can recycle quite a bit of content.

You can see the tentative cover, I hope it stays tentative and we can replace the hen with a nice cute little zebra (a.k.a. donkey with patterns). Between you and me, I think there's a new designer in O'Reilly with a bird fetish.

I expect the first draft for this one to complete within weeks. And no, it's not about implementing the Gang of Four patterns in JavaScript (has been done already by Ross, see above), although there's one chapter on a selected few - Singleton, Factory, Observer, Proxy, Decorator...

Book #3 - Speed Matters

I've contracted with Peachpit Press to write a book about performance targeted mainly at designers. It will be about the business (why go fast), technology (how) and psychology (perception of speed) of web performance. I'm excited about this one for a number of reasons:

  • there's a lot of misconceptions being spread around in designer blogs and books, especially sad when one of the books in question is a sort of a bible for web designers. I mean things like PNG vs. GIF, gzipping and others. I hope I can present a readable, concise and, above all, technically correct text for designers who may find Steve Souders' HPWS, a.k.a. "The Bible" a little too dry because it's from O'Reilly and has no colors
  • the publisher is considering a sort of novel approach to writing the book, fingers crossed, because I believe it's the right way to write technical books.
  • at the very least, the book will be available as early drafts while it's being written, which is new to me, but always wanted to do.
  • the book will be full color - again, new experience to me

The bending here comes from the fact that I'll try to reuse from the perf advent calendar if I can. So some content may be pre-written.

Book #4 - Object-Oriented JavaScript (2nd edition)

The bending here is obvious - it's just a second edition, not a completely new book from scratch. My goal here is:

  • address errata
  • address some excellent critiques (of this otherwise bestselling book!), such as this one by @kangax, which is the article that actually prompted me to pitch a second edition to the publisher. So many thanks to Yuri! Also thanks to Asen who's been sending me invaluable and detailed feedback on the first edition. And now thanks to Asen and Kangax (and also Dmitry) I'm spending some time lurking on comp.lang.javascript mailing list, which is full of great discussions.
  • ECMAScript5 update
  • some concepts such as hoisting, NFE, property attributes, etc
  • one completely new chapter on testing and docs
  • answers to the end-of-chapter exercises - an often-requested update

Hoping this title will not take a lot of time.

And since these 4 books should be finished by the end of August or thereabouts, this will give me whole 4 months (1/3 of an year) to dive into something I've been thinking about, two things actually - CSS and self-publishing.

Book #5 - CSS for web devs

CSS is widely misunderstood by many people, me including. I'm convinced we only use a portion of all that CSS is, and use it badly. I'm not saying it will be CSS: The Good Parts, but I plan to address what I consider bad habits in CSS (mis)use and write a book as a learning experience. This is the best way to learn IMO. It will be self-published and probably available online for free too. And by self-publish I don't mean lulu.com or some of the other resellers, but working with the printer and distributor directly.

Too ambitious? April Fool's?

Probably, but with all the pre-written stuff and other cheating, it may very well be doable. Then I guess I'll take a 5 year break :)