2009-04-23

The Problems With Pagination

Many web sites and applications paginate their content. Often this makes them harder to use.



Partly it's because site-provided pagination interacts poorly with the browser's built-in scrolling, because the content on any given web page usually takes up more than one vertical screen of space, and you have to jump back and forth between site pagination and browser scrolling, hunting down their controls each time you do so.

Sites compound this problem by moving their pagination controls around, so you can't leave your mouse over the Next Page button and assume you can simply click the mouse button repeatedly to jump to subsequent pages.

And pagination control click targets are small, which makes them hard to hit. Page-specific targets are often a single number (1, 2, 3), while the Next Page target is sometimes a single character (>).



Finally, pagination confounds searching through content using a browser's Find in Page feature, since it requires you to switch back and forth between searching for the content on the current page and navigating to the next one until you find the text you're looking for (and beyond, if you're looking for multiple occurrences of it).

And it similarly confounds any other activity in which you want to engage with all the content in an application, like when I wanted to turn off tracebacks/pingbacks for all 56 posts in a Wordpress blog, which is a six step process, and I had to repeat those six steps four times each because Wordpress's list of posts only lets me see 15 at a time.



Nevertheless, there are legitimate uses for pagination:
  • sites whose content is computationally intensive to generate (like search engines) use it to reduce load and cost;

  • those with large quantities of content that strains server, client, and network resources to display all at once (search engines again, the Personas gallery) use it to improve responsiveness;

  • those whose content cannot usefully be browsed in its entirety much of the time (search engines a third time, messaging applications) use it to reduce the cognitive overload of providing all their content at once;

  • and those with an ad-based revenue model (you guessed it, search engines, but also online news and many other sites) use it to show more ads.


For those kinds of sites, pagination makes sense, although doing it right still matters, like giving its controls large click targets and putting them in the same place on every page.

And, for content that spills across multiple screens, including the pagination controls at both the top and bottom of the content, the two scroll positions at which users often find themselves when they want to change pages; or positioning them absolutely, so they're always visible; or only loading a screenful of content at a time, so users never have to both page and scroll; or using endless/infinite scrolling.

Are there even better approaches, and are there ways for the web platform to support them so that web sites don't have to roll their own? Perhaps browsers and servers could collaborate to provide content in screenfuls with integrated scrolling and paging controls that play well together and don't change from site to site.

What do you think?

2009-04-16

syntaxes for iterating arrays in JavaScript

JavaScript lets you iterate arrays using the for each...in statement:
for each (var item in [1, 2, 3]) alert(item);

JavaScript 1.6 added the Array.forEach method:
[1, 2, 3].forEach(function(item) { alert(item) });

I've always preferred Perl's statement modifiers, though, for the popular English-like order of their clauses ("do this for each of those"):
print $_ foreach (1, 2, 3);

JavaScript 1.7 added array comprehensions for array initialization:
var squares = [item * item for each (item in [1, 2, 3])];

I just realized I can (ab)use comprehensions to iterate arrays with Perl-like syntax by throwing away the result:
[alert(item) for each (item in [1, 2, 3])];

I can iterate object properties the same way:
var obj = { foo: 1, bar: 2, baz: 3 };
[alert(name + "=" + obj[name]) for (name in obj)];

Sweet!

2009-04-09

Snowl Integration with Places

alta88 has recently done a bunch of work to integrate Snowl with Places. His initial efforts have focused on getting sources and people into Places, as he describes in this post to the discussion forum.

This will make the list of collections in the next version of Snowl work like Firefox's list of bookmarks in the Bookmarks Sidebar, and sources/people will also show up in the AwesomeBar:



Snowl doesn't yet register the snowl: protocol, however, so selecting those items doesn't do anything (yet). What do you think it should do?

And what additional Places-backed features would be useful?

(Picking up on something dietrich said on Twitter last week, I'd love to see Places get populated with tweets that reference a URL, setting the title of the Places entry to the content of the tweet, so you can use the AwesomeBar to find that site you saw last week in a tweet, as other folks have done with Delicious.)

2009-04-07

Snowl development builds

I've created a distribution channel to get Snowl changes out to its testing community faster. I'll push development snapshots to the channel on a regular basis as significant fixes are committed (or perhaps nightly, if I can automate the process).

Once you install a development build, Firefox will automatically check for newer ones and prompt you to update when it finds one, just as it does for the release builds on AMO.

This is truly the slicing edge. If you use these builds, be prepared for the possibility of regressions and bustage. On the other hand, you'll also get to test new features earlier, like alta88's Places integration work.

Install the latest development build, then discuss it and report bugs you find. (If you don't want to be on the slicing edge, try the latest stabler release instead.)

ISO simple open web image editor

Is there a simple open web image editor? I know about Pixlr, Picnik, and Aviary, but those all use Flash. It seems like it should be possible to make one using Canvas and other modern open web technologies.

In addition to using it for simple photo manipulations, I'd like one that can be embedded into other applications, like Personas (to make it easy for users to make and refine their own personas) and image hosting sites (so users don't have to go to a separate website and import their images to edit them).

In other words, I want the web-based image editing equivalent of Gecko+Firefox: a great open-source image rendering and editing engine with a flagship product that demonstrates its capabilities and an API for embedding it into other applications.

Is there such a thing? Does anyone want to create one?

2009-04-06

The Extension Bootcamp Tutorial

A few weeks ago, as part of Mozilla Labs' Design Challenge, I taught a tutorial on extension development. My goal was to impart the minimum necessary information to build a simple extension using modern best practices. I called it Extension Bootcamp: Zero to "Hello world!" in 45 Minutes.

The tutorial, which borrowed from other ones (including my ancient Tinderstatus tutorial, the venerable Building an Extension, and the new Firefox addons developer guide), covered:
  • what extensions are, what they can do, and what they're made of;
  • how to configure your development environment on Windows, Mac OS X, or Linux;
  • how to construct an extension, including required files and directory structure;
  • how to package and test your extension;
  • a simple extension that inserts XUL into the main browser window via an overlay, styles it with CSS, listens for and responds to a DOM event on it using JavaScript, and localizes its strings with both a DTD and a properties file.
It explicitly excluded:
  • archiving chrome files into a JAR archive (valuable for large, popular, and stable extensions, but not worth the added complexity for the rest);
  • developing XPCOM components (most extensions should use JavaScript modules instead);
  • putting CSS/image files into a separate skin package (Firefox doesn't ship with two themes, which was the original impetus for segregating these files thusly, and most extensions don't support third-party themes, so this is unnecessary).
To conserve time, I didn't cover:
  • developing JavaScript modules;
  • accessing XPCOM components;
  • using tools and documentation to introspect and learn Mozilla code and APIs.
Developing a Firefox extension is pretty complicated, since there are so many different pieces to it, and the tutorial ended up taking two hours, including time for the students to practice each lesson on their own computers (for which my colleague Jono's assistance in fielding questions was invaluable).

The video of the tutorial, edited down to 1.5 hours, as well as the slides and accompanying files are available. If I had time to work more on this, I'd expand it to include the essential topics I didn't cover. I'd also update the Building an Extension tutorial to adopt the pedagogy of this one. If you'd like to do either of those things, please do!

2009-03-31

Personas in Thunderbird

Huang Yaoquan recently contributed a patch to make Personas work in Thunderbird! Here's Thunderbird with the Groovy Blue persona selected:



The patch requires a Thunderbird 3 beta, and it landed after the cutoff for the Personas 1.0 release, so it's not in the version on AMO, but it is in the latest development build, so give that one a spin if you're developmentally inclined, and provide feedback in the discussion forums or by filing bugs.

2009-03-25

JSON JS module for Firefox 3.0/3.5 API compatibility

I've written a JSON module to wrap the incompatible Firefox 3.0 and 3.5 JSON APIs, presenting the 3.5 API on both versions of Firefox, which is useful for extensions that support them both. Import this module to parse and stringify JSON in both 3.0 and 3.5 without having to check the host application's version each time.

Note: don't import this into the global namespace! If you do, you'll hork native Firefox 3.0 code that expects the 3.0 API. Instead, import it into your own object like this:

let MyExtension = {
JSON: null,
...
};
Components.utils.import("chrome://myextension/modules/JSON.js", MyExtension);
// Now MyExtension.JSON is an object implementing the Firefox 3.5 JSON API.

The module also works in Thunderbird 3.0, which uses Firefox 3.5's API (although there's no need to use it for an extension that only supports Thunderbird 3.0). I use it in Personas, which supports both Firefox 3.0 and 3.5 as well as Thunderbird 3.0.

The Firefox 3.5 JSON API is documented in the Mozilla Developer Center article Using JSON in Firefox. This JSON module is documented on Mozilla Labs' JS Modules wiki page.


2009-03-18

Merging with Mercurial 1.0 and Meld

In an earlier post, I described my experience merging changes with Mercurial 0.9.5 and Meld 1.1.5.1 on Ubuntu 8.04.

Things have changed in Ubuntu 8.10, which ships Mercurial 1.0.1. Now Meld displays your version of the file on the left, the other version on the right, and the base version (from which yours and the other are descended) in the middle.

Here it is handling the Merging conflicting changes example from the hg book:



Your job is to reconcile the conflicts between your version on the left and the other version on the right, turning your version on the left into the result of the reconciliation, which you then save to your working directory.

I understand why it can be useful to show the base version, and I think it's ok to use the same pane to show both your version and the final result (although kdiff3 provides a fourth pane beneath these three for the latter, which seems better).

But I don't understand why the base version is in the middle, given that one's primary task in this situation is to compare the differences between the two versions on either side of the window, copying changes from one to the other as needed. Putting another file in the middle just gets in the way of that process (which is presumably why kdiff3 puts the base version on the left and your version in the middle according to the screenshot in the aforementioned example in the hg book).

In fact, I frequently find myself starting a merge by copying the other version of the file into the base version in its entirety. That destroys my ability to refer to the base version, but it places the other version and my version side by side, at which point it's much easier to compare the two and edit my version accordingly:



But maybe I'm missing the point or misusing the tool, and there's some valid reason for the way Mercurial uses Meld. Can someone tell me what it is?

2009-02-04

A XULRunner-Based Unit Test Harness

Prologue

After finding a couple bugs in the Observers JS module recently, I looked around for a simple test harness to exercise it and other JS modules. Weave's (based on Mozilla's xpcshell harness) seemed promising, although it has to hack around various limitations of xpcshell.

Atul Varma had suggested some time ago that a XULRunner-based harness might make sense, so I decided to see if I could modify Weave's harness to run JS module tests using XULRunner instead of xpcshell.

Act 1

That proved doable, although some details were tricky. Sometimes I wondered whether I wouldn't have been better off reusing MochiTest, although it's heavier weight. Other times I wondered why I didn't find this bug about XULRunner-based unit tests sooner, although it's not clear that reusing the attachment in that bug would have been faster.

In any case, I got the harness working with XULRunner and wrote an initial set of tests of the Observers module. To run them, pull the jsmodules repository, change to its test/unit/ subdirectory, and run make with the location of your XULRunner executable (or set the XULRUNNER_BIN environment variable to its location):

hg clone https://hg.mozdev.org/jsmodules
cd jsmodules/test/unit/
make xulrunner_bin=/usr/bin/xulrunner

Since Firefox 3.0 comes with XULRunner, you can use any Firefox 3.0 (or newer) executable instead (except on Linux distros that ship Firefox as a XULRunner app--you have to use the XULRunner executable on those), like /Applications/Firefox.app/Contents/MacOS/firefox-bin on Mac and /c/Program\ Files/Mozilla\ Firefox/firefox.exe on Windows.

Epilogue

Once I got the harness working for jsmodules, I backported it to Weave, where it mostly works, except that IWeaveCrypto.generateKeypair (which calls PK11_GenerateKeyPair) fails, apparently because of SEC_ERROR_TOKEN_NOT_LOGGED_IN.

I have no idea why, but if you do, comment here or in bug 476539, since that's the last blocker to Weave using this harness.