Friday, April 27, 2012

Wakanda and Freshbooks Integration Libary






Background


Freshbooks is a cloud based way to track time, organize expenses and invoice clients.

Wakanda is an open and complete solution for all your Web and mobile business apps utilizing a NoSQL OODB and MDA.

This blog entry is to demonstrate how to integrate Freshbooks with Wakanda via Server Side JavaScript.


This demo shows:


  1. how to find the URL and Token on the Freshbook site
  2. how to convert the Token to base64 with a dummy password
  3. how to organize the SSJS following CommonJS patterns so the code can be 'require'd
  4. how to write OO javascript with inheritance and constructors - a good design pattern from John Resig
  5. how to extend OO classes and add additional behavior
  6. how to use XMLHttpRequest with Security to authenticate with Freshbooks
  7. how to parse the Freshbooks results Server Side
  8. how to process the returned responseText to XML and to JSON
  9. how to extract object instance data from the JSON object
  10. how to write a SSJS script and run it
  11. how to incorporate Unit Testing w/ assert

Video

Here is a video showing the test script and a very brief introduction to the library:  http://www.youtube.com/watch?v=pBGnrgs1ykI

Source code

https://github.com/bartonhammond/Wakanda-Freshbooks

The steps to follow are these:


1.  Create a new solution in Wakanda
2.  Unzip from https://github.com/bartonhammond/Wakanda-Freshbooks into the project
     a. it should look something like (note that modules and scripts are at level w/ WebFolder)



3. Create an account in Freshbooks
4. Login and choose myAccount -> Freshbooks API

5. Copy the URL and Token and edit scripts/play.js 
    a. You don't really need the Token in play.js but it's good to have it available (maybe?)
6. Go to http://www.motobit.com/util/base64-decoder-encoder.asp and enter the Token followed by ':X' which is the password.  See http://developers.freshbooks.com/authentication-2/#TokenBased then click the "Convert the source data' button:


7. Copy the Base64 representation into the scripts/play.js overwriting the current value.
8. Open scripts/play.js and do Run File

Comments about the modules/freshbooks classes.

1. modules/freshbooks/base.js is a class provided by John Resig from http://ejohn.org/blog/simple-javascript-inheritance/ - please read that first to understand the following
2. modules/freshbooks/HttpClient










Line 4 shows how to require the base class called base which is a Class.

Line 5 is HttpClient extending the Class base class.

The init function is provided the URL and base64 hash that is required by the Freshbooks API. They are made class attributes. A new instance of XMLHttpRequest is made each time. One thing to note is that this is an implementation that does not have the responseXML attribute available. We'll see the effect of that later.

The getTagXML is a small function I borrowed from . If you're thinking of extending what I implemented you should be aware of this library as there are some good ideas that I somewhat borrowed from. Note that it's a client side solution.

The send function Posts the data passed into the URL provided. Note that the Authorization is using the base64 hash. On line 36 the responseText is processed and then parsed on the next line to provide a Javascript object. This object will be utilized in the getData function below. Note that on 39 a check if the status of the response from Freshbooks failed. This will occur, for example, when you try to read a Client with an invalid client id. During the video this is what occurs after the destroy method (see below).

The getData function helps to extract the data from the dataObj. You should stop the debugger and look into this.dataObj to see how the data is formatted.

Line 59 shows how to export the definition using the standard CommonJS modules pattern. The reference to the HttpClient is made by the next class, Client.

/**
 * Main XMLHttpRequest wrapper
 */
var base=require('./Class').Class;
var HttpClient = Class.extend({
    //Constructor
    init: function(url, base64 ){
        this.url = url;
        this.base64 = base64;
        this.xhr = new XMLHttpRequest();
        //The parsed response for subclassees
        this.dataObj = {};
    },
    //Helper functin to create XML tag
    //returns value
    getTagXML : function(tag,value){
 if(value == "" || value == null)
  return "";
  
 var result = "<" + tag + ">" + value 
              + "";
 return result;
    },
    //Send the request and parse the respone
    send: function(data) {
        console.log('send: ' + data);
        this.xhr.open('POST', this.url, false);
        this.xhr.setRequestHeader("Content-type", 
           "application/x-www-form-urlencoded");
        this.xhr.setRequestHeader("Authorization", 
           "Basic " + this.base64);
        this.xhr.send(data);

        //Get the response and make a dataobj for 
        //subclasses to parse
        var jsonText = XmlToJSON(this.xhr.responseText,
            "json-bag","response");
        this.dataObj = JSON.parse(jsonText);
        if (this.dataObj.status === 'fail') {
            throw new Error(this.getData('error'));
        }
    },
    getData: function(element0, element1) {
        console.log('getData : ' + element0 + ':' + element1);
        if (typeof element0 === 'undefined') {
            return '';
        }
        if (typeof element0 !== 'undefined' &&
            typeof element1 !== 'undefined') {
            return this.dataObj[element0][0][element1][0].__CDATA;
        }
        
        return this.dataObj[element0][0].__CDATA;

    }
    
});

exports.HttpClient = HttpClient;
Lines 4-5 require the necessary base classes.

Line 7 extends the HttpClient

The init function calls the super class (HttpClient) with the two parameters. It then initializes the only 3 attributes this library is addressing. Obviously this would need to be addressed to be fully functional though the design pattern may remain the same.

The setAllData function calls a helper function to get the element data from the xml.

Now the CRUD functions.

The create function creates the request content and calls the base class send. When the send returns, the data is set into the instance by calling setAllData.

The update function sends the request with the available data that this class supports. Note that the response is only a status signaling if the update occurred.

The destroy method prepares the request and calls the base class to send the content. Again, the response is a status.

/**
 * Client 
 */
var base = require('./Class').Class;
var HttpClient = require('./HttpClient').HttpClient;

var Client = HttpClient.extend({
    /**
     * called by constructor
     */
    init: function(url, base64) {
        this._super(url, base64);
        this.client_id = "";
        this.email = "";
        this.username = "";
    },
    /**
     * Common set data method
     */
    setAllData: function() {
        this.client_id = this.getData('client','client_id');
        this.email = this.getData('client', 'email');
        this.username = this.getData('client', 'username');
    },
    /**
     * Create instance w/ email provided
     */
    create: function() {
        var content = '' 
         + this.getTagXML('client', 
                      this.getTagXML('email', this.email)) 
         + '';
        this.send(content);
        this.client_id = this.getData('client_id');
    },
    /**
     * Read instance and set all available data
     */
    read: function() {
        var content = '' 
            + this.getTagXML('client_id', this.client_id) 
            + '';
        this.send(content);
        this.setAllData();
    },
    update: function() {
        var content = '' 
            + this.getTagXML('client', 
                 this.getTagXML('client_id', this.client_id) 
                 + this.getTagXML('email', this.email) 
          + this.getTagXML('username', this.username)) 
            + '';
        this.send(content);
    },
    /**
     * Can't use reserved word 'delete'
     */
    destroy: function() {
        var content = '' 
            + this.getTagXML('client_id', this.client_id) 
            + '';
        this.send(content);
        
    }
});

exports.Client = Client;


I believe the scripts/play.js is rather simplistic and therefore is not included here. 


 Enjoy!

No comments:

Post a Comment