<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4154210566841915972</id><updated>2011-09-15T09:17:24.001-07:00</updated><category term='vanity'/><category term='linux'/><category term='dissertation'/><category term='xml'/><category term='mediawiki'/><category term='tech'/><category term='bad service'/><category term='milestone'/><category term='humph'/><category term='dom'/><category term='javascript'/><category term='html5'/><category term='wifi'/><category term='jim and druma'/><category term='web'/><category term='design patters'/><category term='scope'/><category term='designMode'/><category term='sketch'/><category term='printing'/><category term='events'/><category term='scriptaculous'/><category term='wikizzle'/><category term='functions'/><category term='dom mutation events'/><category term='mixins'/><category term='chrome'/><category term='s5'/><category term='contentEditable'/><category term='firefox'/><category term='aberystwyth university'/><category term='Public speaking'/><category term='parsers'/><category term='html'/><category term='coding'/><category term='library code'/><category term='design'/><category term='coffee'/><category term='mozilla'/><category term='xhtml'/><category term='wikitext'/><category term='ubuntu'/><category term='prototype'/><category term='Oxford Geek Nights'/><category term='borken'/><title type='text'>Jim Higson's blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>20</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-7126172323048606565</id><published>2009-09-25T09:40:00.000-07:00</published><updated>2009-09-26T04:19:33.094-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='xhtml'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='firefox'/><category scheme='http://www.blogger.com/atom/ns#' term='contentEditable'/><category scheme='http://www.blogger.com/atom/ns#' term='designMode'/><category scheme='http://www.blogger.com/atom/ns#' term='html5'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='html'/><category scheme='http://www.blogger.com/atom/ns#' term='mozilla'/><title type='text'>XHTML, Mozilla Firefox and ContentEditable</title><content type='html'>&lt;p&gt;Can't quite get over that I'm using wifi &lt;em&gt;on a &lt;a href="http://www.oxfordtube.com"&gt;bus&lt;/a&gt;&lt;/em&gt;. Job search is going well, have two companies I'm fairly sure are going to make offers and a few more leads to chase up just in case. More later if I get the jobs.&lt;/p&gt;

&lt;p&gt;Switching from iframe/designMode to div/contentEditable and suddenly nothing was editable. Can't find this mentioned anywhere online so will blog it here: &lt;strong&gt;In Moz, contentEditable does nothing if the document is XHTML&lt;/strong&gt;. In Fiinrefox 3+ and HTML this makes the element editable as per my favourite &lt;a href="http://blog.whatwg.org/the-road-to-html-5-contenteditable" title="HTML 5 blog on contentEditable"&gt;IE proprietary feature turned spec in HTML5&lt;/a&gt;. This isn't so surprising (contentEditable is part of &lt;strong&gt;HTML5&lt;/strong&gt;, not the rather more strictly defined &lt;strong&gt;XHTML&lt;/strong&gt;) but took me a long time to work out. Annoying because now I have to change a thousand &lt;code&gt;.tagName&lt;/code&gt;s to &lt;code&gt;.tagName.toLowerCase()&lt;/code&gt;. Double annoying because I can't use &lt;a href="http://www.prototypejs.org/api/enumerable/pluck"&gt;Array.pluck('tagName')&lt;/a&gt; to get a bunch of tagNames at once. Oh well.&lt;/p&gt;

&lt;p&gt;So why switch? Well, having client-side applications span two documents was creating more problems than it solved. &lt;a href="http://prototypejs.org"&gt;Prototype&lt;/a&gt; tends to make assumptions that there is only one &lt;code&gt;document&lt;/code&gt; and sometimes gets upset when &lt;code&gt;element.ownerDocument != document&lt;/code&gt;. Add to that, dragging and dropping breaks when the cursor wanders into another document and the overhead of loading some of the javascript twice and for a while now I've been planning on maaking this change. Hence &lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/branches/designMode-to-contentEditable/"&gt;branches/designMode-to-contentEditable/&lt;/a&gt; in SVN&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-7126172323048606565?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/7126172323048606565/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=7126172323048606565' title='42 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/7126172323048606565'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/7126172323048606565'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/09/xhtml-mozilla-firefox-and.html' title='XHTML, Mozilla Firefox and ContentEditable'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>42</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-2612898906934661242</id><published>2009-09-21T10:27:00.000-07:00</published><updated>2009-09-26T04:21:31.900-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='dom mutation events'/><category scheme='http://www.blogger.com/atom/ns#' term='dom'/><category scheme='http://www.blogger.com/atom/ns#' term='chrome'/><category scheme='http://www.blogger.com/atom/ns#' term='events'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Chrome and DOM mutation events</title><content type='html'>&lt;p&gt;I just found out the hard way: Chrome (or at least, the &lt;a href="http://dev.chromium.org/getting-involved/dev-channel"&gt;Linux dev builds&lt;/a&gt; but very likely the proper releases too) only fire &lt;a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html"&gt;DOM mutation events&lt;/a&gt; if the element which mutates is in the current document. Very likely Safari suffers from the same bug but until I can get it &lt;a href="http://alicious.com/2009/safari-4-on-linux-with-wine-update/"&gt;the Windows version working under &lt;abbr&gt;WINE&lt;/abbr&gt;&lt;/a&gt; I can only test that theory at work.&lt;/p&gt;

&lt;p&gt;This really quite sucks. Say you generate some XML, convert to HTML and put the HTML on the page, then start changing the XML and want to keep the HTML in sync. Well, you can't, unless you put the XML in the page too and hide it with &lt;code&gt;style="display:none"&lt;/code&gt; or whatever. Luckily, the browsers don't seem to mind when you add weirdly named elements to the page.&lt;/p&gt;

&lt;p&gt;The above example is essentially what I'm doing. The XML is &lt;a href="http://jimhigson.blogspot.com/2009/03/wiki-xml-parser-little-milestone.html"&gt;my wikixml representation&lt;/a&gt; and the HTML is &lt;a href="http://jimhigson.blogspot.com/2009/06/mediawiki-syntax-highlighting.html"&gt;the same, as syntax highlighted HTML&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'll try to work out if this is Chrome or &lt;a href="http://webkit.org/"&gt;webkit&lt;/a&gt; and send a bug report in as appropriate.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-2612898906934661242?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/2612898906934661242/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=2612898906934661242' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2612898906934661242'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2612898906934661242'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/09/chrome-and-dom-mutation-events.html' title='Chrome and DOM mutation events'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-7227560699610155385</id><published>2009-09-20T10:59:00.000-07:00</published><updated>2009-09-20T12:34:37.705-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='mediawiki'/><category scheme='http://www.blogger.com/atom/ns#' term='wikizzle'/><category scheme='http://www.blogger.com/atom/ns#' term='scriptaculous'/><category scheme='http://www.blogger.com/atom/ns#' term='scope'/><category scheme='http://www.blogger.com/atom/ns#' term='design patters'/><category scheme='http://www.blogger.com/atom/ns#' term='prototype'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><category scheme='http://www.blogger.com/atom/ns#' term='mixins'/><category scheme='http://www.blogger.com/atom/ns#' term='library code'/><title type='text'>Mixin factory: Really handy pattern for Classical OOP in Javascript</title><content type='html'>&lt;p&gt;&lt;a href="http://www.prototypejs.org/"&gt;Prototype&lt;/a&gt; Classes have a sort-of &lt;a href="http://en.wikipedia.org/wiki/Multiple_inheritance"&gt;multiple inheritance&lt;/a&gt; feature that lets you throw in Ruby-esque &lt;a href="http://en.wikipedia.org/wiki/Mixin"&gt;mixins&lt;/a&gt; when a Class is defined. There's a &lt;a href="http://www.prototypejs.org/learn/class-inheritance"&gt;pretty good tutorial on the Prototype site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I got a bit fed up my mixins defining not-really-hidden-but-they-start-with-an-underscore-and-have-a-slightly-obscure-name-so-lets-pretend-they-are-actually-private variables. Whenever I wanted to define state on the instance that could be shared between the mixed in functions this was the most obvious way to do this. These aren't proper private variables, they're just normal variables pretending to be&lt;/p&gt;

&lt;p&gt;The basic form for a mixin factory is:&lt;/p&gt;

&lt;pre name="code" class="javascript"&gt;
    var HOP_RUN_MIXIN_FACTORY = {
        make:function( likesToHop ) {

        //  vars here and arguments passed to make are per-Class that uses the mixin
            var perClassVaraible = 'one of these per class using the mixin';


        /*  return the mixin object */
            return {
                hop:function(){   
                    if( likesToHop )
                        alert( 'yay!' );
                    else
                        alert( 'meh' );
                    // ...etc ...
                }
            ,   run:function(){
                    // ...etc ...
                }
            }

        }
    };
&lt;/pre&gt;

Then, in the classes:

&lt;pre name="code" class="javascript"&gt;
var Rabbit = Class.create( HOP_RUN_MIXIN_FACTORY.make( true ),
{
    initialize:function(){
    //  ...etc...
    }
,   makeMoreRabbits:function(){
    //  ...etc...
    }
}
);

var Owl = Class.create( HOP_RUN_MIXIN_FACTORY.make( false ),
{
    initialize:function(){
    //  ...etc...
    }
,   lookArround:function(){
    //  ...etc...
    }
}
);
&lt;/pre&gt;

&lt;p&gt;The point is that this can be used to create slightly different versions of mixins and provides a place to keep per-class variables inside the mixin (eg, in Java, the closest analog is private static fields).&lt;/p&gt;

&lt;h3&gt;Great code, doesn't fix it!&lt;/h3&gt;

&lt;p&gt;It'll probably be a long wait until the uber awsomeness that is &lt;a href="http://webkit.org/blog/138/css-animation/"&gt;CSS transitions&lt;/a&gt; is well supported and until then I'm using the rather lovely, if somewhat breaky, &lt;a href="http://script.aculo.us/"&gt;Scriptaculous&lt;/a&gt; (aka Scripty) to fade elements in and out. CSS would be so much nicer for this, keeping the presentation (eg, that something is fading rather than just appearing) &lt;a href="http://en.wikipedia.org/wiki/Multiple_inheritance"&gt;out of the Javascript&lt;/a&gt;. Oh well, one day.&lt;/p&gt;

&lt;p&gt;When you want to fade something in and out, Scripty puts a surprising amount of work put on the programmer. Two conflicting effects acting at the same time on the same element are bad. Weird, flickery things happen. So before creating the &lt;a href="http://wiki.github.com/madrobby/scriptaculous/effect-appear"&gt;Effect.appear&lt;/a&gt; you have to check the object isn't already appearing or fading: If appearing do nothing. If fading, cancel fade, start appear. I found I had the code to do this in several places: obvious candidate for moving into a mixin.
&lt;/p&gt;

&lt;p&gt;So, I started to implement as a Mixin Factory, storing the appear and fade effects inside the make method, ie like the &lt;code&gt;var perClassVaraible&lt;/code&gt; in the HOP_RUN_MIXIN_FACTORY. Humph, &lt;a href="http://www.jibbering.com/faq/faq_notes/closures.html"&gt;closures&lt;/a&gt; are such a powerful concept but sometimes difficult to see - since the factory method is called once per class rather than once per instantiation, this was only allowing &lt;em&gt;one&lt;/em&gt; instance of the class to do the fade in and out thing at a time. So, back to polluting my objects with &lt;code&gt;this._fade_effect&lt;/code&gt; and &lt;code&gt;this._appear_effect&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;Variation - per-instance state in the mixin&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Genuine&lt;/em&gt; per-instance mixin variables are possible but you have to ignore Class's mixin feature and mixin manually from the initialize method:&lt;/p&gt;

&lt;pre name="code" class="javascript"&gt;
    var Rabbit = Class.create({
            initialize:function() {
                Object.extend( this, HOP_RUN_MIXIN_FACTORY.make() );
            }
        }
    );
&lt;/pre&gt;

&lt;p&gt;One interesting side-effect is that the same mixin can be used to create state per-instance or per-class depending on how the mixin is applied. Maybe useful.&lt;/p&gt;

&lt;h3&gt;Fade Mixin Factory&lt;/h3&gt;

&lt;p&gt;Here is the real code I'm using now to fade in and out, in the form of a mixin factory applied from the &lt;code&gt;initialize&lt;/code&gt; function. As usual, this is also &lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/trunk/javascript/interface/fade_mixin.js?view=log" title="Fade Mixin Factory on Wikizzle subversion web interface"&gt;available from Wikizzle's subversion&lt;/a&gt;, which is probably a more recent and/or better version than here:&lt;/p&gt;

&lt;pre name="code" class="javascript"&gt;
var FADE_MIXIN_FACTORY = 
{
    make:function fade_mixin_factory__make( appear_options, fade_options, element ){

        var appear_effect, fade_effect;

        appear_options = appear_options || {};
        fade_options = fade_options || {};

        appear_options.afterFinish = (appear_options.afterFinish || function(){})
            .wrap( function( proceed ){
                proceed()
                fade_effect = null;
            });

        fade_options.afterFinish = (fade_options.afterFinish || function(){})
            .wrap( function( proceed ){
                proceed();
                appear_effect = null;
            });

    /*    return the mixin we made: */
        return {

            appear: function fade_mixin__appear() {

                if( appear_effect ) {    
                    return;
                }

                if( fade_effect ) {
                    fade_effect.cancel();
                    fade_effect = null;
                }

                appear_options.owner = this;
                appear_effect = new Effect.Appear( element || this.toElement(), 
                                                   appear_options );
            }

        ,    fade: function() {

                if( fade_effect ) {
                    return;
                }

                if( appear_effect ) {
                    appear_effect.cancel();
                    appear_effect = null;
                }

                fade_options.owner = this;
                fade_effect = new Effect.Fade(  element || this.toElement(), 
                                                fade_options );
            }
        };
    }
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-7227560699610155385?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/7227560699610155385/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=7227560699610155385' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/7227560699610155385'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/7227560699610155385'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/09/mixin-factory-really-handy-pattern-for.html' title='Mixin factory: Really handy pattern for Classical OOP in Javascript'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-229727051623585927</id><published>2009-06-10T09:08:00.000-07:00</published><updated>2009-06-10T09:10:59.847-07:00</updated><title type='text'>Did I read that correctly?</title><content type='html'>&lt;p&gt;&lt;strong&gt;'crush'&lt;/strong&gt; is a &lt;a href="http://wiki.whatwg.org/wiki/RelExtensions"&gt;valid rel value for HTML5?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As in, you link to the pages of people you (or whoever you're making the page for) have crushes on&lt;/p&gt;

&lt;p&gt;Bwah?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-229727051623585927?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/229727051623585927/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=229727051623585927' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/229727051623585927'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/229727051623585927'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/06/did-i-read-that-correctly.html' title='Did I read that correctly?'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-2577532325332115118</id><published>2009-06-08T12:32:00.000-07:00</published><updated>2009-06-08T12:40:56.972-07:00</updated><title type='text'>MediaWiki Syntax highlighting</title><content type='html'>I got the chance to hack a bit over the weekend so &lt;a href="http://wikizzle.org"&gt;Wikizzle&lt;/a&gt;'s trunk now has some quite pretty syntax highlighting.

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_UtOFssKybTA/Si1n2BTeZMI/AAAAAAAAACA/PNhjPsQ7dn0/s1600-h/synhi.png"&gt;&lt;img style="cursor:pointer; cursor:hand;width: 400px; height: 261px;" src="http://2.bp.blogspot.com/_UtOFssKybTA/Si1n2BTeZMI/AAAAAAAAACA/PNhjPsQ7dn0/s400/synhi.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5345042510519362754" /&gt;&lt;/a&gt;

Iframes in designmode make DOM events look consistent. It is by far the quirkiest of all browser features I've ever coded with. Really, really, evily quirky. &amp;quot;&lt;a href="http://marijn.haverbeke.nl/codemirror/story.html"&gt;A brutal odyssey to the dark side of the DOM tree&lt;/a&gt;&amp;quot; barely sums it up.

At this stage the non-highlighted code works much better. In fact, the highlighted version just shows once and never updates. Gotta start somewhere.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-2577532325332115118?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/2577532325332115118/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=2577532325332115118' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2577532325332115118'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2577532325332115118'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/06/mediawiki-syntax-highlighting.html' title='MediaWiki Syntax highlighting'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_UtOFssKybTA/Si1n2BTeZMI/AAAAAAAAACA/PNhjPsQ7dn0/s72-c/synhi.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-3864872036252431923</id><published>2009-05-05T11:05:00.000-07:00</published><updated>2009-05-05T11:33:56.117-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='printing'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><title type='text'>Canon MP210 on 64bit Ubuntu</title><content type='html'>&lt;p&gt;This wasn't &lt;em&gt;so&lt;/em&gt; tough but a little more difficult than I'd have liked. (I'd have liked: plug in, works)&lt;/p&gt;

&lt;p&gt;Strangely, the only place carrying the Linux drivers is 
&lt;a href="http://support-au.canon.com.au/EN/search?v%3aproject=ABS-EN&amp;binning-state=model%3d%3dPIXMA%20MP210%0Amenu%3d%3dDownload%0Aos%3d%3dLinux&amp;"&gt;Canon Australia's driver download page&lt;/a&gt;. Weird, I know. But easy to find thanks to Google.&lt;/p&gt;

&lt;p&gt;Get these four files:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;cnijfilter-common_2.80-1_i386.deb&lt;/li&gt;
    &lt;li&gt;cnijfilter-mp210series_2.80-1_i386.deb&lt;/li&gt;
    &lt;li&gt;scangearmp-common_1.10-1_i386.deb&lt;/li&gt;
    &lt;li&gt;scangearmp-mp210series_1.10-1_i386.deb&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The files are all for 32bit, but they seem to work ok if you force apt not to notice that:&lt;/p&gt;

&lt;pre&gt;
sudo dpkg -i --force-architecture *.deb
&lt;/pre&gt;

&lt;p&gt;And... that's it. Set printer up as usual and scan through &lt;a href="http://gimp.org"&gt;The Gimp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
Is is strange that Canon but all the effort into making Linux drivers and then distribute them so badly:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;You have to go into the &lt;strong&gt;command line&lt;/strong&gt;. To install a &lt;strong&gt;printer&lt;/strong&gt;. On a distro targeted at the mainstream. Not good.&lt;/li&gt;
    &lt;li&gt;Only Canon Australia carries the files&lt;/li&gt;
    &lt;li&gt;They are packaged as being 32bit only when really 64bit is fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is all really odd. Stuff like this is expensive to write but (should be) cheap to distribute. I don't get it.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-3864872036252431923?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/3864872036252431923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=3864872036252431923' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/3864872036252431923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/3864872036252431923'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/05/canon-mp210-on-64bit-ubuntu.html' title='Canon MP210 on 64bit Ubuntu'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-1048246740712527626</id><published>2009-03-26T09:13:00.000-07:00</published><updated>2009-03-26T09:18:30.139-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dissertation'/><category scheme='http://www.blogger.com/atom/ns#' term='vanity'/><category scheme='http://www.blogger.com/atom/ns#' term='wikizzle'/><category scheme='http://www.blogger.com/atom/ns#' term='aberystwyth university'/><title type='text'>More vanity postings</title><content type='html'>&lt;p&gt;My friend Mike is doing his finial year &lt;a href="http://www.aber.ac.uk/en"&gt;in my old department&lt;/a&gt;. He just posted on &lt;a href="http://www.facebook.com/home.php#/profile.php?v=feed&amp;id=688104142"&gt;my Facebook Wall&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
Hey Jim, just come back from looking at past dissertations. And Edel Sherratt walked in and just said the best one she read was a little one called 'Wikiwyg'. And started telling us stories about the person who wrote that dissertation.
&lt;/blockquote&gt;

&lt;p&gt;What can I say? I'm touched (Wikiwyg is the old name for &lt;a href="http://wikizzle.org"&gt;Wikizzle&lt;/a&gt;).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-1048246740712527626?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/1048246740712527626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=1048246740712527626' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1048246740712527626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1048246740712527626'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/03/more-vanity-postings.html' title='More vanity postings'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6294345789836714039</id><published>2009-03-23T14:05:00.000-07:00</published><updated>2009-03-23T14:13:37.198-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Oxford Geek Nights'/><category scheme='http://www.blogger.com/atom/ns#' term='s5'/><category scheme='http://www.blogger.com/atom/ns#' term='Public speaking'/><title type='text'>Oxford Geek Night microslot notes</title><content type='html'>&lt;p&gt;I just uploaded &lt;a href="http://wikizzle.org/OGN_talk/"&gt;my Geek Night Notes&lt;/a&gt;. Yep, that's right - the notes are an XHTML/Javascript web app.&lt;/p&gt;

&lt;p&gt;The notes are done in &lt;a href="http://meyerweb.com/eric/tools/s5/"&gt;S5 from MeyerWeb&lt;/a&gt; with &lt;a href="http://code.google.com/p/syntaxhighlighter/"&gt;SyntaxHighlighter&lt;/a&gt; shoehorned in, and then a few little hackings on SyntaxHighlighter so it marks up my &lt;a href="http://www.smarty.net"&gt;Smarty&lt;/a&gt; preprocessor stuff in big red text (because that's what the talk is about!)&lt;/p&gt;

&lt;p&gt;There aren't loads but I only get five minutes so I think this is enough.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6294345789836714039?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6294345789836714039/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6294345789836714039' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6294345789836714039'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6294345789836714039'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/03/oxford-geek-night-microslot-notes.html' title='Oxford Geek Night microslot notes'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6190537492100762769</id><published>2009-03-15T09:17:00.000-07:00</published><updated>2009-03-15T10:08:58.620-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='wikizzle'/><category scheme='http://www.blogger.com/atom/ns#' term='wikitext'/><category scheme='http://www.blogger.com/atom/ns#' term='parsers'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='milestone'/><title type='text'>Wiki XML parser little milestone</title><content type='html'>Continuing the rewrite of my client-side Javascript wikitext parser, this:

&lt;pre&gt;
[[image:grep.jpg|left|200px|a [[wp:en:grep|grep plantation]]]]
&lt;/pre&gt;

Now gives this XML:

&lt;pre name="code" class="xml"&gt;
&amp;lt;link avaliable="unknown" is-image="yes"&amp;gt;
    &amp;lt;token direction="open" type="link"&amp;gt;[[&amp;lt;/token&amp;gt;
    &amp;lt;linktarget&amp;gt;
        &amp;lt;namespace type="media"&amp;gt;
            &amp;lt;key&amp;gt;image&amp;lt;/key&amp;gt;
            &amp;lt;token direction="open+close" type="namespace"&amp;gt;:&amp;lt;/token&amp;gt;
            &amp;lt;article-name&amp;gt;grep.jpg&amp;lt;/article-name&amp;gt;
        &amp;lt;/namespace&amp;gt;
    &amp;lt;/linktarget&amp;gt;
    &amp;lt;token direction="open+close"&amp;gt;|&amp;lt;/token&amp;gt;
    &amp;lt;params&amp;gt;
        &amp;lt;param name="horiz-align"&amp;gt;left&amp;lt;/param&amp;gt;
        &amp;lt;token direction="open+close"&amp;gt;|&amp;lt;/token&amp;gt;
        &amp;lt;param name="size"&amp;gt;
            &amp;lt;quantity&amp;gt;200&amp;lt;/quantity&amp;gt;
            &amp;lt;units&amp;gt;px&amp;lt;/units&amp;gt;
        &amp;lt;/param&amp;gt;
        &amp;lt;token direction="open+close"&amp;gt;|&amp;lt;/token&amp;gt;
        &amp;lt;param name="text"&amp;gt;
            a
            &amp;lt;link avaliable="unknown"&amp;gt;
                &amp;lt;token direction="open" type="link"&amp;gt;[[&amp;lt;/token&amp;gt;
                &amp;lt;linktarget&amp;gt;
                    &amp;lt;namespace type="normal"&amp;gt;
                        &amp;lt;key&amp;gt;wp&amp;lt;/key&amp;gt;
                        &amp;lt;token direction="open+close" type="namespace"&amp;gt;:&amp;lt;/token&amp;gt;
                        &amp;lt;namespace type="normal"&amp;gt;
                            &amp;lt;key&amp;gt;en&amp;lt;/key&amp;gt;
                            &amp;lt;token direction="open+close" type="namespace"&amp;gt;:&amp;lt;/token&amp;gt;
                            &amp;lt;article-name&amp;gt;grep&amp;lt;/article-name&amp;gt;
                        &amp;lt;/namespace&amp;gt;
                    &amp;lt;/namespace&amp;gt;
                &amp;lt;/linktarget&amp;gt;
                &amp;lt;token direction="open+close"&amp;gt;|&amp;lt;/token&amp;gt;
                &amp;lt;params&amp;gt;
                    &amp;lt;param name="text"&amp;gt;grep plantation&amp;lt;/param&amp;gt;
                &amp;lt;/params&amp;gt;
                &amp;lt;token direction="close" type="link"&amp;gt;]]&amp;lt;/token&amp;gt;
            &amp;lt;/link&amp;gt;
        &amp;lt;/param&amp;gt;
    &amp;lt;/params&amp;gt;
    &amp;lt;token direction="close" type="link"&amp;gt;]]&amp;lt;/token&amp;gt;
&amp;lt;/link&amp;gt;&lt;/pre&gt;

Read that and look at the text in the XML - not the attributes or tags, the other bits (if &lt;a href="http://code.google.com/p/syntaxhighlighter/"&gt;SyntaxHighlighter&lt;/a&gt; is working correctly, the black bits) "[[", "image", ":", "grep.jpg" etc... everything from the wikitext is there, in order, with no new text thrown in.

Because all text is preserved and this is just normal browser DOM XML, calling &lt;a href="https://developer.mozilla.org/En/DOM/Node.textContent"&gt;textContent&lt;/a&gt; on it gives us back this:

&lt;pre&gt;
[[image:grep.jpg|left|200px|a [[wp:en:grep|grep plantation]]]]
&lt;/pre&gt;

That is, exactly what we started with. Completely reliable out-and-back conversion. This is going to let me do some &lt;em&gt;amazingly&lt;/em&gt; cool things with wiki editing.

If the XML above looks verbose, remember we have a link inside a link (in mediawiki wikitext, images are links), one of which has two namespaces and the other with lots of parameters. It has to be big to capture the semantics.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6190537492100762769?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6190537492100762769/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6190537492100762769' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6190537492100762769'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6190537492100762769'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/03/wiki-xml-parser-little-milestone.html' title='Wiki XML parser little milestone'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-564334192235648948</id><published>2009-03-05T09:37:00.000-08:00</published><updated>2009-03-05T09:57:34.167-08:00</updated><title type='text'>Prototype can be brittle</title><content type='html'>Wikizzle stopped working in Firefox 3.1 beta 3 because &lt;a href="http://www.prototypejs.org/"&gt;Prototype&lt;/a&gt; started provoking errors. This got me looking into Prototype.

I just put up an &lt;a href="http://users.ox.ac.uk/%7Eadmn2094/test.html"&gt;example page&lt;/a&gt; highlighting how easy it is to break Prototype if you add to Object.prototype. The code:

&lt;pre name="code" class="html"&gt;
&amp;lt;html xmlns=&amp;quot;http://www.w3.org/1999/xhtml&amp;quot;&amp;gt;
    &amp;lt;script&amp;gt;
        Object.prototype.foo = function foo(){ return "@@" };
    &amp;lt;/script&amp;gt; 
          &amp;lt;script src="prototype-1.6.0.3.js" type="text/javascript"&amp;gt;&amp;lt;/script&amp;gt; 
           This page is here as a test
           &amp;lt;script type="text/javascript"&amp;gt;
                   $$('body').first().insert( { top:new Element('div') } );
           &amp;lt;/script&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/pre&gt;

The bit that breaks is this:

&lt;pre name="code" class="javascript"&gt;
writeAttribute: function(element, name, value) {
  element = $(element);
  var attributes = { }, t = Element._attributeTranslations.write;

  if (typeof name == 'object') attributes = name;
  else attributes[name] = Object.isUndefined(value) ? true : value;

  for (var attr in attributes) {
    name = t.names[attr] || attr;
    value = attributes[attr];
    if (t.values[attr]) name = t.values[attr](element, value);
    if (value === false || value === null)
      element.removeAttribute(name);
    else if (value === true)
      element.setAttribute(name, name);
    else element.setAttribute(name, value);
  }
  return element;
},
&lt;/pre&gt;

Simplified:

&lt;pre name="code" class="javascript"&gt;
 for (var attr in attributes) {
     //blah blah blah

    element.setAttribute(attr, attributes[attr]);
 }
&lt;/pre&gt;

The problem is that for in in Javascript enumerates over all the properties, even if they come from the prototype. So extending the Object prototype...

&lt;pre name="code" class="javascript"&gt;
  Object.prototype.foo = function foo(){ return "@@" };
&lt;/pre&gt;

...causes a call to:

&lt;pre name="code" class="javascript"&gt;
  element.setAttribute('foo', function foo(){ return "@@" } );
&lt;/pre&gt;

And DOM attributes can't contain '@'.

Luckily, this is &lt;a href="http://yuiblog.com/blog/2006/09/26/for-in-intrigue/"&gt;probably easy to fix with hasOwnProperty&lt;/a&gt; but I'm surprised nobody noticed this before.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-564334192235648948?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/564334192235648948/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=564334192235648948' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/564334192235648948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/564334192235648948'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/03/prototype-can-be-brittle.html' title='Prototype can be brittle'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-5388648541990473862</id><published>2009-03-04T04:48:00.000-08:00</published><updated>2009-03-04T04:59:00.003-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coffee'/><category scheme='http://www.blogger.com/atom/ns#' term='bad service'/><category scheme='http://www.blogger.com/atom/ns#' term='wifi'/><title type='text'>Oxford Starbucks are rubbish, don't go there</title><content type='html'>&lt;p&gt;"Hmmmm..." that's strange. Which network?&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Jim goes back up to counter&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;"Hey, you have wifi, don't you?"&lt;/p&gt;
&lt;p&gt;Starbucks person: "Yes, five pounds per hour"&lt;/p&gt;
&lt;p&gt;Me: I bought coffee, can't I have it for free?&lt;/p&gt;
&lt;p&gt;Starbucks person: "No"&lt;/p&gt;

&lt;h4&gt;"Wow, you really fail!"&lt;/h4&gt;
&lt;p&gt;When I buy coffee I'm not paying for coffee. I'm paying to sit and drink coffee in a nice environment. If you're a cafe, a nice environment requires free wifi.&lt;/p&gt;
&lt;p&gt;If you &lt;strong&gt;must&lt;/strong&gt; charge for internet, £1 per hour is ok if I'm using your computer. 50p per hour maybe if I'm using my own laptop. Charging £5 per hour for something everyone knows you can get for £10 per month just makes your company look stupid.&lt;/p&gt;
&lt;p&gt;I'm not saying boycott Starbucks. Just don't go in the Oxford one if you need the net&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-5388648541990473862?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/5388648541990473862/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=5388648541990473862' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/5388648541990473862'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/5388648541990473862'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/03/oxford-starbucks-are-rubbish-dont-go.html' title='Oxford Starbucks are rubbish, don&apos;t go there'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-2943741334410802455</id><published>2009-02-24T02:07:00.001-08:00</published><updated>2009-02-25T07:07:40.937-08:00</updated><title type='text'>I'm microslotting at OGN</title><content type='html'>&lt;p&gt;I started the &lt;a href="http://www.softeng.ox.ac.uk/"&gt;Software Engineering Programme&lt;/a&gt; yesterday. Not getting on with mac keyboards but otherwise having a nice time.&lt;/p&gt;

&lt;p&gt;
Sometime last week I got an email about the next &lt;a href="http://oxford.geeknights.net/2009/mar-25th/"&gt;Oxford Geek Night&lt;/a&gt; asking for microslot speakers.

I simultaneously love and hate public speaking. I put myself in front of crowds enough to convincingly fake being comfortable but not enough to actually &lt;strong&gt;be&lt;/strong&gt; comfortable.

My title is "&lt;strong&gt;Big Javascript: preprocessing&lt;/strong&gt;". Hmmm... five minutes for a big subject.&lt;/p&gt;

&lt;h3&gt;Order, Cat, Min Gzip&lt;/h3&gt;

Hacking in isolation it is difficult to see what stuff is fresh and what is routine. Do I mention minning and gzipping or is that old hat now? The &lt;a href="http://www.ox.ac.uk/"&gt;Oxford University homepage&lt;/a&gt; delivers 120k of big jQuery instead of the highly compressed "production" 19k. &lt;a href="http://wikizzle.org/"&gt;Wikizzle&lt;/a&gt; raw is 70 javascript files and 650k but compressed just 90k; this 87% reduction isn't atypical. Even running on localhost the difference is huge - clear cache between clicks and compare load times:

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://wikizzle.org/wikiz"&gt; Wikizzle main page compressed (for deploying)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://wikizzle.org/wikiz?compress=no"&gt; Wikizzle main page uncompressed (for debugging)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

How does this work? In bash I:

&lt;ol&gt;
  &lt;li&gt;&lt;a href="http://unixhelp.ed.ac.uk/CGI/man-cgi?find"&gt;find&lt;/a&gt; all javascript files, pipe into...&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/trunk/utils/order.php?revision=257&amp;amp;c"&gt;a hacky PHP script&lt;/a&gt;. Reads the file list and outputs the list in a runnable order. Pipes to...&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://unixhelp.ed.ac.uk/CGI/man-cgi?xargs"&gt;xargs&lt;/a&gt;; passes the filenames to &lt;a href="http://unixhelp.ed.ac.uk/CGI/man-cgi?cat"&gt;cat&lt;/a&gt;. Cat reads them all into one long stream, that stream goes to...&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;preprocessor&lt;/strong&gt;, pipes into...&lt;/li&gt;
  &lt;li&gt;The &lt;a href="http://www.crockford.com/javascript/jsmin.html"&gt;minner&lt;/a&gt;, pipes into...&lt;/li&gt;
  &lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Gzip"&gt;gzip&lt;/a&gt;. Redirect to our deployable file&lt;/li&gt;
  &lt;li&gt;The file is saved with the md5 in the filename and the server and (since it never changes) is safe to cache forever. Because of the md5, if the code changes the filename changes so different code will never (well, &lt;strong&gt;extremely&lt;/strong&gt; unlikely anyway) be served from the same uri.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;
Wow. Sometimes you don't realise how involved something is until you come to describe it to another person. There is a cool library now to do this called &lt;a href="http://www.37signals.com/svn/posts/1587-introducing-sprockets-javascript-dependency-management-and-concatenation"&gt;Sprockets &lt;/a&gt; by the guys who made Prototype but Sprokets wasn't there when I started Wikizzle and my &lt;a href="http://en.wikipedia.org/wiki/Not_Invented_Here"&gt;NiH&lt;/a&gt; syndrome is strong enough that I'm going to stick with what I have for now.&lt;/p&gt;

&lt;h3&gt;Preprocessing&lt;/h3&gt;

&lt;p&gt;
I think this is the interesting part. I'll probably skip over the above and talk about this bit most. I did a neat hack to put &lt;a href="http://en.wikipedia.org/wiki/C_preprocessor"&gt;C-style preprocessing&lt;/a&gt; into Javascript.&lt;/p&gt;

&lt;p&gt;
As we've seen above, a stream of all the javascript files joined flows through the preprocessor but preprocessing also has to happen if we're running without compression. This is pretty easy to do because Apache implements &lt;a href="http://httpd.apache.org/docs/2.2/handler.html"&gt;handlers&lt;/a&gt; which can be used this to filter files through arbitrary scripts.&lt;/p&gt;

&lt;p&gt;I have to get back to class now. Implementation details to follow.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-2943741334410802455?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/2943741334410802455/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=2943741334410802455' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2943741334410802455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2943741334410802455'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/im-microslotting-at-ogn.html' title='I&apos;m microslotting at &lt;abbr title=&quot;Oxford Geek Night&quot;&gt;OGN&lt;/abbr&gt;'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-1757139346096524028</id><published>2009-02-21T04:51:00.000-08:00</published><updated>2009-02-24T02:05:47.726-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='wikizzle'/><category scheme='http://www.blogger.com/atom/ns#' term='wikitext'/><category scheme='http://www.blogger.com/atom/ns#' term='parsers'/><category scheme='http://www.blogger.com/atom/ns#' term='xml'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Wikitext XML format</title><content type='html'>&lt;p&gt;Today I've been tying up some hacking to revise &lt;a href="http://wikizzle.org"&gt;Wikizzle's&lt;/a&gt; XML wikitext representation. This wikitext: &lt;strong&gt;[[talk:woot]]&lt;/strong&gt; now parses to this:&lt;/p&gt;

&lt;pre name="code" class="xml"&gt;
&amp;lt;article&amp;gt;
 &amp;lt;paragraph lineno=&amp;quot;0&amp;quot; charno=&amp;quot;0&amp;quot;&amp;gt;
  &amp;lt;link avaliable=&amp;quot;unknown&amp;quot;&amp;gt;
   &amp;lt;parse:token direction=&amp;quot;open&amp;quot; type=&amp;quot;link&amp;quot;&amp;gt;[[&amp;lt;/parse:token&amp;gt;
   &amp;lt;linktarget&amp;gt;
    &amp;lt;namespace type=&amp;quot;normal&amp;quot;&amp;gt;
     &amp;lt;key&amp;gt;talk&amp;lt;/key&amp;gt;
     &amp;lt;parse:token direction=&amp;quot;open+close&amp;quot; type=&amp;quot;namespace&amp;quot;&amp;gt;:&amp;lt;/parse:token&amp;gt;
     &amp;lt;article-name&amp;gt;woot&amp;lt;/article-name&amp;gt;
    &amp;lt;/namespace&amp;gt;
   &amp;lt;/linktarget&amp;gt;
   &amp;lt;parse:token direction=&amp;quot;close&amp;quot; type=&amp;quot;link&amp;quot;&amp;gt;]]&amp;lt;/parse:token&amp;gt;
   &amp;lt;linkoptions/&amp;gt;
  &amp;lt;/link&amp;gt;
 &amp;lt;/paragraph&amp;gt;
&amp;lt;/article&amp;gt; 
&lt;/pre&gt;

New things:
&lt;ul&gt;
 &lt;li&gt;The parse namepace hints at where the syntax tree came from. When I convert the wikixml to xhtml parse elements get ignored but lots of interesting applications open up when you know where your tokens are, the first being a syntax highlighting editor. Later, I want to offer a 'pseudo-preview' which overlays some of the wikitext tokens as a learning aid.&lt;/li&gt;
 &lt;li&gt;If you traverse and output all the text nodes in order (in xpath, as easy as '&lt;strong&gt;//text()'&lt;/strong&gt;) you get (pretty much) the original wikitext. I can't say &lt;em&gt;why&lt;/em&gt; this is cool or useful but I'm pretty sure it is. Remember, &lt;a href="http://c2.com/cgi/wiki?BeautyIsOurBusiness"&gt;Beauty is our Business&lt;/a&gt;.
    &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
 Just imagine if you could cleanly go from wikitext to XML and back again - you could programatically alter the parse tree for the user (eg, adding rows to tables) and the wikitext would be rewritten to represent this - fancy editor functions without all that ugly regexp half-parser substitutions.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-1757139346096524028?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/1757139346096524028/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=1757139346096524028' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1757139346096524028'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1757139346096524028'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/wikitext-xml-format.html' title='Wikitext XML format'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6152456715128512930</id><published>2009-02-21T03:26:00.001-08:00</published><updated>2009-02-24T02:06:29.137-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jim and druma'/><category scheme='http://www.blogger.com/atom/ns#' term='sketch'/><title type='text'>Sketch dump</title><content type='html'>&lt;p&gt;A little sketch put itself amongst my notes during a meeting this week. Meet regular drawees of mine Jum and Druma.&lt;/p&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_UtOFssKybTA/SZ_lFN7pjYI/AAAAAAAAABo/xD2RPoEU8xo/s1600-h/bill.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 382px; height: 400px;" src="http://3.bp.blogspot.com/_UtOFssKybTA/SZ_lFN7pjYI/AAAAAAAAABo/xD2RPoEU8xo/s400/bill.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5305210763867688322" /&gt;&lt;/a&gt;

&lt;p&gt;On related news, the new scanner works great. Everyone should buy a Canon MP210, &lt;a href="http://www.dabs.com/productview.aspx?quicklinx=4P8Q&amp;fb=330&amp;InMerch=1"&gt;I paid £40&lt;/a&gt; at dabs. As if that wasn't already stupidly cheap, when I bought mine Canon would send you a £15 cheque &lt;a href="http://en.wikipedia.org/wiki/Rebate_(marketing)"&gt;for filling in a form and posting it back to them&lt;/a&gt; but I don't think they do that anymore. Linux drivers were slightly hard to find but good.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6152456715128512930?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6152456715128512930/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6152456715128512930' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6152456715128512930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6152456715128512930'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/sketch-dump.html' title='Sketch dump'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_UtOFssKybTA/SZ_lFN7pjYI/AAAAAAAAABo/xD2RPoEU8xo/s72-c/bill.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-802320976274399876</id><published>2009-02-11T01:55:00.000-08:00</published><updated>2009-02-24T02:07:02.529-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='prototype'/><category scheme='http://www.blogger.com/atom/ns#' term='library code'/><title type='text'>"Call as much as you like, I'll only answer when I want to."</title><content type='html'>Ok, &lt;a href="http://jimhigson.blogspot.com/2009/02/function-that-only-lets-you-call-it-at.html"&gt;this idea&lt;/a&gt; works now. Blog it.

I don't have unit tests ye but unit testing temporal stuff quickly gets messy and this isn't very complicated so I'll just trust it works and wait until stuff starts to break :-)

&lt;h3&gt;Parameters&lt;/h3&gt;
&lt;h4&gt;name&lt;/h4&gt;
&lt;p&gt;The maximum rate this function can be called at.&lt;/p&gt;&lt;p&gt;For example, if updateInterface() updates your user interface, $('someelement').onchange = updateInterface.maxrate( 200 ); Will update the interface on changes, but not more than once every 200&lt;abbr title="milliseconds"&gt;ms&lt;/abbr&gt;&lt;/p&gt;
&lt;h4&gt;defer&lt;/h4&gt;
&lt;p&gt;If false, unset, calling before we're ready to call again will do nothing. If truey, the calling will queue a call to take place the earliest possible while observing the maximum rate. Set this to true if you want to make sure the interface (or whatever) reacts to the event&lt;/p&gt;

&lt;h3&gt;...and the code!&lt;/h3&gt;
&lt;pre name="code" class="javascript"&gt;
Function.prototype.maxrate =
   function( rate, defer )
   {
       var lastcalled = 0;
       var f = this;
       var scheduled = -1;
  
       return  function()
               {
                   var now = new Date().getTime();
                   var nextchance = lastcalled + rate;

                   if( now &gt; nextchance )
                   {   
                       lastcalled = now;
                       return f.apply( this, arguments );
                   }

                   if( defer &amp;amp;&amp;amp; scheduled == -1 )
                   {
                       var obj = this;
                       var args = arguments;       
                       scheduled = window.setTimeout(
                           function()
                           {
                               var now = new Date().getTime();

                               lastcalled = now;
                               f.apply( obj, args );
                               scheduled = -1;
                           }, nextchance - now );
                   }
               };
   };
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-802320976274399876?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/802320976274399876/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=802320976274399876' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/802320976274399876'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/802320976274399876'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/call-as-much-as-you-like-ill-only.html' title='&quot;Call as much as you like, I&apos;ll only answer when &lt;b&gt;I&lt;/b&gt; want to.&quot;'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6961116241430654930</id><published>2009-02-06T05:40:00.000-08:00</published><updated>2009-02-06T05:54:14.577-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='library code'/><category scheme='http://www.blogger.com/atom/ns#' term='functions'/><title type='text'>A function that only lets you call it at a certain rate</title><content type='html'>Ok, so I have way to turn any function into &lt;a href="http://jimhigson.blogspot.com/2009/02/better-onttimejs.html"&gt;a function that only lets you call it once&lt;/a&gt;.

Now I want one that only lets itself be called up to a maximum rate.

Why is this helpful? Well, let's say you want to listen to a text area and do some fairly expensive thing when it changes. You want to do it quite often but you don't want to do it &lt;strong&gt;every single time&lt;/strong&gt; a key is pressed. You could do this:

&lt;pre name="code" class="javascript"&gt;
function do_something(){
// do something here
}
$('some-text-area').onchange = do_something.maxrate( 2 );
&lt;/pre&gt;

Or, if you like:
&lt;pre name="code" class="javascript"&gt;
$('some-text-area').onchange = 
    function() {
        // do something here
    }.maxrate( 2 );
&lt;/pre&gt;

Of course, you shouldn't really be using onchange directly. Since I like Prototype I'd probably use &lt;a href="http://www.prototypejs.org/api/event/observe"&gt;Element.observe&lt;/a&gt;:
&lt;pre name="code" class="javascript"&gt;
$('some-text-area').observe(  
    function() {
        // do something here
    }.maxrate( 2 ));
&lt;/pre&gt;


So now your text area will do what you want but not more than twice per second.

This is just an idea for now, I'll see if I can get the code stable.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6961116241430654930?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6961116241430654930/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6961116241430654930' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6961116241430654930'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6961116241430654930'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/function-that-only-lets-you-call-it-at.html' title='A function that only lets you call it at a certain rate'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-1068957247717604686</id><published>2009-02-03T06:33:00.001-08:00</published><updated>2009-02-06T05:51:10.104-08:00</updated><title type='text'>A better onttime.js</title><content type='html'>I updated the old &lt;a href="http://jimhigson.blogspot.com/2009/02/one-time-functions-in-javascript.html"&gt;onetime.js&lt;/a&gt;

This doesn't keep the reference to the old function longer than is needed (possibly allowing it to be &lt;a href="http://en.wikipedia.org/wiki/Garbage_collection_(computer_science)"&gt;garbage collected&lt;/a&gt;) and removes the need for a conditional:

&lt;pre name="code" class="js"&gt;
Function.prototype.onetime =
    function()
    {
        var f = this;
 
        return  function()
                {
                    var rtn = f.apply( this, arguments );
                    f = function(){}; //replace f with empty function
                    return rtn;
                };
    };
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-1068957247717604686?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/1068957247717604686/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=1068957247717604686' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1068957247717604686'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/1068957247717604686'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/better-onttimejs.html' title='A better onttime.js'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-2162894835744990532</id><published>2009-02-01T04:57:00.000-08:00</published><updated>2009-02-01T06:00:14.695-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='scriptaculous'/><category scheme='http://www.blogger.com/atom/ns#' term='tech'/><category scheme='http://www.blogger.com/atom/ns#' term='library code'/><category scheme='http://www.blogger.com/atom/ns#' term='coding'/><title type='text'>One-time functions in Javascript</title><content type='html'>This morning I had an idea for one-time functions, a function that only works once.

With a little libarary code any function can be made into a one-time function like this:

&lt;pre name="code" class="javascript"&gt;
function func()
{
    alert( "I won't say this again" );
}
func = func.onetime();
&lt;/pre&gt;

Or, shorthand of the above:

&lt;pre name="code" class="javascript"&gt;
var func =
    function()
    {
        alert( "I won't say this again" );
    }.onetime();
&lt;/pre&gt;

Adding a method to Function that returns an altered version of the function was inspired by &lt;a href="http://www.prototypejs.org/"&gt;Prototype&lt;/a&gt;'s &lt;a href="http://www.prototypejs.org/api/function/bind"&gt;bind&lt;/a&gt;, &lt;a href="http://www.prototypejs.org/api/function/delay"&gt;delay&lt;/a&gt; etc.

The first time a one-time function is called it does whatever it would normally do. The second time, and every other time, it does nothing. This is useful for all kinds of things. For example, if you have a search box like this:

&lt;pre name="code" class="html"&gt;
&amp;lt;input id="searchinput" type="text" name="q" id="searchinput" value="Enter term here" /&amp;gt;
&lt;/pre&gt;

And javascript like this:

&lt;pre name="code" class="js"&gt;
$('searchinput').onfocus = function(){ $('searchinput').value = '' };
&lt;/pre&gt;

Now you have the annoying (mis)behviour that if you click into the search box, type something, go away, then click in again, what you typred before is blanked. There are los of ways you could fix it. Like this:

&lt;pre name="code" class="js"&gt;
$('searchinput').onfocus =
    function( e )
    {
        if( this.value = "Enter term here" )
            this.value = '';
    };
&lt;/pre&gt;

Or like this:

&lt;pre name="code" class="js"&gt;
$('searchinput').onfocus =
    function( e )
    {
        $('searchinput').onfocus = null; //don't do this again
        $('searchinput').value = '';
    };
&lt;/pre&gt;

But I think this is the neatest:

&lt;pre name="code" class="js"&gt;
$('searchinput').onfocus = function(){ $('searchinput').value = '' }.onetime();
&lt;/pre&gt;

Yeah, I know onfocus isn't the modern way but it is the easist to write for these examples.

&lt;h3&gt;Implementation&lt;/h3&gt;

Here's the onetime implementation, or you can get it from &lt;a href="http://wikizzle.org/"&gt;Wikizzle&lt;/a&gt;'s SVN at
&lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/trunk/javascript/util/prototype/onetime.js"&gt;
trunk/javascript/util/prototype/onetime.js&lt;/a&gt;

&lt;pre name="code" class="javascript"&gt;
Function.prototype.onetime =
    function()
    {
        var called = false;
        var orignal_function = this;

        return  function()
                {
                    if( !called )
                    {
                        called = true;
                        return orignal_function.apply( this, arguments );
                    }
                };
    };
&lt;/pre&gt;

And here are the unit tests, using the &lt;a href="http://http://script.aculo.us/"&gt;Scriptaculous&lt;/a&gt; unit test framework:

&lt;pre name="code" class="javascript"&gt;

new Test.Unit.Runner(
{
    test_on_function: function() { with(this) {

    //  a function to just return its own arguments:
        var testfunc =
                function f( arg1 )
                {   return arg1;
                }.onetime();

        assertEqual(    5, testfunc( 5 ) );
 //  second call should be undefined:
        assertEqual(    undefined, testfunc( 5 ) );
    }}

,   test_as_method: function() { with(this) {

        var test_obj =
            {
                give_me_five:   function()
                                {   return 5;
                                }.onetime()
            };

        assertEqual(    5, test_obj.give_me_five() );
        assertNotEqual( 5, test_obj.give_me_five() );
    }}

}, 'testlog' );
&lt;/pre&gt;

...and They pass! :-)

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_UtOFssKybTA/SYWnS3BOLrI/AAAAAAAAABg/ZZzgG3__ox8/s1600-h/unit_tests_shot.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 198px;" src="http://1.bp.blogspot.com/_UtOFssKybTA/SYWnS3BOLrI/AAAAAAAAABg/ZZzgG3__ox8/s320/unit_tests_shot.png" alt="" id="BLOGGER_PHOTO_ID_5297824479119814322" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-2162894835744990532?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/2162894835744990532/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=2162894835744990532' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2162894835744990532'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/2162894835744990532'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/02/one-time-functions-in-javascript.html' title='One-time functions in Javascript'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_UtOFssKybTA/SYWnS3BOLrI/AAAAAAAAABg/ZZzgG3__ox8/s72-c/unit_tests_shot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6708160592053590614</id><published>2009-01-31T05:39:00.000-08:00</published><updated>2009-01-31T05:59:18.863-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='printing'/><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='humph'/><category scheme='http://www.blogger.com/atom/ns#' term='borken'/><title type='text'>Linux printing still (sometimes) sucks</title><content type='html'>It's a while since ESR wrote &lt;a href="http://catb.org/%7Eesr/writings/cups-horror.html"&gt;The Luxury of Ignorance: an Open Source Horror story&lt;/a&gt;.

Today I wanted to print a &lt;a href="http://www.oxford.gov.uk/files/seealsodocs/80211/3695d%20Jan%2DApr%2009%20web%20version.pdf"&gt;PDF of exercise classes&lt;/a&gt; at my local sports centre.

Here's how it looked on my desktop:

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_UtOFssKybTA/SYRUrq603oI/AAAAAAAAABA/B107ZvhRVso/s1600-h/SPM_A0327.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://1.bp.blogspot.com/_UtOFssKybTA/SYRUrq603oI/AAAAAAAAABA/B107ZvhRVso/s320/SPM_A0327.jpg" alt="" id="BLOGGER_PHOTO_ID_5297452170926677634" border="0" /&gt;&lt;/a&gt;

I'm using the normally excellent &lt;a href="http://kpdf.kde.org/"&gt;KPDF&lt;/a&gt; to view the file:

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_UtOFssKybTA/SYRXlw6hdPI/AAAAAAAAABY/rw5b4qaNW-E/s1600-h/pdf.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 208px;" src="http://3.bp.blogspot.com/_UtOFssKybTA/SYRXlw6hdPI/AAAAAAAAABY/rw5b4qaNW-E/s320/pdf.jpg" alt="" id="BLOGGER_PHOTO_ID_5297455367991686386" border="0" /&gt;&lt;/a&gt;

And here's how it looked on paper:

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_UtOFssKybTA/SYRU4YE_xaI/AAAAAAAAABI/6yRGAG8IKPU/s1600-h/SPM_A0328.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_UtOFssKybTA/SYRU4YE_xaI/AAAAAAAAABI/6yRGAG8IKPU/s320/SPM_A0328.jpg" alt="" id="BLOGGER_PHOTO_ID_5297452389207360930" border="0" /&gt;&lt;/a&gt;

Ah. Maybe I need to set it to landscape. Oh. That was landscape. Maybe I should try portrait?

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_UtOFssKybTA/SYRVASxFSiI/AAAAAAAAABQ/HIRjt6w-034/s1600-h/SPM_A0329.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 240px;" src="http://1.bp.blogspot.com/_UtOFssKybTA/SYRVASxFSiI/AAAAAAAAABQ/HIRjt6w-034/s320/SPM_A0329.jpg" alt="" id="BLOGGER_PHOTO_ID_5297452525220612642" border="0" /&gt;&lt;/a&gt;

Yeah, now I made two bits of scrap paper.

Sometimes, Linux printing sucks. I don't have time to search for the options to fix this, I'll just wait until Monday and print from work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6708160592053590614?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6708160592053590614/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6708160592053590614' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6708160592053590614'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6708160592053590614'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/01/linux-printing-still-sometimes-sucks.html' title='Linux printing still (sometimes) sucks'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_UtOFssKybTA/SYRUrq603oI/AAAAAAAAABA/B107ZvhRVso/s72-c/SPM_A0327.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4154210566841915972.post-6016399466385245654</id><published>2009-01-23T02:02:00.000-08:00</published><updated>2009-01-29T15:22:33.177-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='wikizzle'/><category scheme='http://www.blogger.com/atom/ns#' term='prototype'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Prototype Singleton classes</title><content type='html'>Usually a project starts small so you keep it simple and just put everything in global functions. Then it gets big and you have to reorganise a bit because there's to much in scope to keep it all in your mind at once.

That's what I'm doing now for &lt;a href="http://wikizzle.org/"&gt;Wikizzle&lt;/a&gt; now - using &lt;a href="http://en.wikipedia.org/wiki/Object-oriented_programming"&gt;object orientation&lt;/a&gt; to encapsulate all the workings behind a nice structure. I'd never want more than one instance of a lot of these classes so let's figure out a nice Javascript &lt;a href="http://en.wikipedia.org/wiki/Singleton_pattern" title="Page about the Singleton Design Pattern on Wikipedia"&gt;Singleton&lt;/a&gt;.

&lt;h2&gt;Some background&lt;/h2&gt;A Singleton is a just class that you just make one of. That's it. In a browser the document object is an example of a singleton.

After a bit of research I decided you could make a pretty good singleton using &lt;a href="http://www.dhtmlkitchen.com/learn/js/singleton/"&gt;this pattern from DhtmlKitchen&lt;/a&gt; and (if you want) add private methods to it using &lt;a href="http://www.crockford.com/javascript/private.html"&gt;Douglas Crockford's private members pattern&lt;/a&gt;, but really I wanted something a bit more Prototype-y and (gasp!) readable.

If you're not familiar with Prototype's &lt;a href="http://www.prototypejs.org/api/class"&gt;Class&lt;/a&gt;, it is neat. They have an &lt;a href="http://www.prototypejs.org/learn/class-inheritance" title="Tutorial on Prototype's classical model on prototypejs.org"&gt;easy introduction&lt;/a&gt; - &lt;a href="http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/object-orientation/declaring-class"&gt;Dojo&lt;/a&gt; and &lt;a href="http://mootools.net/docs/Class/Class"&gt;Mootools&lt;/a&gt; have much the same but for Wikizzle I decided to Prototype. Generally, there are two ways people make Javascript classy - with a framework (&lt;a href="http://www.prototypejs.org/"&gt;Prototype&lt;/a&gt;, &lt;a href="http://mootools.net/"&gt;Mootools&lt;/a&gt;, &lt;a href="http://dojotoolkit.org/"&gt;Dojo&lt;/a&gt; but not &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt;) that adds some new stuff to make it a bit like using a language with classes, or use the language itself aka &lt;a href="http://yuiblog.com/blog/2007/06/12/module-pattern/"&gt;The Module Pattern&lt;/a&gt;.

I don't like The Module Pattern. Sure, it does everything I want but it feels icky. Yeah, I'm good at JS, yes, I know Javascript well enough that I can read this and see what you're doing:
&lt;pre name="code" class="js"&gt;function (){
return {
      do_this   :function(){}
      do_that   :function(){}
      eat_a_cake:function(){}
  };
}(); /* close anon function and call */
&lt;/pre&gt;...but I don't want to. There's a bit of a meme going round that the Module Pattern is more "Javascripty" because it doesn't rely on libraries &lt;a href="http://foohack.com/2007/08/yui-crockford-module-pattern-vs-prototypes-class-function/"&gt;"doesn’t attempt to re-write the language, but rather to show off what the language can do"&lt;/a&gt;. Bleh. An anon function which gets called as soon as the curly bracket closes isn't using the language as it was intended, it is bending the meaning of a function into shapes it wasn't intended. The source order is unintuitive; you don't see the closure function being called until after all the object's methods so reading start-to-end you're going through possibly hundreads of lines without seeing &lt;span style="font-style: italic;"&gt;how&lt;/span&gt; they're being packaged until the final call. Douglas Crockford &lt;a href="http://tech.groups.yahoo.com/group/jslint_com/message/345"&gt;really likes you to use brackets in your Javascipt&lt;/a&gt;.

The cool thing about the module pattern is that it leans on closures and closures are powerful data hiders. This is almost a big enough deal to make me want to use it. For example, if I have:
&lt;pre name="code" class="js"&gt;
var elvis = function()
{
    var spicy_chicken = new Recepie( /* ... */ );

    return  {
                get_favourite_food: function(){ return spicy_chicken; };
            };

}();
&lt;/pre&gt;
Here we have a private property spicy_chicken, with a getter. Once set, this private value is &lt;a href="http://en.wikipedia.org/wiki/Information_hiding"&gt;properly encapsulated&lt;/a&gt; in the closure and can never be changed. It is completely impossible for some other bit of code to read it without going through the getter. So long as Recepie is &lt;a href="http://en.wikipedia.org/wiki/Immutable_object"&gt;imutable&lt;/a&gt;  it is impossible for some other code to tamper with Elvis's tastes in food. If you wanted the value to be changeable you could add a setter, but otherwise nothing can change it.

Under Prototype classes you can't really do this. Instead I generally go with:

&lt;pre name="code" class="js"&gt;
var Elvis = Class.create(
{
    initialize:
        function()
        {    this._spicy_chicken = new Recepie( /* ... */ );
        }
,   get_favourite_food:
        function()
        {    return this._spicy_chicken;
        }
});
var elvis = new Elvis();
&lt;/pre&gt;

Here the initial underscore char in the attribute name is an informal way of saying it is private. N&lt;strong style="font-weight: normal;"&gt;othing is enforced&lt;/strong&gt;, but this is a &lt;a href="http://www.google.co.uk/search?hl=en&amp;amp;client=firefox-a&amp;amp;rls=org.mozilla%3Aen-US%3Aofficial&amp;amp;hs=94P&amp;amp;q=function+name+underscore+private&amp;amp;btnG=Search&amp;amp;meta="&gt;well recognised convention&lt;/a&gt;. You &lt;span style="font-style: italic;"&gt;could&lt;/span&gt; call:
&lt;pre name="code" class="js"&gt;
elvis._spicy_chicken = new Recepie( 'green leaves', 'balsamic vinegar' );
&lt;/pre&gt;Which we'd all agree would be bad.

There's nothing to stop you doing this but my attitude is the shrug. The attribute isn't properly private but in real-world usage this doesn't cause any problems because programmers tend to know not to directly use things starting with an underscore. Generally my javascript code isn't executed in a hostile environment. Sure, you can put my code in your app and use it in ways I'd rather you didn't, but, seriously, you're only breaking your own application :-).

The above code isn't enforced as a singleton because there's nothing to stop some other code calling new Elvis() again. Like the above, I'm not sure if this would be a problem in real life but if we can protect against it without hurting readability we might as well have protection. Until recently I would write Singletons this like:


&lt;pre name="code" class="js"&gt;
var Elvis = Class.create(
{
    initialize:
        function()
        {
            if( Elvis._instance )
                throw( "you can't create another Elvis. Use Elvis.get() instead" );

            this._spicy_chicken = new Recepie( /* ... */ );
        }
,   get_favourite_food:
        function()
        {
            return this._spicy_chicken;
        }
});

Elvis._instance = new Elvis();
Elvis.get =
function()
{    return Elvis._instance;
};

&lt;/pre&gt;
Not bad but we still have a bunch of instantiation and boiler plate that has to be done for every singleton class we create. Wouldn't it be neat if we could write this once and never bother with it again?
&lt;h2&gt;Now, where was I?&lt;/h2&gt;Ah yes, my very cool new way, &lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/trunk/javascript/util/prototype/singleton.js?view=markup"&gt;singleton.js&lt;/a&gt; lets you just do this:

&lt;pre name="code" class="js"&gt;
/* Makes a singleton prototype Class Use like:

var Elvis = SingletonClass.create( [superclass ,]
{
        initialize:
            function()
            {    be_born();
                 have_music_career();
                 get_fat();
                 pretend_to_die();
            }
,       toString: function(){ return "the one and only Elvis!" }
,       sing:     function(){ alert("Evlis is singing") }
,       swing:    function(){ alert("Evlis is swinging")}
,       live:     function(){ alert("Evlis lives!")}
));
&lt;/pre&gt;

Elvis is now a Singleton. This is a normal Prototype class but calls to new Elvis() will throw an exception. The Class has a method get() which returns the single instance. Having created Elvis, we can use him like:
&lt;pre name="code" class="js"&gt;
Elvis.get().swing();
&lt;/pre&gt;
The instance is hidden by a closure so .get() is the *only* way to get it instance. &lt;strong&gt;Attempting to create another will throw an error and you didn't have to do anything in your initialize function to get this :-)&lt;/strong&gt;

Here's the code:
&lt;pre name="code" class="js"&gt;
/*
SingletonClass.create takes any parameters Class.create does. Returns a Prototype class which cannot
be instantiated, but has a get() function to return the sole instance.
*/
var SingletonClass =
  {
      create:
          function singletonclass__create()
          {
              var ProtoClass = Class.create.apply( Class, arguments );

         //   instance hidden inside our closure:
              var instance;

         /*   extend the Class created above to make a new class that
              can only be instantiated once: */
              ProtoClass =
                  Class.create( ProtoClass,
                                {
                                    initialize:
                                        function( $super )
                                        {
                                            if( instance )
                                                throw( "cannot create another - this is a singleton" );

                                            $super();
                                        }
                                });

              instance = new ProtoClass();

       //     Allow access to instance via a Class function:
              ProtoClass.get = function (){ return instance; };

              return ProtoClass;
          }
  };
&lt;/pre&gt;

There might be a later version by now. Get the &lt;a href="http://svn.tuxfamily.org/viewvc.cgi/wikizzle_main/trunk/javascript/util/prototype/singleton.js?revision=267&amp;content-type=text%2Fplain"&gt;latest singleton.js from Wikizzle trunk&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4154210566841915972-6016399466385245654?l=jimhigson.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jimhigson.blogspot.com/feeds/6016399466385245654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4154210566841915972&amp;postID=6016399466385245654' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6016399466385245654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4154210566841915972/posts/default/6016399466385245654'/><link rel='alternate' type='text/html' href='http://jimhigson.blogspot.com/2009/01/prototype-singleton-classes.html' title='Prototype Singleton classes'/><author><name>Jim Higson</name><uri>http://www.blogger.com/profile/07687277696954247501</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='26' src='http://2.bp.blogspot.com/_UtOFssKybTA/SYIVCTc4fgI/AAAAAAAAAAk/C0Lqlo2QkrY/S220/me.jpg'/></author><thr:total>2</thr:total></entry></feed>
