Lazy Registration - Ajax Patterns

Lazy Registration

From Ajax Patterns

Evidence: 1/3

Tags: Account Authentication Customisation Customization Incremental Login Password Personalisation Personalization Profiling Registration User Verification



Contents

In A Blink

User interacts with E-Commerce site, then logs in later.


Goal Story

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 into his profile and tweaks some of those genre preferences, but leaves the location alone as that was correctly guessed. 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.


Problem

How can the user customize the site, while deferring formal registration?


Forces

  • 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.
  • Users may be willing to provide some information about themselves in order to access a service, but not all the information the system can potentially store.


Solution

Accumulate bits of information about the user as they interact, with formal registration occurring 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 a structure with lots of holes. As the user interacts with the system, some parts of the structure are filled in automatically and others are added by the user themselves. The user is also free to correct any of the filled-in data at any time.

Two particularly notable "holes" are a unique user identifier and a password. It is this combination of attributes that allows the user to access the profile afterwards 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 the 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, if you use a little trick: use an email address as the unique identifier. In fact, this is fairly 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: 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

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 haven't heard of yet. That possibility, in turn, encourages them to add even more links, so as to avoid seeing known resources appear. Admittedly, del.icio.us is a rather techno-centric community at this stage, but there is also evidence of this approach further afield, as discussed in the real-world examples.

Now, it's clear that some systems 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. So you want to sign up for the website's mailing list? Great, the summary profile is already on the side menu, just make sure your email's right. See what happened? 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 it's great for users too.

If some of these techniques seem underhanded, you should be aware that they are manifestations of the technology at hand. It's standard practice for websites to collect data about users. The pattern here is actually about empowering users—instead of secretly building up a corpus of data on a user, you invite them to add value to their own experience by contributing the data themselves.

The following technologies are involved in implementing Lazy Registration:

  • Database: You clearly need a persistent data store in order to capture user profiles and associate them with the session ID that's stored.
  • XMLHttpRequest: Judicious use of XMLHttpRequest Calls are the key to the smooth interaction mode which you are seeking to achieve with this pattern.
  • Cookie Manipulation and Session Tracking: 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 Calls.


Decisions

What kind of data will the profile contain?

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 applications 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.

No question about it, many companies like to accumulate as much data as they can get their hands on. Especially so in an economy where storage is trivially cheap. When a company is acquired, you can see tangible evidence that such data is worth something to someone. It's naïve to assume that usability concerns are all that drive data collection, however, it's important to be aware that laws and regulations apply, as should ethical standards.

How can the profile be accumulated?

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 imaginitive way to gain user data is to pay them for it, or more deviously, pay others for it. Giving away a CD in exchange for data was well and dandy during the dotcom boom, but you'll have to do better than that today. Give the user a service they really need. For example:

  • If you want the user to provide their address, provide localised search features.
  • If you want the user to rate your product, provide recommendations based on their ratings.

How much data should be stored in cookies?

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 in the browser's local state, optimising 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 Calls 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 example, the "I'm on a public terminal" option.)


Real-World Examples

Amazon.com

Amazon has recently begun incorporating Ajaxian techniques, but was relying on Lazy Registration a long time prior. 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.

Kayak

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

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 time you visit.

Madgex

View Madgex Lazy Registration Demo Madgex is a whitelabel job board platform provider, used on about 150 job boards world wide. The new version of their platform provides lazy registration facilities, allowing job seekers to set up email alerts, apply for jobs, save their CV and save their cover letter without having to set up a password. When they choose to register, all the collected information and settings are revealed to them.

Code Examples

Ajax Shop Demo

The Ajax Shop Demo illustrates the kind of user-interface described by this pattern.

Running the demo, you'll notice a few things:

  • After the page initially loads, everything runs smoothly within the browser.
  • You can add items to your cart before registering.
  • 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 appliction 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 actually registering yet.
  • 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.

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.

Here, I'll point out how a few of the features were achieved.

Retrieval of Categories and Items

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 its rendered locally in JavaScript.

Cart Management

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"])) {
   if ($_POST["command"] == "add") {
     $cart->add($_POST["item"]);
   } else if ($_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 ($contents as $itemName => $val) {
   echo "<item>";
   echo "<name>$itemName</name>";
   echo "<amount>$val</amount>";
   echo "</item>";
 }
 echo "</cart>";

In the browser, onCartResponse is registered to render the cart based on the resulting XML.

Mailing Cart Contents

Along with several other fields, the profile block contains the user's email. There is 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 itself, since this might not yet be in the user's profile. Just prior to sending the mail, the server retains the email as part of the user's session. (Again, this is a simplification, because retaining the email would not be necessary—or desirable—if the user was already logged in.)

 function mailCart() {
   ...
   
   $email = $_POST["email"];
   // Add mail to the profile - it's part of the lazy registration.
   $_SESSION['email'] = $email;
   
   ...
 }

After that, 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.

Tracking Favourite Categories

Firstly, there is 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");
    favouriteCategoryOption.text = favouriteCategoryOption.value = category;
    try {
      $("favouriteCategory").add(favouriteCategoryOption, null); // FF
    } catch(ex) {
      $("favouriteCategory").add(category); // IE
    }
    
    ...
  }

There's also a JavaScript-based mode 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 cateogory is tracked according to how many times the item was explored, the selector being 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.

Verifying Password and Email

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. Refer to Gamma et al's (1995) State Pattern for more details on this approach.

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 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" id="password" name="password" />

    <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>
  </div>

All three buttons are declared, and again, their visibility will change depending on current state.

  <input type="button" id="login" value="Login" />
  <input type="button" id="register" value="Register" />
  <input type="button" id="cancel" value="Cancel" />

The login button is only for demonstration purposes and the cancel button resumes the state back to "start", which causes the display to return to its initial state, too. What's most important here is the "Register" button, which drives the process through each state. While the button's label is changed to reflect the action being performed at each state, it is always present until the user is verified. For this reason, its event handler remains the same 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") {
     ...
 }

An alternative design might actually manipulate the DOM itself, adding and removing the elements. Or it might generate the specific HTML for the form at each stage. However, relying on the state pattern for this process makes it easy to quickly alter the flow and appearance of the registration process.


Alternatives

Related Patterns

Direct Login

Direct Login is a companion pattern, since some dynamic behaviour can allow for login and registration to appear on the same form.

Live Form

It's useful to maintain the profile details in a Live Form, so the user can easily add to them and the server can synchronise state and provide opportunities for further enhancement to the profile.

Heartbeat

Where data is held in cookies, it's a good idea to expire them if there is a risk that others may gain access to the browser. Heartbeat helps the server decide if the client is actually active. If not, it may be wise to ensure any sensitive data is wiped from cookies held in the browser.

Guesstimate

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.

Visual Metaphor

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.)

Want to Know More?

External Link

Acknowledgements

The idea to handle lazy registration in this Ajaxian manner was originally proposed by Chris Were ("Tahpot").

Other Links: da Vinci Lawsuits Blog