Sharing code between client and server

If you write any Apps Script using HtmlService - and that's pretty much everyone nowadays, you probably write client side JavaScript as Html files with <script> tags, and server side as .gs files.

But Apps Script is JavaScript, so why treat them differently? You also sometimes end up duplicating useful bits of code for .js purposes that you've already written once for .gs.

Here's how to use the same code for both, or to write code intended to be run client side using the server side .gs IDE environment. Not only does this avoid duplication, it allows you to easily delegate compute intensive things to the client where they tend to run much faster.

Example

Let's say I have a useful set of functions I'm using in .gs to generate unique strings, and I want to also do something like that with something I'm planning to do on the client side. 

Here's my web app. Nothing unusual here.

function doGet() {
    return HtmlService.createTemplateFromFile('clientHtml')
            .evaluate()
            .setSandboxMode(HtmlService.SandboxMode.IFRAME);

}

Here's my clientHtml file. There's no JavaScript code, aside from a call to doTheClientWork(); 



<p>
Sharing the same code both server and client side
</p>

<div id="client">client:</div>
<div id="server">server:</div>

<?!= requireGs (['usefulThings' , 'clientJs']); ?>

<script>

// and run it
    window.onload = function () {
        doTheClientWork();
    };

</script>

Notice the templating of the requireGs() function. This is going to go off to pick up code that is being managed, and potentially used in the .gs server environment, and insert it into the client environment to be run there.

requireGs()

Here's how it works. The ScriptApp.getResource() function returns a Blob that is the code for a .gs file. Using that we can inject it into the HtmlOutput stream, and reuse or manage all out client side JavaScript as if it were server side.

/**
* given an array of .gs file names, it will get the source and return them concatenated for insertion into htmlservice
* like this you can share the same code between client and server side, and use the Apps Script IDE to manage your js code
* @param {string[]} scripts the names of all the scripts needed
* @return {string} the code inside script tags
*/

function requireGs (scripts) {
    return '<script>\n' + scripts.map (function (d) {
        return ScriptApp.getResource(d).getDataAsString();
    })
    .join('\n\n') + '</script>\n';
}

So all you have to do is give a list in your html template of the scripts you'd like included to run on the client, like this.

<?!= requireGs (['usefulThings' , 'clientJs']); ?>

Client side

This code is expected to only run client side, but I'm managing it as a .gs file which means I can use the regular Apps Script IDE. The fact that it refers to things that don't exist in apps script such as the document object is irrelevant, since we never actually execute it server side. Note that it runs the same function on both the client and the server.

/**
* this is stuff I only want to run on the client, but I'll manage it with the apps script IDE
*/
function doTheClientWork () {

    // i'll call getUniqueString, which is shared between both client and server
    document.getElementById("client").innerHTML += generateUniqueString();

    // and we'll run the same thing server side
    google.script.run
        .withSuccessHandler( function (data) {
            document.getElementById("server").innerHTML += data;
        })
        .withFailureHandler ( function (error) {
            document.getElementById("server").innerHTML += error;
        })
        .generateUniqueString();

}


Shared code

Here's the code I want to be able to run both server and client side. Like this it only needs to exist in one place - as a .gs script file. For the purposes of this discussion, it's not important what it does.

/** useful functions
* these are managed server side but we can use them either client or server side
**/
"use strict";

/**

* generateUniqueString
* get a unique string
* @param {number} optAbcLength the length of the alphabetic prefix
* @return {string} a unique string
**/
function generateUniqueString (optAbcLength) {
    var abcLength = isUndefined(optAbcLength) ? 3 : optAbcLength;
    return (new Date().getTime()).toString(36) + arbitraryString(abcLength) ;
}


/**
* check if item is undefined
* @param {*} item the item to check
* @return {boolean} whether it is undefined
**/
function isUndefined (item) {
    return typeof item === 'undefined';
}

/**

* check if item is undefined
* @param {*} item the item to check
* @param {*} defaultValue the default value if undefined
* @return {*} the value with the default applied
**/
function applyDefault (item,defaultValue) {
    return isUndefined(item) ? defaultValue : item;
}

/**
* get an arbitrary alpha string
* @param {number} length of the string to generate
* @return {string} an alpha string
**/
function arbitraryString (length) {
    var s = '';
    for (var i = 0; i < length; i++) {
        s += String.fromCharCode(randBetween ( 97,122));
    }
    return s;
}

/**
* randBetween
* get an random number between x and y
* @param {number} min the lower bound
* @param {number} max the upper bound
* @return {number} the random number
**/
function randBetween(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

oh... and here's the output


For some more discussion and additional ideas on this, take a look at More client server code sharing

For more like this, see  Google Apps Scripts snippets. Why not join our cummunity , follow the blogtwitterG+  . 

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.







Comments