【问题标题】:Identifying Form destination (Spreadsheet AND SHEET)识别表格目的地(电子表格和表格)
【发布时间】:2013-05-27 14:43:10
【问题描述】:

我正在编写一个与 Google 表单的响应表交互的脚本。

FormApp.getActiveForm().getDestinationId()

给我电子表格 ID,但我找不到获取表格本身的方法。用户可以更改它的名称和位置,所以我需要获取它的 id,比如在

Sheet.getSheetId()

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

Form.getItems().length

然后搜索gridItems,将每行的行数相加,相加减一:

+ gridItem.getRows().length - 1

最后,我认为没有办法将每个问题与工作表中的每一列联系起来,而是通过以某种方式将列名称与项目标题进行比较。

谢谢

【问题讨论】:

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


    【解决方案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;
    }
    

    【讨论】:

      【解决方案2】:

      现在有一种方法可以验证具有多个链接表格的 Google 表格文件中的哪个表格对应于当前表格 - 通过使用 Sheet#getFormUrl(),它已添加到 2017 中的 Sheet 类中。

      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

      【讨论】:

      • 这使得matches[0] 为未定义。我前面有电子表格.flush???为什么代码看不到我所看到的 - 链接到表单的新响应表。
      【解决方案3】:

      我也需要这个,但值得注意的是,仍然没有应用程序脚本方法可以促进它。最后,我着手寻找一种可靠的方法来确定工作表 id,这就是我通过编程解决方法得到的结果:

      1. 添加一个临时表单项,其标题是随机字符串(或类似的合适的字符串)
      2. 等待新的对应列添加到目标工作表(通常需要几秒钟)
      3. 查看目标中的每个工作表,直到在标题行中找到这个新的表单项标题字符串
      4. 删除已添加的临时表单项
      5. 等待工作表中的相应列与表单取消链接并变为可删除(通常需要几秒钟)
      6. 删除临时表单项对应的列
      7. 返回工作表 ID

      我相信有些人不会喜欢这种方法,因为它会修改表单和电子表格,但它确实运作良好。

      加上必要的等待时间,执行所有查找/清理操作大约需要 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);
        }
      }

      【讨论】:

        【解决方案4】:

        要获取电子表格,一旦获得 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
        

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

        最后,我认为没有办法将每个问题与工作表中的每一列联系起来,而是通过以某种方式将列名称与项目标题进行比较。

        【讨论】:

        • 感谢您的回答。在我看来,没有办法得到响应表;我们只能假设它是零索引一。可悲的是,我正在处理许多可移动的工作表......你计算标题的方式比我的好。我在右边添加了我自己的标题,但我只需要识别它们就可以不计算它们。
        • +YoArgentino IMO,无法以编程方式检索目标 sheet 确实有点疏忽。您可能想在问题跟踪器中提出问题:code.google.com/p/google-apps-script-issues/issues/list
        • 我想您可以通过分析“表单提交”触发器中传递的参数来访问它:function myFunction(e) { Logger.log(e.range.getSheet().getName()); },但这似乎是一个笨拙的解决方法。
        • 虽然工作表可能会移动,但如果您获得正确的一次,您可以将其 ID 存储为脚本属性 - 然后无论它被重命名为什么或移动到哪里,您都会可以通过ID打开它。
        • @Mogstad,谢谢,这正是我正在做的,在第一次提示用户输入工作表名称之后。
        猜你喜欢
        • 1970-01-01
        • 2022-11-28
        • 1970-01-01
        • 2023-03-29
        • 2019-02-24
        • 2017-10-19
        • 2015-02-15
        • 2018-02-10
        • 2021-09-07
        相关资源
        最近更新 更多