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

时间:2013-03-05 19:25:19

标签: google-apps-script google-sheets google-apps-script-addon

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

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

会触发onEdit吗?如果是这样,我想在ScriptDb中维护行数或列数,然后检查每次都会这样做,但是时间成本很高,因为getMaxRows()已经很慢了,并且接触到ScriptDb就像好。

您怎么看?

5 个答案:

答案 0 :(得分:22)

Google添加了一个“On Change”事件,可检测行/列插入/删除以及其他类型的更改,您可以在changeType的允许值下看到here的类型。以下是here中有关如何向项目添加触发器的说明,以便在“On Change”事件发生时调用您的函数。

  

要通过脚本编辑器中的对话框手动创建可安装触发器,请执行以下步骤:

     
      
  1. 从脚本编辑器中选择编辑>当前项目的触发器
  2.   
  3. 点击以下链接:未设置触发器。点击此处立即添加一个
  4.   
  5. 运行下,选择要触发的功能名称。
  6.   
  7. 活动下,选择时间驱动或脚本为bound的Google App(例如,来自电子表格强>)。
  8.   
  9. 选择并配置您要创建的触发器类型(例如,小时计时器运行每小时打开时触发)。
  10.   
  11. (可选)点击通知,以配置触发功能失败后如何以及何时通过电子邮件与您联系。
  12.   
  13. 点击保存
  14.   

在第4步中,您可以选择从电子表格,在步骤5中,您可以选择在更改时。这应该具有您正在寻找的效果。如果您尝试在附加组件中使用此选项以分发给用户,还可以选择以编程方式添加触发器并请求授权。两者都在Installable Triggers文档中详细说明。

答案 1 :(得分:16)

有许多编辑操作无法触发onEdit(),这不是一个全面的列表,有many more reported exclusions

如果您想知道电子​​表格中有多少行,则需要120毫秒才能执行:

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

我已经展示it's faster to write a value to a sheet而不是使用ScriptDB。你可以期待一个微不足道的时间来写一个小范围,大约1ms。

因此,如果 可以检测到要添加的行或列,则注册更改的时间不会超过二十分之一秒。此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.)");
  }
}

答案 2 :(得分:3)

我最近刚刚使用过另一种方式。每次触发onEdit()时,它都会返回event object(e),为您提供有关正在发生的事情的一些有价值的信息。

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

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

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

//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;
}

答案 3 :(得分:2)

我遇到了麻烦,直到我赋予脚本权限。否则,PropertiesService功能将无效。一旦我这样做,我能够通过以下代码检测插入了哪一行:

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"));
}

答案 4 :(得分:0)

我一直在玩onEdit和onChange。 onEdit响应允许您访问已编辑的行。不幸的是,onChange响应不允许你这样做。因此,对于强大的解决方案,您似乎需要吸引两个触发器。如果工作表不需要空行/列,则以下脚本将删除所有新添加的行/列,删除所有空行/列(如果用户批量添加行/列),然后警告用户他们无法添加行或列列:

//////////////////////
// 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.');
}