- Custom Functions in Google Sheets
- Custom Menus and User Interfaces in Google Sheets
- Basics of Storing Data in Google Sheets
- Reading Data from a Spreadsheet into JavaScript Objects
- Writing Data from JavaScript Objects to a Spreadsheet
Custom Functions in Google Sheets
If the built-in spreadsheet functions of Google Sheets aren't enough for your needs, you can write custom functions with Apps Script.
A custom function (sometimes called a user-defined function) is similar to a
spreadsheet formula like =SUM(A1:A5)
except that you define the
formula’s behavior in Apps Script. For example, you could create a custom
function, in2mm()
, that converts a value from inches to
millimeters, then use the formula in your spreadsheet by typing
=in2mm(A1)
or =in2mm(10)
into a cell.
To learn more about custom functions, try the Menus, Macros, and Custom Functions 5-minute quickstart, or take a look at the more in-depth Custom Functions in Spreadsheets tutorial.
Custom Menus and User Interfaces in Google Sheets
You can easily customize Google Sheets by adding custom menus and dialog boxes. To learn the basics of creating menus, see the guide to menus. To learn about customizing the content of a dialog box, see the guide to Html Service or the guide to Ui Service.
You can also attach a script function to an image or drawing within a spreadsheet; the function will execute when a user clicks on the image or drawing. To learn more, see Images and Drawings in Google Sheets.
Basics of Storing Data in Google Sheets
Storing data in a spreadsheet is most commonly done for scripts which are bound to a spreadsheet, since those scripts have a built-in notion of an active spreadsheet. The examples in this document are referring to a script bound to a spreadsheet. However, standalone scripts can also store data in spreadsheets, though they will need to explicitly specify a spreadsheet to open and use, via SpreadsheetApp.openById()
.
Google Sheets has some limits that you should take into account when deciding whether or not to use a spreadsheet to store your script's data. For example, a spreadsheet is limited to 400,000 total cells across all sheets and 256 columns per sheet. See this help center article for the full listing of limits.
The simplest way of saving and retrieving data in a spreadsheet is to simply save each piece of data in its own cell. You can launch the Script Editor from a spreadsheet by choosing Tools > Script Editor.
Suppose you have a list of product names and product numbers that you store in a spreadsheet, as shown in the image below.

As you can see, the product names are stored in column A and the product numbers are stored in column B.
To retrieve the data from the spreadsheet, you must get access to the spreadsheet where the data is stored, get the range in the spreadsheet that holds the data, and then get the values of the cells. The example below shows how to retrieve and log the product names and product numbers.
function printProductInfo() { var sheet = SpreadsheetApp.getActiveSheet(); var data = sheet.getDataRange().getValues(); for (var i = 0; i < data.length; i++) { Logger.log("Product name: " + data[i][0]); Logger.log("Product number: " + data[i][1]); } }
To view the data that has been logged, choose View > Logs... from the Script Editor.
To store data, such as a new product name and number to the spreadsheet, you could add the following code to the script.
function addProduct() { var sheet = SpreadsheetApp.getActiveSheet(); sheet.appendRow(["Cotton Sweatshirt XL", "css004"]); }
This code appends a new row at the bottom of the spreadsheet, with the values specified. If you run this function, you'll see a new row added to the spreadsheet.
Reading Data from a Spreadsheet into JavaScript Objects
This section guides you through the steps of easily reading structured data from a spreadsheet and creating JavaScript objects to facilitate access to the data. It provides some useful helper functions, such as getRowsData
, that can be reused in your scripts.
Setting up a spreadsheet and the example script
- Open the spreadsheet template for this tutorial.
- Create your own copy of the spreadsheet by clicking on the File > Make a copy. You can now edit the spreadsheet and start writing scripts.
- Open the Script Editor from Tools > Script Editor.
- You should see the 'reading_data' file in the project.
- Run the function
runExample
. You should see a message box that shows the information of the third employee in the spreadsheet table.

In the next section, you'll learn how the script reads in the data from the spreadsheet.
Reading tables row-by-row into JavaScript Objects
First, find the code for therunExample
function in the Script Editor.
// Get the range of cells that store employee data. var employeeDataRange = ss.getRangeByName("employeeData");
Google Sheets lets users define Named Ranges of cells. In this example, the range "employeeData" has been defined as "A2:E45". You can verify this by going to the spreadsheet, clicking on Data > Named and protected ranges, and then selecting 'employeeData'.
Named ranges are very convenient and flexible when writing spreadsheet scripts, and you should consider using them instead of hard coding ranges in scripts. Read more information about Named Ranges here.
// For every row of employee data, generate an employee object. var employeeObjects = getRowsData(sheet, employeeDataRange);
The function
getRowsData
is defined just below
runExample
and is where most of the work is done in this example. You are encouraged to
reuse it and tweak it in your future scripts.
var thirdEmployee = employeeObjects[2];
At this point, all data from the the table in the spreadsheet has been read
into
employeeObjects
. For every row in the table, a JavaScript object has been created. The code
above retrieves the information about the third employee in the table
(Maryanne Packard). Note that Arrays in JavaScript are 0-based, so the first
entry is employeeObjects[0]
.
var stringToDisplay = "The third row is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName; stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the "; stringToDisplay += thirdEmployee.department + " department and with phone number "; stringToDisplay += thirdEmployee.phoneNumber;
The code above simply generates a string with all the data from an employee
object. Note how column names have become mixed-case object properties
(for example, data from the column 'First Name' can be read using
object.firstName
).
You should now be able to use
getRowsData
in any of your spreadsheets. Note that you need to manually copy all functions
in the example except
runExample
to your other spreadsheets.
Understanding how getRowsData
works
This section explains in detail how the getRowsData
function
works. It covers both the Spreadsheet
service and general JavaScript
concepts.
- Function
getRowsData
- a JavaScript 2-dimensional Array that contains all the values in
the range of data. In this example,
range.getValues()[0][1]
returns data for the first row and second column: "Berger". - an Array of normalized names of columns. We will analyze the
normalizeHeaders
function later. - Function
getObjects
- Column name normalization functions
// Normalizes a string, by removing all non-alphanumeric characters and using mixed case // to separate words. The output will always start with a lower case letter. // This function is designed to produce JavaScript object property names. // Arguments: // - header: string to normalize // Examples: // "First Name" -> "firstName" // "Market Cap (millions) -> "marketCapMillions // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" function normalizeHeader(header) { var key = ""; var upperCase = false; for (var i = 0; i < header.length; ++i) { var letter = header[i]; if (letter == " " && key.length > 0) { upperCase = true; continue; } if (!isAlnum(letter)) { continue; } if (key.length == 0 && isDigit(letter)) { continue; // first character must be a letter } if (upperCase) { upperCase = false; key += letter.toUpperCase(); } else { key += letter.toLowerCase(); } } return key; }
This function is pure JavaScript. It basically iterates over the input
header
string character-by-character, removes all non alphanumeric characters and returns a mixed-case string that is easy to use to define JavaScript object properties.// Returns an Array of normalized Strings. // Arguments: // - headers: Array of Strings to normalize function normalizeHeaders(headers) { var keys = []; for (var i = 0; i < headers.length; ++i) { var key = normalizeHeader(headers[i]); if (key.length > 0) { keys.push(key); } } return keys; }
This function simply normalizes one-by-one all the strings defined in the
headers
Array of Strings.
// getRowsData iterates row-by-row on the input range and returns an array of objects. // Each object contains all the data for a given row, indexed by its normalized column name. // Arguments: // - sheet: the sheet object that contains the data to be processed // - range: the exact range of cells where the data is stored // - columnHeadersRowIndex: specifies the row number where the column names are stored. // This argument is optional and it defaults to the row immediately above range; // Returns an Array of objects. function getRowsData(sheet, range, columnHeadersRowIndex) { columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
Function
getRowsData
takes three arguments. The last one,
columnHeadersRowIndex
is optional and defaults to the row index of the row immediately above the
range
argument. This is implemented in the first line of code in the function. If
columnHeadersRowIndex
is not specified when calling the function, then it is set to
range.getRowIndex()-1
,
where the function
getRowIndex
returns the index of the first row in a
Range
object.
var numColumns = range.getEndColumn() - range.getColumn() + 1; var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
A new Range
object is created that contains a single row of data with
all the relevant column names.
var headers = headersRange.getValues()[0]; return getObjects(range.getValues(), normalizeHeaders(headers));
One way you can quickly debug problems is by creating message boxes to show
relevant data. For instance, in the code above, you can insert a line of
code that prints the list of column names stored in the
headers
variable. Note that you need to call the standard JavaScript function
toSource
to serialize JavaScript Arrays. The resulting code will look like this:
var headers = headersRange.getValues()[0]; Browser.msgBox(headers.toSource()); return getObjects(range.getValues(), normalizeHeaders(headers));
The
getObjects
function is called with the following two parameters:
function getObjects(data, keys) { var objects = []; for (var i = 0; i < data.length; ++i) { var object = {}; var hasData = false; for (var j = 0; j < data[i].length; ++j) { var cellData = data[i][j]; if (isCellEmpty(cellData)) { continue; } object[keys[j]] = cellData; hasData = true; } if (hasData) { objects.push(object); } } return objects; }
The first for loop iterates over each row in the
data
2-dimensional array. The second loop iterates over colums in a given row and processes
data for a given cell. Every non-empty generated object is appended to an
Array of objects that is returned.
object[keys[j]] = cellData;
This is the key statement that sets the object property with name
keys[j]
to the value
cellData
.
Reading tables column-by-column into JavaScript Objects
Now that you feel comfortable using
getRowsData
, this section explains how you can read data column-by-column instead
of row-by-row.
The second sheet in the spreadsheet contains a table with the same data from previous sections, but organized in columns:
How would you implement a version of
getColumnsData
? One way to do this is to reuse some of the code
described above.
function runExercise() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheets()[1]; // Get the range of cells that store employee data. var employeeDataRange = sheet.getRange("B1:F5"); // For every row of employee data, generate an employee object. var employeeObjects = getColumnsData(sheet, employeeDataRange); var thirdEmployee = employeeObjects[2]; var stringToDisplay = "The third column is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName; stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the "; stringToDisplay += thirdEmployee.department + " department and with phone number "; stringToDisplay += thirdEmployee.phoneNumber; Browser.msgBox(stringToDisplay); } // Given a JavaScript 2d Array, this function returns the transposed table. // Arguments: // - data: JavaScript 2d Array // Returns a JavaScript 2d Array // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. function arrayTranspose(data) { if (data.length == 0 || data[0].length == 0) { return null; } var ret = []; for (var i = 0; i < data[0].length; ++i) { ret.push([]); } for (var i = 0; i < data.length; ++i) { for (var j = 0; j < data[i].length; ++j) { ret[j][i] = data[i][j]; } } return ret; } // getColumnsData iterates column by column in the input range and returns an array of objects. // Each object contains all the data for a given column, indexed by its normalized row name. // Arguments: // - sheet: the sheet object that contains the data to be processed // - range: the exact range of cells where the data is stored // - rowHeadersColumnIndex: specifies the column number where the row names are stored. // This argument is optional and it defaults to the column immediately left of the range; // Returns an Array of objects. function getColumnsData(sheet, range, rowHeadersColumnIndex) { rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); return getObjects(arrayTranspose(range.getValues()), headers); }
Running the function
runExercise
should return the same data as in Section 1:
Full Sample Code
Examples
function runExample() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheets()[0]; // Get the range of cells that store employee data. var employeeDataRange = ss.getRangeByName("employeeData"); // For every row of employee data, generate an employee object. var employeeObjects = getRowsData(sheet, employeeDataRange); var thirdEmployee = employeeObjects[2]; var stringToDisplay = "The third row is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName; stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the "; stringToDisplay += thirdEmployee.department + " department and with phone number "; stringToDisplay += thirdEmployee.phoneNumber; Browser.msgBox(stringToDisplay); } function runExercise() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheets()[1]; // Get the range of cells that store employee data. var employeeDataRange = sheet.getRange("B1:F5"); // For every row of employee data, generate an employee object. var employeeObjects = getColumnsData(sheet, employeeDataRange); var thirdEmployee = employeeObjects[2]; var stringToDisplay = "The third column is: " + thirdEmployee.firstName + " " + thirdEmployee.lastName; stringToDisplay += " (id #" + thirdEmployee.employeeId + ") working in the "; stringToDisplay += thirdEmployee.department + " department and with phone number "; stringToDisplay += thirdEmployee.phoneNumber; ss.msgBox(stringToDisplay); }
Library
// getRowsData iterates row by row in the input range and returns an array of objects. // Each object contains all the data for a given row, indexed by its normalized column name. // Arguments: // - sheet: the sheet object that contains the data to be processed // - range: the exact range of cells where the data is stored // - columnHeadersRowIndex: specifies the row number where the column names are stored. // This argument is optional and it defaults to the row immediately above range; // Returns an Array of objects. function getRowsData(sheet, range, columnHeadersRowIndex) { columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1; var numColumns = range.getLastColumn() - range.getColumn() + 1; var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns); var headers = headersRange.getValues()[0]; return getObjects(range.getValues(), normalizeHeaders(headers)); } // getColumnsData iterates column by column in the input range and returns an array of objects. // Each object contains all the data for a given column, indexed by its normalized row name. // Arguments: // - sheet: the sheet object that contains the data to be processed // - range: the exact range of cells where the data is stored // - rowHeadersColumnIndex: specifies the column number where the row names are stored. // This argument is optional and it defaults to the column immediately left of the range; // Returns an Array of objects. function getColumnsData(sheet, range, rowHeadersColumnIndex) { rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1; var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues(); var headers = normalizeHeaders(arrayTranspose(headersTmp)[0]); return getObjects(arrayTranspose(range.getValues()), headers); } // For every row of data in data, generates an object that contains the data. Names of // object fields are defined in keys. // Arguments: // - data: JavaScript 2d array // - keys: Array of Strings that define the property names for the objects to create function getObjects(data, keys) { var objects = []; for (var i = 0; i < data.length; ++i) { var object = {}; var hasData = false; for (var j = 0; j < data[i].length; ++j) { var cellData = data[i][j]; if (isCellEmpty(cellData)) { continue; } object[keys[j]] = cellData; hasData = true; } if (hasData) { objects.push(object); } } return objects; } // Returns an Array of normalized Strings. // Arguments: // - headers: Array of Strings to normalize function normalizeHeaders(headers) { var keys = []; for (var i = 0; i < headers.length; ++i) { var key = normalizeHeader(headers[i]); if (key.length > 0) { keys.push(key); } } return keys; } // Normalizes a string, by removing all alphanumeric characters and using mixed case // to separate words. The output will always start with a lower case letter. // This function is designed to produce JavaScript object property names. // Arguments: // - header: string to normalize // Examples: // "First Name" -> "firstName" // "Market Cap (millions) -> "marketCapMillions // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" function normalizeHeader(header) { var key = ""; var upperCase = false; for (var i = 0; i < header.length; ++i) { var letter = header[i]; if (letter == " " && key.length > 0) { upperCase = true; continue; } if (!isAlnum(letter)) { continue; } if (key.length == 0 && isDigit(letter)) { continue; // first character must be a letter } if (upperCase) { upperCase = false; key += letter.toUpperCase(); } else { key += letter.toLowerCase(); } } return key; } // Returns true if the cell where cellData was read from is empty. // Arguments: // - cellData: string function isCellEmpty(cellData) { return typeof(cellData) == "string" && cellData == ""; } // Returns true if the character char is alphabetical, false otherwise. function isAlnum(char) { return char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z' || isDigit(char); } // Returns true if the character char is a digit, false otherwise. function isDigit(char) { return char >= '0' && char <= '9'; } // Given a JavaScript 2d Array, this function returns the transposed table. // Arguments: // - data: JavaScript 2d Array // Returns a JavaScript 2d Array // Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]]. function arrayTranspose(data) { if (data.length == 0 || data[0].length == 0) { return null; } var ret = []; for (var i = 0; i < data[0].length; ++i) { ret.push([]); } for (var i = 0; i < data.length; ++i) { for (var j = 0; j < data[i].length; ++j) { ret[j][i] = data[i][j]; } } return ret; }
Writing Data from JavaScript Objects to a Spreadsheet
This section guides you through the steps of easily reading structured data
from a spreadsheet and populating a second spreadsheet with different views of
the data. It provides a helper function setRowsData
that you can
reuse in your scripts.
Setting up a spreadsheet and the example script
- Open the template spreadsheet for this tutorial.
- Create your own copy of the spreadsheet by clicking on File > Make a copy. You can now edit the spreadsheet and start writing scripts.
- Open the Script Editor from Tools > Script Editor.
- You should see a project with a single file named "writing_data".
The objective of this example is to:
- Read all the data from this other
spreadsheet
- Dynamically create one Sheet per department name
- Insert employee data (First Name, Last Name and Department) in the appropriate Sheet
Run the function runExample
and check out the results.
You should see:
- 5 different Sheets: Sheet1, then in alphabetical order Engineering,
Legal, Marketing, Sales
- Every department Sheet should contain the information about employees
in the that department.
The next section explains how the example code works.
Writing data to a spreadsheet
In this section, we will explain how therunExample
function works. Here's the full code for it:
function runExample() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheets()[0]; var dataSs = SpreadsheetApp.openById(DATA_SPREADSHEET_ID); var dataSheet = dataSs.getSheets()[0]; // Fetch all the data var data = getRowsData(dataSheet); // This is the data we want to display var columnNames = ["First Name", "Last Name", "Department"]; // Index data by department name var dataByDepartment = {}; var departments = []; for (var i = 0; i < data.length; ++i) { var rowData = data[i]; if (!dataByDepartment[rowData.department]) { dataByDepartment[rowData.department] = []; departments.push(rowData.department); } dataByDepartment[rowData.department].push(rowData); } departments.sort(); var headerBackgroundColor = dataSheet.getRange(1, 1).getBackgroundColor(); for (var i = 0; i < departments.length; ++i) { var sheet = ss.getSheetByName(departments[i]) || ss.insertSheet(departments[i], ss.getSheets().length); sheet.clear(); var headersRange = sheet.getRange(1, 1, 1, columnNames.length); headersRange.setValues([columnNames]); headersRange.setBackgroundColor(headerBackgroundColor); setRowsData(sheet, dataByDepartment[departments[i]]); } }
Here are some comments on the relevant parts:
var dataSs = SpreadsheetApp.openById(DATA_SPREADSHEET_ID);
This is how the spreadsheet that contains the data is opened.
DATA_SPREADSHEET_ID
is the Spreadsheet identified (
0AlNd4P4KLiq8cktUT0xINDFIT0syZ0xvc2Y3ZDZQMWc
), which can be extracted from this spreadsheet
URL
var data = getRowsData(dataSheet);
For every row of data, a JavaScript object is created. This is explained in
more detail in the section on Reading tables row-by-row into JavaScript Objects above. Note that
dataSheet
is not part of the active spreadsheet. In this example, data looks like:
[ {firstName: "John", lastName: "Berger", employeeId: 3512, department: "Sales", phoneNumber: "(212) 123-4560"}, {firstName: "Patrick", lastName: "Benson", employeeId: 1342, department: "Engineering", phoneNumber: "(212) 123-4561"}, ... ]
// Index data by department name var dataByDepartment = {}; var departments = []; for (var i = 0; i < data.length; ++i) { var rowData = data[i]; if (!dataByDepartment[rowData.department]) { dataByDepartment[rowData.department] = []; departments.push(rowData.department); } dataByDepartment[rowData.department].push(rowData); }
The code above simply creates a list of objects per department name.
departments.sort();
The list of department names is sorted alphabetically, so that all new Sheets are sorted.
var headerBackgroundColor = dataSheet.getRange(1, 1).getBackgroundColor();
The background color of the header cells in the data Sheet is extracted to be reused in the dynamically generated Sheets.
for (var i = 0; i < departments.length; ++i) { var sheet = ss.getSheetByName(departments[i]) || ss.insertSheet(departments[i], ss.getSheets().length); sheet.clear(); var headersRange = sheet.getRange(1, 1, 1, columnNames.length); headersRange.setValues([columnNames]); headersRange.setBackgroundColor(headerBackgroundColor); setRowsData(sheet, dataByDepartment[departments[i]]); }
For every department name found in the data Sheet:
- Locate the sheet which matches the name (or create a new one)
- Clear the contents of the sheet
- Set the column names in the first row of the sheet and set their background color
- For every Object in
dataByDepartment[departments[i]]
, fill in one row of data in the sheet. See details aboutsetRowsData
in the next section
Understanding how setRowsData
works
This section explains in detail how setRowsData
works.
// setRowsData fills in one row of data per object defined in the objects Array. // For every Column, it checks if data objects define a value for it. // Arguments: // - sheet: the Sheet Object where the data will be written // - objects: an Array of Objects, each of which contains data for a row // - optHeadersRange: a Range of cells where the column headers are defined. This // defaults to the entire first row in sheet. // - optFirstDataRowIndex: index of the first row where data should be written. This // defaults to the row immediately below the headers. function setRowsData(sheet, objects, optHeadersRange, optFirstDataRowIndex) { var headersRange = optHeadersRange || sheet.getRange(1, 1, 1, sheet.getMaxColumns()); var firstDataRowIndex = optFirstDataRowIndex || headersRange.getRowIndex() + 1;
Compute the Range of cells that contain the column headers if none is specified. Compute the index of the first data row if none is specified.
var headers = normalizeHeaders(headersRange.getValues()[0]);
Normalized headers are the names of object properties. Note that the Array of Strings may contain empty Strings.
var data = []; for (var i = 0; i < objects.length; ++i) { var values = [] for (j = 0; j < headers.length; ++j) { var header = headers[j]; // If the header is non-empty and the object value is 0... if ((header.length > 0) && (objects[i][header] == 0)) { values.push(0); } // If the header is empty or the object value is empty... else if ((!(header.length > 0)) || (objects[i][header]=='')) { values.push(''); } else { values.push(objects[i][header]); } } data.push(values); }
This loop generates a JavaScript 2-dimensional Array that correspond to values of spreadsheet cells to write. For each object, for each column check if the object defines a property corresponding to the column name. If it does, fill in the property value, otherwise fill in with an empty string.
var destinationRange = sheet.getRange(firstDataRowIndex, headersRange.getColumnIndex(), objects.length, headers.length);
Compute the Range of cells where data should be dumped based on the first data row index and the number of objects.
destinationRange.setValues(data);
Set the values of the spreadsheet cells from the 2-dimensional JavaScript Array
generated above. Note that rows of data below destinationRange
will not be cleared.
Full Sample Code
Example Code
// This is where the data used in this example will be retrieved from: // https://docs.google.com/spreadsheet/ccc?key=0AlNd4P4KLiq8cktUT0xINDFIT0syZ0xvc2Y3ZDZQMWc#gid=0 var DATA_SPREADSHEET_ID = "0AlNd4P4KLiq8cktUT0xINDFIT0syZ0xvc2Y3ZDZQMWc" function runExample() { var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheets()[0]; var dataSs = SpreadsheetApp.openById(DATA_SPREADSHEET_ID); var dataSheet = dataSs.getSheets()[0]; // Fetch all the data var data = getRowsData(dataSheet); // This is the data we want to display var columnNames = ["First Name", "Last Name", "Department"]; // Index data by department name var dataByDepartment = {}; var departments = []; for (var i = 0; i < data.length; ++i) { var rowData = data[i]; if (!dataByDepartment[rowData.department]) { dataByDepartment[rowData.department] = []; departments.push(rowData.department); } dataByDepartment[rowData.department].push(rowData); } departments.sort(); var headerBackgroundColor = dataSheet.getRange(1, 1).getBackgroundColor(); for (var i = 0; i < departments.length; ++i) { var sheet = ss.getSheetByName(departments[i]) || ss.insertSheet(departments[i], ss.getSheets().length); sheet.clear(); var headersRange = sheet.getRange(1, 1, 1, columnNames.length); headersRange.setValues([columnNames]); headersRange.setBackgroundColor(headerBackgroundColor); setRowsData(sheet, dataByDepartment[departments[i]]); } }
SetRowsData
// setRowsData fills in one row of data per object defined in the objects Array. // For every Column, it checks if data objects define a value for it. // Arguments: // - sheet: the Sheet Object where the data will be written // - objects: an Array of Objects, each of which contains data for a row // - optHeadersRange: a Range of cells where the column headers are defined. This // defaults to the entire first row in sheet. // - optFirstDataRowIndex: index of the first row where data should be written. This // defaults to the row immediately below the headers. function setRowsData(sheet, objects, optHeadersRange, optFirstDataRowIndex) { var headersRange = optHeadersRange || sheet.getRange(1, 1, 1, sheet.getMaxColumns()); var firstDataRowIndex = optFirstDataRowIndex || headersRange.getRowIndex() + 1; var headers = normalizeHeaders(headersRange.getValues()[0]); var data = []; for (var i = 0; i < objects.length; ++i) { var values = [] for (j = 0; j < headers.length; ++j) { var header = headers[j]; // If the header is non-empty and the object value is 0... if ((header.length > 0) && (objects[i][header] == 0)) { values.push(0); } // If the header is empty or the object value is empty... else if ((!(header.length > 0)) || (objects[i][header]=='')) { values.push(''); } else { values.push(objects[i][header]); } } data.push(values); } var destinationRange = sheet.getRange(firstDataRowIndex, headersRange.getColumnIndex(), objects.length, headers.length); destinationRange.setValues(data); }
Code reused from Reading Spreadsheet Data using JavaScript Objects
// getRowsData iterates row by row in the input range and returns an array of objects. // Each object contains all the data for a given row, indexed by its normalized column name. // Arguments: // - sheet: the sheet object that contains the data to be processed // - range: the exact range of cells where the data is stored // This argument is optional and it defaults to all the cells except those in the first row // or all the cells below columnHeadersRowIndex (if defined). // - columnHeadersRowIndex: specifies the row number where the column names are stored. // This argument is optional and it defaults to the row immediately above range; // Returns an Array of objects. function getRowsData(sheet, range, columnHeadersRowIndex) { var headersIndex = columnHeadersRowIndex || range ? range.getRowIndex() - 1 : 1; var dataRange = range || sheet.getRange(headersIndex + 1, 1, sheet.getMaxRows() - headersIndex, sheet.getMaxColumns()); var numColumns = dataRange.getLastColumn() - dataRange.getColumn() + 1; var headersRange = sheet.getRange(headersIndex, dataRange.getColumn(), 1, numColumns); var headers = headersRange.getValues()[0]; return getObjects(dataRange.getValues(), normalizeHeaders(headers)); } // For every row of data in data, generates an object that contains the data. Names of // object fields are defined in keys. // Arguments: // - data: JavaScript 2d array // - keys: Array of Strings that define the property names for the objects to create function getObjects(data, keys) { var objects = []; for (var i = 0; i < data.length; ++i) { var object = {}; var hasData = false; for (var j = 0; j < data[i].length; ++j) { var cellData = data[i][j]; if (isCellEmpty(cellData)) { continue; } object[keys[j]] = cellData; hasData = true; } if (hasData) { objects.push(object); } } return objects; } // Returns an Array of normalized Strings. // Empty Strings are returned for all Strings that could not be successfully normalized. // Arguments: // - headers: Array of Strings to normalize function normalizeHeaders(headers) { var keys = []; for (var i = 0; i < headers.length; ++i) { keys.push(normalizeHeader(headers[i])); } return keys; } // Normalizes a string, by removing all alphanumeric characters and using mixed case // to separate words. The output will always start with a lower case letter. // This function is designed to produce JavaScript object property names. // Arguments: // - header: string to normalize // Examples: // "First Name" -> "firstName" // "Market Cap (millions) -> "marketCapMillions // "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored" function normalizeHeader(header) { var key = ""; var upperCase = false; for (var i = 0; i < header.length; ++i) { var letter = header[i]; if (letter == " " && key.length > 0) { upperCase = true; continue; } if (!isAlnum(letter)) { continue; } if (key.length == 0 && isDigit(letter)) { continue; // first character must be a letter } if (upperCase) { upperCase = false; key += letter.toUpperCase(); } else { key += letter.toLowerCase(); } } return key; } // Returns true if the cell where cellData was read from is empty. // Arguments: // - cellData: string function isCellEmpty(cellData) { return typeof(cellData) == "string" && cellData == ""; } // Returns true if the character char is alphabetical, false otherwise. function isAlnum(char) { return char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z' || isDigit(char); } // Returns true if the character char is a digit, false otherwise. function isDigit(char) { return char >= '0' && char <= '9'; }