syntonicC
syntonicC

Reputation: 370

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

Update #2: Okay, I'm pretty sure my error in update #1 was because of indexing out of bounds over the array (I'm still not used to JS indexing at 0). But here is the new problem... if I write out the different combinations of the loop manually, setting the page index to 1 in moveItem() like so:

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);
...

...I don't get any errors but the items end up on different pages! What is going on?


Update #1:: Using Sandy Good's answer as well as a script I found at this WordPress blog, I have managed to get closer to what I needed. I believe Sandy Good misinterpreted what I wanted to do because I wasn't specific enough in my question.

I would like to:

  1. Get all items from a page (section header, images, question etc)
  2. Put them into an array
  3. Do this for all pages, adding these arrays to an array (i.e: [[all items from page 1][all items from page 2][all items from page 3]...])
  4. Shuffle the elements of this array
  5. Repopulate a new form with each element of this array. In this way, page order will be randomized.

My JavaScript skills are poor (this is the first time I've used it). There is a step that produces null entries and I don't know why... I had to remove them manually. I am not able to complete step 5 as I get the following error:

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

"Item,Item,Item" is the array element containing all the items from a particular page. So it seems that I can't add three items to a page at a time? Or is something else going on here?

Here is my code:

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

Original post: I have used Google forms to create a questionnaire. For my purposes, each question needs to be on a separate page but I need the pages to be randomized. A quick Google search shows this feature has not been added yet.

I see that the Form class in the Google apps script has a number of methods that alter/give access to various properties of Google Forms. Since I do not know Javascript and am not too familiar with Google apps/API I would like to know if what I am trying to do is even possible before diving in and figuring it all out.

If it is possible, I would appreciate any insight on what methods would be relevant for this task just to give me some direction to get started.

Based on comments from Sandy Good and two SE questions found here and here, this is the code I have so far:

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

Upvotes: 2

Views: 3485

Answers (2)

alexcavalli
alexcavalli

Reputation: 536

Try this. There's a few "constants" to be set at the top of the function, check the comments. Form file copying and opening borrowed from Sandy Good's answer, thanks!

// 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:
// https://stackoverflow.com/a/15030117/4280232
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);
  });
};

Upvotes: 1

Alan Wells
Alan Wells

Reputation: 31300

I tested this code. It created a new Form, and then shuffled the questions in the new Form. It excludes page breaks, images and section headers. You need to provide a source file ID for the original template Form. This function has 3 inner sub-functions. The inner functions are at the top, and they are called at the bottom of the outer function. The arrayOfIDs variable does not need to be returned or passed to another function because it is available in the outer scope.

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

Upvotes: 1

Related Questions