Sign in Best Practices: Reflecting the User

In the recent Google+ Developers Live episode: Google+ Sign-In Best Practices: Greeting the user and reflecting the profile, I showed you a few tweaks that you can make to your site’s experience to make it more engaging. This blog post covers this topic and has more details on what is going on in the code running behind the scenes.

The setup: Is too cute!

The demo site that I have created is istoocute.com, the following image shows how it  looks:

istoocute_preview

If you take a glance at the site layout, I have done a few things of note:

  • The Google+ Sign-in button is placed in the top right
  • I’m inviting the user to sign in with a message on the left

I’m doing this because I really want the user to sign in. Once the user signs in, various personal touches can be added to the site such as that person’s connections that they have shared with this app, their public profile information, and content they have shared. Another detail you may have noticed is that I’m also following the common convention of placing the button the top right corner. I’m doing this because it is one of the hot spots for a user’s visual attention.

Let’s take a quick look at the relevant HTML code in index.html for the area that changes when the user signs in, the user bar.

  <div class="userbar">
    <span id="signin">
     <span id="cuteMessage">Hey, lover of cuteness! Sign in with Google+ to
         improve your experience.</span>
     <span id="gConnect">
        <span class="g-signin"
            data-scope="https://www.googleapis.com/auth/plus.login"
            data-requestvisibleactions="http://schemas.google.com/AddActivity"
            data-clientId="xxxxxxxxxxxx.apps.googleusercontent.com"
            data-callback="onSigninCallback"
            data-theme="dark"
            data-cookiepolicy="single_host_origin">
        </span>
      </span>
      <span id="profileArea" style="display:none"></span>
      <span id="authButtons" class= style="display:none">
        <img id="disconnect" height="25px" src="images/disconnect.png" alt="Disconnect"
            title="Disconnect" onClick="helper.showDisconnectDialog()"
            ></img>
      </span>
    </span>
  </div>

This is basic code that you may have seen before in the Google+ Sign-In documentation. The boilerplate pattern is:

  • The Google+ Sign-In button is configured with a client id from the Google APIs console.
  • There is an HTML container around the target container. This is done because the Google+ API will override information about the container and you will not be able to reference it.
  • There is a set of elements that are hidden or empty until the user is signed in.

The Google APIs are loaded asynchronously with the following code in index.html:

/**
  * Load the Google+ JavaScript client libraries.
  */
  (function() {
    var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
    po.src = 'http://apis.google.com/js/auth:plusone.js?onload=startApp';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
  })();

When this code loads, elements on the page get rendered by the script.  Also, the gapi object becomes visible to the JavaScript global context so that it can be used later to perform Google+ API calls.

A good (albeit basic!) sign in experience

Now, we have a basic site where clicking the button will prompt the user to sign in. Once a user signs in, they are presented with a screen such as the following:

istoocute_basic_signedin

When the user signs in, the following events occur:

  • The message inviting the user is replaced with basic information about the user
  • The Google+ Sign-In button is replaced with a disconnect button

This is a good start! The steps that are performed here indicate to the user that they no longer need to sign in and now any features that are relevant for a signed in user appear. This change in the site is visible to the user and the clear connection between the site and the user has begun. In taking these steps, the site is also doing the right thing in accordance with the Google+ developer policies such as:

The Google+ Sign-In button code references a callback method, onSigninCallback, which is implemented globally in index.html as:

/**
  * Global callback handler for signin
  *
  * This function passes the data from the Google+ Sign-In callback
  * to the helper module.
  *
  * @param result Stores information about the result from the Google+
  *        Sign-In button such as the OAuth v2 access token.
  */
  function onSigninCallback(result){
    helper.onSigninCallback(result);
  }

In order to use the JavaScript module pattern, this global callback then forwards the information from the callback to a module, helper. The helper implementation for the callback in app.js is:

    /**
     * Hides the sign in button and starts the post-authorization operations.
     *
     * @param {Object} authResult An Object which contains the access token and
     *   other authentication information.
     */
    onSigninCallback: function(authResult) {
        if (authResult['access_token']) {
          console.log("Result from sign-in:");
          console.log(authResult);

          // Setup the base url for API calls
          helper.authResult = authResult;

          // Success.
          // Hide the sign-in button
          $('#gConnect').hide();

          // Reflect the user profile
          gapi.client.load('plus','v1', function(){
            helper.connect();
          });
        } else if (authResult['error']) {
          // You can handle various error conditions here
        }
    },

Information about the response is logged, the Google+ Sign-In button is hidden, and then the connect method is called. The connect method is where you could then connect the user with a server-side component of your application, but for now we will just be working client-side. What the connect method does now is just update the UI components to reflect the user is connected. The following code shows how it’s done in app.js:

    /**
     * Calls the /api/connect endpoint to connect the user with the server.
     *
     * @param {Object} token An Object which contains the access token and
     *   other authentication information.
     */
    connect: function(){
      gapi.client.plus.people.get({userId:'me'}).execute(
        function(result) {
          helper.user = result;

          $('#profileArea').hide();
          $('#cuteMessage').hide();
          $('#profileArea').html('Signed in as ' + helper.user.displayName + '!')
          $('#authButtons').show();
        }
      );
    },

The profile area is populated with the user information, the welcome message is hidden, and then the buttons that are relevant for an authorized user render.

So this is a good experience, let’s think about ways that we can tweak it and make it better!

Making it better: Using the profile to reflect the user

At this point, you are retrieving all of the user’s public profile information from Google+. This is great because you can now use those details to draw the user into your site experience more. Using the JavaScript console, you can get more information about the user’s profile object. The following JSON object is an example of a profile:

"{"kind":"plus#person","etag":""G6azxXlXkwWbV_x-oI3I6szOTWs/yTJJYwMEsQGhNwSGWKYTzgSP4rU"","nickname":"Gus","gender":"male","urls":[{"value":"http://twitter.com/gguuss","label":"gguuss"},{"value":"https://www.facebook.com/gclassy","label":"gclassy"},{"value":"http://soundcloud.com/yourfriendgus","label":"yourfriendgus"},{"value":"http://blog.gusclass.com","label":"Help!  I'm trapped in a code factory!"}],"objectType":"person","id":"109716647623830091721","displayName":"Gus Class (Gus)","name":{"familyName":"Class","givenName":"Gus"},"tagline":"It's ok, everything breaks eventually.","braggingRights":"In the past I have created my own social network, file sharing network, web mail system, teleconferencing robots that are remotely controllable, embedded controllers for researchers, distributed systems, and own set of AIs for robots as well as the specific hardware for them.  I worked on Windows up through Windows 8, and do much more than just software engineering.","aboutMe":"I&#39;m a developer programs engineer for the Google+ platform.<div><br /></div><div>I program samples that demonstrate Google+ functionality in a variety of scenarios and platforms.</div><div><br /><div><div>Circle me to get information on how to drive users to your site using Google+ and develop on top of the Google+ platform.</div></div></div>","url":"http://plus.google.com/+GusClass","image":{"url":"http://lh4.googleusercontent.com/-m_WMJgYMNZc/AAAAAAAAAAI/AAAAAAAAQDQ/qtCmfkhAzIM/photo.jpg?sz=50"},"organizations":[{"name":"University of Washington Business School","title":"Business","type":"school","startDate":"2006","endDate":"2008","primary":false},{"name":"University of Washington","title":"Computers and Software Systems","type":"school","startDate":"1999","endDate":"2003","primary":false},{"name":"Reno High School","type":"school","startDate":"1996","endDate":"1999","primary":false},{"name":"Google","title":"Developer Advocate","type":"work","startDate":"2012","primary":true},{"name":"Microsoft","title":"Programmer/Writer/Evangelist","type":"work","startDate":"2005","endDate":"2012","primary":false},{"name":"Spam Arrest","title":"Developer","type":"work","startDate":"2003","endDate":"2005","primary":false}],"placesLived":[{"value":"San Francisco, CA","primary":true},{"value":"Reno, NV"},{"value":"Bellevue, WA"},{"value":"Reno, NV"},{"value":"Gainesville, Florida"},{"value":"Seattle, WA"},{"value":"Ensenada, Mexico"}],"isPlusUser":true,"language":"en","ageRange":{"min":21},"verified":true,"cover":{"layout":"banner","coverPhoto":{"url":"https://lh4.googleusercontent.com/-WQJjFLYeRCM/UW4epZAE70I/AAAAAAAAP1Q/Jj0JuHdREw8/s1000-fcrop64=1,239b0000ff9fbbc4/DSC01874.JPG","height":624,"width":940},"coverInfo":{"topImageOffset":0,"leftImageOffset":-113}},"result":{"kind":"plus#person","etag":""G6azxXlXkwWbV_x-oI3I6szOTWs/yTJJYwMEsQGhNwSGWKYTzgSP4rU"","nickname":"Gus","gender":"male","urls":[{"value":"http://twitter.com/gguuss","label":"gguuss"},{"value":"https://www.facebook.com/gclassy","label":"gclassy"},{"value":"http://soundcloud.com/yourfriendgus","label":"yourfriendgus"},{"value":"http://blog.gusclass.com","label":"Help!  I'm trapped in a code factory!"}],"objectType":"person","id":"109716647623830091721","displayName":"Gus Class (Gus)","name":{"familyName":"Class","givenName":"Gus"},"tagline":"It's ok, everything breaks eventually.","braggingRights":"In the past I have created my own social network, file sharing network, web mail system, teleconferencing robots that are remotely controllable, embedded controllers for researchers, distributed systems, and own set of AIs for robots as well as the specific hardware for them.  I worked on Windows up through Windows 8, and do much more than just software engineering.","aboutMe":"I&#39;m a developer programs engineer for the Google+ platform.<div><br /></div><div>I program samples that demonstrate Google+ functionality in a variety of scenarios and platforms.</div><div><br /><div><div>Circle me to get information on how to drive users to your site using Google+ and develop on top of the Google+ platform.</div></div></div>","url":"http://plus.google.com/+GusClass","image":{"url":"http://lh4.googleusercontent.com/-m_WMJgYMNZc/AAAAAAAAAAI/AAAAAAAAQDQ/qtCmfkhAzIM/photo.jpg?sz=50"},"organizations":[{"name":"University of Washington Business School","title":"Business","type":"school","startDate":"2006","endDate":"2008","primary":false},{"name":"University of Washington","title":"Computers and Software Systems","type":"school","startDate":"1999","endDate":"2003","primary":false},{"name":"Reno High School","type":"school","startDate":"1996","endDate":"1999","primary":false},{"name":"Google","title":"Developer Advocate","type":"work","startDate":"2012","primary":true},{"name":"Microsoft","title":"Programmer/Writer/Evangelist","type":"work","startDate":"2005","endDate":"2012","primary":false},{"name":"Spam Arrest","title":"Developer","type":"work","startDate":"2003","endDate":"2005","primary":false}],"placesLived":[{"value":"San Francisco, CA","primary":true},{"value":"Reno, NV"},{"value":"Bellevue, WA"},{"value":"Reno, NV"},{"value":"Gainesville, Florida"},{"value":"Seattle, WA"},{"value":"Ensenada, Mexico"}],"isPlusUser":true,"language":"en","ageRange":{"min":21},"verified":true,"cover":{"layout":"banner","coverPhoto":{"url":"https://lh4.googleusercontent.com/-WQJjFLYeRCM/UW4epZAE70I/AAAAAAAAP1Q/Jj0JuHdREw8/s1000-fcrop64=1,239b0000ff9fbbc4/DSC01874.JPG","height":624,"width":940},"coverInfo":{"topImageOffset":0,"leftImageOffset":-113}}}}"

 

From a quick glance at the available data, the most obvious choice for using this information to bring the user in is to show the user’s profile picture from Google+. Let’s update the code so that it now will add the profile picture when the user signs in.

In the code, another method in the helper module renders the HTML for the user. This method is implemented as follows in app.js:

    /**
     * Gets HTML markup representing a user's profile
     *
     * @param {Object} The user object.
     */
    getProfileHTML: function(user){
      var html = '<table><td><a target="_blank" href="' + user.url + '">' + '<img src="' +
          user.image.url + '" alt="' + user.displayName + '" title="' +
          user.displayName + '" height="35" />' + '</a></td><td>' + 'Signed in' +
          ' as:<br>' + user.displayName + '</td></tr></table>';
      return html;
    },

Pretty simple, right! You can now replace the code in connect where the user’s display name is used to instead insert this HTML. The full sources would be as follows:

 

    /**
     * Calls the /api/connect endpoint to connect the user with the server.
     *
     * @param {Object} token An Object which contains the access token and
     *   other authentication information.
     */
    connect: function(){
      gapi.client.plus.people.get({userId:'me'}).execute(
        function(result) {
          helper.user = result;
          $('#profileArea').hide();
          $('#cuteMessage').hide();

          // These lines were changed
          var html = helper.getProfileHTML(result);
          $('#profileArea').html(html);
          // ----------
          $('#profileArea').show();
          $('#authButtons').show();
        }
      );
    },

The relevant two lines of code are called out in the code, and you can see that the change is just a tiny tweak on the original code. When the site renders now, it looks as follows:

istoocute_better_signedin

The user’s profile is there and is reflected back to them. When this is done, the user knows that it is them who is connected with the site and that their picture and name are now a part of that site’s experience. This is still better because a familiar human face is there on the site to draw the user in. You can see this experience by opening the #better anchor tag on http://istoocute.com

Let’s look at a final way that you can improve the experience.

Making it awesome!

The final tweak that we are going to make is to animate the user’s profile when they sign in. You can see a demo of this using the #awesome anchor on http://istoocute.com.

Using motion is a great way to grab the user’s attention. In this particular case, I want to draw the user in more by bouncing the profile photo. I’ll call out that this specific animation would not be tasteful on every site but on my particular site, the effect works and it does it’s job of drawing the user’s attention to the fact that they are logged in.

Let’s look at the jQueryUI code I added to animate the profile in app.js.

          $('#profileArea' ).effect( 'bounce', null, 500, null);

This small tweak in the code is all it took to render that bounce effect which makes the social touches on the site much more noticeable.

Conclusions

Think about creative ways that you can make the most of the user’s information right when you sign them in. By making tweaks on what happens after the user signs in, you can really engage them and turn them from visitors into users. As demonstrated in the example, small changes can have a tremendous impact on what the user experiences when visiting your site.