检测用户在Google电子表格中插入行或列,并在脚本中作出反应

在Google Apps脚本中,其中一个基本工具是电子表格中的onEdit触发器,它使我们能够检测用户何时编辑单元格并对其作出反应。

用户插入行或列时如何? 有没有办法来检测?

会触发一个onEdit? 如果是这样的话,我想在ScriptDb中保留一些行数或列数,然后检查每一次会做什么,但是会花费很多时间,因为getMaxRows()已经非常慢了,而且到达ScriptDb的时间是好。

你怎么看 ?

有许多编辑操作不会触发onEdit() ,这不是一个全面的列表,还有更多的报告排除 :

  • 表格提交问题4568 。
  • 插入或删除行或列问题1363
  • 问题2476的外观变化(字体,格式,alignment,边框)
  • 范围填充(例如,从1,2,3开始;select这些单元格,然后拖动“句柄”以用4,5,6填充更多单元格…) 问题399
  • 由脚本编写的值。 问题338
  • 查找/replace问题1754
  • 第1568期 , 第1119期

如果您确实想知道电子​​表格中有多less行,则需要大约120ms才能执行:

 var numCols = SpreadsheetApp.getActiveSheet().getRange("1:1").getLastColumn(); var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow(); 

我已经表明, 将一个值写入表单比使用ScriptDB要快。 你可以预计一个微不足道的时间写一个小范围,大约1ms。

因此,如果您可以检测到要添加的行或列,那么注册更改所花费的时间不会超过二分之一秒。 这个onEdit()演示了一种技术来测量电子表格的范围,并报告表单维度的变化。 (要testing,添加或删除行或列,然后进行触发onEdit()的编辑onEdit() 。它还包含计时器 – 随时尝试其他测量和/或存储值的方法,以查看哪些方法最适合您。

 function onEdit() { // Use start & stop to time operations var start = new Date().getTime(); // We want the size of the sheet, so will select ranges across and down the // whole sheet. Cannot use getDataRange(), as it selects only occupied cells. var numCols = SpreadsheetApp.getActiveSheet().getRange("1:1").getLastColumn() var numRows = SpreadsheetApp.getActiveSheet().getRange("A:A").getLastRow(); var stop = new Date().getTime(); var timeToMeasure = (stop-start); // Did things change? var oldSize = SpreadsheetApp.getActiveSheet().getRange("A1:B1").getValues(); if (oldSize[0][0] != numCols || oldSize[0][1] != numRows) { // Yes, they did - Let's store the new dimensions start = new Date().getTime(); SpreadsheetApp.getActiveSheet().getRange("A1:B1").setValues([[numCols,numRows]]); var stop = new Date().getTime(); var timeToStore = (stop-start); Browser.msgBox("Sheet is "+numCols+" by "+numRows+"." +" ("+timeToMeasure+"ms to measure, "+timeToStore+"ms to store.)"); } } 

Google添加了一个“On Change”事件,该事件检测行/列插入/删除以及其他types的更改,您可以在 changeType的允许值下看到这些 changeType 。 以下是从这里的详细说明如何添加一个触发器到您的项目的指示,以便您可能有“On Change”事件时调用您的函数。

要通过脚本编辑器中的对话框手动创build可安装的触发器,请按照下列步骤操作:

  1. 在脚本编辑器中,select“ 编辑”>“当前项目的触发器”
  2. 点击链接,说: 没有触发器设置。 点击这里现在添加一个
  3. “运行”下 ,select要触发的function的名称。
  4. 事件下 ,select时间驱动或脚本绑定的Google App(例如, 从电子表格 )。
  5. select并configuration要创build的触发器的types(例如, 每小时运行一次小时计时器开启触发器)。
  6. 或者,点击“ 通知” ,configuration您的触发function失败时,如何以及何时通过电子邮件与您联系。
  7. 点击保存

在步骤4中,您可以select从电子表格 ,在步骤5中select开启更改 。 这应该有你要找的效果。 如果您正尝试在附加组件中将其用于分发给用户,则还可以通过编程方式添加触发器并请求授权。 两者都在“可安装触发器”文档中详细介绍。

还有另一种方法,我最近刚刚使用。 每次onEdit()被触发时,它都会返回一个事件对象 (e),它提供了一些有关正在发生的事情的有价值的信息。

例如,它给你的范围,你可以从e.range检索。 从那里你可以用许多不同的方式来横切,例如,知道正在编辑哪一行。 但是e对象中还有更多有用的数据。 它为您提供您编辑的单元格的“oldvalue”( e.oldValue )和新的值( e.value )。

将所有这些信息混合在一起的一种可能的方法是获得与正在编辑的行对应的范围,然后检查单元格是否为空(但是您刚编辑的单元格)以及是否没有oldValue。

这不一定对应于电子表格的最后一行,而是一个空行。 如果您与填写数据的方式一致,则可能适用于您:

 //val = inserted value (e.value); //old = old Value (e.oldValue); //col = number of column being edited //arr = array with the indexes of the columns that should be completed so as to make a new row [0,1,2...n] function isInsert(old, val, col, arr){ if((typeof val != "object")&&!old&&(arr.some(isNotEmpty, col))) return true; else return false; } function isNotEmpty(el){ if(this == el) return true; } 

直到我给脚本授权之前,我一直有这个麻烦。 否则,PropertiesServicefunction将不起作用。 一旦我做了,我能够检测到哪一行被插入以下代码:

 var props = PropertiesService.getUserProperties(); function onEdit(e) { props.setProperty("firstRow", e.range.getRow()); props.setProperty("lastRow", e.range.getLastRow()); } function onChange(e){ if(e.changeType=="INSERT_ROW") SpreadsheetApp.getUi().alert("Inserted Rows: " + props.getProperty("firstRow") + " - " + props.getProperty("lastRow")); } 

我一直在玩onEdit和onChange。 onEdit响应允许您访问编辑的行。 不幸的是,onChange响应不允许你这样做。 所以,为了一个健壮的解决scheme,看起来你需要吸引两个触发器。 如果您的工作表不需要空行/列,则下面的脚本将删除所有新添加的行/列,删除所有空白行/列(以防用户批量添加行/列),然后警告用户不能添加行或列:

 ////////////////////// // Global Variables // ////////////////////// var SHEET = SpreadsheetApp.getActiveSheet(); var PROPERTIES = PropertiesService.getScriptProperties(); //////////////////// // Event Triggers // //////////////////// /** * Track original sheet row/column count and register onChange trigger. */ function onOpen() { // Set original dimensions PROPERTIES.setProperty('rows', SHEET.getMaxRows()); PROPERTIES.setProperty('columns', SHEET.getMaxColumns()); // Create onChange trigger ScriptApp .newTrigger('deleteNewRowsAndColumns') .forSpreadsheet(SpreadsheetApp.getActive()) .onChange() .create(); } /** * If new rows or columns were added to the sheet * warn the user that they cannot perform these * actions and delete empty (new) rows and columns. * * @param e */ function deleteNewRowsAndColumns(e) { switch(e.changeType) { case 'INSERT_COLUMN': removeEmptyColumns(); warn(); break; case 'INSERT_ROW': removeEmptyRows(); warn(); break; default: return } } /////////////// // Utilities // /////////////// /** * Remove empty columns. * * This function assumes you have a header row in which * all columns should have a value. Change headerRow value * if your headers are not in row 1. */ function removeEmptyColumns() { var maxColumns = SHEET.getMaxColumns(); var lastColumn = SHEET.getLastColumn(); if (maxColumns - lastColumn != 0) { // New column(s) were added to the end of the sheet. SHEET.deleteColumns(lastColumn + 1, maxColumns - lastColumn); } else { // New column was added in the middle of the sheet. // Start from last column and work backwards, delete // first column found with empty header cell. var headerRow = 1; var headers = SHEET.getRange(headerRow, 1, 1, lastColumn).getValues()[0]; for (var col = lastColumn; col >= 1; col--) { if (headers[col -1] == '') { SHEET.deleteColumn(col); // Since can only insert one column to the left // or right at a time, can safely exit here; break; } } } } /** * Remove empty rows. * * This function assumes that all rows should * have data in the first cell. */ function removeEmptyRows() { var maxRows = SHEET.getMaxRows(); var lastRow = SHEET.getLastRow(); if (maxRows-lastRow != 0) { // New row(s) were added to the end of the sheet. SHEET.deleteRows(lastRow + 1, maxRows - lastRow); } else { // New row was added in the middle of the sheet. // Start from last column and work backwards, delete // first empty column found. var values = SHEET.getRange('A:A').getValues(); var startIndex = values.length - 1; for (var i = startIndex; i >= 0; i--) { if (values[i] && values[i][0] == '') { SHEET.deleteRow(i + 1); // User can bulk add rows to the bottom of the file // but can only add 1 above or below at a time in the // middle of the file, so it's safe to exit here. break; } } } } /** * Return user warning message about adding new rows and columns */ function warn() { SpreadsheetApp.getUi().alert('You cannot add new rows or columns.'); }