Showing posts with label patterns. Show all posts
Showing posts with label patterns. Show all posts

Sunday, September 07, 2008

Love Buxfer. Use Twitter. So why not Twixfer?

Today I sat down to find out a different use for Javascript. Browsed through my everyday task list, found out a couple of candidates, and targeted Buxfer transaction creation. I'm also growing fond of Ubiquity, for the Firefox browser (and is now the most important reason I'm not moving to Google's Chrome, but I digress). This post, despite being about a Buxfer contribution, will be more about the Ubiquity development process I underwent.

So let me explain what I aimed for. Ubiquity allows to enter text controls, with auto completion in a HUD over the Firefox window. So I wanted to do something similar to what Bruno Lopes did with his BuxferSubmit Dashboard widget (for Mac OSX) - plain simple addition of a Buxfer transaction, with a description and an amount. All I have to do is (after having Ubiquity installed and running) to setup a Javascript file with a piece of code I'll explain hereafter.

The heart of a Ubiquity command is defined with the commandfunction CmdUtils.CreateCommand(argument-object). This is, in fact, the only call you have to make in that file to set things up. So what's in an argument literal object? the basic attributes, like the name of the command, it's description or the icon to identify it (quite useful!). But there are a few arguments that deserve special attention

  • execute. This must be a function with no arguments that is responsible for doing whatever the command is supposed to do after <return> is pressed.
  • preview. This is the function that is called at each key press on the Ubiquity HUD, to compute the string that appears to help the user find out what the command is going to do.
  • takes. A description of the words the command receives, and a type for them. Each type can be specified by hand, but several are already provided, like noun_arb_text for arbitrary text or noun_type_date for a date string.
  • modifiers. These are words that offer secondary semantics to the command, and are specified just like the “takes” argument elements. Think of examples like “list-birthdays” for today's birthdays and “list-birthdays for next week”. The modifier “for” would be assigned to “next week”, and a special type can be setup to parse the string and compute the date from today.

To find examples about how to use the CreateCommand, load the following URI in Firefox: chrome://ubiquity/content/builtincmds.js (obviously only after having ubiquity up and running).

That's about it. What I found out was that the presence of a preview functionality can often be more useful than the execution of the command! Think about a currency conversion, or an algebraic expression calculation. I don't want to perform anything upon a <return>, except perhaps have the result copied to the clipboard. What I really want is to have the result appear in the HUD and change dynamically as I keep filling in the command text.

Let's move on to the implementation itself. One thing I found out quickly was that the Buxfer's APi needed a username and password to be able to post a transaction. That would be the case in most web applications I know about. But this API doesn't offer, for now, any means to open a page to ask for credentials - nor would I desire that, since I'm typing a textual command to keep the input at a minimum! Ubiquity doesn't have a thorough documentation, yet, and browsing the javascript XUL files within the chrome “filesystem” of a Firefox add-on is not something I like to call user-friendly. Why is this important? I thought the command could take a peek into the Firefox's secured passwords and use the Buxfer, without the user having to care a bit about it. But then again, an exception would have to be made in order for first timers be able to use the command… I was about to set up a proxy web service in Google's App Engine when I remembered two happy coincidences:

  • Buxfer allows Twitter messages to be sent with the same syntax as those we can send from an API (or from a cell phone).
  • Twitter has a nice API, that does exactly what I was aiming for: it points it's requests to they're site, and if you're not logged in, it asks for your credentials using HTTP Basic Authentication. This way, Firefox aks you if it may remember the password, and actually does it :)

The result: I ended up with two commands, one using the Buxfer API, and another using Twitter's. But since Twitter's the most user friendly (easier and faster to use), I'm switching my transaction input to the brand new Twixfer (It's weekend, my imagination needs some more rest to come up with a better name!). Feel free to take a peak into the homepage. The source consists of a couple of files, so I thought a Google Code project would be over-killing it… But if this thing starts to get any sort of user input/contributions, I'll gladly move it there. For now, leave comments at the Twixfer site. As usual, feel free to leave feedback about anything.

[EDIT: Today I had a tip from a gentle soul about retrieving password from Firefox's session. So Buxfer API is usable by itself, now. Check the same link and update your command, if needed. It has lots of cleaning up and code re-factoring done. Nevertheless, it was fun to toy around with the Twixfer concept!]

Friday, February 01, 2008

User interface paradigms for data search and offline data access

I've stumbled upon this interesting and illustrated article on data lookup user interface design patterns (on both web and desktop applications, finally we seem to be starting to forget there's a difference!):

Here are two design paradigms for handling large amounts of data, not to be confused (or combined) as web design meets desktop in rich Internet applications.

[From Seek or Show: Two Design Paradigms for Lots of Data « Theresaneil’s Weblog]

The post describes two main groups of patterns, based on the way a user chooses what he/she wants to search (pictures linked from the article mentioned above):

  • The Seek Paradigm - The user has to input a search criteria in order to access some search results.
  • The Show Paradigm - The user is presented with several search results (or categories), and he may drill-down the search by clicking on the desired categories or selecting certain results to eliminate unwanted items.

This got me thinking in terms of offline work, and I can easily qualify both approaches as orthogonal to the online vs. offline decision. It is merely a user interface option, which is a good thing. To understand this, picture a lookup for book titles on a library. To go offline, one would have the client application to fetch them all from the server, thus making both kind of interface patterns possible without contacting the server. Similarly, if we wanted to know how many clients had already rented a book (assuming the algorithm implied a run through each client's details to look for their rental - I know, it's not the smartest way to do that, but it's only an illustrative example!), the client wouldn't be able to fetch all the other clients' details, hence not being able to use any of the search paradigms.

So the effort that has to be done, from the application logic design point of view, is related to how the services (those accessed by the user interface) access the information stored in the database. Let's draw some pseudo-code* for the book rental example above (the second example).

  • Approach 1: The all-powerful service. This is the kind of service we would have written up until a few months ago, considering any sort of offline execution to be an academic dream or an edgy unfeasible mostly unsuccessful experiment. Basically it assumes that it is being executed with super-user privileges, hence able to access all stored data at any time with no constraints whatsoever.
  • Aproach 2: The helpful, aware and conscious service. This is the service that knows that its code may be executed in many places, and so it tries to make a scarce usage of the stored data as much as it can.

Using approach 1 we could code a service like this:

(define-service get-number-of-book-rentals (book)
(let ((all-clients (get-storage-data "clients"))
(result 0))
(dolist (client all-clients)
(set result (+ result
(client-book-rentals client book))))
result))

As you can see, while this would work perfectly on the server, the client would never be able to to the (get-storage-data "clients")call, given its lack of permission to fetch them all to the client-side storage. This is the sort of services that must be thought over and rewritten - or, better yet, automatically translated to something more useful!

By using approach 2, we would instead code something in the lines of:

(define-service get-number-of-book-rentals
(:inputs (book)
:storage-data (books-rent-by-all-clients (get-total-rentals-from-clients)))
(let ((number-of-books 0))
(dolist (client-rentals (filter book-rent-by-all-clients
:field "book"
:value book))
(set number-of-books (+ number-of-books client-rentals)))))
number-of-books))

Here it's clear that the service knows what data is going to be needed from the storage. With this information, the server can fetch it for the client when it wants to disconnect, and the client is now able to perform the same functionality without having to run through the clients (read, without having to have access to all clients' personal details). Relevant, noteworthy changes between the examples:

  • the service has to be declared with more information (like the storage-data requirements)
  • the storage data requirements code is always performed on the server. The client counterpart consists of a lookup on the client-side storage for the previously fetched information. This way there is no risk of information not being available.
  • If the service is to be run online, it also runs equally well, with no performance penalties.
  • There must be either a mentality change or a tool to translate services made with the approach 1 to the approach 2!

I'm already laying out some code to achieve something like this. Expect news in the following week or two.

*Disclaimer - despite being in a lisp pseudo-dialect, all examples could exist in the language of your choice. It's really just a matter of personal preference!