Google-Apps Script For Beginners Sample Chapter
Google-Apps Script For Beginners Sample Chapter
Serge Gabet
A look at the available methods at https://developers.google.com/appsscript/reference/document will tell you more about the plethora of methods than a description in this book ever could, even if I had a 100 pages to ll. So, let us focus on a couple of pleasant features illustrated with practical examples in the following sections.
We already know how to get data from a spreadsheet (see Chapter 1, Enhancing Spreadsheets) and that using getValues() will return an array of arrays with all the cells' data in a raw format. Each row will be an array of values corresponding to each cell; these arrays will in turn be wrapped in an array. We also see in our example that the rst row in the sheet has headers that we may want to use to get the structure of the data.
[ 64 ]
Chapter 4
Speaking of structure, if you read the tutorials on Google's help page (https://developers.google.com/apps-script/articles), you have probably noticed that they use a method to get data from a spreadsheet as objects, with the object of each row having its cell properties labelled using header values (https://developers.google.com/apps-script/guides/sheets#reading). There are actually many ways to manipulate data from spreadsheets. A Two-Dimensional (2D) array (in other words, an array of arrays) is a pleasant one. JavaScript-named objects are also nice to use and I have no real opinion about which is best, so I suggest that this time we give the object method a try. The next time will use pure array manipulation. Both approaches begin the same way: select a range in the sheet and get its values, then isolate the headers to know what data it has. This is done using the following code:
var data = SpreadsheetApp.getActive().getDataRange().getValues(); var headers = data.shift();
var data contains all the cells in an array of arrays. The shift() method removes the rst array (which is equal to the rst row) and assigns it to var headers. See the example documentation from w3schools at the following link: http://www.w3schools.com/jsref/jsref_shift.asp
[ 65 ]
That is the common part of the two approaches. From now on, we must change our script to get JavaScript objects. As explained in the Google tutorial, it is advisable to use a CamelCase format for the object's variable names. It suggests a script to do so, but we're going to try something else, such as playing with a few string and array methods. See the comments in the following code and use the Logger to see every step of the process:
function camelise(stringArray) { var camelised = [];// this will hold the function result for(var n in stringArray){ Logger.log('title original = '+stringArray[n]) var title = stringArray[n].replace(/\W/gi,' ').split(' '); // replace every non "word" char with space and split string into an array (split on space) Logger.log('title array alphanumeric only = '+title) var result = ''; for(var t in title){ if(title[t].replace(/ /g,'')==''){ continue }; // skip any empty field result+=title[t].replace(/ /g,'')+'|'; // compose a result string with | separators (easier to see in the Logger) and no spaces } title = result.toLowerCase().split('|'); // everything in lowercase and convert back to an array splitting on | separators var camelCase = '';// initialize the result string for(t=0 ; t<title.length-1 ; t++){ // let's handle each word separately, the first word (index 0) should remain unchanged if(t==0){ var capWord = title[t]; }else{ capWord = title[t].substring(0,1).toUpperCase()+title[t]. substr(1); } // every following item gets a capital first letter camelCase+=capWord;// compose output result } // view each result in the Logger and store in an array Logger.log('camelCase = '+camelCase); camelised.push(camelCase); } // return the array of headers in CamelCase format return camelised; }
[ 66 ]
Chapter 4
Now we can test the preceding code using a special temporary function such as the one in the following code snippet:
function testGetHeadersAsCamelCase(){ var data = SpreadsheetApp.getActive().getDataRange().getValues();// we get all the cells with data in one call var headers = data.shift();// the first row is headers row and we remove this row from data altogether var ccHeaders = camelise(headers); for(var n in headers){ // create a loop Logger.log(headers[n]+' becomes >> '+ccHeaders[n]); } }
Now that we have our headers in the right format, that is CamelCase, we can read the data in the sheet using the headers as keys and each cell row as value to build JavaScript Objects with key:value pairs as properties; the following code snippet depicts this:
function getObjects(data, keys) { var objects = [];// create an empty array variable for (var i = 0; i < data.length; ++i) {// iterate sheet data var object = {}; // create an empty "object" variable and then iterate each row's content for (var j = 0; j < data[i].length; ++j) { var cellData = data[i][j]; // check if cell is empty or contains any white space if (cellData.toString().replace(/ /g,'')=='') { continue; }else{ object[keys[j]] = cellData;// assign value to key } } [ 67 ]
Embedding Scripts in Text Documents objects.push(object);//store every object in array } return objects; }
Create a new document to hold the values as shown in the following piece of code:
var docId = DocumentApp.create('Recipes in a doc').getId(); Logger.log(docId);
We use another function to populate the document with text, tables, and images. In this chapter, I have called this function exportToDoc(docId,objects,keys, headers). It has four parameters: the newly created document ID, the array of objects, the keys (in case we need them), and the headers to show eld information. The next part of the code is not very hard to read. The various style denitions are dened as global variables at the end of the script as usual, but the data handling itself is very simple, thanks to the array-of-objects structure. The document formatting function is reproduced in the following code with comments on the important parts:
function exportToDoc(docId,objects,keys,headers){ var doc = DocumentApp.openById(docId); var body = doc.getBody(); var docHeader = doc.addHeader().appendParagraph('My favourite recipes\rCreated by script on ' +Utilities.formatDate(new Date(), Session. getTimeZone(),'MMM dd @ HH:mm')); // use the style defined outside of the function and use an alignment setting docHeader.setAttributes(styleHeader).setAlignment(DocumentApp. HorizontalAlignment.CENTER); for(var n in objects){ body.appendParagraph('An idea for a meal with '+objects[n] [keys[1]]+' composed mainly of '+objects[n][keys[2]]+' for '+objects[n][keys[5]]+' :').setAttributes(styleBase); body.appendParagraph('The name of this recipe is "'+objects[n] [keys[0]]+'" but I invented it myself \rso you can change it if you want').setAttributes(styleBase); body.appendHorizontalRule(); body.appendParagraph('List of '+headers[3]+' :'); var table = []; var ing = objects[n][keys[3]].split(','); for(var i in ing){ table.push(['You must have '+ ing[i]]) };
[ 68 ]
Chapter 4 body.appendTable(table).setAttributes(styleTable); body.appendParagraph('Try to get some free time, it will take approximately '+objects[n][keys[4]]+', then clean up your kitchen and '+objects[n][keys[6]]).setAttributes(styleDirections); body.appendHorizontalRule(); var image = DriveApp.getFileById(objects[n][keys[7]].split('d/') [1].split('/')[0]).getBlob();// retrieve ID from URL // https://drive.google.com/file/d/0B3qSFd3iikE3UmpjelRQdlZmQXc/ edit?usp=sharing This is a typical link body.appendParagraph('good apetite ;) ').appendInlineImage(image). setWidth(300).setHeight(200).getParent().setAttributes(styleImage); body.appendHorizontalRule(); if(n<objects.length){body.appendPageBreak()}; } }
The style denitions are grouped at the end of the script. They can even be stored in a separate script le within the same project. This is shown in the following code snippet:
// Style definitions as global variables var bodyStyle = {};// define a style for body, margin etc... bodyStyle[DocumentApp.Attribute.MARGIN_LEFT] = 30; bodyStyle[DocumentApp.Attribute.MARGIN_BOTTOM] = 20; bodyStyle[DocumentApp.Attribute.MARGIN_RIGHT] = 30; bodyStyle[DocumentApp.Attribute.MARGIN_TOP] = 20; body.setAttributes(bodyStyle); var styleBase = {};// a "base" style for paragraphs styleBase[DocumentApp.Attribute.FONT_SIZE] = 11; styleBase[DocumentApp.Attribute.FONT_FAMILY] = DocumentApp. FontFamily.AMARANTH; styleBase[DocumentApp.Attribute.FOREGROUND_COLOR] = "#444400"; var styleHeader = {};// define a style for document header styleHeader[DocumentApp.Attribute.BACKGROUND_COLOR] = '#eeeeff' styleHeader[DocumentApp.Attribute.FONT_SIZE] = 16; styleHeader[DocumentApp.Attribute.FONT_FAMILY] = DocumentApp. FontFamily.CORSIVA; styleHeader[DocumentApp.Attribute.FOREGROUND_COLOR] = '#0000aa'; var styleTable = {};// define a style for table styleTable[DocumentApp.Attribute.FONT_SIZE] = 10; styleTable[DocumentApp.Attribute.FONT_FAMILY] =DocumentApp. FontFamily.AMARANTH; styleTable[DocumentApp.Attribute.FOREGROUND_COLOR] = "#005500"; styleTable[DocumentApp.Attribute.BORDER_WIDTH] = 0 ;
[ 69 ]
Embedding Scripts in Text Documents var styleDirections = {};// define a style for direction paragraph styleDirections[DocumentApp.Attribute.FONT_SIZE] = 12; styleDirections[DocumentApp.Attribute.FONT_FAMILY] = DocumentApp. FontFamily.CONSOLAS; styleDirections[DocumentApp.Attribute.ITALIC] = true; styleDirections[DocumentApp.Attribute.FOREGROUND_COLOR] = "#000066"; var styleImage = {}; styleImage[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.CENTER; // end of file
We can now write a main function that will call all the others in turn; the function generateDoc() will be the one to call to create the document as shown in the following code snippet:
function generateDoc(){ var data = SpreadsheetApp.getActive().getDataRange().getValues(); // we first get all the cells with data in one call var headers = data.shift(); // shift is an array function the removes the first row // and assign it to the variable headers var keys = getHeadersAsCamelCase(headers); // get the CamelCase formatted headers using a separate function var objects = getObjects(data, keys); for(var n in objects){ for(var k in keys){ Logger.log('Object '+n+' properties '+keys[k]+' = '+objects[n] [keys[k]]); } } // we get all the values using 2 loops var docId = DocumentApp.create('Recipes in a doc').getId(); //create a doc as a container for our recipes Logger.log(docId); exportToDoc(docId,objects,keys,headers); }
Running generateDoc() is going to create a multipage document with one recipe per page with a better layout and an inline image.
[ 70 ]
Chapter 4
The next step would be to create a form linked to the source spreadsheet (as seen in Chapter 2, Create and Manipulate Forms) so that all your friends can add their own recipes. With a single click, you will be ready to publish a cookbook with no effort. If you do, just let me know, I love cooking! The following screenshot shows the document created:
[ 71 ]
Chapter 4 if (type =='PARAGRAPH'){ el[j]=element.getText(); if(el[j]=='###'){ element.removeFromParent(); // remove the ### placeholder Doc.insertImage(j, image); // 'image' is the image file as blob } } } }
Embedding Scripts in Text Documents if( n % 2 == 0){ // every odd row will be right aligned, every even row will be left aligned styleEvent[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.LEFT; }else{ styleEvent[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = DocumentApp.HorizontalAlignment.RIGHT; } var bgColor = parseInt(0xff*(n+1)/events.length); // fade color to white using hex values Logger.log('color = #'+bgColor.toString(16)+'ffff'); styleEvent[DocumentApp.Attribute.FOREGROUND_COLOR] = '#222222'; styleEvent[DocumentApp.Attribute.BACKGROUND_COLOR] = '#'+bgColor. toString(16)+'ffff'; body.appendParagraph(events[n].getTitle()+'\r'+events[n]. getDescription()+'\ron '+Utilities.formatDate(events[n]. getStartTime(), Session.getTimeZone(),'MMM dd, yyyy')+'\r@'+events[n]. getLocation()).setAttributes(styleEvent); } }
[ 74 ]
Chapter 4
In the preceding code, we used some pure JavaScript methods such as Math.floor and % that are well explained on many JavaScript reference websites (for example,
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Global_Objects/Math).
Spend some time reading the documentation about all the elements that you can include in a document and all the data sources you can use. In the next chapter, we will use a document embedded in a Google website and update it automatically everyday with data from a spreadsheet.
Summary
Document-embedded scripts can be used on many different occasions; we have seen how to build or change the content of a document and analyze what is already in it; this process could easily be developed to create an elegant mail-merge workow, getting data directly from your Gmail contact. In Chapter 7, Using User Interfaces in Spreadsheets and Documents, we'll see that we can also add a custom user interface to a document and create new features that make the documents even more attractive and efcient. In the next chapter, we will learn about embedding scripts on Google sites.
[ 75 ]
Alternatively, you can buy the book from Amazon, BN.com, Computer Manuals and most internet book retailers.
www.PacktPub.com