
Account, Authentication, Customisation, Customization, Incremental, Login, Password, Personalisation, Personalization, Profiling, Registration, User, Verification
It's Saturday afternoon, and Stuart is busy planning the evening's activities. He visits a band listings website, where he clicks on a map to zoom into his local area. Even though he's never visited the site before, a profile is already being constructed, which he can see on the top of the site. At this stage, the profile guesses at his location based on his actions so far. As he browses some of the jazz bands, the profile starts to show an increasing preference for jazz bands, and some of the ads reflect that. Since the jazz thing is a one-off idea, he goes in to his profile and tweaks some of those genre preferences, but leaves the location alone as the system's guess was correct. Finally, he decides to make a booking, at which point he establishes a password for future access to the same profile and also his address, which is posted back to the profile.
Public websites thrive on registered users. Registered users receive personalised content, which means the website is able to deliver greater value per user. And registered users can also receive more focused advertising material.
For non-public websites, such as extranets used by external customers, registration may be a necessity.
Most users don't like giving their personal information to a web server. They have concerns about their own privacy and the security of the information. Furthermore, the registration process is often time-consuming.
Many users spend time familiarising themselves with a site before registering. In some cases, a user might interact with a site for years before formally establishing an account. There is a lot of valuable information that can be gained from this interaction, which will benefit both the website owner and the user.
Accumulate bits of information on the user as they interact while deferring formal registration until later on. As soon as the user visits the website, a user account is immediately created for them, with auto-generated ID, and set in a cookie which will remain in the browser. It doesn't matter if they never return - unused IDs can be cleared after a few months.
As the user interacts with the application, the account accumulates data. In many cases, the data is explicitly contributed by the user, and it's advisable to expose this kind of information so the user can actually populate it. In this way, the initial profile may be seen as structure with lots of holes. Some holes are eventually filled out automatically and others by the user themselves. The user is also free to correct any of the filled-in data at any time (Figure 1.86, “User Profile”).
Two particularly notable "holes" are unique user identifier and a password. It is this combination of attributes that allows the user to access the profile after from another machine or a different browser. They will also allow the profile to survive any deletion of cookies in the user's browser. So, while this pattern is generally about gradual accumulation of profile data, there remains a significant milestone in the user-application relationship, the moment at which user ID and password are established.
Do the user ID and password have to be provided simultaneously? No, even that can be incremental, as long as you make the email address the unique identifier. In fact, this is pretty common nowadays. Email is usually required anyway, and it's unique, so why not make it the user ID? In the context of Lazy Registration, though, there's an additional benefit, as the email might be accumulated in the natural flow of events - the site might add the user to an announcements list, for example. In some cases, the email might even be verified during this process.
Sceptics may wonder why a user would want to actively work with their profile. The answer was formulated in a web usability pattern called "Carrot and a Stick":
Determine what users consider to be a "valuable" carrot. Offer the end user a portion of that carrot before you request personal information. The content is withheld ("the stick") until the requested information is provided.
Thus, users will only enter information if there is a perceived benefit to them. There is plenty of evidence this occurs - witness the social bookmarking phenomenon, where thousands of users make public their personal links. By exposing their profile, many of those users are hoping the system will point them in the direction of related resources they have not yet heard of.
Some websites have used this pattern for years, so what does it have to do with Ajax? “Lazy Registration” aims for a smooth approach, where the barrier is low for each new user contribution. For instance, you sign up for a website's mailing list, and your email is automatically added to your profile and shown on the side of the page. With Ajax, there's no need to break the flow. No more "just go over there for a few minutes, then come back here, and if you're lucky, you might be looking at something similar to what you can see now". That's a big win for websites aiming to drop the barrier of registration and great for users too.
It's standard practice for websites to collect data about users, and the aim of this pattern is to empowering them to contribute to this - instead of covertly building up a corpus of data on a user, you invite them to add value to their own experience by contributing and maintaining the data themselves [17].
Several technologies are involved in “Lazy Registration”.
You clearly need a persistent data store in order to retain user profiles.
Passing profile information back and forth with “XMLHttpRequest Call”s is the key to the smooth interaction mode which you are seeking to achieve with this pattern.
A cookie must reside in the user's browser, associated with your domain and identifying a unique session ID, which can serve as a key on the server-side to locate details about the user each time they access the website. In conventional applications, the cookie is pushed from the browser as a header in the response. That's fine for Ajaxian “Lazy Registration” when the user first accesses the system, though sometimes it may be convenient to use a more Ajax-oriented approach. The first such approach is to manipulate the cookie in Javascript. The second is to set the cookie using the response from an “XMLHttpRequest Call”, which all browsers are apparently happy to deal with in the same way as they deal with cookies in regular page reloads. In practice, you're unlikely to be playing with cookies anyway. Most modern environments contain session-tracking frameworks that do the low-level cookie manipulation for you (see also “Direct Login”). They generally use either URL-rewriting or cookie manipulation, and you need the latter to make this pattern work most effectively. Since responses from XMLHttpRequest set cookies in the same way as entire page reloads, you should be able to change session data while servicing “XMLHttpRequest Call”s.
Usability and functionality concerns will drive decisions on what data is accumulated. By envisioning how users will interact with the website, you can decide what kind of data must be there to support the interaction. For example:
Do you want to provide localised ads? You'll need to know where the user lives.
Do you want to use collaborative filtering? You'll need to capture the user's preferences.
In addition, consider that some Ajax Apps are used all day long by users, such as employees working on an intranet website. For that reason, the profile might also contain preferences similar to those on conventional desktop applications. Many options will be application-specific, but a few generic examples include:
Enabling and disabling visual effects, such as “One-Second Spotlight”.
Setting “Heartbeat”-related parameters, e.g. how long will timeout be, will the system prompt the user when it's coming up.
Customising “Status Area” display.
One issue that arises with Lazy Registration is clearing of data. What if a user visits once and never comes back? You probably don't want to keep that data sitting there forever. Typically, you will probably have a script running daily to delete (or archive) user records whose last login was, say three months ago.
You might know what data you need, but are users willing to give it to you? This comes back to the carrot-and-stick argument: you need to provide users a service that will make it worthwhile for them to provide that data. In addition, you need to communicate the benefit, and you must be able to assure them that the data will be safe and secure.
The least imaginative way to gain user data is to pay them for it, or, more deviously, pay others for it. Giving away a T-Shirt in exchange for data was fine during the dotcom boom, but hopefully you can do better than that. Give the user a service they really need. For example:
If you want the user to provide their email, offer to send email notifications.
If you want the user to provide an ID and password, help them understand the benefits: they can log in from anywhere and the data will survive a hard drive crash.
If you want the user to provide their physical address, provide localised search features.
If you want the user to rate your product, provide recommendations based on their ratings.
How much you store in cookies depends on your general approach to the Ajax implementation: is the application browser-centric or server-centric? A browser-centric choice would be to pack as much as possible to the browser's local state, so as to optimise performance while running a full-fledged Javascript application, with a little server-side synchronisation. A server-centric approach would rely only on data held server-side, with the browser accessing additional data using “XMLHttpRequest Call”s, on a need-to-know basis.
One special concern is the security of cookies. If users access the application from a public PC, there's the risk of unauthorised access. In this case, it's especially advisable not to store sensitive information in the browser, and offer the possibility of cleaning cookies at the end of the session. (For instance, call the option "I'm on a public terminal".)
Steve Lacey's MemeFlow is a portal with RSS-backed “Portlet”s and its use of Lazy Registration is characteristic of several other Portals (Figure 1.87, “Memeflow”). You can immediately build up a collection of your favourite feeds and when you provide your username and password later on, those feeds will remain.
Alexander Kirk's Blummy is a bookmarklet manager (Figure 1.88, “Blummy”). You can start adding bookmarklets to a personal "Blummy" container straightaway, which has a unique URL. When you register, you'll get a URL with your own name, but the old URL remains valid, so you can keep the bookmark you created before registration.
Kayak is a travel search engine which retains queries you've made. A query is history is available for non-registered users, and it becomes part of your profile once registered.
Palmsphere showcases Palm applications for download and purchase. Each item has a "Favourite" button - if checked, the item is one of your "Favourites". The Favourites list is summarised in your "Member Center" area, even if you've never registered, and retained in a cookie for the next you visit.
Amazon has begun incorporating Ajax features only recently, but blazed the trail for Lazy Registration a long time ago. Visit Amazon as a new user, browse for just a few seconds, and here's what you'll see before even beginning to register or log in:
Shopping cart, which you can add items to.
Recently Viewed Items.
Page You Made - showing recent views and books related to those.
Ability to update your history, by deleting items you viewed.
Ability to turn off "Page You Made".
Ability to search for a friend's public wishlist.
The Ajax Shop Demo illustrates the kind of user-interface described by this pattern (Figure 1.89, “Ajax Shop Demo”).
Running the demo, you'll notice a few things:
You can add items to your cart right away.
The application guesses your favourite category by watching what you're looking at. If you prefer, you can override the application's guess, and the application will no longer attempt to adjust it.
By offering you the ability to send the cart contents to your email address, the application provides an incentive to add your email to the profile, without yet formally registering.
The password and email verification process itself is unintrusive - the main flow of the site is uninterrupted. Even while you're waiting for verification mail, you can continue to play around with the main content. Figure 1.90, “Ajax Shop Demo with Email Address and Password Entered” shows the application when the user has gone as far as entering an email address and password.
To keep things simple, it doesn't actually use a persistent data store - all information is held in the session. That's definitely not advisable for a real system, because you don't want to store passwords and other sensitive data there. Also, it means the user in theory could bypass the email verification by inspecting the cookie. Nevertheless, the application demonstrates Lazy Registration from the user's perspective, and the underlying code provides some illustration of what's required to develop such an application.
Following are some of the features and how they were achieved.
The application maintains the flow by avoiding any page reloads when categories and items are accessed. No information about categories or items are hard-coded; generic REST services are used to extract the data and rendered locally in Javascript.
Again, the only real relevance of cart management is that page reloads are avoided. The cart contents are tracked in the session, so they should be present when the user resumes using the website. When the user adds something to the cart, the Javascript cart is not directly altered. Instead, the new item is posted to the server as XML:
function onAddItemClicked(item) {
var vars = {
command: 'add',
item: item
}
ajaxCaller.postForXML("cart.phtml", vars, onCartResponse);
}
Likewise, when the cart is cleared:
function onCartClearClicked() {
var vars = {
command: 'clear'
}
ajaxCaller.postForXML("cart.phtml", vars, onCartResponse);
}
For both operations, the server retrieves the session cart and alters its state:
$cart = $_SESSION['cart'];
if (isset($_POST["command"]) && $_POST["command"]=="add") {
$item = $_POST["item"];
$cart->add($item);
} else if (isset($_POST["command"]) && $_POST["command"]=="clear") {
$cart->clear();
}
Then, the server outputs the final state as an XML response:
header("Content-type: text/xml");
echo "<cart>";
$contents = $cart->getContents();
foreach (array_keys($contents) as $itemName) {
echo "<item>";
echo "<name>$itemName</name>";
echo "<amount>".$contents[$itemName]."</amount>";
echo "</item>";
}
echo "</cart>";
In the browser, onCartResponse is registered to render the cart based on the resulting XML.
Along with several other fields, the profile block contains the user's email. There's also a clickable "Mail" field on the cart:
<div>
<div class="userLabel">Email:</div>
<input type="text" id="email" name="email" />
</div>
...
<span id="cartMail">Mail Contents</span>
....
$("cartMail").onclick = onCartMailClicked;
When the user clicks on cartMail, the server checks that the email has been filled in, and simply uploads a POST message for the mail to occur. In this case, there's no feedback to the web user, so the callback function is blank:
vars = {
command: "mailCart",
email: email
}
ajaxCaller.postForPlainText("cart.phtml", vars, function() {});
The server receives not only the command, but the email address itself, since this might not yet be in the user's profile. Just prior to sending the mail, the server retains the address as part of the user's session[18]:
function mailCart() {
...
$email = $_POST["email"];
// Add mail to the profile - it's part of the lazy registration.
$_SESSION['email'] = $email;
...
}
Then, it's a simple matter of constructing a message from the server-side cart state, and sending the email to the specified address using standard server-side libraries.
There's a fixed "favourite category" selector in the HTML. It begins empty, and is populated when the categories are loaded:
<div id="favouriteCategoryInfo">
My Best Category: <select id="favouriteCategory"></select>
</div>
function onAllCategoriesResponse(xml, ignoredHeaders, ignoredContext) {
...
categoryExplores[category] = 0;
favouriteCategoryOption = document.createElement("option");
...
}
There's also a mode variable to indicate whether the favourite category selection is automated or not. It begins in automated mode:
var isFavouriteCategoryAutomated = true;
If in automated mode, the script watches each time the user explores an item. Each category is tracked according to how many times the item was explored, and the selector is altered if a new maximum is reached:
var categoryExplores = new Array();
...
function onExploreClicked(category) {
...
if (isFavouriteCategoryAutomated) {
categoryExplores[category]++;
favouriteCategory = $("favouriteCategory").value;
favouriteCategoryExplores = categoryExplores[favouriteCategory];
if (categoryExplores[category] > favouriteCategoryExplores) {
$("favouriteCategory").value = category;
}
}
}
As soon as the user decides to overwrite this guesstimate, by manually setting the preference, it stays manual forever:
$("favouriteCategory").onclick = function() {
isFavouriteCategoryAutomated = false;
}
For the sake of simplicity, this field is not actually tracked in the server, though it could easily be persisted as part of the user's profile.
Now for the most important part. The user's finally willing to verify password and email. These could potentially be broken into two separate verification activities, but they both fit together as a formal registration step, so they are combined in the demo.
The trick is to manage the process with a little state transition logic. The registration is broken into a few states, with transitions between them. Each state requires handling events in a slightly different way. Each transition involves altering the UI a little to reflect what the user can do.
registerState holds the current state:
/*
"start": When page is loaded
"mustSendMail": When instructions and verify password field shown
"mustVerifySecretNumber": When email sent and user must enter secret
number inside email
"verified": When user is successfully logged in
*/
var registerState = "start";
The html for this demo contains all the necessary fields and buttons. Their visibility is toggled based on the current state. For example, following are the password and password verification fields. The password field is always shown until the user is at "verified" stage, while the "verify password" field is only shown after the user initiates the registration process:
<div id="passwordInfo">
<div class="userLabel">Password:</div>
<input type="password"ael id="password" name="password"/>
</div>
<div id="verifyPasswordInfo">
<div id="regHeader">Demo Registration</div>
<div class="regInstructions">
<strong>1.</strong> Please ensure email address is correct and
password is <strong>not</strong> confidential, then verify your
password below.
</div>
<div class="userLabel">Verify Password:</div>
<input id="verifyPassword" type="password" name="verifyPassword" />
</div>
All three buttons are declared, and again their visibility will change depending on current state:
<input type="button" id="login" value="Login"></button>
<input type="button" id="register" value="Register"></button>
<input type="button" id="cancel" value="Cancel"></button>
What's most important here is the "Register" button, which drives the process through each state. The Cancel button resumes the state back to "start", which causes the display to return to its initial state too. The Login button is purely for demonstration purposes. The Register is present until the user is verified, and its label changes at each stage of the registration process. Its event handler remains the same throughout, and decides what to do based on the current state:
function onRegisterClicked() {
if (registerState=="start") {
registerState = "mustSendMail";
} else if (registerState=="mustSendMail") {
var submissionOK = sendMail();
if (submissionOK) {
registerState = "mustVerifySecretNumber";
} else {
return;
}
} else if (registerState=="mustVerifySecretNumber") {
verifySecretNumber();
}
onRegistrationStateChanged();
}
And onRegistrationStateChanged() purely exists to reveal and hide fields, and change the button label, based on the current state:
function onRegistrationStateChanged() {
if (registerState=="start") {
$("userForm").reset();
$("login").style.display = "inline";
$("verifyPasswordInfo").style.display = "none";
$("secretNumberInfo").style.display = "none";
$("verifiedInfo").style.display="none";
$("cancel").style.display = "none";
$("register").value="Register";
} else if (registerState=="mustSendMail") {
...
} else if (registerState=="mustVerifySecretNumber") {
...
} else if (registerState=="verified") {
...
}
“Direct Login” is a companion pattern, since some dynamic behaviour can allow for login and registration appear on the same form.
It's useful to maintain the profile details in a “Live Form”, so the user can easily add to them, the server can synchronise state, and provide opportunities for further enhancement to the profile.
When data is held in cookies, it's important to expire them if there's a risk that others may gain access to the browser. “Timeout” helps the server decide if the client is still really active. If not, it may be wise to ensure any sensitive data is wiped from cookies held in the browser.
Lazy Registration can sometimes involve inferring information about the user's profile by monitoring their behaviour. Thus, it embraces the same fuzzy principles as “Guesstimate”, where a guess is acknowledged to be imprecise, but better than no guess.
A good salesman works the same way. While assumptions might be made based on a prospect's behavior, the salesman is always listening and those assumptions are open to challenge. (Malcolm Gladwell detailed this pattern of successful salespeople in Blink.)
The idea to handle Lazy Registration in this Ajaxian manner was originally proposed by Chris Were ("Tahpot").
[17] The Attention Trust is an organisation which promotes this idea.
[18] Again, this is a simplification, because retaining the address would not be necessary - or desirable - if the user was already logged in.