Fixing Recursive onResize Calls on the iPad (iOS 4.2 and 4.3)

We're writing Pageforest applications for the iPad (e.g., Quoridor).  One really confusing issue popped up that seems to be a bug only when running in the full-screen mode of the iPad.

When we turn the display from portrait to landscape we would get a window.resize event.  But then, as a side-effect of measuring a DOM element (like reading elem.offsetHeight), a RECURSIVE CALL to our window.resize event handler is called.  I couldn't believe it - but it's true.  This is the one case I've seen where the browser makes a re-entrant call to an event handling function.

To fix it, we created a simple function to serialize all calls to our real event handler:

    $(window).resize( function () { setTimeout(onResize, 0); } );

Now all calls to our onResize function will be serialized, instead of occuring re-entrantly while we are in the middle of handling the first resize event.

We're not sure why our app is getting two calls to resize during a portrait to landscape flip - it looks like it might be giving us a smaller intermediate size, for the purposes of displaying a rotation animation of the page to the new orientation.

Offline Applications with Pageforest

HTML5 applications support an offline mode - all the files needed for an application can be saved in the client, so it can run without and internet connect.  For a great explanation of offline applications see:

 

http://diveintohtml5.org/offline.html

 

As you can see from the article, the concept is simple - but it can be a hassle to use:

  1. Create a Manifest file, and list all of the application files that you wanto be available offline.  Be sure your server serves this file using mime type text/cache-manifest.  You can see an example of a manifest file here: http://scratch.pageforest.com/app.manifest
  2. Add a reference to the manifest in the <html> tag of your main application page (all the html pages for your application, if you have more than one).

Seems simple enough.  The problem comes in when you change your application files.

  1. If your files change, but your don't modify your manifest, then the browser will not know to re-download the new files, and will instead use the out-dated version stored in its cache.  So, you have to be sure to modify the manifest file in some way (usually by updating a version number in the file).
  2. If you add a file, you have to, of course, remember to add it to the manifest, or it won't be available when your application is offline.

pf.py to the rescue - autogenerated app.manifest

In order to simplify the process for Pageforest applications, I've introduced a new command into the pf.py utility:

$ pf.py manifest

When you issue this command, an app.manifest file will be automatically created for you, listing all the files in your application.  More than that, when an app.manifest file exists in the root directory of your application, it will be automatically be updated whenever you do a put command to the server.  So if any files change or are added, you don't have to manually update the manifest file yourself.

Customizing Manifest Files

You may want to add additional resources that are not part of your private application files to your manifest.  For example, the scratch application adds:

http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js
/lib/beta/css/client.css
/lib/beta/js/json2.min.js
/lib/beta/js/utils.js
/static/images/appbar/green-left.png
/static/images/appbar/green-center.png
/static/images/appbar/green-right.png
/static/images/appbar/down.png
/static/images/appbar/logo.png

When placed above the AUTOGENERATED line in your manifest file, pf.py will preserve these lines and not modify them.

Excluding Files From a Manifest

 

If you have a large number of big files, you may not want to include them in the manifest (browsers will typically not download more than a few megabtes of data to the application cache). To exclude them from being added to your manifest, you can put an EXCLUDE directive in the auto generated section of your app.manifest:

#!EXCLUDE: big-images

Any files in the big-images directory will now be omitted from the manifest.

 

Pageforest at Seattle JS Meetup

I presented Pageforest to the Seattle JavaScript user's group last night.

It was great to have the forcing function of a presentation to wrap up some loose ends, and get the site into shape to work well.

The other speaker, Jordan Dobson showed of his amazingly pristine designs for iPhone apps using web-kit CSS craziness.  He's able to to some "no-images" layouts that look beautiful, perform great, and load really quickly on the phone.

I also met Thomas Yip, of BeeDesk at a Startup Weekend event this week.  He's looking at using Pageforest for his new product - could be the "killer app" to show off our platform?

The iPad platform

I just got back from a 3 week trip where I left my laptop behind, and only took my iPad (and my mobile phone).  Spending more time with the iPad has convinced me that it represents a sea-change just as important as Windows was in the early PC evolution.  What makes the iPad such an interesting new platform?

  1. Form factor - This is the most obvious difference between the PC platform (Mac and Windows) and the iPad.  The fact that you can hold the screen in your lap and interact with a touch-screen interface.  Microsoft has proved that this is NOT sufficient, as we've seen a lot of failures in the tablet form factor for Windows-based machines.  But Apple's implementation of a touch-screen computer is particularly simple and elegant.  It is a pleasure to use unlike any other PC experience I've had.
  2. Application Security  - I've been hearing more and more that iPad users are installing and using MANY more applications than Windows or Mac users.  When I reflect on my own behavior, I realize how hesitant I've been to install Windows applications for fear of corrupting my system (crashes, slow-downs, security holes).  The way iOS manages applications it effectively creates a sandbox so your apps can't really damage your system as a whole.  And the single-application running at a time, ensures that your system resources cannot be consumed by background applications running without your knowledge.
  3. App Store - The most important aspect of the iPad platform is the iTunes App Store.  The iPad would not have been successful without the previous success of the iPhone.  Not only did it solve the chicken and egg problem of having a large number of apps available on launch-day, but it also had several years of building up a developer community that was familiar with building apps for the iPhone/iPad platform.  Because Apple manages the distribution and payment of applications, App developers have a frictionless means of selling their applications to users.  It boggles the mind that Microsoft did not long ago develop a similar system for Windows.  Perhaps they didn't think it was necessary, since anyone could distribute Windows apps on their own.  But they did not count on the perceived complexity and trust issues that would prevent most users from being promiscuous purchasers of Windows applications.

But there is a dark-side to the Apple-owned iPad/iPhone platform; the fact that it is controlled by a single company which exercises strict control on entrance into the App market and protects it's own competitive interests by rejecting applications that they themselves want to monetize (they have already had the US Justice department get involved in anti-trust complaints, such as when Google tried to deliver it's Google Voice service on the iPhone).

I'm looking forward to an Android-based tablet, and the introduction of another App marketplace being run by Amazon.  That will give developers a great choice with more than one option for application distribution.

I also think there's an opportunity for the HTML5 platform to evolve into a competing API/platform for application distribution.  Web apps will evolve to have all the capabilities of the iOS platform (offline support, local storage, rich graphics (SVG + Canvas), camera and microphone support, etc.).  But we still lack an App Store equivalent for web apps.  Google announced one around Chrome at Google IO in 2010 - perhaps we will see them ship it with the introduction of Chrome-based tablets.

 

DRY (Don't Repeat Yourself) Design Patterns

One of the Pageforest applications I've been working on has prompted me to write a template generation language (modeled after Django's Template Language - DTL).  DTL has a number of features that allow you to re-purpose templates and combine them in interesting ways.  Yet, as a template language, it does not use the familiar concepts we, as programmers, are used to in our primary programming languages.

This got me thinking about the generalized patterns we use to help us design complex systems, by decomposing them into simpler parts.  I'll compare DTL with JavaScript to show how the facilities in one comapare to those in the other.

  • Includes - DTL has a {% include "file.html" %} tag that can be used for basic composition of one template inside another.  This is much like functional decomposition in a programming language; you can call a function from within another function:
        function foo() {
            bar();
        }
     But note that DTL does not support parameter substitution - or function arguments.  This makes {% include %} a much less powerful and useful feature (and, in fact, is not much used in most Django applications).
  • Inheritance - DTL allows one template to extend  another via  the {% extends "base.html" %} tag.  This turns out to be a very practical way to concisely design templates for different pages in an application, and yet enforce some regularity in the top-level design of the web site.  Developers would typically create a master template that describes the top level layout and navigation for their site, and then individual site pages can extend that template, and replace sections with page-specific content.

    This is accomplished with {% block name %} tags.  A child template can re-define any named block that appears in the template it extends.  This is similar to inheritance-based composition in class-based languages.  You can think of each block as a method in a base class, which can be selectively over-ridden by derived classes.

Yet, DTL is missing some important techniques for composition.

  • Parameterized templates - As mentioned above, there is not method of invoking a template, but replacing formal parameters from a calling template.  Variables in templates are basically dynamically scoped, so a calling template can define a variable that it knows will be used in an included template.
  • Template as datatype - Just as we frequently create functions dynamically in JavaScript, and pass them around as data, it would be similarly useful to allow templates to be stored as data, passed in variables, and then invoked dynamically.  There is not "eval" or "apply" function that would enable this type if template programming.

DTL is a very nice, and constrained, template definition language.  And I like that fact that it does not expose the full complexity of a programming language to its users (which was it's creator's design philosophy).  Yet it would be very useful to build language extensions via functional-like programming practices directly in the template language, without having to resort to escape functions that require writing custom tags and filters in Python.

A Mandelbrot Set Viewer - Pushing the Envelope with Blob Storage

As we've been developing the Pageforest service, we've been developing sample applications in parallel.  We think the best way to motivate features to support in our service, is to actually build applications that utilize those services.  In fact, we explicitly stated that we won't implement any feature that we don't have an imminent need from a JavaScript application developer (including ourselves!).

If you've seen our simple Scratch application sample, you'll see that a simple Pageforest application can be written in just a few lines of code.  And your application can save and load data from a cloud data-store on behalf of your users.

What you may not have realized, is that documents can contain much more than a single JSON blob storage.  In fact, each "document" in our system, can also have associated with it, any number of child "Blobs".  The permissions for reading and writing these blobs are controlled by their parent document.  Blobs can contain any Internet data-type, including images, sounds, pdf's, html documents, javascript files, or JSON persistence.

With this feature in mind, we decided to push the envelope with a Mandelbrot set viewer application with the following goals:

  • Use the Canvas element to draw the Mandelbrot set using JavaScript only (no flash, no plug-ins).
  • Cache images rendered in the client, into Blobs in the data store - so that once any user has viewed a region of the set, it would be available to any other user without having to recompute it.
  • Use HTML 5's Web Workers to compute image tiles in the background - keeping the UI responsive even when the CPU is busy with intensive image processing.
  • Use Google's Map (v3) API to provide a famiilar navigation interface to the Mandelbrot set, making it as easy to pan and zoom over the Mandelbrot set as it is to view maps and satellite imagery of the Earth.
  • Use the spare CPU cycles of concurrent connected browsers, to create a peer-to-peer compute cloud to further speed up calculation of desired image tiles.

We started the project over the Memorial Day weekend, and today we have a working prototype that meets all but the last goal.

Mandelbrot-blog

If you would like to play with the Mandelbrot Set Viewer, be aware that you must be signed in to Pageforest in order to generate tiles (you should be able to view existing image tiles without signing in).

The way the Mandelbrot Viewer works, is that whenever the map UI generates a request for an image tile (all of the tiles at all of the magnifications have been assigned names according to their position - even if the tile hasn't been generated yet), we simultaneously query the Blob store to see if the tile exists.  If it doesn't, we queue up a tile creation task and send it to a Worker.  Because workers don't have direct access to Canvas elements for drawing, we compute the data for the bitmap in the Worker, and send that back to the parent window when the Worker is done.  It can then be quickly saved into a Canvas element, converted to a PNG file, and then uploaded to the server.

We had some difficulty getting compatability across browsers to support raw binary upload's via AJAX, so we instead just send a base64 (text) encoded version of the file, and decode the data on the server before storing it.

Once the tile is generated, we update the url in the map image, so the browser attempts to download the tile again.

As is all of Pageforest's code, this example has been made open source.  You're welcome to make a copy to make your own variations.

M2