WebWait Upgrade: Deletion, Quick Entry of Multiple Sites

I made a couple of upgrades to WebWait this week.

First, you can now delete entries. This is useful for curating a set of results, since you can save a result set for others to try out, or for later analysis and comparison. If I get requests, I may also allow users to re-order the results for that reason too. You can only delete a trial when it is complete, i.e. when all calls to it have been made. This is not ideal, but would require significant rework. Again, I’ll respond to feedback on this one.

Second, you can now enter multiple URLs in the input field, separating them by space. I received a request from a Dzmitry Markovich, who wanted to time hundreds of websites at once, and figured I should support it. It’s an undocumented feature intended for advanced users, as I don’t have time to think about how to integrate it into the UI without compromising the simplicity of the design (e.g. with a text area; I should at least mention the feature in the FAQ, which is now ancient.) In the case of someone wanting to enter dozens or hundreds of websites, they need to cut-and-paste into the input field.

Funnily enough, both of these features had previously existed, but were removed when I overhauled the architecture to make way for the benchmark saving feature. The new architecture, being based on semantic events, made the deletion feature rather easy. The multiple-URL feature was also quite straightforward, as it would be with any architecture.

This is the latest of four updates in WebWait’s lifetime of as many years. Slowly but surely :).

Is this what the App of 2015 looks like? HTML5 + CoffeeScript + Less + WebStore + PhoneGap + Apparatio

My weekend project was a Hacker News Reader (source code). It works on Chrome, Safari, and Opera. Sorry, Firefox doesn’t work at present, reasons why speculated below. Mostly started this project because I want one with Twitter integration, but also to play with a few technologies, which I did. Among things explored and demonstrated:

  • Server-Less App The app is entirely browser-side. The server serves only as a repository of static files. Content comes from the iHackerNews unofficial API, by way of JSONP, the protocol we’ll still use for cross-domain calls until CORS becomes eminent in the hopefully not-too-distant future.
  • CoffeeScript. The app logic is written in CoffeeScript, and compiled to JavaScript in the browser with coffee-script.js.
  • Less. The UI styling is written in Less and compiled to CSS with less.js.
  • Chrome Web App. The app is also a hosted web app on the Chrome Web Store, which is to say (a) it can be discovered by users of the store; (b) it can be installed into the New Tab page of Chrome, for convenience sake; (c) if I wanted to, and added some extra server smarts, I could charge for it. I wrapped it up as an app with Paul’s super-handy Appmator utility.
  • Mobile HTML5. Although I didn’t (yet) optimise the site for HTML5, I did make it available on mobile, with the right icons for being added to the Home Page, and used Build.PhoneGap.com and Apparatio to roll it up as a native mobile app.

CoffeeScript

I wanted to try out CoffeeScript. While I’ve been skeptical about languages which are said to “compile down” to JavaScript, as if JavaScript was a complicated tangle of 1s and 0s rather than the high-level language it is…CoffeeScript is different. It’s more like JavaScript++ as the name implies. While I do find JavaScript to be pretty good, its syntax can get too verbose. Especially for closures, where you have too many curly braces and “function” keywords getting in the way of the actual logic we care about. You can also be more expressive with keywords like “unless”.

As ~98% of code people write never sees high-scale production use, I like to opt for quick and dynamic over tedious and “it just feels like the right way to do it”. So I employed coffee-script.js to compile dynamically, in the browser when the web app loads, instead of pre-compiling it on the command-line. Doing it dynamically means I can just type some code and reload the browser page, about the smallest possible debug-test cycle that is possible.

I found it slow-going at first, because I ran into some coffee-script.js gotchas and learned a few things about the language on the way:

  • Keep CoffeeScript in a separate server. Although CoffeeScript supports inline CoffeeScripts in your HTML (<script type=”text/coffeescript”>…here is my script….</script>) – perfect for quick hacking), I recommend you stick them in a separate file (<script type=”text/coffeescript” src=”myscript.coffee”></script>. I can’t explain exactly why, but when it was inline, things were note very deterministic and I found my CoffeeScript script was getting executed three times!
  • Use a server, not file:/// URIs. As with less.js, CoffeeScript mocks the browser’s script-downloading behaviour, using XMLHttpRequest to pull down any scripts specified in “text/coffeescript” script tags. Unforuntately, Chrome doesn’t let you XHR one file from another file, so you need to set up a server, if only to serve these static files over HTTP.
  • Don’t bother with window.onload (or jQuery intializer). I *think* coffee-script.js guarantees any CoffeeScripts you provide will be executed after the window has already loaded. I haven’t confirmed this, but seems to be that way on Chrome, Safari, and Opera. OTOH, the app doesn’t work on Firefox – CoffeeScript isn’t executed at all – and this may be related.
  • Declare before you use. A consequence of the previous rule is that your initialisation sequence will run in the order you specify it. This is different to the usual JS situation where you make a window.onload handler, which might call things that you’ve declared further down in the file. So in the code further down, note that I declare “show” before I call it. Pretty obvious, but JS habits and lack of descriptive error messages meant it caught me out.
  • Whitespace matters. There are no semi-colons and no braces here; similar to Python, whitespace dictates structure. However, you can also use parantheses to keep things on the same line, so I didn’t find this threatening to concise code.
  • Use “window” for global. Declarations like “x=5″ out in the open will *not* make global variables; and nor is there a “var” keyword. The only way to hook into global space is to explicitly reference the “window” object; e.g. “window.x = 5″.

How’s the code look? See for yourself, CoffeeScript keeps it pretty tight. I couldn’t find many examples of jQuery-CoffeeScript combos, so this will hopefully be useful in that regard:

javascript
< view plain text >
  1. storiesTemplate = _.template($("#storiesTemplate").html())
  2. show = () ->
  3.   $.getJSON "http://api.ihackernews.com/page?format=jsonp&callback=?",
  4.     (storyInfo) -> $("#stories").fadeOut () ->
  5.       $(this).html(storiesTemplate(storyInfo))
  6.       $(this).fadeIn()
  7.  
  8. show()
  9. setInterval show, 10*60*1000
  10.  
  11. $(".story").live "click", (ev) ->
  12.   return if $(ev.target).closest("a").length
  13.   window.open($(".url", this).attr("href"), "hackernews"+Math.random());

  • Less fails silently. Unfotunately, you don’t get error messages, so develop things gradually and be prepared to undo.

Less

If I was going to take a sip of CoffeeScript, it would only be fair to munch on a slice of Less too. If CoffeeScript is JavaScript++, then Less is CSS+. It’s really augmented CSS, because you can present a regular CSS stylesheet and it will be valid Less too. That’s not true at all about CoffeeScript; a typical JavaScript script has Buckley’s of passing the CoffeeScript compiler.

The other thing they have in common is JavaScript compilers. Which is not the case with SASS for instance.

Less talk:

  • Same XHR issues. As with CoffeeScript, the Chrome cross-domain restriction means you have to use a server even though the files are static. Also, it doesn’t seem to be possible to include Less stylesheets inline, or to get hold of the less compiler, which I think is a shame if true.
  • Silent fail. If any Less doesn’t parse, the entire stylesheet won’t be applied, and there’s no error message.

So some downsides, but the upside was a cleaner stylesheet with less redundancy. Gradient macro makes the ridiculously verbose gradient declarations completely DRY:

  1. /*****************************************************************************
  2.    MACROS
  3. *****************************************************************************/
  4. .linearGradient(@color1:#ccc, @color2:#ddd) {
  5.   background-image:-webkit-gradient(linear, left bottom, left top, color-stop(0, @color1), color-stop(100%, @color2));
  6.   background-image:-moz-linear-gradient(center bottom, @color1 0, @color2 100%);
  7.   background-image:-o-linear-gradient(center bottom, @color1 0, @color2 100%);
  8.   background-image:linear-gradient(center bottom, @color1 0, @color2 100%);
  9.   background-color: @color1;
  10. }
  11. .postedBy:hover, .commentsLink:hover span { .linearGradient(#fa0, #f80); }

Elements can be nested in their parent selectors:

  1. .commentsLink,.commentsLink:visited {
  2.   position: absolute; top: 50%; right: 1em; height: 1em;
  3.   ...
  4.  
  5.   span {
  6.     ...
  7.     border-radius: 0.3em;
  8.   }
  9.  
  10. }

… and so can pseudo-selectors:

  1. .story {
  2.   padding: 0.5em 9em 0.5em @leftIndent;
  3.   border: 1px solid black;  border-bottom-width: 0;
  4.   background: #fff6f6;  cursor: pointer;
  5.   position: relative;
  6.  
  7.   &:hover { .linearGradient(#fc0, #fa0); }
  8.  
  9.   &:last-child { border-bottom-width: 1px; }
  10.    
  11. }

And you can do cross-browser CSS calculations today:

  1. .commentsLink,.commentsLink:visited {
  2.    ...
  3.   margin-top: -0.5em-@commentsLinkPadding;
  4.  
  5.   span {
  6.     ...
  7.     padding: 0em + @commentsLinkPadding;
  8.   }
  9.  
  10. }

Chrome Web Store

This is a full-screen, focused, app which therefore meets the criteria for Web Apps I outlined in Thinking In Web Apps. (It would be more so if it showed the actual stories and comments in the app too, but that’s something for later.)

So it made sense to submit to the Chrome Web Store. Fortunately, my colleague Paul Kinlan had the gall to attempt building a web app that packages up Chrome apps … and then he actually pulled it off! Appmator asks you for the address of your web app, asks a few other basic questions, and then makes a Zip file you can drag to your desktop and upload to the store. All in all, a 30 minute submission to the Chrome Web Store and a nice way for people to discover your web app. I would only recommend this for true “Apps” as opposed to general websites, and ideally those apps should be tailored to a full-screen experience.

Anyway, it’s on the store now – Hacker News landing page.

PhoneGap, PhoneGap Build, Apparatio

The app is NOT optimised for mobile at this time, but the cool thing about HTML5 is, for the most part, it “just worked” when I pointed iPad and Android phones to it. Some things need improvement, but this is good to see.

For exploration purposes, I decided to build it as a native mobile app anyway. I was lucky enough to receive beta invites for PhoneGap Build and Apparatio, two web services which let you do PhoneGap without actually installing anything on your PC. I built this app with both products.

Apparatio, which I used previously, worked nicely and I was able to get a native Android app.

PhoneGap Build supports GitHub like Apparatio, but also allows uploading a zip file. I actually had a problem pointing it at my GitHub repo (as with Apparatio, it needs to provide clearer instructions on exactly what form of URL is required). After I uploaded the zip file, it showed me a dashboard of 4 packages being built, and just went ahead and built each of them. Nice and simple!

Android, WebOS, Symbian, Nokia. Conspicuous by its absence is iOS, which Apparatio does support.

DropBox on my Macbook and Galaxy made it easy to copy the APK downloads to my Galaxy and install it. The Apparatio one took some time to download initially, but worked fine. The

Other Notes

Block-level “a” element, NOT. HTML5 makes it possible for “a” elements to be block-level, i.e. to include other tags including other “a” elements. This would be ideal for the Hacker News Reader UI, where the entire story block is a link to the story itself, but the block also contains links to comments and authors. It turned out to be legal in modern browsers, but with blatantly wrong layout, as someone else found earlier. Shame as I had to introduce considerably more JavaScriptCoffeeScript and CSSLess.

InnerHTML, Templating, and Live(). Seems like every project I do these days, I’m building dynamic content with innerHTML, as opposed to DOM manipulation. The first reason is I’ve become accustomed and satisfied with templating, and see it as a growing trend thanks to language-agnostic formats like Mustache as well as server-side JavaScript. (Though in this case, it’s a pure browser-side app.) So make a template and stick it on the page with innerHTML (or jQuery’s .html() ). The second reason is jQuery’s live() command makes it easy to handle events against the newly formed elements, which was previously a pain with innerHTML().

Setting text size. As you can see, text size varies with the number of points a story has. The relevant line appears in the template:

font-size:<%= Math.log(Math.log(Math.max(s.points,10))) %>em;

Using a log scale ensures a story with 2000 points isn’t 2000 times bigger than a story with 2 points. To dampen the effect further, which I think is important in mobile displays, I used a double-log and applied a minimum threshold of 10 points.

Future Things

There was actually an itch to be scratched here, beyond trying out various tech. Specifically, I’ve been wanting a mobile HN reader that would: (a) tweet; (b) quickly mark for instapaper reading, and ideally when I’m offline. (It would show the latest stories it saw when online, and cue them up for later…in general, I wish more offline apps would support cueing things like Tweets and Read Laters for when you’re back online.)

Fin.

So is this the Web App of 2015 or not?

There are definitely elements here that are not in wide use now, but will be in wide use by 2015. The whole “write once, run many” aspect of HTML5 is just getting wheels. It’s now clear that Apple won’t dominate the landscape by 2015, so regardless of their ever-fluctuating stance, HTML5 will be able to power the apps of the majority of smartphones and tablets…not to mention the possibility of apps on TV, cars, watches, washing machines, etc etc etc. (Can you tell it’s been CES week?)

And with HTML5 being able to power these apps, we’ll need ways to fit them into the App ecosystem, and that’s where PhoneGap and friends fit in. As I’ve said in various places, I always found it painful to have to set up Eclipse, PhoneGap SDK, and so on just to make a native app from HTML5. These are the things HTML5 developers don’t want to do, and the PhoneGap team is well aware of it, hence their PhoneGap Build effort. Apparatio provides relief in the same way and we can expect the same from others, especially so in light of Heroku’s high-valuation acquisition bringing attention to the whole idea of cloud programming. GitHub is also a vital component of this vision becoming reality. The next step will be to automate deployment into the stores, hopefully something that app-hungry stores will be happy to facilitate.

The Chrome Web Store is a related concept aiming to “app-ize” we apps. Mozilla is investigating a similar idea, and we’ll see a lot more of it in the next few years too. Some people say “but it’s just a bookmark”, an understandable reaction, but I think people are starting to get it now. It should only be an app if it really is an app, ie. if the underlying URL is really a media-rich, focused, app rather than a big collection of information and hyperlinks. And the Web Store is not just a place where you can buy and install apps; it’s also a directory where you can find all the cool apps on the web, with screenshots and reviews and comments to help you decide if they’re worth trying out.

Offline is important in the app story too (although this app doesn’t (yet) do any offline). You don’t type in URLs when you’re on a plane, but you do click on app icons to launch them. So offline web apps need an app-like presence to meet user expectations.

How about those languages, CoffeeScript and Less? CoffeeScript seems promising and I’ll definitely be using it more. At the same time, I’m weary because I feel like JavaScript may be “good enough” and CoffeeScript becomes a maintenance problem if others need to see your code. But the leap is not a big one.

As for Less, I’m more convinced it’s the way to go. We nowadays have an absurd proliferation of “-webkit-” “-moz-” “-o-” “-msie-” prefixes and the various syntaxes to go with them. It’s for good reason, but it’s also as much an affront to the DRY principle as any agilist could ever fear, and it’s unfortunate the standards don’t provide better support for dealing with this sort of thing. Likewise, the need for variables/constants and calculations. Less allows for all of that, and in a way that will work on legacy browsers even when some of these features do come about. So, yes, I’ll be committing to Less in the future.

What’s very cool about both Less and CoffeeScript is the JavaScript frameworks. It’s ideal for starting off a project, and when performance gets to be an issue, you can easily switch to the compiled version after that.

Finally, I’ll mention that templating is a design pattern that’s also going to be big in 2015. There’s much more framework support these days and it’s easy to use the same templates on the browser and the server.

Yoink: Extracting All Scripts and Stylesheets on the Page

This here is a script that will put the code of all scripts and stylesheets into a single variable. Usage:

javascript
< view plain text >
  1. yoink(function(all) {
  2.   console.log("scripts", all.scripts); // string array
  3.   console.log("stylesheets", all.stylesheets); // string array
  4. });

As you can see, you have to provide a callback because this stuff is asynchronous. It downloads the files one at a time to keep things simple and without using any fancy-pants Ajax queueing library. And right now, timeouts won’t be too graceful. Also, browsers will typically allow scripts and stylesheets to work cross-domain, but of course XHR won’t. So any external scripts and stylesheets are unfortunately out of reach and will not be captured. This is an especially tragic circumstance in the case where they are hosted on a different subdomain. But, as they say, “hey”. I don’t know what that means, but, as they say, “hey”.

Yoink will download inline scripts (script tags with bodies), remote scripts (script tags having a “src” attribute), inline style attributes (elements having a “style” attribute), inline style tags (style tags with bodies), and remote stylesheets (links of type “text/css”).

There are several purposes. I did a quick hack version of this a while back to improve logging info; in principle, a logging method could introspect on the source tree to show extra context info (locate the log message I’m currently outputting, and then say which line it’s coming from, for example).

In the case of Single Page Applications a la TiddlyWiki, a plugin might use Yoink to inline all the remote content.

Right now, I’m more interested in using it for browser extensions, such as the Feature Creep extension I suggested, to show what’s happening on the page. Some of that stuff you can work out by inspecting the DOM, but other stuff … show me the code!

Get it here – http://gist.github.com/378850. Shown below for convenience:

javascript
< view plain text >
  1. function yoink(complete) {
  2.  
  3.   var all={
  4.     scripts: [],
  5.     stylesheets: []
  6.   }
  7.  
  8.   function downloadResources(urls, onComplete) {
  9.  
  10.     var resources = [];
  11.  
  12.    (function inlineResource(index) {
  13.  
  14.       if (index==urls.length) {
  15.         onComplete(resources);
  16.         return;
  17.       }
  18.  
  19.       var xhr = new XMLHttpRequest();
  20.       xhr.open("GET", urls[index], true);
  21.       xhr.onreadystatechange = function() {
  22.         if (xhr.readyState!=4) return;
  23.         if (xhr.status==200) resources.push(xhr.responseText);
  24.         inlineResource(index+1);
  25.       }
  26.       xhr.send();
  27.     })(0);
  28.  
  29.   }
  30.  
  31.   var scripts = document.getElementsByTagName("script");
  32.   var scriptSources = [];
  33.   for (var i=0; i<scripts.length; i++) {
  34.     if (scripts[i].src) scriptSources.push(scripts[i].src); // remote
  35.     else all.scripts.push(scripts[i].innerHTML); // inline
  36.   };
  37.   downloadResources(scriptSources, function(resources) {
  38.     all.scripts = all.scripts.concat(resources);
  39.  
  40.     var styles = document.getElementsByTagName("styles");
  41.     for (var i=0; i<styles.length; i++) { all.stylesheets.push(styles[i]); };
  42.     var allElements = document.getElementsByTagName("*");
  43.     for (var i=0; i<allElements.length; i++) {
  44.       var inlineStyle = allElements[i].getAttribute("style")
  45.       if (inlineStyle) all.stylesheets.push(inlineStyle);
  46.     };
  47.     var links = document.getElementsByTagName("link");
  48.     var cssHrefs = [];
  49.     for (var i=0; i<links.length; i++) {
  50.       if (links[i].type=="text/css") cssHrefs.push(links[i].href);
  51.     };
  52.     downloadResources(cssHrefs, function(resources) {
  53.       all.stylesheets = all.stylesheets.concat(resources);
  54.       complete(all);
  55.     });
  56.  
  57.   });
  58.  
  59. }

WebWait Updated

One of the projects I wanted to work on in my time off was WebWait.

It finally does what I wanted it to do all along: Permanently record benchmarks. You can get a unique URL for each benchmarking session you run by hitting Save. Funny – WebWait was running as a Rails app for several years, but was always a pure browser-side client until I finally completed this. (Ultimately, I didn’t use Rails anyway – see below.)

Some other enhancements too …

  • Bar charts (using the Google Charts API):

  • Expanded dashboard, showing a box for each site you’ve trialled. (Previously, there was only one box ever shown, the latest one. Since people like taking screenshots of these, I figured it would be cool to include multiple boxes.)

  • There is also a JSON view of the trials that were recorded.

It would be cool to expand it further, for example allowing pages to be cross-linked when they benchmark the same trial. But I’ll leave it here and see where demand takes it.

The new WebWait server is powered by the incredibly productive trio of Ruby, Sinatra, and TokyoCabinet. On the browser, it’s still jQuery, with the DataTables plugin and my own jQuery iFrame plugin. Hosted on SliceHost (where I have now happily migrated many things.)

InfoBoxPlugin: A TiddlyWiki Plugin for InfoBoxen

InfoBoxPlugin - Lists tiddlers in a table (by mahemoff)

G’Day, here’s a new tiddlywiki plugin I’ve been working on: InfoBoxPlugin. It’s based on the equally-monikered infoBox in MediaWiki/Wikipedia, which you’ll see in any article that is marked “current event” or “controversial”, for example, on the big W. I find infoBoxes elegant, as they are unobtrusive enough to let you get on reading the article, and are easily ignored in the same way as web ads, but when you do focus on them, they are clear in meaning and support pattern recognition, with each type of infoBox having its own look and distinct icon.

The infoBox macro makes infoBoxen that look as shown in the diagram above. In the simplest case, you type this into a tiddler:

<<infoBox>>This is good stuff – pay attention mkay.>>

Pretty straightforward. One funniness here is the bracket asymmetry. We’re trying to do something similar to XML tags, what with their start tag and attribs, followed by body, followed by closing tag, so you’d expect to see:

<<infoBox>>This is good stuff – pay attention mkay.<</infoBox>>

or sumptink like dat. But one of my lessons was that tiddlywiki has a somewhat unusual convention, enshrined only in the gradient macro to my knowledge, for the former syntax. The only way to do something else would be to refactor or replicate or hijack the core code, and I don’t fancy it, and in any event it would go against the standard already set by gradient. In any event, I was pleased enough when Jeremy showed me the example of the gradient tag, which shows that this kind of “macro body” is even possible, so I settled with that.

Okay, so that’s nice, you can do a simple message, but where it gets more to the point is where you build up a family of infoBox types for your TiddlyWiki, each having a separate definition tiddler. For example, you will often want a “warning” infoBox to appear in various places. So you make a “warningInfoBox” tiddler, with the following text:

|background|#fdd|
|borderColor|#f66|
|headingColor|#800|
|heading|Danger, Will Robinson!!!|
|messageFontStyle|normal|
|messageColor|#900|
|message|Please follow these instructions carefully.|
|iconURL|icons##stop|
|iconWidth|40|

And then in a tiddler, you just write <<infoBox warning>>>> The association between “warning” and “warningInfoBox” is an enforced convention; my macro just appends “InfoBox” to the type you declare. The double-double closing bracket is a consequence of what I said above, combined with the fact that the definition has a pre-defined “message”. This is often going to be what we want, but not always. In fact, all of the fields above are optional, so you could leave out “message” and then the warning macro would have to specify it (if you wanted a message, that is). i.e. <<infoBox warning>>This Is Serious Mum.>> (… because the undeniably-talented, always-controversial, Aussie band This Is Serious Mum – aka TISM – popped into my head as an instructive example for this warning.)

Some other things to say

  • This plugin is part of a greater effort to pull out the goodies from TiddlyGuv into reusable modules. TiddlyGuv was the first thing I worked on upon joining Osmosoft, and when I looked it at with fresh eyes recently, I realised how much of what I built is just generic TiddlyWiki functionality. I now have a better understanding of what makes an independent TiddlyWiki plugin, and indeed I have a much greater respect for the whole modularity concept in TiddlyWiki, because it really satisfies all the traditional software engineering principles of encapsulation, orthogonality, etc etc in a neat bite-size way, which I will have to explain more about elsewhere. infoBox is the first of several plugins that need to be exorcised from the TiddlyGuv base.
  • The “macro body” thing is actually quite easy – you just call wikifier.subWikify(domEl, ">>"); where domEl is some DOM element. This will keep reading from the end of the macro definition to the next occurrence of >>, and fill domEl with the results of wikifying that content. It would be nice if there was a function to let you just get the string, instead of sticking it in the DOM, but you could still achieve that yourself by shoving it into a hidden element and capturing its innerHTML. The only complication for me was a common one these days of jumping between JQuery-land and traditional DOM-land, since TiddlyWiki now ships with JQuery, but much of the infrastructure has not (yet) been retrofitted to talk JQuery. Once I did a JQuery-to-DOM conversion, it worked fine.
  • Another interesting detour that happened here concerned cross-referencing tiddlers. Something you will want often want to do with infoBoxes is use icons. In a single-file TiddlyWiki, often intended to be offline, the icons would need to be data: URIs. This applies to other plugins too, so I dedcided to look into it a bit. I wanted a way for the “iconURL” slice in the InfoBox definition to either be a regular URL, or a reference to another tiddler containing the data: URI, so as to isolate it away. This led me to learn more about transclusion. With thanks to @FND, and after some experimentation, I found the easiest way was to support linking to sections of another tiddler. See how it works in the demo tiddlywiki, where an “icons” tiddler contains the data:URI icons.

Wow, I didn’t expect to say so much about what is not a major plugin. It comes at a time when I’ve been making some realisations about tiddlywiki, hence the verbeage.

SimpleMessagePlugin: Unobtrusive TiddlyWiki Status Message

To simplify TiddlyGuv message rendering, I made “SimpleMessagePlugin”. It removes the message box 1 second after a message was shown (using displayMessage). In the event another message appears in that time, it appends the message (as it normally does) and extends the message box’s lifetime by a second. In other words, it always closes a second after the last message was shown. The algorithm is a pretty similar throttling deal as Ajaxagram.

Demo here.

Latest version from SimpleMessagePlugin.

JQuery IFrame Plugin

This article by Nick Zakas, covering some technical issues in iframe loading, triggered me to surface a JQuery IFrame plugin I made a little while ago, which supports loading IFrames. (I did tweet it at the time, though I’ve since changed the location.)

JQuery IFrame plugin

The plugin basically tells you, the programmer of the parent document, when your child iframe has loaded. It also has some other bells and whistles, like timing the load duration.

It’s inspired by the use cases of WebWait (http://webwait.com) and the Scrumptious trails player (http://scrumptious.tv). Both are applications whose whole mission in life is to open up an external page in an iframe, and do something when it has loaded.

There’s already an IFrame JQuery plugin, but it’s to do with controlling what’s inside the iframe, i.e. the situation which is only possible when the iframes are from the same domain. What I’m dealing with here is the parent knowing when the iframe is loaded, regardless of when it comes from and agnostic to what it does.

Modal Dialog JQuery Plugin

This has been a while coming, but I made a little “yet another modal dialog lightbox JQuery plugin” thing this week.

Demo and Download for Modal Dialog – JQuery Plugin

It was driven by TiddlyDocs, but I’ve been wanting one anyway for a while. Mostly because lightbox libraries generally do some hocus-pocus on page load, like applying to everything marked rel=”lightbox”, but don’t let you dynamically produce the lightbox yourself. That’s fine for pages that are just a static image gallery, but not useful to someone building a dynamic web app.

I’ve subsequently used nyromodal, on good advice, but wanted something smalller and with a simple API for my main use case, which is just showing some text.

The plugin also simplifies work by not requiring you to install a separate CSS file. Doing that, and linking to it, as well as installing any images, is something that always slows me down when I want to start using a graphical client. In keeping with the “happy path” You-Aint-Gonna-Need-It (YAGNI) mantra, I’d rather keep a library to a single Javascript file – be evil and do styling in the library by default, but still support users who want to put custom CSS in a stylesheet.

Towards A Single Page Application Framework

Tonight, I was thinking of making a Twitter app to manage my various accounts (I have ~dormant accounts related to projects like @webwait and @listoftweets). The app would be holding username and password details for each of these accounts, so it made sense to build it as a Single Page Application (SPA). This way, a more paranoid user could always keep the app in their local file system, while a less paranoid user could always stick the file on a protected server somewhere, having configured all the username-password details.

TiddlyWiki is a framework for developing SPAs. One might say it’s the framework for developing SPAs, since there are no prominent alternatives. So my obvious choice was a TiddlyWiki. However, not too long ago, the TiddlyWiki core guys extracted out the secret sauce for SPAs: the ingenius bit of code that saves files without requiring any browser extensions. (I’ve tried to explain this to people and it always leaves even the most brilliant minds a little dumbstruck, but yes TiddlyWiki demonstrates there are pragmatic hacks that can be used to read a file into the browser and then write it out again.) I was keen to explore this saving mechanism, so experimentation ensued.

The file management techniques ship conveniently in a JQuery plugin, jQuery.twFile. There is a basic demo which lets you edit the entire text and save it. The demo confused me at first – because it was editing the entire body of the file, I wasn’t sure how to translate that info into what I, as an application developer needed. The demo is useful for framework developers understanding the plugin, but less so for application developers. So I extracted it into a demo that is still minimalistic, but closer to the kind of thing you’d do in an application.

The SPA demo is here.

Once I did that, I realised what’s required is a SPA framework. In practice, most developers using twFile will be keeping all the HTML, CSS, and Javascript in a single file, so it’s possible to build a higher-level abstraction on twFile, so that developers can focus only on the content. I built the demo in a way that distinguished what was boilerplate framework code and what was application-specific HTML, CSS, and Javascript.

It was all still in one file, and that’s fine for many developers – you can give the developer the file, tell them “edit these bits”, and they can come up with something functional. I decided to extract things further though, and found the Jinja templating framework to be useful here. Jinja has a concept of template inheritance, so you can easily build up an “include” system. The net effect is I was able to encapsulate SPA logic in a single file, which was passed through a Jinja processor to produce the executable HTML document.

The basic SPA logic is shown below:

  1. {% extends "spa-template.html" %}
  2. {% block title %}JQuery SPA demo{% endblock %}
  3. {% block css %}
  4.   etc etc
  5.   body { background: black; padding: 0; margin: 0; font-family: Gill Sans, sans-serif; }
  6.   h1 { background: white; color: black; padding: 10px 10px 0; margin: 0; height: 52px; }
  7. {% endblock %}
  8. {% block html %}
  9.   <h1>
  10.   <img id="logo" src="data:image/jpeg,%FFetc etc"/>
  11.     <span id="title">JQuery-SPA Demo</span>
  12.   </h1>
  13.  
  14.   <div id="main">
  15.  
  16.     <ol>
  17.       <li>Save this file to your local file system and open your local copy in the browser (using a file:/// URI). The browser might ask for special permission for this document, and you will need to grant it.</li>
  18.  
  19.       <li>Type your message: <input id="message" value="change me"></input></li>
  20.  
  21.       <li>Save this page: <input id="saveButton" value="Save" type="button"></li>
  22.  
  23.       <li>Hit shift-reload to perform a clean reload the page and observe that your message has been saved.</li>
  24.     </ol>
  25.  
  26.     <h3>What is this?</h3>
  27.  
  28.     <p>This is a demo of an experimental Single Page Application I am building atop <a href="http://jquery.tiddlywiki.org/twFile.html">jQuery.twFile</a>, the file saving plugin extracted from TiddlyWiki. Forked from <a href="http://jquery.tiddlywiki.org/twFileDemo.html">this twFile Demo</a>. Only tested on Firefox for now.</p>
  29.  
  30.   </div>
  31. {% endblock %}
  32. {% block javascript %}
  33.     $.spa.save = function(text) {
  34.       return text.replace(/<input id="message".*?></input>/,
  35.               '<input id="message" value="'+$("#message").val()+'"></input>');
  36.     }
  37. {% endblock %}

So it contains separate HTML/CSS/Javascript blocks. Probably not a good engineering practice (though it doesn’t impact on the end-user experience, which is always a single file), but it’s convenient for now. The key SPA logic is here:

  1. <li>Type your message: <input id="message" value="change me"></input></li>
  2.       ....
  3.     $.spa.save = function(text) {
  4.       return text.replace(/<input id="message".*?></input>/,
  5.               '<input id="message" value="'+$("#message").val()+'"></input>');
  6.     }

As an app developer, all you have to do is override $.spa.save (which in retrospect should be renamed as it doesn’t actually perform the save). This function receives the text of the file as it was stored on disk when the page loaded. It must then return the text that should be saved to disk. Thus, it must store application state in HTML, probably by performing some kind of substitution.

Having put this together, I’m keen to proceed with the envisioned Twitter app. It’s not yet clear if there’s any mileage here over a regular TiddlyWiki, but as someone who is more familiar with starting apps from a blank HTML page (or a simple Project Deploy* template), it might end up being a more familiar way to kick an app off. Watch this space.

ColourLovers API – JSON Demos

About a year ago, I was excited to discover ColourLovers had an API. There is some great data about colours and palettes on that site, and some great possibilities for mashups – some as general-purpose art projects, and some as tools for designers.

However, I noticed there was no JSON-P interface, so there was no way to write a pure browser-based mashup. I got in touch with them, and heard back from Chris Williams, who to my pleasant surprise was willing to introduce JSON-P. And so we had some email discussion about the various options for the API interface (e.g. do you call it “jsonp” versus “callback”; do you use “real JSONP” versus just a callback function; do you hardcode the name of the callback function versus let the caller specify it as a parameter), and ultimately Chris went ahead and implemented it a short time later and published the API on January 26, 2009. Fantastic!

I offered to make some demos illustrating code usage, and did around that time, but never got around to publishing them until now. I’ve released the demos here. There’s a couple of JQuery demos, and one raw Javascript demo. I had fun making them and have plans to do a lot more with this API.