Wednesday, 20 August 2014

Google Drive and User Interfaces

I have explored a few ways of interacting with Google Drive files and folders for a simple document management system. The user-interface needs to present folders and files which represent real world artefacts (eg people , places). Business operations are initiated by the user making a selection on a folder or file name.

The Drive and Google Apps scripting features have developed over time and I have tried using


For me, using the Google Drive SDK  has proven the simplest to build into a responsive user interface and best performing for the user.



The UI Service approach provided an early introduction to interacting with Google Apps script but it is very limited in function and locks the user interface into the script. It carries all the overheads of generating the user interface at run time as well.

HTMLService does provide the familiarity of HTML/CSS/JS development but has limitations and performance impacts caused by CAJA.

Calling a Google Apps Script from the Google Drive user interface suits users who spend there time interacting with Drive directly but selection of action on folder/file becomes a multiclick operation and subsequent user interface has the limitations of HTMLService .

The filepicker provided for Google Drive is functional and has the advantage of being ready to use with good performance. However the pop-up iframe approach may not suit all design styles and customisation is limited. In particular, presenting the file/folder list initially random order slows down the user interaction.

The direct use of the Google Drive SDK has been simplified recently  by improved documentation of the authorisation process. I took the quickstart example and built an html page of links from folder names corresponding to people of interest. It is then simple to incorporate this into a comprehensive user interface for the business user.

Performance and flexibility is the key for this approach. I get a few hundred folder references generate in 3 seconds and can then have a js expert work in there usual toolset to develop responsive bootstrap, jQuery or whatever interface style is required.

Modifed Quickstart example.

<html>
  <head>
    <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
    <script type="text/javascript">
      var CLIENT_ID = '70v406.apps.googleusercontent.com';
      var SCOPES = 'https://www.googleapis.com/auth/drive';

      /**
       * Called when the client library is loaded to start the auth flow.
       */
      function handleClientLoad() {
          console.log('handleClientLoad:'+new Date().getTime())
        window.setTimeout(checkAuth, 1);
      }

      /**
       * Check if the current user has authorized the application.
       */
      function checkAuth() {
                    console.log('CheckAuth:'+new Date().getTime())
        gapi.auth.authorize(
            {'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': true},
            handleAuthResult);
      }

      /**
       * Called when authorization server replies.
       *
       * @param {Object} authResult Authorization result.
       
      
      */
      function handleAuthResult(authResult) {
                    console.log('handleAuthResult:'+new Date().getTime())
        var authButton = document.getElementById('authorizeButton');
        authButton.style.display = 'none';

        if (authResult && !authResult.error) {
          // Access token has been successfully retrieved, requests can be sent to the API.
          getItems();
        } else {
          // No access token could be retrieved, show the button to start the authorization flow.
          authButton.style.display = 'block';
          authButton.onclick = function() {
              gapi.auth.authorize(
                  {'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': false},
                  handleAuthResult);
          };
        }
      }

 /*  List folders based on a search
* specify the owning folder and mimetype = folder in the search parameter (q)
* set maxResults  (can do paged calls if actual results exceed 1000) 
 */
      
function getItems() {
    console.log('getItems:'+new Date().getTime())
    var start = new Date();
    var request = gapi.client.request({
      'path': 'drive/v2/files',
      'method': 'GET',
      'params': {
        'q': 'mimeType="application/vnd.google-apps.folder" and "0B90FGJizRd-gX25PTS1CTUF0eHM" in parents  and trashed = false',
       'maxResults': '400'
      }
     });
     request.execute(listItems);
     console.log("elapsed: "+(new Date()-start))
}
/* Sort the result returned from drive api into title order
*   insert html paragraph for each title

*/

function listItems(resp) {
              console.log('listitems:'+new Date().getTime())
    var start = new Date().getTime();
    var result = resp.items.sort(function(a, b){
     var nameA=a.title.toLowerCase(), nameB=b.title.toLowerCase()
     if (nameA < nameB) //sort string ascending
      return -1 
     if (nameA > nameB)
      return 1
     return 0 //default return value (no sorting)
    });

 console.log('items: '+result.length)
    var i = 0;
    var element = document.getElementById("div1");  // insertion point in html
      for (i=0;i<result.length;i++) {
          var para = document.createElement("p"); // create a link element in a para
          var aref = document.createElement("a");
          var node = document.createTextNode(result[i].title);
           aref.setAttribute('href',result[i].alternateLink); // link to driver folder
           aref.appendChild(node);
           para.appendChild(aref);
    
        element.appendChild(para);
      }
    console.log("elapsed: "+(new Date().getTime()-start));
}
    </script>
    <script type="text/javascript" src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
  </head>
  <body>
      <input type="button" id="authorizeButton" style="display: none" value="Authorize" />
  <h1>List</h1>
  <div id='div1'></div>
  </body>
</html>