Google 应用程序脚本可以用于随机化 Google 表单上的页面顺序吗?

Can Google apps script be used to randomize page order on Google forms?

更新 #2: 好的,我很确定我在更新 #1 中的错误是因为索引超出数组范围(我仍然不习惯JS 索引为 0)。但这是新问题......如果我手动写出循环的不同组合,在 moveItem() 中将页面索引设置为 1,如下所示:

newForm.moveItem(itemsArray[0][0], 1);
newForm.moveItem(itemsArray[0][1], 1);
newForm.moveItem(itemsArray[0][2], 1);
newForm.moveItem(itemsArray[1][0], 1);
newForm.moveItem(itemsArray[1][1], 1);
newForm.moveItem(itemsArray[1][2], 1);
newForm.moveItem(itemsArray[2][0], 1);
...

...我没有收到任何错误,但项目最终出现在不同的页面上!怎么回事?


更新 #1::使用 Sandy Good 的回答以及我在 this WordPress 博客上找到的脚本,我已经设法接近我需要的东西.我认为 Sandy Good 误解了我想做的事情,因为我的问题不够具体。

我愿意:

  1. 获取页面中的所有项目(部分 header、图像、问题等)
  2. 将它们放入一个数组
  3. 对所有页面执行此操作,将这些数组添加到一个数组中(即:[[all items from page 1][all items from page 2][all items from page 3]...]
  4. 打乱这个数组的元素
  5. 用这个数组的每个元素重新填充一个新表单。这样,页面顺序将被随机化。

我的JavaScript技能很差(第一次用)。有一个步骤会产生空条目,我不知道为什么......我不得不手动删除它们。我无法完成第 5 步,因为出现以下错误:

Cannot convert Item,Item,Item to (class).

"Item,Item,Item" 是包含特定页面中所有项目的数组元素。所以我似乎不能一次向页面添加三个项目?还是这里发生了其他事情?

这是我的代码:

function shuffleForms() {
  var itemsArray,shuffleQuestionsInNewForm,fncGetQuestionID,
      newFormFile,newForm,newID,shuffle, sections;

  // Copy template form by ID, set a new name
  newFormFile = DriveApp.getFileById('1prfcl-RhaD4gn0b2oP4sbcKaRcZT5XoCAQCbLm1PR7I')
               .makeCopy();
  newFormFile.setName('AAAAA_Shuffled_Form');

  // Get ID of new form and open it
  newID = newFormFile.getId();
  newForm = FormApp.openById(newID);

  // Initialize array to put IDs in
  itemsArray = [];

  function getPageItems(thisPageNum) {
    Logger.log("Getting items for page number: " + thisPageNum );
    var thisPageItems = []; // Used for result
    var thisPageBreakIndex = getPageItem(thisPageNum).getIndex();
    Logger.log( "This is index num : " + thisPageBreakIndex );

    // Get all items from page
    var allItems = newForm.getItems();
    thisPageItems.push(allItems[thisPageBreakIndex]);
    Logger.log( "Added pagebreak item: " + allItems[thisPageBreakIndex].getIndex() );
    for( var i = thisPageBreakIndex+1; ( i < allItems.length ) && ( allItems[i].getType() != FormApp.ItemType.PAGE_BREAK ); ++i ) {
      thisPageItems.push(allItems[i]);
      Logger.log( "Added non-pagebreak item: " + allItems[i].getIndex() );
    }
    return thisPageItems;
  }

  function shuffle(array) {
    var currentIndex = array.length, temporaryValue, randomIndex;

    Logger.log('shuffle ran')

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

      // Pick a remaining element...
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;

      // And swap it with the current element.
      temporaryValue = array[currentIndex];
      array[currentIndex] = array[randomIndex];
      array[randomIndex] = temporaryValue;
  }

  return array;
  }

  function shuffleAndMove() {

    // Get page items for all pages into an array
    for(i = 2; i <= 5; i++) {
      itemsArray[i] = getPageItems(i);
    }

    // Removes null values from array
    itemsArray = itemsArray.filter(function(x){return x});

    // Shuffle page items
    itemsArray = shuffle(itemsArray);

    // Move page items to the new form
    for(i = 2; i <= 5; ++i) {
      newForm.moveItem(itemsArray[i], i);
    }
  }

  shuffleAndMove();
} 

原文 post: 我已经使用 Google 表格创建了问卷。出于我的目的,每个问题都需要在一个单独的页面上,但我需要将页面随机化。快速 Google 搜索显示此功能尚未添加。

我看到 Google 应用程序脚本中的 Form class 有许多方法可以 alter/give 访问 Google 表单的各种属性。由于我不知道 Javascript 并且不太熟悉 Google apps/API 我想知道我正在尝试做的事情是否有可能在深入研究并弄清楚之前是否可行。

如果可能的话,我将不胜感激任何关于与此任务相关的方法的见解,只是为了给我一些开始的方向。

根据 Sandy Good 的评论和发现的两个 SE 问题 here and here,这是我目前的代码:

// Script to shuffle question in a Google Form when the questions are in separate sections

function shuffleFormSections() {
  getQuestionID();
  createNewShuffledForm();
}

// Get question IDs
  function getQuestionID() {
    var form = FormApp.getActiveForm();
    var items = form.getItems();
    arrayID = [];
    for (var i in items) { 
      arrayID[i] = items[i].getId();
    }
    // Logger.log(arrayID);
    return(arrayID);
}

// Shuffle function
  function shuffle(a) {
    var j, x, i;
    for (i = a.length; i; i--) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        a[i - 1] = a[j];
        a[j] = x;
    }
}

// Shuffle IDs and create new form with new question order
function createNewShuffledForm() {
    shuffle(arrayID);
    // Logger.log(arrayID);
    var newForm = FormApp.create('Shuffled Form');
    for (var i in arrayID) {
      arrayID[i].getItemsbyId();
  }
}

我测试了这段代码。它创建了一个新表格,然后在新表格中打乱了问题。它不包括分页符、图像和部分 headers。您需要为原始模板表单提供源文件 ID。这个函数内部有3个sub-functions。内部函数在顶部,它们在外部函数的底部被调用。 arrayOfIDs 变量不需要返回或传递给另一个函数,因为它在外部范围内可用。

function shuffleFormSections() {
  var arrayOfIDs,shuffleQuestionsInNewForm,fncGetQuestionID,
      newFormFile,newForm,newID,items,shuffle;

  newFormFile = DriveApp.getFileById('Put the source file ID here')
               .makeCopy();
  newFormFile.setName('AAAAA_Shuffled_Form');

  newID = newFormFile.getId();

  newForm = FormApp.openById(newID);

  arrayOfIDs = [];

  fncGetQuestionID = function() {
    var i,L,thisID,thisItem,thisType;

    items = newForm.getItems();
    L = items.length;

    for (i=0;i<L;i++) {
      thisItem = items[i];
      thisType = thisItem.getType();

      if (thisType === FormApp.ItemType.PAGE_BREAK || 
      thisType === FormApp.ItemType.SECTION_HEADER || 
      thisType === FormApp.ItemType.IMAGE) {
       continue; 
      }

      thisID = thisItem.getId();
      arrayOfIDs.push(thisID);
    }

    Logger.log('arrayOfIDs: ' + arrayOfIDs);
    //the array arrayOfIDs does not need to be returned since it is available
    //in the outermost scope
  }// End of fncGetQuestionID function

  shuffle = function() {// Shuffle function
    var j, x, i;
    Logger.log('shuffle ran')

    for (i = arrayOfIDs.length; i; i--) {
      j = Math.floor(Math.random() * i);
      Logger.log('j: ' + j)

      x = arrayOfIDs[i - 1];
      Logger.log('x: ' + x)

      arrayOfIDs[i - 1] = arrayOfIDs[j];

      arrayOfIDs[j] = x;
    }

    Logger.log('arrayOfIDs: ' + arrayOfIDs)
  }

  shuffleQuestionsInNewForm = function() {
    var i,L,thisID,thisItem,thisQuestion,questionType;

    L = arrayOfIDs.length;

    for (i=0;i<L;i++) {
      thisID = arrayOfIDs[i];
      Logger.log('thisID: ' + thisID)
      thisItem = newForm.getItemById(thisID);

      newForm.moveItem(thisItem, i)
    }
  }

  fncGetQuestionID();//Get all the question ID's and put them into an array
  shuffle();

  shuffleQuestionsInNewForm();
}

试试这个。函数顶部有几个"constants"要设置,查看注释。表格文件的复制和打开借鉴了Sandy Good的回答,谢谢!

// This is the function to run, all the others here are helper functions
// You'll need to set your source file id and your destination file name in the
// constants at the top of this function here.
// It appears that the "Title" page does not count as a page, so you don't need
// to include it in the PAGES_AT_BEGINNING_TO_NOT_SHUFFLE count. 
function shuffleFormPages() {   
  // UPDATE THESE CONSTANTS AS NEEDED
  var PAGES_AT_BEGINNING_TO_NOT_SHUFFLE = 2; // preserve X intro pages; shuffle everything after page X
  var SOURCE_FILE_ID = 'YOUR_SOURCE_FILE_ID_HERE'; 
  var DESTINATION_FILE_NAME = 'YOUR_DESTINATION_FILE_NAME_HERE';

  // Copy template form by ID, set a new name
  var newFormFile = DriveApp.getFileById(SOURCE_FILE_ID).makeCopy();
  newFormFile.setName(DESTINATION_FILE_NAME);

  // Open the duplicated form file as a form
  var newForm = FormApp.openById(newFormFile.getId());

  var pages = extractPages(newForm);
  shuffleEndOfPages(pages, PAGES_AT_BEGINNING_TO_NOT_SHUFFLE); 
  var shuffledFormItems = flatten(pages);

  setFormItems(newForm, shuffledFormItems);  
}

// Builds an array of "page" arrays. Each page array starts with a page break
// and continues until the next page break.
function extractPages(form) {
  var formItems = form.getItems();
  var currentPage = [];
  var allPages = [];

  formItems.forEach(function(item) { 
    if (item.getType() == FormApp.ItemType.PAGE_BREAK && currentPage.length > 0) {
      // found a page break (and it isn't the first one)
      allPages.push(currentPage); // push what we've built for this page onto the output array
      currentPage = [item]; // reset the current page to just this most recent item
    } else {
      currentPage.push(item);
    }
  });
  // We've got the last page dangling, so add it
  allPages.push(currentPage);
  return allPages;
};

// startIndex is the array index to start shuffling from. E.g. to start
// shuffling on page 5, startIndex should be 4. startIndex could also be thought
// of as the number of pages to keep unshuffled.
// This function has no return value, it just mutates pages
function shuffleEndOfPages(pages, startIndex) {
  var currentIndex = pages.length;

  // While there remain elements to shuffle...
  while (currentIndex > startIndex) {
    // Pick an element between startIndex and currentIndex (inclusive)
    var randomIndex = Math.floor(Math.random() * (currentIndex - startIndex)) + startIndex;

    currentIndex -= 1;

    // And swap it with the current element.
    var temporaryValue = pages[currentIndex];
    pages[currentIndex] = pages[randomIndex];
    pages[randomIndex] = temporaryValue;
  }
};

// Sourced from elsewhere on SO:
// 
function flatten(array) {
  return array.reduce(
    function (flattenedArray, toFlatten) {
      return flattenedArray.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
    }, 
    []
  );
};

// No safety checks around items being the same as the form length or whatever.
// This mutates form.
function setFormItems(form, items) {
  items.forEach(function(item, index) {
    form.moveItem(item, index);
  });
};