Christian Heilmann

Author Archive

Fencing in the habitat – doing things right and getting the accessibility wrong

Saturday, April 26th, 2008

This is my talk given at AbilityNet’s Accessbility 2.0 conference in London today. In it I am talking about how we try to sell accessibility and the mistakes we make while we do so.

[tags]accessibility,conference,presentation,usability,common sense,abilitynet,london[/tags]

Hacking SlideShare’s embed adding a preview and be a lot shorter and readable

Thursday, April 17th, 2008

Edit: There is a bug in the script (see comments) but somehow Googlecode does not allow me to edit my own file. I will fix it once I got around that issue.The bug reported in the comments is now fixed, sadly enough I also had to re-write the converter as Google code does not allow me to replace an older version of a download (or is there a trick?). The new file is called previewer2.js

As readers of this blog know, I am a big fan of SlideShare as a distribution platform for my presentation slides. However, there are some things that annoy me about it.

One of them is the rather verbose embed code SlideShare offers you:



That is quite a mouthful and the main issue is that when you use several slide embeds in one document, you’ll slow down the rendering of your page as each of these Flash embeds need to be instantiated and tries to pre-cache the first three slides from S3.

I’ve analyzed the code a bit, added some other info I found in the RSS feed and came up with a small JavaScript that embeds slides in a different way. All you need there is the following code:



This gives slideshare the same SEO link love but is a lot less to add. Instead of the full slide include, you’ll get a preview image you can click that gets replaced with the flash movie. The following are examples:

Now, in order to convert one to the other you could do it by hand, or use the slideshare embed converter or install the Greasemonkey script

So far this is a hack, but I talked to Jonathan Boutelle about it yesterday night at the San Francisco JavaScript meetup and he is happy to pursue this idea further. My wishlist:

  • A larger preview image
  • A rest API call that gives me this information in a readable manner

Example of an unobtrusive, lazy-loading badge using the Twitter API

Friday, April 11th, 2008

Following questions I had about my talk at Highland Fling about badges for distribution and a twitter nagging by Tantek about the official twitter badge I thought I’d have a go at creating a twitter badge following some of the best practices I mentioned in my talk. Here’s the result.

The markup

Instead of HTML containers that will be seeded with real data when JavaScript is available and pointless when it isn’t, I wanted to build on top of HTML that makes sense without scripting and get all the info my script needs from there.






Example of a unobtrusive, lazy loading twitterbadge






In order to customise the badge, I allow for CSS classes with information to be added to the main container:






Example of a unobtrusive, lazy loading twitterbadge






They mean the following:

  • amount-n defines the amount of tweets to be displayed with n being an integer
  • skin-name defines the skin you want to use (for now this is grey and blue)
  • userinfo defines if the user’s avatar, name and location should be displayed.

The script

Here’s the full script and we’ll go through the bits one by one.

twitterbadge = function(){
var config = {
countDefault:5,
badgeID:'twitterbadge',
userID:'twitterbadgeuser',
tweetsID:'twitterbadgetweets',
userinfo:'userinfo',
stylesmatch:/skin-(w+)/,
amountmatch:/amount-(d+)/,
styles:{
'grey':'twitterbadge.css',
'blue':'twitterbadgeblue.css'
}
};
var badge = document.getElementById(config.badgeID);
if(badge){
var link = badge.getElementsByTagName('a')[0];
if(link){
var classdata = badge.className;
var head = document.getElementsByTagName('head')[0];
var amount = config.amountmatch.exec(classdata);
var amount = amount ? amount[1] : config.countDefault;
var skin = config.stylesmatch.exec(classdata);
if(skin && skin[1]){
var style = document.createElement('link');
style.setAttribute('rel','stylesheet');
style.setAttribute('type','text/css');
style.setAttribute('href',config.styles[skin[1]]);
head.insertBefore(style,head.firstChild);
}
var name = link.href.split('/');
var resturl = 'http://twitter.com/statuses/user_timeline/' +
name[name.length-1] + '.json?callback=' +
'twitterbadge.show&count=' + amount;
var script = document.createElement('script');
script.src = resturl;
script.type = 'text/javascript';
function show(result){
if(classdata.indexOf(config.userinfo) != -1){
var user = document.createElement('p');
user.id = config.userID;
var img = document.createElement('img');
img.src = result[0].user.profile_image_url;
img.alt = result[0].user.name;
user.appendChild(img);
var ul = document.createElement('ul');
var data = ['screen_name','name','location'];
for(var i=0;data[i];i++){
if(result[0].user[data[i]]){
var li = document.createElement('li');
li.appendChild(document.createTextNode(result[0].user[data[i]]));
ul.appendChild(li);
}
}
user.appendChild(ul);
badge.appendChild(user);
}
var tweets = document.createElement('ul');
tweets.id = config.tweetsID;
for(var i=0,j=result.length;i 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
if (delta < 60) {
return 'less than a minute ago';
} else if(delta < 120) {
return 'about a minute ago';
} else if(delta < (60*60)) {
return (parseInt(delta / 60)).toString() + ' minutes ago';
} else if(delta < (120*60)) {
return 'about an hour ago';
} else if(delta < (24*60*60)) {
return 'about ' + (parseInt(delta / 3600)).toString() + ' hours ago';
} else if(delta < (48*60*60)) {
return '1 day ago';
} else {
return (parseInt(delta / 86400)).toString() + ' days ago';
}
}
}
}
return {
show:show,
init:function(){
head.appendChild(script);
}
};
}();
twitterbadge.init();

I am using the revealing module pattern to keep code short and avoid global callback methods. However, there is a slight Opera oddity with generated script nodes in module patterns so we have to deviate from the norm there with an extra init() method call after the main module.

The first thing I thought of providing is a configuration object for the script. This makes it easy to change settings of it without having to hunt through the whole script and is just a nice service for the implementer:


twitterbadge = function(){
var config = {
countDefault:5,
badgeID:'twitterbadge',
userID:'twitterbadgeuser',
tweetsID:'twitterbadgetweets',
stylesmatch:/skin-(w+)/,
amountmatch:/amount-(d+)/,
styles:{
'grey':'twitterbadge.css',
'blue':'twitterbadgeblue.css'
}
};

Here we have all the IDs in use, the style names and the corresponding file names and the regular expressions to get the data from the CSS class name. All of the IDs and classes are hooks to define your own skins. There is also a countDefault variable to define how many items should be shown when the amount class is not set.


var badge = document.getElementById(config.badgeID);
if(badge){
var link = badge.getElementsByTagName('a')[0];
if(link){

I test for the badge and that it contains a link as this is where we will get all our configuration data from.


var classdata = badge.className;
var head = document.getElementsByTagName('head')[0];
var amount = config.amountmatch.exec(classdata);
var amount = amount ? amount[1] : config.countDefault;
var skin = config.stylesmatch.exec(classdata);
if(skin && skin[1]){
var style = document.createElement('link');
style.setAttribute('rel','stylesheet');
style.setAttribute('type','text/css');
style.setAttribute('href',config.styles[skin[1]]);
head.insertBefore(style,head.firstChild);
}

Then I am ready to read the information from the class. I set a shortcut to the document head and read the amount of tweets to be displayed. If there is no amount-n class set I fall back to the default.

Next is the skin, I check if the class was set and if that is the case I create a new link element pointing to the right skin. I get the href from the configuration styles object.

Notice that I use insertBefore() to add the style to the head of the document and not appendChild(). This ensures to a degree that the skin css file will not override settings that might be in other stylesheets. The last included style sheet rules them all.


var name = link.href.split('/');
var resturl = 'http://twitter.com/statuses/user_timeline/' +
name[name.length-1] + '.json?callback=' +
'twitterbadge.show&count=' + amount;
var script = document.createElement('script');
script.src = resturl;
script.type = 'text/javascript';

Now it is time to find the user name (by splitting the href attribute of the link) and assemble the REST url to get the twitter data. Normally I would have added the new script node to the head directly aftwerwards, but Opera doesn’t like this.


function show(result){
if(classdata.indexOf(config.userinfo) != -1){
var user = document.createElement('p');
user.id = config.userID;
var img = document.createElement('img');
img.src = result[0].user.profile_image_url;
img.alt = result[0].user.name;
user.appendChild(img);
var ul = document.createElement('ul');
var data = ['screen_name','name','location'];
for(var i=0;data[i];i++){
if(result[0].user[data[i]]){
var li = document.createElement('li');
li.appendChild(document.createTextNode(result[0].user[data[i]]));
ul.appendChild(li);
}
}
user.appendChild(ul);
badge.appendChild(user);
}

Now it is time to start the core functionality: the show method that will be invoked by the twitter REST API callback. I check if the userinfo has been set and create the markup accordingly. Nothing amazing here.


var tweets = document.createElement('ul');
tweets.id = config.tweetsID;
for(var i=0,j=result.length;i

Next I get the tweets information, assemble a list and add it to the badge.


function relative_time(time_value) {
[...]
}
}
}
return {
show:show,
init:function(){
head.appendChild(script);
}
};
}();
twitterbadge.init();

The relative_time method is actually taken from the original twitter badge and calculates how old the tweets are. I end the module with a return statement that defines the public methods (in this case only show) and add the script node to call the REST API in an init method. This is only necessary to fix the Opera issue.

Download and Example

You can download the twitter badge and see it in action.

[tags]unobtrusive,badge,twitter,api,json,thehighlandfling,highlandfling08,highlandfling,thehighlandfling08[/tags]

Opera, REST APIs, Module Patterns and generated script nodes

Friday, April 11th, 2008

I just came across an annoying thing in Opera. I love using the Module Pattern for designing my JavaScripts and I love generating script nodes on the fly for REST APIs that give me back JSON. The following example using the twitter API works swimmingly on Firefox and Safari (have to check IE later, but I’d be surprised if it didn’t):



However, if you test this in Opera you get an error:


JavaScript - file://localhost/Applications/MAMP/htdocs/operacallbackfail.html
Inline script thread
Error:
name: ReferenceError
message: Statement on line 1: Reference to undefined variable: twitterbadge
Backtrace:
Line 1 of linked script http://twitter.com/statuses/user_timeline/codepo8.json?callback=twitterbadge.show&count=10
twitterbadge.show([{user : {screen_name : "codepo8",...08"}]);

It seems like the newly generated script node calls the method of the module before the module has been created. In other words, newly generated script nodes halt the execution of the code that generated them. The following example works across browsers, including Opera:



Not too daunting a fix, but it feels wrong to have to have an extra method and another line calling it.

Try it out for yourself: Opera bug with dynamic script nodes and module pattern

[tags]Opera,REST,JSON,bug,dynamic,javascript,scriptnodes[/tags]

Adobe onAir show in London

Thursday, April 10th, 2008

I almost didn’t hear about Adobe’s on Air conference until it was upon us and the list of attendees was full. Luckily I got hold of one of the organizers and got a ticket that way (thanks Mike !).

I have to admit that I was dreading the whole thing to be a terrible marketing-driven show and tell of out-of-the-box solutions that solve every problem web development throws at you. This was my experience with a lot of large product company shows in the past – I was proven wrong.

The onAir tour was a great experience, both in terms of organization and content. For a whole day (doors opening at 9.15am and the event closing at 6pm) several speakers told us all about Air – from low level command line building via using different IDEs all the way to deployment, automated update and security of your applications.

The schedule was very tight with a few breaks in between and a larger lunch break. There was no feeling of boredom ever as all speakers kept their presentations snappy and hands-on. If you got in a lull, the rock-steady wireless could keep you busy (although I realized that live-twittering what is going on angers folk though).

People already versed in the “Flash/Flex scene” that came to the conference said that for them a lot was not news, but I think the idea of the onAir tour was not to preach to the Flash crowd but to expand the developer community for the product. Talking to several “Adobe virgins” I got the impression they met their goal. Sam Clark for example told me he came with very low expectations but very strongly considers getting into Air development now.

Of course all of this is post-show enthusiasm, but there are a lot of things Air does that really makes it interesting for web developers:

  • you can use the technologies you are already using (HTML/CSS/JS)
  • you don’t have to worry about cross-browser and cross-platform incompatibilities (you work with WebKit, which also gives you alpha transparency, rounded corners and all the other CSS goodies we so crave to have cross-browser)
  • as a JavaScript developer you have reach you never had before – you can access the file system, create and access SQLite Databases or access 10MB of encrypted storage for your application (I remember messing around with .hta and COM objects to do this in JS once, not fun)
  • You have full access to the native windows and menus of the operating system, thus being able to write applications that look and feel exactly like any other the user is already familiar with.
  • The security model is much more sophisticated than what we have to deal with in JavaScript and browsers. That said, the option to be able to re-assign file associations for your application does sound potentially dangerous.

Of course not all is rosy about Air and the only presentation that showed the issues when implementing it on a large scale was the one by the BBC.

  • Air applications need to be installed, which is something that does spook out users paranoid about viruses. Ironically this is the only way to keep them secure – but it is a hurdle. The web installer badges are a nice way to ease this process.
  • The accessibility support is bad, this needs to get fixed, starting with proper keyboard support
  • Air applications seem to take up a lot of RAM when they run for a long time. According to Jonathan Snook this is largely caused by the library that creates growl windows and once this is fixed we’ll have less problems.
  • The installer is only available in English and needs to be i18n ready.

It is very interesting to see how all the web technologies seem to merge sooner or later with the common denominator being JavaScript. Seeing what Flash developers do with almost the same language I’ve used for years but unhindered by browser restrictions is pretty interesting and looks like a good challenge to marry the best practice quality ideas we found in the hostile browser world with this “let’s try if we can do it” attitude.

I also very much like the fact that Adobe promised to release all the presentation videos on their site after the road show and that they even provide an API to access all the media accumulated during the ride.

Of course there was schwag to go, in this case T-shirts and some goodies that were given out using raffle tickets. There was a tad of an embarrassing moment when I won twice, once with my own ticket and secondly with Steve Webster’s (who had to finish a project and couldn’t come). Hence I drew another winner and gave my prize away.

Good job, I am looking forward to the next event.

[tags]onairtour,onair2008london,conference,adobe,air,javascript,css[/tags]