API Docs for:
Show:

File: TBAWrapper.js

/**
 * Static class for accessing native functionality; the two publicly global variables exposed by this class and the
 * desktop version of the JS bridge are tbaWrapper and tbaWrapperJ; so don't use these variable names elsewhere in your code
 * Don't try accessing the tbaWrapperJ variable directly; always use tbaWrapper.<br /><br />
 * This class works in conjunction with a config.json file that handles certain things that need to be setup prior to the DOM being initialized.  The config file has the following attributes:<br />
 * - window_title: The title to be given to the window; this is only applicable to the desktop version of the wrapper & will show in the window bar<br />
 * - open_full_screen: A boolean as to whether this app should open in full screen mode: only applicable to desktop version of the wrapper<br />
 * - background_color: A hex code string representing the color to show during loading of the window: only applicable to desktop verstion of the wrapper<br />
 * - preferred_width: A number representing the number of pixels in width this window should consume; is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - preferred_height: A number represenging the number of pixels in height this window should consume: is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - max_width: A number represenging the number of pixels in width this window is allowed to resize to: is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - max_height: A number represenging the number of pixels in height this window is allowed to resize to: is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - min_width: A number represenging the number of pixels in width this window is allowed to shrink to: is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - min_height: A number represenging the number of pixels in height this window is allowed to shrink to: is superseeded by open_full_screen if true: only applicable to desktop verstion of the wrapper<br />
 * - redirect_listeners: Array of Objects with the following string keys: listen_for, redir-params.  When a url change is caught; it is sent back to index.html with the redir-params attached as GET data<br />
 * - registerForPushNotifications: Boolean, if true the following pushData array is taken into consideration: Only Applicable to iOS<br />
 * - pushData: Array with the following keys: registrationURL (String), the URL to send device id registration requests to.  postDataString (string), url encoded post data to be sent along with the registration request.  The post key deviceid is appended to the string automatically.  pushRegistrationTypes (Array), array of strings representing the types of noticiations this device wants to receive.  Can be "Badge", "Alert", "Sound", "NewsStandContentAvailability".  Typically will just be Alert and Badge<br /><br />*Note: if you want to control the time signs up for push and send custom data with the device id (e.g. user id after they've logged in) set the registerForPushNotifications config to false, and use the registerForCustomPushNotifications function instead
 * @class TBAWrapper
 * @constructor
 */
TBAWrapper = function (){
    //-------------------------Private Member variables; not designed to be read only----------------------------//
    var parent = this;
    this.isExecuting = false;
    this.isListingDirectory = false;
    this.apiIsExecuting = false;
    this.queryCallBack = null;
    this.lsCallBack = null;
    this.apiCallBack = null;
    this.apiErrorCallBack = null;
    this.downloadCallBack = null;
    this.downloadProgressCallBack = null;
    this.downloadErrorCallBack = null;
    this.fileName = null;
    this.unzipCallback = null;
    this.readStringCallback = null;
	/**
	 * This property can be used to discern if the wrapper is currently running as a desktop app
	 * @property isDesktop
	 * @type {Boolean}
	 * @default "the current execution environment"
	 */
	this.isDesktop = (navigator.userAgent.indexOf('JavaFX') > -1);
	this.queryQueue = [];
	this.ttsCompleteCallback = null;
	this.isSpeakingCallBack = null;
	/**
	 * This property can be used to discern if the wrapper is currently running as an iOS app
	 * @property isiOS
	 * @type {Boolean}
	 * @default "the current execution environment"
	 */
	this.isiOS = ((navigator.userAgent.indexOf('iPad') > -1) || (navigator.userAgent.indexOf('iPhone') > -1));
	/**
	 * This property can be used to discern if the wrapper is currently running as an android app
	 * @property isAndroid
	 * @type {Boolean}
	 * @default "the current execution environment"
	 */
	this.isAndroid = (!this.isiOS && !this.isDesktop);
	/**
	 * This property can be used to discern the version of iOS that is being run; 0 if not iOS
	 * @property iOSVersion
	 * @type {Number}
	 * @default 0
	 */
	this.iOSVersion = (this.isiOS) ? parseFloat(/(([0-9]){1,2}([_]){1}([0-9]){1}){1}/.exec(navigator.userAgent)[0].replace('_','.')) : 0;
	/**
	 * This property can be used to discern the version of Android that is being run; 0 if not Android
	 * @property androidVersion
	 * @type {Number}
	 * @default 0
	 */
	this.androidVersion = 0;
	this.languageCodeCallBack = null;
	this.isRecordingCallBack = null;
	this.playRecordingCallBack = null;
    this.uploadFilesCallBackProgress = null;
    this.uploadFilesCallBackError = null;
    this.isPortraitCallBack = null;
	this.getThumbsCallBack = null;
	this.getPhotoCallBack = null;
    this.createPDFCallBack = null;
	
    /**
     *
     *  Base64 encode / decode
     *  http://www.webtoolkit.info/
     *
     */
    this.Base64 = {
        // private property
        _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
        /**
		 * Function to base64 encode a string; can be useful for transmitting formatted text as GET or POST data
		 * @method Base64.encode
		 * @param {String} input - The string to be encoded
		 * @return {String} - The encoded string
		 */
        encode : function (input) {
            var output = "";
            var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
            var i = 0;
            input = parent.Base64._utf8_encode(input);
            while (i < input.length) {
                chr1 = input.charCodeAt(i++);
                chr2 = input.charCodeAt(i++);
                chr3 = input.charCodeAt(i++);
                enc1 = chr1 >> 2;
                enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                enc4 = chr3 & 63;
                if (isNaN(chr2)) {
                    enc3 = enc4 = 64;
                } else if (isNaN(chr3)) {
                    enc4 = 64;
                }
                output = output +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
            }
            return output;
        },
        
        /**
		 * Function to base64 decode a string; can be useful for transmitting formatted text as GET or POST data
		 * @method Base64.decode
		 * @param {String} input - The string to be decoded
		 * @return {String} - The decoded string
		 */
        decode : function (input) {
            var output = "";
            var chr1, chr2, chr3;
            var enc1, enc2, enc3, enc4;
            var i = 0;
            input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
            while (i < input.length) {
                enc1 = this._keyStr.indexOf(input.charAt(i++));
                enc2 = this._keyStr.indexOf(input.charAt(i++));
                enc3 = this._keyStr.indexOf(input.charAt(i++));
                enc4 = this._keyStr.indexOf(input.charAt(i++));
                chr1 = (enc1 << 2) | (enc2 >> 4);
                chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                chr3 = ((enc3 & 3) << 6) | enc4;
                output = output + String.fromCharCode(chr1);
                if (enc3 != 64) {
                    output = output + String.fromCharCode(chr2);
                }
                if (enc4 != 64) {
                    output = output + String.fromCharCode(chr3);
                }
            }
            output = parent.Base64._utf8_decode(output);
            return output;
        },
        // private method for UTF-8 encoding
        _utf8_encode : function (string) {
            string = ''+string;//in case a non string version is passed
            string = string.replace(/\r\n/g,"\n");
            var utftext = "";
            for (var n = 0; n < string.length; n++) {
                var c = string.charCodeAt(n);
                if (c < 128) {
                    utftext += String.fromCharCode(c);
                }else if((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                }else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
            }
            return utftext;
        },
        // private method for UTF-8 decoding
        _utf8_decode : function (utftext) {
            var string = "";
            var i = 0;
            var c = c1 = c2 = 0;
            while ( i < utftext.length ) {
                c = utftext.charCodeAt(i);
                if (c < 128) {
                    string += String.fromCharCode(c);
                    i++;
                }
                else if((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i+1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                }
                else {
                    c2 = utftext.charCodeAt(i+1);
                    c3 = utftext.charCodeAt(i+2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }
            }
            return string;
        }
    };
};

//-------------------------Public native hook member functions--------------------------//

/**
 * Function to get the current orientation of the device; helps in iOS landscape where the javascript screen.width/height still returns the portrait dimensions even if landscape
 * If you want to listen for orientation changes; you are encouraged to use an event listener like the one below in conjunction with this method:<br />
    window.onresize = function(event) {<br />
        &nbsp;&nbsp;tbaWrapper.currentOrientation(function(r){some change handler code...});<br />
    };<br />
 * @method currentOrientation
 * @param {Function} callBack - Function that accepts one String argument; Portrait, PortraitUpsideDown, LandscapeLeft, LandscapeRight
 */
TBAWrapper.prototype.currentOrientation = function(callBack){
    if(callBack){
        this.isPortraitCallBack = callBack;
    }
    if(this.isiOS){
        window.location = "currentorientation://://foo/bar?sid="+Math.random();
    }
};

/**
 * Function to dispatch the call back for the isPortrait function
 * @param response - Boolean
 */
TBAWrapper.prototype.currentOrientationResponse = function(response){
    if(this.isPortraitCallBack){
        this.isPortraitCallBack(response);
        this.isPortraitCallBack = null;
    }
};

/**
 * Executes an sql query to interface with sqlite - consult http://sqlite.org/docs.html docs for syntax specifics.  Subsequent queries are queued until the first one completes
 * @method executeSQLQuery
 * @param {String} query - (required), the query to be executed in SQL format
 * @param {Array} bindings - (optional), an array of values to bind to any question marks in the query; this method uses prepared statements and automatically escapes strings so as to avoid SQL injection; e.g.: insert into foo(bar) values(?); could be a query, which would require a binding array with one value to bind to the ? place holder
 * @param {Function} callBack - (optional), the function to be executed when the query result is retured; Function should receive only one argument; an array of objects representing the resultset.
 */
TBAWrapper.prototype.executeSQLQuery = function(query,bindings,callBack){
	this.queryQueue.push({
		query:query,
		bindings:bindings,
		callBack:callBack	
	});
	if(this.queryQueue.length == 1){
		this.nextQuery();
	};
};

TBAWrapper.prototype.nextQuery = function(){	
	var query = this.queryQueue[0].query;
	var bindings = this.queryQueue[0].bindings;	
    var st = '',i
    if(!this.isExecuting){
        if(bindings && bindings.length){
            st = '&bindings='+this.Base64.encode(this.array2json(bindings));
        }
        this.isExecuting = true;
		if(this.isDesktop){
			setTimeout('tbaWrapperJ.executeSQLQuery("'+this.Base64.encode(query)+'","'+this.Base64.encode(this.array2json(bindings))+'");',1);
		}else{
        	window.location = "sqlquery://foo/bar?sid="+Math.random()+"&query="+this.Base64.encode(query)+st;
		}
    }else{
        this.log('Error; you cannot run a query while another is executing');
    }
}; 

/**
 * Returns the JSON fromt the currently executing SQL Query
 * @param result - A JSON Array string; each array item will be a JSON Object representing a row of the results returned
 *          if the query wasn't a select statement; an empty array will be returned; All error information is printed through the xcode / terminal console...
 */
TBAWrapper.prototype.sqlQueryReturned = function(result){
    this.isExecuting = false;
	if(this.queryQueue[0].callBack){
   		this.queryQueue[0].callBack(this.jsonParse(result));
	};
	this.queryQueue.shift();
	if(this.queryQueue.length > 0){
		this.nextQuery();
	};
};

/**
 * Begins an SQLite transaction; wrapping a series of inserts / updates in a transaction has significant performance increases for sqlite; don't forget to end your transaction.
 * @method beginTransaction
 */
TBAWrapper.prototype.beginTransaction = function(){
	if(this.isDesktop){
		setTimeout('tbaWrapperJ.beginTransaction();',1);
	}else{
    	window.location = "begintransaction://foo/bar?sid="+Math.random();
	}
};

/**
 * Begins an SQLite transaction; wrapping a series of inserts / updates in a transaction has significant performance increases for sqlite; you must begin a transaction first
 * @method endTransaction
 */
TBAWrapper.prototype.endTransaction = function(){
	if(this.isDesktop){
		setTimeout('tbaWrapperJ.endTransaction();',1);
	}else{
    	window.location = "endtransaction://foo/bar?sid="+Math.random();
	}
};

/**
 * Downloads a file from the internet asynchronously per the specified argument. Progress updates are sent to a call back function.  Only 1 file can be downloading at a time
 * @method downloadFile
 * @param {String} url - (required), the absolute address of the external file to download
 * @param {Function} progressCallBack - (optional), a function with one decimal argument representing progress, and one string argument representing the file name; to execute when progress updates are received
 * @param {Function} completeCallBack - (optional), a function with one string argument representing the file name; to execute if the file download completes
 * @param {Function} errorCallBack - (optional), a function with two string arguments - one representing the file name; to execute if the download fails.  The other the status code for the failure - 0 means that the download failed after the initial response from the server.
 * @return {String} - A unique local file name with the same filetype extension of the file specified in the URL.
 */
TBAWrapper.prototype.downloadFile = function(url,progressCallBack,completeCallBack,errorCallBack){
    var f = new Date().getTime()+"_"+(Math.round(Math.random()*1000000))+url.substring(url.lastIndexOf("."));
    this.downloadProgressCallBack = progressCallBack;
    this.downloadCallBack = completeCallBack;
    this.downloadErrorCallBack = errorCallBack;
    this.fileName = f;
	if(this.isDesktop){
		tbaWrapperJ.downloadFile(this.Base64.encode(f), this.Base64.encode(url));
	}else{
    	window.location = "dwnld://foo/bar?handle="+this.Base64.encode(f)+"&url="+this.Base64.encode(url);
	}
    return f;
};

/**
 * Queries a web based api to retrieve the response from that API; using native code to do this solves the crossdomain origin issue with JS.  Queries are synchronous, hence this method can't be called again if the previous call hasn't returned yet.
 * @method queryAPI
 * @param {String} url - (required), the absolute address of the external API to query
 * @param {String} key - (required), a private key that can be sent to the remote server via POST - hence if its an https connection it will be secure; use 'none' if you don't want this feature
 * @param {String} value - (required), the value that will accompany the key; use 'none' if you don't want this feature
 * @param {Function} completeCallBack - (optional), a function that accepts one string argument to execute when API lookup is complete
 * @param {Function} errorCallBack - (optional), a function that accepts one integer argument containing the status code returned; a code of 0 means that the operation failed after the initial connection response
 */
TBAWrapper.prototype.queryAPI = function(url,key, value, completeCallBack, errorCallBack){
    if(!this.apiIsExecuting){
        this.apiIsExecuting = true;
        this.apiCallBack = completeCallBack;
        this.apiErrorCallBack = errorCallBack;
		if(this.isDesktop){
			tbaWrapperJ.queryAPI(this.Base64.encode(url),this.Base64.encode(key),this.Base64.encode(value));
		}else{
        	window.location = "queryapi://foo/bar?sid="+Math.random()+"&url="+this.Base64.encode(url)+"&key="+this.Base64.encode(key)+"&value="+this.Base64.encode(value);
		}
    }
};

/**
 * Returns the result from the current API call that is underway
 * @param result - String, the code response from the api call (e.g. same as choosing 'view source' in your browser window)
 */
TBAWrapper.prototype.apiReturned = function(result){
    this.apiIsExecuting = false;
    if(this.apiCallBack){
        this.apiCallBack(result);
    }
};

/**
 * A listener to report on failed API querries
 * @param status - int, the HTTP status code if available; otherwise 0  zero would probably denote that the datastream failed mid connection, rather than after the initial response from the server.
 */
TBAWrapper.prototype.apiQueryFailed = function(status){
    this.apiIsExecuting = false;
    if(this.apiErrorCallBack){
        this.apiErrorCallBack(status);
    }
};

/**
 * Can be used to update your interface with file download progress
 * @param progress - Number, decimal representing the fraction of currently downloaded data for the file currently downloading
 */
TBAWrapper.prototype.didReceiveFileProgressUpdate = function(progress){
    if(this.downloadProgressCallBack){
        this.downloadProgressCallBack(progress,this.fileName);
    }
};

/**
 * Listener to notify if a file has finished downloading
 * @param fileID - String, the string returned form the downloadFile funciton
 */
TBAWrapper.prototype.fileDidCompleteDownload = function(fileID){
    if(this.downloadCallBack){
        this.downloadCallBack(this.fileName);
        this.fileName = null;
    }
};

/**
 * Listener to notify if a file download failed
 * @param fileID - String, the string returned form the downloadFile funciton
 * @param status - int, the HTTP status code if available; otherwise 0  zero would probably denote that the datastream failed mid connection, rather than after the initial response from the server.
 */
TBAWrapper.prototype.fileDownloadFailed = function(fileID,status){
    if(this.downloadErrorCallBack){
        this.downloadErrorCallBack(this.fileName, status);
    }
};

/**
 * List directory contents
 * @method ls
 * @param {String} path - (required), the path relative to the documents directory for this app; '/' is the path for the root of the docs dir; everything else is relative to that.  Path cannot be an empty string; must be '/' at the very list in order to list the root; or for example /mydir/  to list the contents of a directory called 'mydir' within the documents directory.  All paths must contain a trailing /  (e.g. /mydir/ rather than /mydir)
 * @param {Function} callBack - (optional), a funciton to call that accepts an array of JSON objects as its only argument containing the file names, as well as a URL that can be used to access the file from within the HTML view. e.g. [{"name" : "file.jpg", "url" : "file://docsdir/file.jpg"},...]
 */
TBAWrapper.prototype.ls = function(path, callBack){
    if(!this.isListingDirectory){
        this.isListingDirectory = true;
        this.lsCallBack = callBack;
		if(this.isDesktop){
			tbaWrapperJ.ls(this.Base64.encode(path));
		}else{
        	window.location = "ls://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
		}
    }
};

/**
 * Returns the result from the currently executing directory listing
 * @param result - String, JSON formatted string in JSON array format with each file or dir name as well as a URL as to how you can access it from your html code; e.g. [{"name" : "file.jpg", "url" : "file://docsdir/file.jpg"},...]
 */
TBAWrapper.prototype.lsreturned = function(result){
    this.isListingDirectory = false;
    if(this.lsCallBack){
        this.lsCallBack(this.jsonParse(result));
    }
};

/**
 * Move a file from source to dest.  All files are stored within the documents directory of app; which in this case equates to '/'; src and dest paths must begin with '/'
 * @method mv
 * @param {String} src - (required), The path to the file that resides within the documents directory where '/' is the root of the documents directory
 * @param {String} dest - (required), The path to the file that resides within the documents directory where '/' is the root of the documents directory
 */
TBAWrapper.prototype.mv = function(src, dest){
	if(this.isDesktop){
		tbaWrapperJ.mv(this.Base64.encode(src),this.Base64.encode(dest));
	}else{
		window.location = "mv://foo/bar?sid="+Math.random()+"&src="+this.Base64.encode(src)+"&dest="+this.Base64.encode(dest);
	}
};

/**
 * Copy a file from source to dest.  All files are stored within the documents directory of app; which in this case equates to '/'; src and dest paths must begin with '/'
 * @method cp
 * @param {String} src - (required), The path to the file that resides within the documents directory where '/' is the root of the documents directory
 * @param {String} dest - (required), The path to the file that resides within the documents directory where '/' is the root of the documents directory
 */
TBAWrapper.prototype.cp = function(src, dest){
	if(this.isDesktop){
		tbaWrapperJ.cp(this.Base64.encode(src),this.Base64.encode(dest));
	}else{
    	window.location = "cp://foo/bar?sid="+Math.random()+"&src="+this.Base64.encode(src)+"&dest="+this.Base64.encode(dest);
	}
};

/**
 * Delete a file.  All files are stored within the documents directory of app; which in this case equates to '/'; path must begin with '/'
 * @method rm
 * @param {String} path - (required), The path to the file that resides within the documents directory where '/' is the root of the documents directory
 */
TBAWrapper.prototype.rm = function(path){
	if(this.isDesktop){
		tbaWrapperJ.rm(this.Base64.encode(path));
	}else{
    	window.location = "rm://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
	}
};

/**
 * Make a directory. All files are stored within the documents directory of app; which in this case equates to '/'; path must begin with '/'; this method will also create intermediate directoryies; so you can call mkdir('/some_parent/some_sub'); and if '/some_parent' doesn't exist, it will be created for you
 * @method mkdir
 * @param {String} path - (required), The path to the directory that resides within the documents directory where '/' is the root of the documents directory
 */
TBAWrapper.prototype.mkdir = function(path){
	if(this.isDesktop){
		tbaWrapperJ.mkdir(this.Base64.encode(path));
	}else{
    	window.location = "mkdir://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
	}
};

/**
 * Write a string to a file.  Assumes this is a new file (e.g. doesn't append).
 * @method writeStringToFile
 * @param {String} theString - (required), The string that is to be written to file
 * @param {String} path - (required), The path to the file that is to be created that resides within the documents directory where '/' is the root of the documents directory
 */
TBAWrapper.prototype.writeStringToFile = function(theString, path){
    if(this.isiOS){
        window.location = "writestring://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path)+"&string="+this.Base64.encode(theString);
    }
};

/**
 * Read a a file into a string.
 * @method readStringFromFile
 * @param {String} path - (required), The path to the file that is to be created that resides within the documents directory where '/' is the root of the documents directory
 * @param {Function} callBack - (required), The function to call when the string has been read.  Should have only one argument that accepts the return string.
 */
TBAWrapper.prototype.readStringFromFile = function(path, callBack){
    this.readStringCallback = callBack;
    if(this.isiOS){
        window.location = "readstring://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
    }
};

/**
 * Event listener method to inform you that the current unzip process has completed.
 */
TBAWrapper.prototype.readStringCompleted = function(theString){
    if(this.readStringCallback){
        this.readStringCallback(theString);
    }
};

/**
 * Turns the current on screen visuals into PDF document
 * @method createPDF
 * @param {String} path - (required), The path to the file that is to be created that resides within the documents directory where '/' is the root of the documents directory
 * @param {Function} callBack - (required), The function to call when the PDF has finished creating.  Shouldn't have any arguments
 */
TBAWrapper.prototype.createPDF = function(path, callBack){
    this.createPDFCallBack = callBack;
    if(this.isiOS){
        window.location = "createpdf://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
    }
};

/**
 * Open a PDF in a native viewer
 * @method openPDF
 * @param {String} path - (required), The path to the PDF that resides within the documents directory where '/' is the root of the documents directory
 * @param {Boolean} isMainBundle - (required), Whether this path is for the main bundle or not.  If not the path is for the documents directory.  The main bundle is the list of files that are dragged into the left hand file browser in Xcode and deployed at install time.  Main bundle files are read only, whereas documents directory files can be written, read, and deleted.
 */
TBAWrapper.prototype.openPDF = function(path, isMainBundle){
    if(this.isiOS){
        window.location = "openpdf://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path)+"&ismainbundle="+isMainBundle;
    }
};

TBAWrapper.prototype.didCreatePDF = function(){
    this.createPDFCallBack();
    this.createPDFCallBack = null;
};

/**
 * Unzips a zip file to the same directory that the zip file resides in
 * @method unzip
 * @param {String} path - (required), the path to the zip file; path must begin with '/' which maps to the documents directory within this application.  Only one unzip process can be underway at any given point in time.
 * @param {Function} callBack - (optional), a function to call when the unzipping is complete that accepts no arguments
 */
TBAWrapper.prototype.unzip = function(path, callBack){
    this.unzipCallback = callBack
	if(this.isDesktop){
		tbaWrapperJ.unzip(this.Base64.encode(path));
	}else{
    	window.location = "unzip://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
	}
};

/**
 * Event listener method to inform you that the current unzip process has completed.
 */
TBAWrapper.prototype.unzipCompleted = function(){
    if(this.unzipCallback){
        this.unzipCallback();
    }
};

/**
 * Native console logging function; passes the argument to the console of the native system for ouput to the native console
 * @method log
 * @param {String} Message - (required), the message to be echoed
 */
TBAWrapper.prototype.log = function(msg){
	if(this.isDesktop){
		tbaWrapperJ.log(this.Base64.encode(msg));
	}else{
    	window.location = "log://foo/bar?sid="+Math.random()+"&msg="+this.Base64.encode(msg);
	}
};

/**
 * Function to create a screen shot of the currently display application window - currently only supported on desktop
 * @method writeScreenShotToPath
 * @param {String} path - (required ), path as to where to write the image within the documents directory.  '/' is considered the root of the docs dir.  Paths must end in .png; as the file written is PNG.
 */
TBAWrapper.prototype.writeScreenShotToPath = function(path){
	if(this.isDesktop){
		tbaWrapperJ.writeScreenShotToPath(this.Base64.encode(path));
	}else{
    	//do nothing
	}
};

/**
 * Function to write a folder full of PNGs into a PDF (one per page).  Only supported on desktop
 * @method writePNGSToPDF 
 * @param {String} src - (required), the directory path containing the PNGs to write (only png files will be written); must be a directory
 * @param {String} dest - (required), the path to the PDF file name to be created and written to.
 */
TBAWrapper.prototype.writePNGSToPDF = function(src, dest){
	if(this.isDesktop){
		tbaWrapperJ.writePNGSToPDF(this.Base64.encode(src),this.Base64.encode(dest));
	}else{
    	//do nothing
	}
};

/**
 * Function to launch a web page in the user's default browser rather than inside the wrapper window
 * @method launchNativeDefaultBrowser 
 * @param {String} url - (required), the url of the page to load in a native browser
 */
TBAWrapper.prototype.launchNativeDefaultBrowser = function(url){
	if(this.isDesktop){
		tbaWrapperJ.launchNativeDefaultBrowser(this.Base64.encode(url));
	}else{
    	window.location = "launchbrowser://foo/bar?sid="+Math.random()+"&url="+this.Base64.encode(url);
	}
};

/**
 * Function to authenticate against an oAuth or SAML sso end point  
 * @param uid - String, the user id to authenticate with
 * @param pass - String, the password to authenticate with
 * @param idp - String, the URL of the authentication end point to authenticate against
 */
TBAWrapper.prototype.doSSOAuth = function(uid, pass, idp){
	if(this.isDesktop){
		//tbaWrapperJ.writePNGSToPDF(this.Base64.encode(src),this.Base64.encode(dest));
	}else{
    	window.location = "ssosignin://foo/bar?sid="+Math.random()+"&pass="+this.Base64.encode(pass)+"&uid="+this.Base64.encode(uid)+"&idp="+this.Base64.encode(idp);
	}
};

/**
 * Function for text to speech; only supported on iOS 7+ and Android 2.2+; speaks a text string only if a text string isn't already being spoken.
 * @method speak
 * @param {String} text - (required), the phrase to be spoken
 * @param {Number} pitch - (required), a decimal number that controls the pitch of speech.  1 is normal
 * @param {Number} rate - (required), a decimal number that controls the rate of speech; 1 is normal, 2 is twice as fast, and 0.5 is half as fast
 * @param {String} accent - (required), the country code representing the accent of the phrase to be spoken (see getLanguageCodes for a list of these that are available on the user's device)
 * @param {Function} completeCallback - (optional), to call when the speech utterance is completed
 * @return {Boolean} - Whether the utterance will be spoken or not based on whether it is supported by the current environment
 */
TBAWrapper.prototype.speak = function(text, pitch, rate, accent, completeCallback){
	if(completeCallback){
		this.ttsCompleteCallback = completeCallback;	
	}
	if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			window.location = "speak://foo/bar?sid="+Math.random()+"&text="+this.Base64.encode(text)+"&pitch="+pitch+"&rate="+rate+"&accent="+accent;
		}
		return true;
	}
	return false;
};

/**
 * Function to derrive what language accents are available for the text to speech funcitonality; this list varies depending on the locale of the user and is determined at the OS level
 * @method getLanguageCodes
 * @param {Function} callBack - (required), Call back funciton that receives an array in the following format as its only argument: [{code : "US", display : "US English"}...];
 */
TBAWrapper.prototype.getLanguageCodes = function(callBack){
	this.log("1");
	this.languageCodeCallBack = callBack;
	if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			this.log("2");
			window.location = "getlanguagecodes://foo/bar?sid="+Math.random();
		}
	}
};

/**
 * Function to ask if the iOS or Android operating system is currently speaking
 * @method isSpeaking
 * @param {Function} callBack - (required), the call back function to process the response from the native hook; the function should receive one boolean argument to pass the result through
 * @return {Boolean} - Whether the answer could be given or not depending on whether the current execution environment supports this function
 */
TBAWrapper.prototype.isSpeaking = function(callBack){
	this.isSpeakingCallBack = callBack;
	if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			window.location = "isspeaking://foo/bar?sid="+Math.random();
		}
		return true;
	}
	return false;
};


/**
 * Function to pause current speech utterance - has no effect if not speaking already
 * @method pauseSpeech
 */
TBAWrapper.prototype.pauseSpeech = function(){
	if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			window.location = "pausespeech://foo/bar?sid="+Math.random();
		}
	}
};

/**
 * Function to resume current speech utterance - has no effect if not paused already
 * @method resumeSpeech
 */
TBAWrapper.prototype.resumeSpeech = function(){
	if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			window.location = "resumespeech://foo/bar?sid="+Math.random();
		}
	}
};

/**
 * Function to stop current speech utterance - has no effect if not speaking already
 * @method stopSpeech
 */
TBAWrapper.prototype.stopSpeech = function(){
    if((this.iOSVersion >= 7) || (this.androidVersion >= 2.2)){
		if(this.isiOS){
			window.location = "stopspeech://foo/bar?sid="+Math.random();
		}
	}
};

/**
 * Function that gets called when a speech utterance is completed
 */
TBAWrapper.prototype.didFinishSpeaking = function(){
	this.ttsCompleteCallback();
	this.ttsCompleteCallback = null;
};

/**
 * Function to dispatch the call back for the isSpeaking funciton
 * @param answer - boolean, the answer received
 */
TBAWrapper.prototype.isSpeakingAnswer = function(answer){
	this.isSpeakingCallBack(answer);
	this.isSpeakingCallBack = null;
};


/**
 * Function to dispatch the call back for the language code funciton
 * @param answer - boolean, the answer received
 */
TBAWrapper.prototype.languageCallBack = function(answer){
	this.languageCodeCallBack(answer);
	this.languageCodeCallBack = null;
};

/**
 * Function to open the Galderma Market from within the iDash framework.  Most Galderma internal apps need to have an iDash button to link the user back to their main directory; firing this method will open this.  Only supported on iOS
 * Also, for the galderma market to be able to find this app in the other direction - you should add the folowing code to the <dict> node in the info.plist file - where UNIQUEAPPID = your own unique identifier that you can make up:<br />
 *  &lt;key&gt;CFBundleURLTypes&lt;/key&gt;<br />
 *  &lt;array&gt;<br />
 *      &nbsp;&nbsp;&lt;dict&gt;<br />
 *          &nbsp;&nbsp;&nbsp;&nbsp;&lt;key&gt;CFBundleURLName&lt;/key&gt;<br />
 *          &nbsp;&nbsp;&nbsp;&nbsp;&lt;string&gt;&lt;/string&gt;<br />
 *          &nbsp;&nbsp;&nbsp;&nbsp;&lt;key&gt;CFBundleURLSchemes&lt;/key&gt;<br />
 *          &nbsp;&nbsp;&nbsp;&nbsp;&lt;array&gt;<br />
 *              &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;string&gt;UINIQUEAPPID&lt;/string&gt;<br />
 *          &nbsp;&nbsp;&nbsp;&nbsp;&lt;/array&gt;<br />
 *      &nbsp;&nbsp;&lt;/dict&gt;<br />
 *  &lt;/array&gt;
 * @method openGaldermaMarket
 */
TBAWrapper.prototype.openGaldermaMarket = function(){
    if(this.isiOS){
        window.location = "galdmkt://foo/bar?sid="+Math.random();
    }
};

/**
 * Function to check if an audio recording is underway
 * @method isRecording
 * @param {Function} callBack - (required), a call back function whose only argument is a boolean
 */
TBAWrapper.prototype.isRecording = function(callBack){
	 if(callBack != null){
		this.isRecordingCallBack = callBack;
		if(this.isiOS){
			window.location = "isrecording://foo/bar?sid="+Math.random()
		}
	 }
};
 
/**
 * Function to dispatch the call back for the isRecording function
 * @param isRec - Boolean, the result as to whether the system is currently recording or not
 */
TBAWrapper.prototype.isRecordingAnswer = function(isRec){
	if(this.isRecordingCallBack){
		this.isRecordingCallBack(isRec);
		this.isRecordingCallBack = null;	
	}
};

/**
 * Function to record a audio file from the mobile device's microphone.  Won't do anything if a recording is already in progress - only supported on iOS
 * @method startRecording
 * @param {String} path - (required), The path to the file to record; it is stored in the documents directory, where '/' is the root of the documents directory.  For iOS; filenames must end in .m4a
 */
TBAWrapper.prototype.startRecording = function(path){
	if(this.isiOS){
		window.location = "startrecording://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
	}
};

/**
 * Function to stop an audio recording from the mobile device's microphone. Won't do anything if a recording hasn't already been started.  Only supported on iOS
 * @method stopRecording
 */
TBAWrapper.prototype.stopRecording = function(){
	if(this.isiOS){
		window.location = "stoprecording://foo/bar?sid="+Math.random();
	}
};

/**
 * Function to play an audio file - presumably one that has been created from the recording funcitonality; but may work for other local file types.  Only supported on iOS
 * @method playRecording
 * @param {String} path - (required), a path to the file to play from the documents directory; where '/' is the root of the documents directory; for iOS should end in a .m4a extension
 * @param {Function} callBack - (optional), the call back funciton to call when the file has finished playing
 */
TBAWrapper.prototype.playRecording = function(path, callBack){
	if(callBack){
		this.playRecordingCallBack = callBack;
	}
	if(this.isiOS){
		window.location = "playrecording://foo/bar?sid="+Math.random()+"&path="+this.Base64.encode(path);
	}
};

/**
 * Function to stop the playback of an audio file.  Only supported on iOS
 * @method stopPlaying
 */
TBAWrapper.prototype.stopPlaying = function(){
	if(this.isiOS){
		window.location = "stopplaying://foo/bar?sid="+Math.random();
	}
	if(this.playRecordingCallBack){
		this.playRecordingCallBack = null;
	}
};

/**
 * Function to dispatch the call back for the playRecording function
 */
TBAWrapper.prototype.audioPlayerDidFinishPlaying = function(){
	if(this.playRecordingCallBack){
		this.playRecordingCallBack();
		this.playRecordingCallBack = null;
	}
};

/**
 * Function to upload one or more files to a server.  If a current upload batch is under way; you cannot start another one until its completed. Only supported on iOS
 * Sample PHP code to receive the file upload:<br />
 * <?php<br />
 * set_time_limit(2000);<br />
 * if(isset($_FILES['userfile'])) {<br />
 *    &nbsp;&nbsp;$directory = $_SERVER['DOCUMENT_ROOT'] . '/upload/';//where you want to store your file?<br />
 *    &nbsp;&nbsp;$file = basename($_FILES['userfile']['name']);<br />
 *    &nbsp;&nbsp;$uploadfile = $directory . $file;<br />
 *    &nbsp;&nbsp;error_log('uptest: file=' . $file);<br />
 *    &nbsp;&nbsp;if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {<br />
 *        &nbsp;&nbsp;&nbsp;&nbsp;echo '{json: "to return to app"}';<br />
 *        &nbsp;&nbsp;&nbsp;&nbsp;error_log('uptest: successful');<br />
 *    &nbsp;&nbsp;} else {<br />
 *        &nbsp;&nbsp;&nbsp;&nbsp;error_log('uptest: unsuccessful');<br />
 *    &nbsp;&nbsp;}<br />
 * } else {<br />
 *    &nbsp;&nbsp;error_log('uptest: empty file data');<br />
 * }<br />
 * ?>
 * @method uploadFiles
 * @param {Array} paths - (required), of Strings representing the local files that need to be uploaded.  Paths all reference the local documents directory as their base; the root of which is accessed by '/'
 * @param {String} url - (required), the URL to upload to
 * @param {Array} headers - (optional) Array of objects.  Each object must have one key / value pair which will be used as HTTP headers to be sent to the server with the upload request
 * @param {Function} callBackProgress - (optional), the call back that will tell you when each file in the group has completed its upload. The function should accept three arguments;
 * first is number of files completed; the second is the total number of files in this batch; the third is a string returned from the server upload page
 * @param {Function} callBackError - (optoinal), the call back to be executed if there is an error with this upload batch; should take no arguments; once an error is encountered the batch is automatically cancelled.
 */
TBAWrapper.prototype.uploadFiles = function(paths, url, headers, callBackProgress, callBackError){
    var p = this;
    if(callBackProgress){
        this.uploadFilesCallBackProgress = callBackProgress;
    }
    if(callBackError){
        this.uploadFilesCallBackError = callBackError;
    }
    if(this.isiOS){
        window.location = "uploadfiles://foo/bar?sid="+Math.random()+"&paths="+this.Base64.encode(this.array2json(paths))+"&url="+this.Base64.encode(url)+"&headers="+((headers) ? this.Base64.encode(p.array2json(headers)) : "[]");
    }
};

/**
 * Function to dispatch callback for file upload errors
 */
TBAWrapper.prototype.uploadFileError = function(){
    if(this.uploadFilesCallBackError){
        this.uploadFilesCallBackError();
        this.uploadFilesCallBackError = null;
    }
};

/**
 * Function to dispatch callback for file upload progress
 */
TBAWrapper.prototype.uploadFileProgress = function(completed, total, response){
    if(this.uploadFilesCallBackProgress){
        this.uploadFilesCallBackProgress(completed, total, this.Base64.decode(response));
        if(completed == total){
            this.uploadFilesCallBackProgress = null;
        }
    }
};

/**
 * Function to get a list of all photos from the shared photos on the device.  The base64 data returned in the call back can be inserted into the src attribute of an image tag like this:<br />&lt;img alt="Embedded Image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA..." /&gt;
 * @method getSharedPhotoThumbs
 * @param {Function} callBack - (required), the call back function whose only argument is an array of key value pair objects with format: {name: "foo.png", base64data : "+tI4ZEgpzQn7RhIXia4uoX5Pl9mVtap"}
 */
TBAWrapper.prototype.getSharedPhotoThumbs = function(callBack){
	this.getThumbsCallBack = callBack;
    if(this.isiOS){
        window.location = "getsharedphotothumbs://foo/bar?sid="+Math.random();
    }
};

/**
 * Funciton to dispatch the call back for receiving the shared photo thumbs
 */
TBAWrapper.prototype.gotSharedPhotoThumbs = function(dataString){
	this.getThumbsCallBack(this.jsonParse(dataString));
	this.getThumbsCallBack = null;
};

/**
 * Function to retrieve the base64 code for a full sized shared photo off of the device based off of the file name returned from the getSharedPhotoThumbs function
 * @method getSharedPhoto
 * @param photoName {String} photoName - (required), the name of the photo as returned from the getSharedPhotoThumbs function
 * @param callBack {Function} callBack - (required), the call back function to execute; should only accept one argument which will be a string of the base64 code to use for the full size image
 */
TBAWrapper.prototype.getSharedPhoto = function(photoName,callBack){
	this.getPhotoCallBack = callBack;
    if(this.isiOS){
        window.location = "getsharedphoto://foo/bar?sid="+Math.random()+"&name="+photoName;
    }
};

/**
 * Function to dispatch call back
 */
TBAWrapper.prototype.gotSharedPhoto = function(base64Data){
	this.getPhotoCallBack(base64Data);
	this.getPhotoCallBack = null;
};

/**
 * Function to open the default iOS directions view for locations
 * @method getDirections
 * @param lat {Number} lat - (required), latitude of the destination
 * @param lon {Number} lon - (required), longitude of the destination
 */
TBAWrapper.prototype.getDirections = function(lat,lon){
    if(this.isiOS){
        window.location = "getdirections://foo/bar?sid="+Math.random()+"&lat="+this.Base64.encode(lat+"")+"&lon="+this.Base64.encode(lon+"");
    }
};

/**
 * Function to customize the push notifications regsitration workflow; this allows you to control when the push notifications request is sent (e.g. not immediately upon app install).  This function still uses all of the other push config options in the config.json file, but allows you to override the postDataString value with a dynamic value passed as the query argument.  The onus is on the developer to ensure that they only call this function once per app install; or at sensible times (e.g. if the app has a settings panel for controlling push notifications outside of the iOS push notification settings view).
 * @method registerForCustomPushNotifications
 * @param query {String} - (required), a URL encoded post query that is to be appended to the URL registration HTTP request as a post query string
 */
TBAWrapper.prototype.registerForCustomPushNotifications = function(query){
    if(this.isiOS){
        window.location = "custom://foo/bar?sid="+Math.random()+"&query="+this.Base64.encode(query+"");
    }
};

/**
 * Function to receive the device ID back if the user accepts the request to allow push notifications.  This function is not a call back to any other method; as the user may or may not allow push notifications.
 * @method didReceiveDeviceID
 * @param did {String} - (required), this is the device ID returned for this app from iOS when the user accepts a request for push notifications
 */
TBAWrapper.prototype.didReceiveDeviceID = function(did){
    //todo: insert your own code here to handle device ID
};

/**
 * Function to open the native iOS email client & populate subject, message, and attachments.  If the device is unable to send emails (e.g. mail app isn't setup yet) the user is notified with a generic alert.
 * @method openEmailClient
 * @param subject {String} - (required), the subject line for the email
 * @param message {String} - (required), the message body for the email
 * @param attachments {Array} - (required), an array of JS objects with the following format: {path : "file/path.ext", mime : "type/ext", name : "foo.pdf"} where name is the desired file display name.
 * @param isMainBundle {Boolean} - (required), whether the supplied file path(s) from the attachment array are for the main bundle hard coded into the app; or the dynamic docs directory.
 * @param to {Array} - (required), an array of strings representing the intended recipients of the email
 * @param cc {String} - (required), an array of strings representing the intended cc recipients of the email
 * @param bcc {String} - (required), an array of strings representing the intended bcc recipients of the email
 * @param isHTML {Boolean} - (required), whether the message body is given as HTML or plain text
 */
TBAWrapper.prototype.openEmailClient = function(subject, message, attachments, isMainBundle, to, cc, bcc, isHTML){
    if(this.isiOS){
        window.location = "openemail://foo/bar?sid="+Math.random()+"&subject="+this.Base64.encode(subject+"")+"&message="+this.Base64.encode(message+"")+"&attachments="+this.Base64.encode(this.array2json(attachments))+"&ismainbundle="+this.Base64.encode(isMainBundle+"")+"&to="+this.Base64.encode(this.array2json(to))+"&cc="+this.Base64.encode(this.array2json(cc))+"&bcc="+this.Base64.encode(this.array2json(bcc))+"&ishtml="+this.Base64.encode(isHTML+"");
    }
};

/**
 * Light weight, secure, internal convenience method for parsing JSON strings into objects; doesn't use eval; so its quite secure.  https://code.google.com/p/json-sans-eval/
 * @method jsonParse
 * @param {String} input - (required), the JSON string to turn into an object
 * @return {Object} - An Object or an Array depending on the format of the input string provided
 */
TBAWrapper.prototype.jsonParse=function(){
	var r="(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)",k='(?:[^\\0-\\x08\\x0a-\\x1f"\\\\]|\\\\(?:["/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';k='(?:"'+k+'*")';var s=new RegExp("(?:false|true|null|[\\{\\}\\[\\]]|"+r+"|"+k+")","g"),t=new RegExp("\\\\(?:([^u])|u(.{4}))","g"),u={'"':'"',"/":"/","\\":"\\",b:"\u0008",f:"\u000c",n:"\n",r:"\r",t:"\t"};function v(h,j,e){return j?u[j]:String.fromCharCode(parseInt(e,16))}var w=new String(""),x=Object.hasOwnProperty;return function(h,j){h=h.match(s);var e,c=h[0],l=false;if("{"===c)e={};else if("["===c)e=[];else{e=[];l=true}for(var b,d=[e],m=1-l,y=h.length;m<y;++m){c=h[m];var a;switch(c.charCodeAt(0)){default:a=d[0];a[b||a.length]=+c;b=void 0;break;case 34:c=c.substring(1,c.length-1);if(c.indexOf("\\")!==-1)c=c.replace(t,v);a=d[0];if(!b)if(a instanceof Array)b=a.length;else{b=c||w;break}a[b]=c;b=void 0;break;case 91:a=d[0];d.unshift(a[b||a.length]=[]);b=void 0;break;case 93:d.shift();break;case 102:a=d[0];a[b||a.length]=false;b=void 0;break;case 110:a=d[0];a[b||a.length]=null;b=void 0;break;case 116:a=d[0];a[b||a.length]=true;b=void 0;break;case 123:a=d[0];d.unshift(a[b||a.length]={});b=void 0;break;case 125:d.shift();break}}if(l){if(d.length!==1)throw new Error;e=e[0]}else if(d.length)throw new Error;if(j){var p=function(n,o){var f=n[o];if(f&&typeof f==="object"){var i=null;for(var g in f)if(x.call(f,g)&&f!==n){var q=p(f,g);if(q!==void 0)f[g]=q;else{i||(i=[]);i.push(g)}}if(i)for(g=i.length;--g>=0;)delete f[i[g]]}return j.call(n,o,f)};e=p({"":e},"")}return e}}();


/**
 * Light weight, internal convenience method for creating JSON strings from Objects or from Arrays.
 * @method array2json
 * @param {Object} arr - (required), An Object or an Array to turn into a JSON string
 * @return {String} - The JSON string representing the Object or Array that was parsed as an argument
 */
TBAWrapper.prototype.array2json = function(arr) {
    var parts = [];
    var is_list = (Object.prototype.toString.apply(arr) === '[object Array]');

    for(var key in arr) {
    	var value = arr[key];
        if(typeof value == "object") { //Custom handling for arrays
            if(is_list) parts.push(this.array2json(value)); /* :RECURSION: */
            else parts.push('"' + key + '":' + this.array2json(value)); /* :RECURSION: */
            //else parts[key] = array2json(value); /* :RECURSION: */
            
        } else {
            var str = "";
            if(!is_list) str = '"' + key + '":';

            //Custom handling for multiple data types
            if(typeof value == "number") str += value; //Numbers
            else if(value === false) str += 'false'; //The booleans
            else if(value === true) str += 'true';
            else str += '"' + value.replace(/"/g, '\\\"') + '"'; //All other things
            // :TODO: Is there any more datatype we should be in the lookout for? (Functions?)

            parts.push(str);
        }
    }
    var json = parts.join(",");
    
    if(is_list) return '[' + json + ']';//Return numerical JSON
    return '{' + json + '}';//Return associative JSON
};

var tbaWrapper = new TBAWrapper();