├── LICENSE ├── README.md ├── SpreadsheetManager.js └── SpreadsheetManager.ts /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David Cook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Upwork: https://www.upwork.com/freelancers/~0141d415013a6613d0 2 | 3 | # Spreadsheet Manager 4 | 5 | The first class to import into any Google Apps Script project using Google Sheets! 6 | 7 | I was fed up of typing out the same lines of code with every project so created this class to abstract out any repetitive logic to make life much easier when working in Google Apps Script ⏩ 8 | 9 | ## Init 🚀 10 | 11 | To use this in your project add your workbook and the sheet name as parameters: 12 | 13 | const ss = SpreadsheetApp.getActiveSpreadsheet(); 14 | const managedSheet = new SpreadsheetManager(ss, "Sheet1"); 15 | 16 | The default is for the column headers to be in row 1 of your spreadsheet. If this is not the case, set an alternative header row by adding an additional parameter to the constructor. 17 | 18 | const managedSheet = new SpreadsheetManager(ss, "Sheet1", {headerRow:2}); 19 | 20 | ## Updating values ✏ 21 | 22 | Instead of always having to call `getDataRange()` and then `getValues()` when working with a new sheet, this is all taken care of in the class constructor. 23 | 24 | The `SpreadsheetManager.values` attribute holds a 2d array of your spreadsheets values for you to manipulate without updating the sheet. 25 | 26 | When you are finished, run `SpreadsheetManager.updateAllValues()` to apply the changes to the sheet. In my scripts, this is nearly always the last line in my main functions. This method also calls `SpreadsheetApp.flush()` so it can be used at any point in the script without running into issues where the sheet hasn't fully updated before the next line is run. 27 | 28 | ## Loop through rows 🔁 29 | 30 | The number one use of Google Apps Script in Sheets is iterating through all of the rows in a dataset to get or update values. Normally this is done in a for loop with the column indexes referenced by number like so: 31 | 32 | for (let rowNumber = 0; rowNumber < dataValues.length; rowNumber++){ 33 | var valueFromColumnA = dataValues[rowNumber][0] 34 | var valueFromColumnB = dataValues[rowNumber][1] 35 | var valueFromColumnC = dataValues[rowNumber][2] 36 | } 37 | 38 | Not only is this cumbersome but it is also hard to maintain. If columns are added to or removed from the sheet, you have to update all of the column numbers. It's especially difficult to map the numbers to the column letters. 39 | 40 | SpreadsheetManager makes this so much easier. The `forEachRow()` function allows you to iterate through the rows via a callback function to which a custom `_Row` class is passed. `_Row` contains the `col` method which references cells by column name. For example: 41 | 42 | managedSpreadsheet.forEachRow((row, rowIndex) => { 43 | const id = row.col('id'); // gets the value from the 'id' column in the sheet 44 | const name = row.col('name'); // gets the value from the 'name' column in the sheet 45 | 46 | // To update a value, simply pass a second argument to the col method; 47 | const newPrice = 1000; 48 | row.col('price', newPrice); 49 | // This change is only applied to the managedSpreadsheet.values property. Don't forget to call updateAllValues() at the and to apply changes to the sheet. 50 | }); 51 | 52 | ## Rows as objects 👩‍💻 53 | 54 | By default, Google Apps Script treats spreadsheets as 2d arrays. SpreadsheetManager allows us to pass objects into a sheet to make it easier to keep track of what values mean and to allow for spreadsheets to be updated without breaking scripts. 55 | 56 | #### Get a row as an object 57 | 58 | In the `_Row` object described above, it is possible to get an entire row's values as a single object: 59 | 60 | managedSpreadsheet.forEachRow((row, rowIndex) => { 61 | const rowObj = row.createObject(); 62 | // now the rowObj acts as a dictionary with values mapped to column headers 63 | 64 | const newPrice = rowObj.price + 10; 65 | // note any changes to the rowObj will not be reflected in the values property of the SpreadsheetManager class. 66 | }); 67 | 68 | #### Adding objects to rows 69 | 70 | The `addNewRowsFromObjects` method allows you to pass an array of objects directly to the sheet's values, without needing to map anything. If the object's keys match the column headers, that row will be updated with the relevant value. If the key doesn't exist, it is ignored. 71 | 72 | This is particularly useful when working with API responses. 73 | 74 | const response = UrlFetchApp('http.....'); // Let's get an array of users from an API 75 | const json = JSON.parse(response); 76 | \* 77 | Assuming that the attributes in the API response match the column headers in our sheet, we can pass the response straight in. 78 | If not, mapping to an array of objects is just one additional step. 79 | */ 80 | managedSpreadsheet.addNewRowsFromObjects(json.users); 81 | managedSpreadsheet.updateAllValues() 82 | 83 | ## Values in a column 📈 84 | 85 | If you only need the values from a single column, `getValuesInColumn` makes life easy. Simply enter the column header name and decide if you want the results as a 2d or flat array. 86 | 87 | const ids_2d_array = managedSpreadsheet.getValuesInColumn('id') // returns all values from the id column [[1],[2],[3]] 88 | const ids_flat_array = managedSpreadsheet.getValuesInColumn('id', true) // returns [1,2,3] 89 | 90 | To just update a single column, use `pasteValuesToColumn`. 91 | 92 | // Changing ids from 1,2,3 to 10001, 10002, 10003 93 | const newIds = ids_flat_array.map(oldId => [oldId + 10000]); 94 | const newIdColumn = managedSpreadsheet.pasteValuesToColumn('id', newIds) 95 | 96 | # I hope this helps! 😀 97 | 98 | Please feel free to get in touch with feedback or make pull requests if you see any bugs or want to add new features 99 | 100 | -------------------------------------------------------------------------------- /SpreadsheetManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | Coded by Dave Cook 3 | www.davecookcodes.com 4 | */ 5 | 6 | class SpreadsheetManager { 7 | constructor(wb, sheetName, options) { 8 | const headerRow = options ? options.headerRow : 1; 9 | this.headerRow = headerRow; 10 | this.wb = wb; 11 | this.sheet = this.wb.getSheetByName(sheetName); 12 | if (!this.sheet) return; 13 | this.values = this.getSheetValues(headerRow); 14 | this.rowHeaders = this.getRowHeaders(this.values[0]); 15 | } 16 | 17 | /** 18 | * 19 | * 20 | * @param variable[][] or variable[] rows 21 | * @memberof SpreadsheetManager 22 | */ 23 | addNewRows(rows) { 24 | if (!typeof rows[0] === "object") { 25 | rows = [rows]; 26 | } 27 | const { sheet } = this; 28 | const lastRow = sheet.getLastRow(); 29 | const range = sheet.getRange(lastRow + 1, 1, rows.length, rows[0].length); 30 | range.setValues(rows); 31 | } 32 | 33 | /** 34 | * @param Object[] Array of object representing data to be entered into each row 35 | * each attribute of each object must be equivalent to an attribute in rowheaders 36 | * @memberof SpreadsheetManager 37 | */ 38 | addNewRowsFromObjects(objects = []) { 39 | const { rowHeaders } = this; 40 | const newRows = objects.map((obj) => { 41 | const newRow = []; 42 | for (let header in rowHeaders) { 43 | const colIndex = rowHeaders[header]; 44 | newRow[colIndex] = obj[header] || ""; 45 | } 46 | return newRow; 47 | }); 48 | this.addNewRows(newRows); 49 | } 50 | 51 | /** 52 | * 53 | * 54 | * @param _Row[] row 55 | * @returns object of values with column headers as keys 56 | * @memberof SpreadsheetManager 57 | */ 58 | createObjectFromRow(row) { 59 | const { rowHeaders } = this; 60 | const obj = {}; 61 | for (let key in rowHeaders) { 62 | try { 63 | obj[key] = row.col(rowHeaders[key]); 64 | } catch (err) { 65 | Logger.log(err); 66 | } 67 | } 68 | return obj; 69 | } 70 | 71 | clearSheet() { 72 | const { sheet, headerRow } = this; 73 | sheet 74 | .getRange(headerRow + 1, 1, sheet.getLastRow(), sheet.getLastColumn()) 75 | .clearContent(); 76 | SpreadsheetApp.flush(); 77 | } 78 | 79 | /** 80 | * 81 | * 82 | * @memberof SpreadsheetManager 83 | */ 84 | clearSheetAndPasteValues() { 85 | const { sheet, values } = this; 86 | sheet.getDataRange().clearContent(); 87 | sheet.getRange(1, 1, values.length, values[0].length).setValues(values); 88 | SpreadsheetApp.flush(); 89 | } 90 | 91 | /** 92 | * 93 | * @desc loops through all rows 94 | * @param {function} callback 95 | * @param {object} options can specify 'bottomUp' as true to reverse direction of loop 96 | * @memberof SpreadsheetManager 97 | */ 98 | forEachRow(callback, options = {}) { 99 | if (options.bottomUp) { 100 | for (let i = this.values.length - 1; i > 0; i--) { 101 | const row = new _Row(this.values[i], this.rowHeaders); 102 | const val = callback(row, i); 103 | if (val) return val; 104 | } 105 | } else { 106 | for (let i = 1; i < this.values.length; i++) { 107 | const row = new _Row(this.values[i], this.rowHeaders); 108 | const val = callback(row, i); 109 | if (val) return val; 110 | } 111 | } 112 | } 113 | /** 114 | * 115 | * 116 | * @returns array 117 | * @memberof SpreadsheetManager 118 | */ 119 | getLastRow() { 120 | const { values } = this; 121 | const lastRowIndex = values.length - 1; 122 | if (lastRowIndex >= 0) { 123 | return values[lastRowIndex]; 124 | } 125 | } 126 | /** 127 | * @desc creates an array to reference column number by header name 128 | * @param string[] topRow 129 | * @return obj - {header:int,header:int,...} 130 | */ 131 | 132 | getRowsAsObjects() { 133 | const objects = []; 134 | this.forEachRow((row) => { 135 | const obj = row.createObject(); 136 | objects.push(obj); 137 | }); 138 | return objects; 139 | } 140 | 141 | getRowHeaders(topRow) { 142 | const obj = {}; 143 | for (let c = 0; c < topRow.length; c++) { 144 | //removes line breaks and multiple spaces 145 | const cell = topRow[c] 146 | .replace(/(\r\n|\n|\r)/gm, " ") 147 | .replace(/\s\s+/g, " "); 148 | obj[cell] = c; 149 | } 150 | return obj; 151 | } 152 | /** 153 | * @desc sets values attribute for object 154 | * @return array of data from sheet 155 | */ 156 | getSheetValues() { 157 | const lastRow = this.sheet.getLastRow() + 1; 158 | const lastColumn = this.sheet.getLastColumn(); 159 | const values = this.sheet 160 | .getRange(this.headerRow, 1, lastRow - this.headerRow, lastColumn) 161 | .getValues(); 162 | return values; 163 | } 164 | /** 165 | * @desc gets values in column by column header name 166 | * @param string headerName 167 | * @param bool valuesOnly = when true, function returns 1d array. When false, 2d array 168 | * @return array of data from sheet 169 | */ 170 | getValuesInColumn(headerName, valuesOnly = false) { 171 | const { values, rowHeaders } = this; 172 | if (rowHeaders.hasOwnProperty(headerName)) { 173 | const columnIndex = rowHeaders[headerName]; 174 | 175 | return values.slice(1).map((row) => { 176 | const cell = valuesOnly ? row[columnIndex] : [row[columnIndex]]; 177 | return cell; 178 | }); 179 | } else { 180 | Logger.log(`${headerName} not found in row headers`); 181 | return false; 182 | } 183 | } 184 | /** 185 | * @desc paste formatted column into sheet by header name 186 | * @param string headerName 187 | */ 188 | pasteValuesToColumn(headerName, columnArray) { 189 | const { sheet, rowHeaders } = this; 190 | if (rowHeaders.hasOwnProperty(headerName)) { 191 | const columnIndex = rowHeaders[headerName]; 192 | 193 | const pasteRange = sheet.getRange( 194 | 2, 195 | columnIndex + 1, 196 | columnArray.length, 197 | 1 198 | ); 199 | const pasteAddress = pasteRange.getA1Notation(); 200 | pasteRange.setValues(columnArray); 201 | } else { 202 | Logger.log(`${headerName} not found in row headers`); 203 | return false; 204 | } 205 | } 206 | /** 207 | * @desc updates sheet with values from this.values; 208 | */ 209 | updateAllValues() { 210 | const { values, sheet } = this; 211 | sheet.getRange(1, 1, values.length, values[0].length).setValues(values); 212 | SpreadsheetApp.flush(); 213 | } 214 | } 215 | 216 | class _Row { 217 | /** 218 | *Creates an instance of _Row. 219 | * @param string[] row 220 | * @param object headers 221 | * @memberof _Row 222 | */ 223 | constructor(row, headers) { 224 | this.values = row; 225 | this.headers = headers; 226 | } 227 | 228 | createObject() { 229 | const { values, headers } = this; 230 | const obj = {}; 231 | for (let header in headers) { 232 | const index = headers[header]; 233 | obj[header] = values[index]; 234 | } 235 | return obj; 236 | } 237 | 238 | col(headerName, value) { 239 | const colIndex = this.headers[headerName]; 240 | try { 241 | if (value) { 242 | this.values[colIndex] = value; 243 | return; 244 | } else { 245 | return this.values[colIndex]; 246 | } 247 | } catch (err) { 248 | Logger.log(`${headerName} isn't a column in ${row.toString()}`, err); 249 | } 250 | } 251 | 252 | populate(obj) { 253 | const { headers } = this; 254 | for (let header in headers) { 255 | if (obj[header]) { 256 | const index = headers[header]; 257 | this.values[index] = obj[header]; 258 | } 259 | } 260 | } 261 | } 262 | 263 | // module.exports = SpreadsheetManager; 264 | -------------------------------------------------------------------------------- /SpreadsheetManager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | Coded by Dave Cook 3 | www.davecookcodes.com 4 | */ 5 | 6 | namespace SpreadsheetManagerTypes { 7 | export interface Options { 8 | headerRow: number; 9 | firstColumn?: number; 10 | lastColumn?: number; 11 | } 12 | export interface RowHeaders { 13 | [key: string]: number; 14 | } 15 | 16 | export type GenericRowValue = string | number | Date | boolean | undefined; 17 | 18 | export interface GenericRowObject { 19 | _rowIndex: number; 20 | [key: string]: SpreadsheetManagerTypes.GenericRowValue; 21 | } 22 | } 23 | 24 | interface SpreadsheetManager { 25 | row: SpreadsheetManagerTypes.GenericRowValue[]; 26 | wb: GoogleAppsScript.Spreadsheet.Spreadsheet; 27 | sheet: GoogleAppsScript.Spreadsheet.Sheet | null; 28 | values: SpreadsheetManagerTypes.GenericRowValue[][]; 29 | rowHeaders: SpreadsheetManagerTypes.RowHeaders; 30 | headerRow: number; 31 | firstColumn: number; 32 | lastColumn: number; 33 | } 34 | 35 | class SpreadsheetManager { 36 | constructor( 37 | wb: GoogleAppsScript.Spreadsheet.Spreadsheet, 38 | sheetName: string, 39 | options?: SpreadsheetManagerTypes.Options 40 | ) { 41 | const headerRow: number = options ? options.headerRow : 1; 42 | 43 | this.headerRow = headerRow; 44 | this.wb = wb; 45 | this.sheet = this.wb.getSheetByName(sheetName); 46 | this.firstColumn = options?.firstColumn || 1; 47 | this.lastColumn = 48 | options?.lastColumn || (this.sheet?.getLastColumn() as number); 49 | if (!this.sheet) return; 50 | this.values = this.getSheetValues(); 51 | this.rowHeaders = this.getRowHeaders(this.values[0]); 52 | } 53 | 54 | /** 55 | * 56 | * 57 | * @param variable[][] or variable[] rows 58 | * @memberof SpreadsheetManager 59 | */ 60 | addNewRows(rows: Array[]) { 61 | const { sheet } = this; 62 | if (!sheet) return; 63 | const lastRow = sheet.getLastRow(); 64 | const range = sheet.getRange(lastRow + 1, 1, rows.length, rows[0].length); 65 | range.setValues(rows); 66 | } 67 | 68 | /** 69 | * @param Object[] Array of object representing data to be entered into each row 70 | * each attribute of each object must be equivalent to an attribute in rowheaders 71 | * @memberof SpreadsheetManager 72 | */ 73 | addNewRowsFromObjects(objects: SpreadsheetManagerTypes.GenericRowObject[]) { 74 | const { rowHeaders } = this; 75 | const newRows = objects.map((obj) => { 76 | const newRow: Array = []; 77 | for (let header in rowHeaders) { 78 | const colIndex: number = rowHeaders[header]; 79 | if (obj[header] === undefined) { 80 | newRow[colIndex] = ""; 81 | } else { 82 | newRow[colIndex] = obj[header]; 83 | } 84 | } 85 | return newRow; 86 | }); 87 | this.addNewRows(newRows); 88 | } 89 | 90 | /** 91 | * 92 | * 93 | * @param _Row[] row 94 | * @returns object of values with column headers as keys 95 | * @memberof SpreadsheetManager 96 | */ 97 | createObjectFromRow(row: _Row) { 98 | const { rowHeaders } = this; 99 | const obj: SpreadsheetManagerTypes.GenericRowObject = { 100 | _rowIndex: row._rowIndex, 101 | }; 102 | for (let key in rowHeaders) { 103 | try { 104 | obj[key] = row.col(key); 105 | } catch (err) { 106 | Logger.log(err); 107 | } 108 | } 109 | return obj; 110 | } 111 | 112 | clearSheet(): void { 113 | if (!this.sheet) return; 114 | const { sheet, headerRow } = this; 115 | sheet 116 | .getRange(headerRow + 1, 1, sheet.getLastRow(), sheet.getLastColumn()) 117 | .clearContent(); 118 | this.values = this.values.slice(0, 1); 119 | SpreadsheetApp.flush(); 120 | } 121 | 122 | /** 123 | * 124 | * 125 | * @memberof SpreadsheetManager 126 | */ 127 | clearSheetAndPasteValues() { 128 | if (!this.sheet) return; 129 | const { sheet, values } = this; 130 | sheet.getDataRange().clearContent(); 131 | sheet.getRange(1, 1, values.length, values[0].length).setValues(values); 132 | SpreadsheetApp.flush(); 133 | } 134 | 135 | createRowFromObject(obj: SpreadsheetManagerTypes.GenericRowObject) { 136 | const newRow: Array = []; 137 | for (let header in this.rowHeaders) { 138 | const colIndex: number = this.rowHeaders[header]; 139 | if (obj[header] === undefined) { 140 | newRow[colIndex] = ""; 141 | } else { 142 | newRow[colIndex] = obj[header]; 143 | } 144 | } 145 | return newRow; 146 | } 147 | 148 | /** 149 | * 150 | * @desc loops through all rows 151 | * @param {function} callback 152 | * @param {object} options can specify 'bottomUp' as true to reverse direction of loop 153 | * @memberof SpreadsheetManager 154 | */ 155 | forEachRow(callback: Function, options?: { bottomUp?: boolean }) { 156 | if (options?.bottomUp) { 157 | for (let i = this.values.length - 1; i > 0; i--) { 158 | const row = new _Row( 159 | this.values[i], 160 | this.rowHeaders, 161 | this, 162 | i + this.headerRow 163 | ); 164 | const val = callback(row, i); 165 | if (val) return val; 166 | } 167 | } else { 168 | for (let i = 1; i < this.values.length; i++) { 169 | const row = new _Row( 170 | this.values[i], 171 | this.rowHeaders, 172 | this, 173 | i + this.headerRow 174 | ); 175 | const val = callback(row, i); 176 | if (val) return val; 177 | } 178 | } 179 | } 180 | /** 181 | * 182 | * 183 | * @returns array 184 | * @memberof SpreadsheetManager 185 | */ 186 | getLastRow() { 187 | const { values } = this; 188 | const lastRowIndex = values.length - 1; 189 | if (lastRowIndex >= 0) { 190 | return values[lastRowIndex]; 191 | } 192 | } 193 | /** 194 | * @desc creates an array to reference column number by header name 195 | * @param string[] topRow 196 | * @return obj - {header:int,header:int,...} 197 | */ 198 | getRowHeaders( 199 | topRow: SpreadsheetManagerTypes.GenericRowValue[] 200 | ): SpreadsheetManagerTypes.RowHeaders { 201 | const obj: SpreadsheetManagerTypes.RowHeaders = {}; 202 | for (let c = 0; c < topRow.length; c++) { 203 | //removes line breaks and multiple spaces 204 | const cell: string = String(topRow[c]) 205 | .replace(/(\r\n|\n|\r)/gm, " ") 206 | .replace(/\s\s+/g, " "); 207 | obj[cell] = c; 208 | } 209 | return obj; 210 | } 211 | 212 | getRowsAsObjects() { 213 | const objects: any[] = []; 214 | this.forEachRow((row: _Row) => { 215 | const obj = row.createObject(); 216 | objects.push(obj); 217 | }); 218 | return objects; 219 | } 220 | /** 221 | * @desc sets values attribute for object 222 | * @return array of data from sheet 223 | */ 224 | getSheetValues(): Array[] { 225 | if (!this.sheet) return [[]]; 226 | const lastRow = this.sheet.getLastRow() + 1; 227 | const values: Array[] = this.sheet 228 | .getRange( 229 | this.headerRow, 230 | this.firstColumn, 231 | lastRow - this.headerRow, 232 | this.lastColumn 233 | ) 234 | .getValues(); 235 | return values; 236 | } 237 | /** 238 | * @desc gets values in column by column header name 239 | * @param string headerName 240 | * @param bool valuesOnly = when true, function returns 1d array. When false, 2d array 241 | * @return array of data from sheet 242 | */ 243 | getValuesInColumn(headerName: string, valuesOnly = false) { 244 | const { values, rowHeaders } = this; 245 | if (rowHeaders.hasOwnProperty(headerName)) { 246 | const columnIndex = rowHeaders[headerName]; 247 | 248 | return values.slice(1).map((row) => { 249 | const cell = valuesOnly ? row[columnIndex] : [row[columnIndex]]; 250 | return cell; 251 | }); 252 | } else { 253 | Logger.log(`${headerName} not found in row headers`); 254 | return false; 255 | } 256 | } 257 | /** 258 | * @desc paste formatted column into sheet by header name 259 | * @param string headerName 260 | */ 261 | pasteValuesToColumn( 262 | headerName: string, 263 | columnArray: Array[] 264 | ) { 265 | if (!this.sheet) return; 266 | const { sheet, rowHeaders } = this; 267 | if (rowHeaders.hasOwnProperty(headerName)) { 268 | const columnIndex = rowHeaders[headerName]; 269 | 270 | const pasteRange = sheet.getRange( 271 | this.headerRow + 1, 272 | columnIndex + 1, 273 | columnArray.length, 274 | 1 275 | ); 276 | const pasteAddress = pasteRange.getA1Notation(); 277 | pasteRange.setValues(columnArray); 278 | } else { 279 | Logger.log(`${headerName} not found in row headers`); 280 | return false; 281 | } 282 | } 283 | /** 284 | * @desc updates sheet with values from this.values; 285 | */ 286 | updateAllValues() { 287 | if (!this.sheet) return; 288 | const { values, sheet } = this; 289 | sheet 290 | .getRange( 291 | this.headerRow, 292 | this.firstColumn, 293 | values.length, 294 | values[0].length 295 | ) 296 | .setValues(values); 297 | SpreadsheetApp.flush(); 298 | } 299 | 300 | updateOneRow(row: SpreadsheetManagerTypes.GenericRowObject) { 301 | const newRow = this.createRowFromObject(row); 302 | this.sheet 303 | ?.getRange(row._rowIndex, this.firstColumn, 1, newRow.length) 304 | .setValues([newRow]); 305 | } 306 | } 307 | 308 | interface _Row { 309 | _rowIndex: number; 310 | values: SpreadsheetManagerTypes.GenericRowValue[]; 311 | headers: SpreadsheetManagerTypes.RowHeaders; 312 | parent: SpreadsheetManager; 313 | } 314 | class _Row { 315 | /** 316 | *Creates an instance of _Row. 317 | * @param string[] row 318 | * @param object headers 319 | * @memberof _Row 320 | */ 321 | constructor( 322 | row: SpreadsheetManagerTypes.GenericRowValue[], 323 | headers: SpreadsheetManagerTypes.RowHeaders, 324 | parent: SpreadsheetManager, 325 | rowIndex: number 326 | ) { 327 | this._rowIndex = rowIndex; 328 | this.values = row; 329 | this.headers = headers; 330 | this.parent = parent; 331 | } 332 | 333 | createObject() { 334 | const { values, headers } = this; 335 | const obj: { [key: string]: any } = {}; 336 | for (let header in headers) { 337 | const index = headers[header]; 338 | obj[header] = values[index]; 339 | obj._rowIndex = this._rowIndex; 340 | } 341 | return obj; 342 | } 343 | 344 | col( 345 | headerName: string, 346 | value?: any 347 | ): SpreadsheetManagerTypes.GenericRowValue { 348 | const colIndex = this.headers[headerName]; 349 | try { 350 | if (value) { 351 | this.values[colIndex] = value; 352 | return value; 353 | } else { 354 | return this.values[colIndex]; 355 | } 356 | } catch (err) { 357 | Logger.log(`${headerName} isn't a column`, err); 358 | return ""; 359 | } 360 | return ""; 361 | } 362 | 363 | update() { 364 | const { parent } = this; 365 | parent.sheet 366 | ?.getRange(this._rowIndex, 1, 1, this.values.length) 367 | .setValues([this.values]); 368 | } 369 | } 370 | --------------------------------------------------------------------------------