Software As She’s Developed

Mahemoff’s Podcast/Blog - Web, Programming, Usability from the Author of ‘Ajax Design Patterns’ (AjaxPatterns.org)

Software As She’s Developed header image 1

WebWait in the Wild

December 11th, 2008 · No Comments

I haven’t blogged about WebWait since launching it almost two years ago. But it’s been quietly growing into quite a pretty well known site among certain communities in various languages. What I like seeing is people are actually getting value from it…it’s not just a novelty, but a tool for them. Here are some examples.

A conversation about blog timings … and soup recipes:

At December 7, 2008 6:24 PM, Blogger Arfi Binsted said… I love clear soup like this. Kalyn, for some reason, almost 2 months, my browser goes so slowly when I come to your blog. I am not sure where it comes from. Today, I try to open it from the link on my blogroll, it works! Just as well it is a click away from a miracle just to happen for Barbara :) hugs. At December 7, 2008 6:41 PM, Blogger Kalyn said… Maris, this is one of the best parts of blogging in my opinion. Arfi, thanks for telling me. I don’t know what’s going on because I use Webwait.com and check and my blog always seems fine there. I’m going to remember to check back with you and see if it is still a problem. I do hope you’re right that it’s a good omen!

A softly, softly, twitter encouragement for the TechCrunch boys:

liking http://webwait.com/ - http://fav.or.it took 3.04s - (pretty quick!) - @techcrunch took 13s!! - sort it out boys

A tech journalist compares browser speeds:

I used WebWait.com to test how quickly Chrome 0.2, Firefox 3, Safari 3.1, and Internet Explorer 7 loaded the InformationWeek.com home page. The results for three page loads averaged were: Firefox (5.21s) Safari (6.34s), Chrome (6.48s), Internet Explorer (8.90s).

A reviewer notes how much caching helps and I discover in the process I can almost grok Italian geek-speak:

La seconda volta grazie alla cache WebWait ci ha messo 5.17. Un tempo discreto direi.

A blogger produces a “webwait research report” - schweet!:

Ko nih, tanya sket pun tak boleh. Oklaa aku tunggu ko buat positioning tuh. Sambil2 tu jom buat research… :: WebWait Research Report :: Site: farisfakri.com: Average load time after 15 runs: 0.11s telescopictext.com: Average load time after 15 runs: 0.12s google.com: Average load time after 15 runs: 1.11s Blog: ladycoder.com: Average load time after 15 runs: 5.29s blog.farisfakri.com: Average load time after 15 runs: 8.12s soleh.net: Average load time after 15 runs: 9.02s noktahhitam.com: Average load time after 15 runs: 10.07s life4hire.berceloteh.com: Average load time after 15 runs: 11.99s kujie2.com: Average load time after 15 runs: 14.10s berceloteh.com: Average load time after 15 runs: 14.52s Terima kasih Yam, kerana memberi aku kerja. Laporan kajian aku mendapati feedjit, nuffnang, mybloglog, dsb adalah antara yang menjadi punca utama kelembapan loading sesebuah blog. (MM - Google Translate says “Thank Yam, because I gave work. I found the study report feedjit, nuffnang, blogspot, etc. are among the main source of humidity loading a blog.”)

WebWait is just one way to get an impression of speed, as the FAQ explains. And in cases like those above, it can give people a handy snapshot without relying on any browser-specific plugins.

People also love screencapping their webwait results, as this google images search illustrates. It would be nice to somehow make a gallery of those. Anyway, I’ve got some time off coming up, and one of my projects will be to make some long-overdue updates to the site, while ensuring it stays dead simple to use.

→ No CommentsTags: HumansAndTech · SoftwareDev

Requirements Analysis: Better than a Dilbert Cartoon

December 4th, 2008 · No Comments

These true anecdotes about sum it up::

7. Client can’t articulate a single desired user goal. He also can’t articulate a business strategy, an online strategy, a reason for the site’s existence, or a goal or metric for improving the website. In spite of all that, client has designed his own heavily detailed wireframes.

10. On the eve of delivery, the previously uninvolved “vision guy” sends drawings of his idea of what the web layout should look like. These drawings have nothing to do with the user research you conducted, nor with the approved recommendations, nor with the approved wireframes, nor with the approved final design, nor with the approved final additional page layouts, nor with the approved HTML templates that you are now integrating into the CMS.

18. As approved, stripped-down “social networking web application” site is about to ship, a previously uninvolved marketing guy starts telling you, your client, and your client’s boss that the minimalist look “doesn’t knock me out.” A discussion of what the site’s 18-year-old users want, backed by research, does not dent the determination of the 52-year-old marketing guy to demand a rethink of the approved design to be more appealing to his aesthetic sensibility.

→ No CommentsTags: SoftwareDev

TiddlyWeb user authentication

December 4th, 2008 · No Comments

I've been getting to grips with TiddlyWeb and authentication lately.

The following plugin code will set the username in the TiddlyWiki client to match the username that was presented to the server. It's simply a one-liner which delegates to quirksmodes' cookie handling library. This all assumes tiddlyweb is using the authentication challenger, as there's no standard on cookie names.

JAVASCRIPT:
  1. config.options.txtUserName = readCookie("tiddlyweb_user").split(":")[0].substr(1);
  2.  
  3. // http://www.quirksmode.org/js/cookies.html
  4. function readCookie(name) {
  5.   var nameEQ = name + "=";
  6.   var ca = document.cookie.split(';');
  7.   for(var i=0;i <ca.length;i++) {
  8.     var c = ca[i];
  9.     while (c.charAt(0)==' ') c = c.substring(1,c.length);
  10.     if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  11.   }
  12.   return null;
  13. }


BTW this isn't essential. When a user uploads something, TiddlyWeb will automatically set the modifier to the username on the server anyway. So the next time the user sees it, it will have the right username anyway. But having the right username in the client will make things right from the start and it feels cleaner to upload the tiddler with the right username data, even if it's ignored. I could imagine future versions/plugins on the server that allow any username to be uploaded, with some algorithm to check if it's allowed. (ie if admin, allow all usernames.)

→ No CommentsTags: SoftwareDev

Chasing super

December 4th, 2008 · 1 Comment

A lot of discussion around object-oriented Javascript involves finding cunning methods to get a super reference. This is sometimes a reference to the super class, the super instance, or the super version of the current method. The latest installment in an Ajaxian posting this week on work by Erik Arvidsson. It makes for an interesting enough intellectual exercise and I have full respect for anyone who can pull these things off. But as Erik himself has commented, is it really worth it? ("Once again I find that adding code to make things simpler in JS is unsuccessful. The costs for adding better ways to do things don’t pay for itself. (Except for the inherits function of course.")

In the case of super...how useful is super - any of the types of super - in practice? In a more general sense, it seems that most discussions of OO and inheritance in Javascript focus more on mimicking the features of typical OO languages and less about how they are actually used. We're thinking more about the design patterns of implementation - how to "do OO" - than the much more important design patterns of application.

super isn't very useful for two reasons:

  • Inheritance is overrated.

    Inheritance is often considered the killer app of OO. In fact, the killer app is encapsulation - combining data and behaviour in a single model. Inheritance is a great feature, but it's icing on the cake compared to the magnificence of encapsulation. Also, as the uber-uber GoF patterns book emphasised, delegation often trumps inheritance. In enterprise Java-land, this has now been firmly entrenched by the dependency injection "revolution". The idea is basically small dumb things, loosely connected, just like the Unix philosophy and that of Web 2.0. You have a small Strategy class that does just that, and you can inject it into many and varied classes. You just wire up the dependencies, outside of both classes. What you don't have is a whopping big monolithic class with loads of subclasses that each have their own subclasses doing different things. The delegation model relies on contract inheritance, i.e. Interfaces in Java terms, but not on behaviour inheritance. Inheritance is often confused for design-by-contract, especially in languages like C++ which don't have an explicit Interface construct.

    The popularity of tools supporting delegation and dependency injection, Spring in particular, means many developers are learning this principle by sheer osmosis if not explicitly. Likewise, the duck typing of languages like Ruby and Python - and, notably for our purposes, Javascript - means you can do this stuff well without any special frameworks. Furthermore, even with Ajax starting to reach some level of maturity, most Ajax apps are orders of magnitude smaller than those enterprise apps whose complexity is a key motivation for inheritance. Inheritance? Good, very good...but overrated.

  • super is a code smell.

    For those occasions when inheritance is appropriate, super still remains inappropriate in most cases. If you take a gander at the aforementioned GoF patterns, you'll see that most inheritance-related patterns rely on the superclass calling particular methods on the subclass (usually protected methods). These are methods the superclass has explicitly defined and knows about. As long as the protected method fulfills its contract correctly, everything works nicely. There's no need to call super.

The more fundamental point here? Javascript ain't Java! Every language is unique.

→ 1 CommentTags: SoftwareDev

BashPodder mod - add podcasts to iTunes

December 4th, 2008 · No Comments

As a podcatcher (among other things), iTunes sucks. Badly. iPodder is nicer, mainly because I can keep my follow list in the cloud at PodNova. However, it (or the combination with podnova) often ends up downloading gigs of old stuff, on some particular feeds. Worse, it consumes obscene quantities of memory and CPU, with its UI being unresponsive to the point of being unusable, like 30 second or more delays for each gesture. This is on an early macbook.

Anyway, I decided to rectify the situation and go back to bashpodder, a tiny shell script which proves the point that a podcatcher need not be grandiose, nor a resource gobbler. It's also cool as it's easily customisable for anyone with some bash-fu. I modded it a few years back to keep my follow list in the cloud. (I believe clouds were called "servers" back then.)

I've recently modded bashpodder to add files to iTunes. Yes, I still like iTunes and I definitely like the i* players which are, for most intents and purposes, constrained to the universe of iTunes. As for it's podcatcher, not cool. The interface for exploring podcasts is cumbersome, and the result, the downloaded podcasts, are not handle with care. For example, if you download podcasts with iTunes, it marks them out specially as podcasts, and there's no way to, say, delete all podcasts older than a week. If they're normal tracks added from an external catcher, they're just regular MP3s and you can do what you like with them. And you can't keep your follow list in the cloud!

So here's bashpodder modified to add to itunes. (The itunes part I added is the HERE doc section beginning with /usr/bin/osascript. You could easily extend it to, say, tag podcasts from certain feeds with a certain album name.)

Click on "Plain Text" and cut-and-paste it into a shell file. Easiest would be to download the several files required for bashpodder (there should be a mod to make it just a single self-modifying file), and replace bashpodder.shell contents with that below.

JAVASCRIPT:
  1. #!/bin/bash
  2. # By Linc 10/1/2004
  3. # Find the latest script at http://linc.homeunix.org:8080/scripts/bashpodder
  4. # Revision 1.2 09/14/2006 - Many Contributers!
  5. # If you use this and have made improvements or have comments
  6. # drop me an email at linc dot fessenden at gmail dot com
  7. # I'd appreciate it!
  8. # Make script crontab friendly:
  9. cd $(dirname $0)
  10. # datadir is the directory you want podcasts saved to:
  11. datadir=$(date +%Y-%m-%d)
  12. # create datadir if necessary:
  13. mkdir -p $datadir
  14. # Delete any temp file:
  15. rm -f temp.log
  16. # Read the bp.conf file and wget any url not already in the podcast.log file:
  17. while read feed
  18.   do
  19.   podcast=`echo $feed | cut -f 1 -d ' '`
  20.   echo $podcast
  21.   file=$(xsltproc parse_enclosure.xsl $podcast 2> /dev/null || wget -q $podcast -O - | tr '\r' '\n' | tr \' \" | sed -n 's/.*url="\([^"]*\)".*/\1/p')
  22.   for url in $file ; do
  23.     echo "Retrieving $url"
  24.     echo $url>> temp.log
  25.     if ! grep "$url" podcast.log> /dev/null
  26.       then
  27.       # wget -t 10 -U BashPodder -c -q -O $datadir/$(echo "$url" | awk -F'/' {'print $NF'} | awk -F'=' {'print $NF'} | awk -F'?' {'print $1'}) "$url"
  28.       outpath=$datadir/$(echo "$url" | awk -F'/' {'print $NF'} | awk -F'=' {'print $NF'} | awk -F'?' {'print $1'})
  29.       curl --retry 10 -C - $url> $outpath
  30.       fullpath=`pwd`/"$outpath"
  31.       /usr/bin/osascript <<-EOF
  32.         tell application "iTunes"
  33.           set posix_path to "$fullpath"
  34.           set mac_path to posix_path as POSIX file
  35.           set new_track to add mac_path
  36.           set genre of new_track to "*Podcast"
  37.         end tell
  38. EOF
  39.     fi
  40.     done
  41.   done <bp.conf
  42. # Move dynamically created log file to permanent log file:
  43. cat podcast.log>> temp.log
  44. sort temp.log | uniq> podcast.log
  45. rm temp.log
  46. # Create an m3u playlist:
  47. ls $datadir | grep -v m3u> $datadir/podcast.m3u


→ No CommentsTags: SoftwareDev

WordPress “Edit This” Links via Ajax

November 25th, 2008 · 1 Comment

Working on a WordPress customisation recently, I added an "Edit This" link which only logged-in people can see. To get caching right, the server always outputs the same thing - an invisible link - and only in the browser does the decision get made to show the link or not. This exemplifies the pattern I only ever identified as "Ajax as a Remedy for the Cacheability-Personalization Dilemma", but which I will now call "Browser-Side Personalisation".

In this case, the personalised content is not secret, so it's fine to output it in the HTML, and simply make it invisible by default. And it's not mission-critical either, so the app degrades nicely if Javascript isn't present - it simply won't be displayed and the user will have to go into the "Manage Posts" area and look up the post from there.

To output "Edit This" from the server, within a WordPress loop:

JAVASCRIPT:
  1. <div class="editPostLink"><? edit_post_link() ?></div>


Links of this nature are rendered invisible within layout.css:

CSS:
  1. .editPostLink { display: none; }


... but we switch them on if the user is logged in:

JAVASCRIPT:
  1. var loggedIn = /wordpress_logged_in/.test(document.cookie);
  2.   if (loggedIn) $(".editPostLink").show(); // it's easy with JQuery


→ 1 CommentTags: SoftwareDev

Man pages that read more like legal contracts

November 17th, 2008 · No Comments

Case in point: I want to use "find" to find files I've recently created.

man find (BSD edition):

-ctime n[smhdw] If no units are specified, this primary evaluates to true if the difference between the time of last modification time and the time find was started, rounded up to the next full 24-hour period, is n 24-hour periods.

If units are specified, this primary evaluates to true if the difference between the time of last modification time and the time find was started is exactly n units. Please refer to the -atime primary description for information on supported time units.

Humane documentation FAIL! The precision here is all well and good to include somewhere, but unnecessary for most usages. Please, just tell me what I need to know and Don't Make Me Think!.

What I wanted to see:


-mtime t

Include only files modified t days ago. You will typically want to specify a +/- sign. e.g. "-ctime +3" for files created 3 or more days ago. To measure in another unit of time, you can append one of the following to t: s for seconds, m for minutes, h for hours, d for days, w for weeks, but note that "-ctime" unfortunately only works if the time is at least one day. For example, "find . -ctime 1d6h" will return files created in the past 30 seconds.

Technical detail: (the legalese stuff goes here. 99% of users will never need it and never read it.)


The improved version focuses on what the typical user needs to know, uses active voice, avoids the cross-reference (it's documentation, not code, so embrace redundancy!), and most importantly, includes an example. (The man page does include examples at the bottom, but (a) they should be included against each component too; and (b) they still shy away from concrete values: "-newer ttt"!!!)

A lot of these man pages have been around for two decades or more, with minimal changes to my knowledge; I'd love to see someone like Ubuntu sponsor an effort to bring them up to date. Plain English without dumbing down.

→ No CommentsTags: SoftwareDev

Guid0: A Javascript GUID Generator

November 13th, 2008 · 4 Comments

Guid0.js

Guid0 is a GUID library for Javascript. Okay, it doesn't yet do official, bona fide, 128-bit, GUIDs yet, mainly for API design reasons. But this is a library you might find useful if you want to generate a unique ID in your Ajax app.

Usage:

JAVASCRIPT:
  1. guid = new Guid();
  2. guid.generate(); // Returns a unique ID, e.g. "dkvagrkx1rt"


With options:

JAVASCRIPT:
  1. guid = new Guid(
  2.   {
  3.     chars: Guid.constants.base85// or you could say "abc" if you only wanted those chars to appear
  4.     epoch: "June 1, 2003",
  5.     counterSequenceLength: 2, // a counter field appended to the end
  6.     randomSequenceLength: 2 // a random field appended to the end
  7.   }
  8. )


The demo:

The demo generates a bunch of GUIDs. They look almost the same because most of the GUID is just a time representation in Base N (where N is the number of characters in the GUID's configured charset).

I made Guid0 in conjunction with TiddlyWiki work. Within a store, tiddlers are keyed on their title. In the typical case, the user has entered a title to describe the content. But when we get into application territory - using TiddlyWiki as an application framework - there are reasons why we want the more familiar database approach of automagically generating the primary key. These are several reasons for this:

  • Tiddlers with no meaningful title. Sometimes, tiddlers are just containers for text someone has entered. A perfect example is the Comments Plugin I've been working on (separate blog post pending). A comment is a tiddler. When you have a blog or forum or whatever with comments, you usually don't invite users title their comment...it's not very enticing. So in this case, the comment is the tiddler text and the title must be auto-generated so we can put it in the store.
  • Renaming. As with the usual motivation for auto-generated IDs over meaningful IDs, renaming support is sometimes required. In one case, I have a user submitting a first cut of something, and an admin coming along later and cleansing it. We can't expect the user to get the name right the first time, and there are several tiddlers referencing the initial tiddler (via custom fields) by the time it might be renamed.
  • Can't ensure uniqueness. With multiple users, you don't always want to force them into choosing different titles for each tiddler. If the comments plugin did include titles for instance, you wouldn't want a uniqueness constraint. Using GUIDs, you could support multiple titles with the same display name.

The library is generic - not TiddlyWiki-specific - but I've wrapped it into a small TiddlyWiki plugin. In the future, we may expand the plugin. For example, each time a new tiddler is created, the plugin may intercept the call and give the tiddler a GUID title, and stick the title in a "name" field.

I'll update this post when proper 128-bit GUID is supported.

→ 4 CommentsTags: SoftwareDev

Offline Sound: No Flash, No File

October 21st, 2008 · 11 Comments

I just did some tinkering with offline sound and it turns out you can embed an audio clip in an HTML file, and play it without using Flash. I could have demo'd this in a raw HTML file, but it was just as easy to stick it in a Tiddlywiki file. So I made a TiddlyWiki called JinglyWiki. Click on the button and it will play a sound. If you grab the file (safest using a tool like Curl or wget, as the browser will sometimes Save As something different), you can run it offline and it will still play the sound.

JinglyWiki started after an office conversation yesterday. We had a flaky wifi connection and were pleased to be able to play a beat on http://instantrimshot.com to signify a re-connection. This got us thinking about playing beats in Tiddlywiki. Jeremy mentioned the possibility of data: URIs, and I thought back to Reinier's great effort in playing sound without Flash. I wondered if it would work with data: URIs and to my pleasant surprise, it did. At least in FF3, which is all I've tested so far. I also forgot how tiny the code is to play audio without Flash - you just add an element to the page.

The point of JinglyWiki is you can stick a 300K file on your local file system or a USB stick and play gratuitous sounds without being online. Your iPod cost you several hundred bucks, whereas JinglyWiki is entirely free. Free as in you can spend your money on beer instead. JinglyWiki may well be the best portable music player you never paid a penny for. Groovy.

It would be so much more useful with some code to dynamically generate WAVs, but that's a project for another day.

As for the name, it's a play on the much more important project Phil Hawksworth has initiated to build a JQuery based Tiddlywiki framework: JigglyWiki. I think this nascent effort is going to produce some wonderful plugins for the JQuery community and be an important enough project to derive novelty joke names out of :), so I'm setting the trend here.

The data itself is 15KB for about a second of voice content.

Here's the plugin code. Trivial or wot?!!!

JAVASCRIPT:
  1. config.macros.jingle = {}
  2. config.macros.jingle.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
  3.  
  4.   button = document.createElement("button");
  5.   button.style.fontSize = "4em";
  6.   button.innerHTML = "Play Me! Play Me!"
  7.   button.onclick = function(ev) {
  8.     startWav(SOUND_URL);
  9.   }
  10.   place.appendChild(button);
  11.  
  12.   // cancel dbl-click so we can follow our natural urge to click on the button incessantly
  13.   story.getTiddler(tiddler.title).ondblclick = function() {}
  14.  
  15. }
  16.  
  17. // non-flash sound handling adapted from http://www.zwitserloot.com/files/soundkit/soundcheck.html
  18.  
  19. var embedEl;
  20.  
  21. function startWav(uri) {
  22.         stopWav();
  23.         embedEl = document.createElement("embed");
  24.         embedEl.setAttribute("src", uri);
  25.         embedEl.setAttribute("hidden", true);
  26.         embedEl.setAttribute("autostart", true);
  27.         document.body.appendChild(embedEl);
  28. }
  29.  
  30. function stopWav() {
  31.   if (embedEl) document.body.removeChild(embedEl);
  32.   embedEl = null;
  33. }
  34.  
  35. // To make a new sound, cut and paste into hixie's handy Data URI Kitchen
  36. // http://software.hixie.ch/utilities/cgi/data/data.pl
  37.  
  38. var SOUND_URL="data:audio/x-wav,RIFF%17%1C%00%00WAVEfmt%20%10%00%00%00%01%00%01%00%40%1F%00%00%40 ........................" // snipped


→ 11 CommentsTags: SoftwareDev

For Browser Extensions, Grease is the Word

October 20th, 2008 · 5 Comments

Chrome now has Greasemonkey support. The Chromium patch came from Aaron Boodman, who is at once a Google employee and the brains behind the original Firefox Greasemonkey extension.

It makes me wonder if this is the plan for Chrome add-ons. Forget about anything like Firefox's add-on mechanism and just rely on Greasemonkey. With the right APIs, it's all you need.

For a long time, I have been confused and disturbed by the disparity between Greasemonkey and Firefox extensions. Creating a Greasemonkey extension is dead simple for any Ajax developer; creating a bona fide Firefox extension is more complicated, and involves writing the kind of meta-descriptions and JARs that most Ajax folk avoid. (The kind of simplicity mantra that has made JQuery king of the hill for now.)

You might recall I automagically ported the domain teleporter Greasemonkey script to a Firefox extension a while back. That this was possible, and easy, demonstrates that the Firefox extension mechanism could be made a lot simpler. I thought there was talk of doing it for FF3, but it didn't happen.

What do Firefox extensions do that Greasemonkey can't? Nothing Greasemonkey can't get around.

  • Extended access - manipulating the Browser Object Model, accessing local file system, etc. All of this could be possible from a Greasemonkey script with the right APIs available. There are security implications, of course, but as long as users are aware of who can do what, it's no different from what we have now. It may be even better, due to Greasemonkey's built-in wildcard-based URL filtering, so that certain apps might only be limited to certain domains.
  • Metadata - certain metadata is present in an extension. This could just as easily be part of Greasemonkey's metadata.

It's my hope that Google's vision for Chrome plugins is Greasemonkey on Steroids, and that this unleashes a whole new ecosystem of powerful add-ons that have until now required too much effort to build.

→ 5 CommentsTags: SoftwareDev