识别表单目的地(电子表格和表格)

时间:2013-05-27 14:43:10

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

我正在制作一个与Google表格的回复表互动的脚本。

FormApp.getActiveForm().getDestinationId()

给我电子表格ID,但我找不到获取表格的方法。用户可以更改其名称和位置,因此我需要获取其ID,例如

Sheet.getSheetId()

我还必须确定响应使用的列数。它不等于表格中的问题数量。我可以计算表格中的项目数量:

Form.getItems().length

然后搜索gridItems,在每个中添加行数并将它们减去1:

+ gridItem.getRows().length - 1

最后,我认为没有办法将每个问题与表格中的每一列相关联,而是通过比较某些列名称与项目标题。

谢谢

4 个答案:

答案 0 :(得分:3)

现在,有一种方法可以通过使用Sheet#getFormUrl()来验证Google表格文件中具有多个链接表单的哪个工作表与当前表单相对应,该方法已添加到{中的Sheet类中{3}}。

function getFormResponseSheet_(wkbkId, formUrl) {
  const matches = SpreadsheetApp.openById(wkbkId).getSheets().filter(
    function (sheet) {
      return sheet.getFormUrl() === formUrl;
    });
  return matches[0]; // a `Sheet` or `undefined`
}
function foo() {
  const form = FormApp.getActiveForm();
  const destSheet = getFormResponseSheet_(form.getDestinationId(), form.getPublishedUrl());
  if (!destSheet)
    throw new Error("No sheets in destination with form url '" + form.getPublishedUrl() + "'");
  // do stuff with the linked response destination sheet.
}

如果您取消了表单和目标电子表格的链接,那么显然您将无法使用getDestinationIdgetFormUrl

答案 1 :(得分:1)

我也需要这个,并且显然仍然没有便于它的应用程序脚本方法。最后,我着手寻找一种可靠的方法来确定工作表ID,这就是我通过编程方法解决的问题:

  1. 添加一个临时表单项,其标题是随机字符串(或类似的东西)
  2. 等待新的相应列添加到目标工作表(通常需要几秒钟)
  3. 查看目标中的每个工作表,直到在标题行中找到此新表单项标题字符串
  4. 删除已添加的临时表单项
  5. 等待工作表中的相应列从表单中取消链接并变为可删除(通常需要几秒钟)
  6. 删除与临时表单项
  7. 对应的列
  8. 返回工作表ID
  9. 我确定有些人不喜欢这种方式,因为它会修改表单和电子表格,但它确实运行良好。

    如果包含必要的等待时间,则执行所有查找/清理操作大约需要12秒。

    这是我方法的代码,以防其他人想要使用它。

    
    
    // Takes Apps Script 'Form' object as single paramater
    // The second parameter 'obj', is for recursion (do not pass a second parameter)
    // Return value is either: 
    // - null (if the form is not linked to any spreadsheet)
    // - sheetId [int]
    // An error is thrown if the operations are taking too long
    
    function getFormDestinationSheetId(form, obj) {
    
      var obj = obj || {}; // Initialise object to be passed between recursions of this function
    
      obj.attempts = (obj.attempts || 1);
    
      Logger.log('Attempt #' + obj.attempts);
    
      if (obj.attempts > 14) {
        throw 'Unable to determine destination sheet id, too many failed attempts, taking too long. Sorry!';
      }
    
      obj.spreadsheetId = obj.spreadsheetId || form.getDestinationId();
    
      if (!obj.spreadsheetId) {
        return null; // This means there actually is no spreadsheet destination set at all.
    
      } else {
        var tempFormItemTitle = '### IF YOU SEE THIS, PLEASE IGNORE! ###';
    
        if (!obj.tempFormItemId && !obj.sheetId) { // If the sheet id exists from a previous recusion, we're just in a clean up phase
          // Check that temp item does not already exist in form
          form.getItems(FormApp.ItemType.TEXT).map(function(textItem) {
            var textItemTitle = textItem.getTitle();
            Logger.log('Checking against form text item: ' + textItemTitle);
            if (textItemTitle === tempFormItemTitle) {
              obj.tempFormItemId = textItem.getId();
              Logger.log('Found matching form text item reusing item id: ' + obj.tempFormItemId);
            }
            return 0;
          }); // Note: Just using map as handy iterator, don't need to assign the output to anything
    
          if (!obj.tempFormItemId) {
            Logger.log('Adding temporary item to form');
            obj.tempFormItemId = form.addTextItem().setTitle(tempFormItemTitle).getId();
          }
        }
    
        obj.spreadsheet = obj.spreadsheet || SpreadsheetApp.openById(obj.spreadsheetId);
        obj.sheets = obj.sheets || obj.spreadsheet.getSheets();
        obj.sheetId = obj.sheetId || null;
    
        var sheetHeaderRow = null;
    
        for (var i = 0, x = obj.sheets.length; i < x; i++) {
          sheetHeaderRow = obj.sheets[i].getSheetValues(1, 1, 1, -1)[0];
    
          for (var j = 0, y = sheetHeaderRow.length; j < y; j++) {
            if (sheetHeaderRow[j] === tempFormItemTitle) {
              obj.sheetId = obj.sheets[i].getSheetId();
              Logger.log('Temporary item title found in header row of sheet id: ' + obj.sheetId);
              break;
            }
          }
          if (obj.sheetId) break;
        }
    
        // Time to start cleaning things up a bit!
        if (obj.sheetId) {
    
          if (obj.tempFormItemId) {
            try {
              form.deleteItem(form.getItemById(obj.tempFormItemId));
              obj.tempFormItemId = null;
              Logger.log('Successfully deleted temporary form item');
            } catch (e) {
              Logger.log('Tried to delete temporary form item, but it seems it was already deleted');
            }
          }
    
          if (obj.sheetId && !obj.tempFormItemId && !obj.tempColumnDeleted) {
            try {
              obj.sheets[i].deleteColumn(j + 1);
              obj.tempColumnDeleted = true;
              Logger.log('Successfully deleted temporary column');
            } catch (e) {
              Logger.log('Could not delete temporary column as it was still attached to the form');
            }
          }
    
          if (!obj.tempFormItemId && obj.tempColumnDeleted) {
            Logger.log('Completed!');
            return obj.sheetId;
          }
        }
    
        SpreadsheetApp.flush(); // Just in case this helps!
    
        // Normally this process takes three passes, and a delay of 4.5 secs seems to make it work in only 3 passes most of the time
        // Perhaps if many people are submitting forms/editing the spreadsheet, this delay would not be long enough, I don't know.
        obj.delay = ((obj.delay || 4500));
    
        // If this point is reached then we're not quite finished, so try again after a little delay
        Logger.log('Delay before trying again: ' + obj.delay / 1000 + ' secs');
        Utilities.sleep(obj.delay);
        obj.attempts++;
    
        return getFormDestinationSheetId(form, obj);
      }
    }
    &#13;
    &#13;
    &#13;

答案 2 :(得分:1)

@tehhowch非常接近正确答案,但是代码存在问题:无法保证form.getPublishedUrl()sheet.getFormUrl()将返回完全相同的字符串。在我的情况下,form.getPublishedUrl()返回了一个以https://docs.google.com/forms/d/e/{id}/viewform形式形成的URL,而sheet.getFormUrl()返回了https://docs.google.com/forms/d/{id}/viewform。由于表单ID是URL的一部分,因此更可靠的实现是:

function get_form_destination_sheet(form) {
    const form_id = form.getId();
    const destination_id = form.getDestinationId();
    if (destination_id) {
        const spreadsheet = SpreadsheetApp.openById(destination_id);
        const matches = spreadsheet.getSheets().filter(function (sheet) {
            const url = sheet.getFormUrl();
            return url && url.indexOf(form_id) > -1;
        });
        return matches.length > 0 ? matches[0] : null; 
    }
    return null;
}

答案 3 :(得分:0)

要获取电子表格,一旦您拥有DestinationID,请使用SpreadsheetApp.openById()。完成后,您可以检索工作表数组,并按索引获取响应表,无论其名称如何。

var destId = FormApp.getActiveForm().getDestinationId();
var ss = SpreadsheetApp.openById(destId);
var respSheet = ss.getSheets()[0];  // Forms typically go into sheet 0.
...

从这一点来说,您可以使用其他电子表格服务方法操纵电子表格中的数据。

  

我还必须确定响应使用的列数。它不等于表格中的问题数量。我可以计算表单中的项目数...(但这与电子表格不匹配)

你是对的 - 当前项目的数量不等于电子表格中的列数。每个响应在目标表中占用的列数包括已从表单中删除的任何问题,并排除非问题的项目。此外,电子表格中列的顺序是创建问题的顺序 - 当您重新排列表单或插入新问题时,电子表格列顺序不会反映新订单。

假设电子表格中的唯一列来自表单,以下是您可以使用它们的方法:

...
var data = respSheet.getDataRange().getValues(); // 2d array of form responses
var headers =  data[0];  // timestamp and all questions
var numColumns = headers.length;  // count headers
var numResponses = data.length - 1; // count responses

你的最后一点是正确的,你需要关联名称。

  

最后,我认为没有办法将每个问题与表格中的每一列相关联,而是通过比较某些列名称与项目标题。