1

I am a high school teacher who is trying to get their school to vote electronically in Google Apps using the "realsmpart" voTer script, and it isn't working as expected.

Here is the YouTube video about the script that was followed to set it up.

Here is the script:

function voTer(){
var sss = SpreadsheetApp.getActiveSpreadsheet();
var form = sss.getSheetByName("Sheet1");
var range = form.getRange(1,2,form.getLastRow(),1);
var lastEntry = range.getCell(form.getLastRow(),1).getValue();
var data = form.getRange(1,2,form.getLastRow()-1,1).getValues();
for (var i = 0; i < data.length; i++) {
  var row = data[i];
  var entry = row[0]; 
  if (entry == lastEntry){
var subject = "You Have Submitted!"; //Email subject
var recipient = lastEntry;
var body = "Hi there,<br><br>You've already submitted this form before, so this      
submission will not count.<br>Please contact ***, if you do want to change your answer.    
<br><br>Best,<br>***"; //Email body

if (recipient!=''){
      MailApp.sendEmail(recipient, subject, body,{htmlBody:body});
    }
    form.getRange(form.getLastRow(),2,1,form.getLastColumn()).clear();
    break;        
      }
    }
}

We do not need to specify the range, so instead (to not get this error message again) we will write into the script to simply ignore this. Add the script below next to the area with the error.

//ignore this warning

Here are the instructions on how to use the script, which I have followed and still can not get the script to work.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
  • How many fields are in the form, and what are the fields? E.g. Voter Name, King, Queen? Does every voter have a Google account? The `//ignore this warning` comment in the video is simply a way to get a msg to the viewer in the video, since there is no audio. If the voter enters their name, then the form would record the voter name. But then people could enter bogus names and vote multiple times. So, depending on how honest your voters are, that might not work. If the voter has a Google account, and is signed in, the code could get their user name and record it in the spreadsheet. – Alan Wells Sep 05 '14 at 03:34
  • In the script editor, there is the **VIEW** menu. And in that menu is a **EXECUTION TRANSCRIPT** menu item. That will show information about the script that ran. You can also add `Logger.log("var name: " + recipient)` to the code to print variable values into the log. Is the script running when the form is submitted, and does it show an execution error? – Alan Wells Sep 05 '14 at 03:47
  • @Christine : You don't mention what exactly is not working... this is a fairly simple script, I suppose you are using it in a domain (your school) and that you followed instruction about collecting email address from the users filling the form automatically , did you ? please specify the issue you are having. – Serge insas Sep 05 '14 at 06:12

1 Answers1

1

The original script was written for the 'old' Forms offering, and hasn't been updated. As a result, the hard-coded reference to Sheet1 is wrong, as 'new' Forms will create a "Form Responses" sheet. The reliance on column numbers is "brittle", as the script assumes that the respondents' email addresses will be in column 2 (B), immediately after the Timestamp. In practice, the order of questions in the spreadsheet depends on the order they were added to the Form - and since a new form is pre-populated by a dummy question, and the email address column is treated as a hidden question in the Form, the email address will NEVER be in column 2, even if you delete the dummy question. So that brittle code is now broken.

And that whole //ignore this warning thing? It's just a comment, it won't stop an error from being detected.

The good news is that there is a better way.

Instead of hard-coding the particulars, we can utilize the Event object that is provided to the trigger function. Read about the Sheets Form Submit event object.

To index into specific columns, I've included a helper function, columnNum(). It's written as a spreadsheet custom function, so it yields 1-based indexes for compatibility with built-in functions.

Code.gs

This script is easier to customize than the original "voTer", as all the variables you need to fiddle with are at the top. The body of the email can be modified if necessary - each line appears as a separate string in an Array.

/**
 * To customize the email sent to re-voters, change the values
 * of the following variables.
 */
var subject = "Invalid Vote Received";                //Email subject
var electoralOfficer = "your electoral officer";      // Person to contact for re-vote
var emailSignature = "Election committee";            // Signature on email
var timezone = Session.getScriptTimeZone();           // Timezone - use caution

function oneVote(vote) {
  var colUserName = columnNum("Username");

  // use the provided event object to get references to required objects
  var sheet = vote.range.getSheet();
  var ballot = vote.range.getRowIndex();
  var voter = vote.namedValues["Username"][0];

  // Get list of all previous votes
  var prevVoters = sheet.getRange(1, colUserName, ballot-1)
                        .getValues()  // Get email addresses as 2-dim array
                        .join(',')    // Join into a comma-deliminated string
                        .split(',')   // Then split into one-dimensional array

  // Check whether this voter previously voted
  var previousVote = prevVoters.indexOf(voter);

  // if they did, remind them then delete the new vote
  if (previousVote > -1) {
  var previousVoteTime = sheet.getRange(previousVote+1,columnNum("Timestamp")).getValue();
    var body =                                   //Email body
        ["Hi there,",
         "",
         "You previously voted on #TIMESTAMP#, so this submission will not count.",
         "Please contact #ELECTORALOFFICER# if you do want to change your vote.",
         "",
         "Thanks,",
         "#SIGNATURE#"
        ].join('<br>')
         .replace('#TIMESTAMP#',Utilities.formatDate(previousVoteTime, timezone, "MMM d 'at' h:mm a"))
         .replace('#ELECTORALOFFICER#', electoralOfficer)
         .replace('#SIGNATURE#', emailSignature)
    MailApp.sendEmail(voter, subject, body,{htmlBody:body});

    sheet.deleteRow(ballot);  // Could move to a "spoiled ballots" list instead.
  }
}

/**
 * Get the index of columnName.
 * @customfunction
 *
 * @param {String}    columnName  Column header to find
 * @param {Number}    headerRow   Column headers row index (Optional, default 1)
 * @returns {Number}  Column index, 0 if not found
 */
function columnNum(columnName,headerRow) {
  headerRow = headerRow ? headerRow - 1 : 0;  // Default: assume column headers in first row
  var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  // Validate input
  if (headerRow < 0 || headerRow >= rows.length)
    throw new Error( "headerRow out of range 1..maxRows" );

  var headers = rows[headerRow];
  return headers.indexOf(columnName)+1;
}

Install-able trigger

Remember to set up the trigger to run when forms are submitted.

Trigger

Test function

For testing trigger functions, it's best to simulate the events that will be received under normal operation. This function reads the existing form responses and feeds them to oneVote() as new submissions. See How can I test a trigger function in GAS?

/**
 * Test function simulates multiple form submission events for oneVote()
 * trigger function, by reading "active" spreadsheet. Make sure your
 * spreadsheet session is open to the Form Responses first.
 *
 * From https://stackoverflow.com/a/16089067
 */
function test_oneVote() {
  var dataRange = SpreadsheetApp.getActiveSheet().getDataRange();
  var data = dataRange.getValues();
  var headers = data[0];
  // Start at last row, work backwards to allow for deletions, skip headers in row 0
  for (var row=data.length-1; row > 0; row--) {
    var e = {};
    e.values = data[row];
    e.range = dataRange.offset(row,0,1,data[0].length);
    e.namedValues = {};
    // Loop through headers to create values & namedValues properties
    for (var col=0; col<headers.length; col++) {
      e.namedValues[headers[col]] = [e.values[col]];
    }
    // Skip invalid rows
    if (e.namedValues["Username"] === '') continue;
    // Pass the simulated event to oneVote
    oneVote(e);
  }
}
Community
  • 1
  • 1
Mogsdad
  • 44,709
  • 21
  • 151
  • 275