3

This is driving me crazy, I have a simple Google Form that accepts subscriber code and their slot timings. My purpose is to get data via Google Forms and save it to Google Sheets without having any clashes in the slots timings.

To show you an example, I am attaching an image of my form and sheet. After the first entry was done, if another slot is to be given to same subscriber, we need to ensure that the slot timings do not clash with the saved timings in google sheet that is assigned to the same subscriber. If you see the second entry, it just accepts it and for this we need a validation check which is not available. My question is, is it even possible to have an onClick validation on the submit button? I have been searching for it on google like crazy but haven't got a solution. If this is not possible to do it in Google forms, what are my alternatives or is there any workaround available?

Eagerly awaiting responses for my query. Thank you.

enter image description here

3
  • There is no way to interact with a live form prior to submission. Commented Jul 21, 2021 at 18:12
  • Just as I thought... Thanks for your reply. Could you pls suggest alternative or workaround? May be change in front end and have something else instead of google form? something that can connect with google sheets as I want my data to go in google sheets only. Can we use google sheets as front end where lot of people can use it at the same time and yet not have edit access to the data? If you feel like replying to this comment, please elaborate as I am a not so good in programming. Thanks Commented Jul 21, 2021 at 18:38
  • I would create a web app and use my own html forms. Commented Jul 21, 2021 at 19:11

2 Answers 2

4

As @Cooper pointed out, it seems best that you create your own web app.

After a few hours of work I've made a web app that will work for you using Google Apps Script. Here are the spreadsheet and script file.

Server Side Codes:

var ss = SpreadsheetApp.getActiveSheet()

function doGet() {
  return HtmlService.createTemplateFromFile('form').evaluate()
}

function submitData (data) {
  
  ss.appendRow([new Date(),data.ssCode, new Date(data.startDateTime), new Date(data.endDateTime)])
}

function isAvailible (start, end) {
  var data = ss.getDataRange().getValues()
  var isAvailible = true;
  var message = '';

  data.forEach(row => {

    if(start > row[2].valueOf() && start < row[3].valueOf() || end > row[2].valueOf() && end < row[3].valueOf()) {
      isAvailible = false;
      message = new Date(row[2].valueOf()) +'<br> to ' + new Date(row[3].valueOf()) + '<br> isn\'t availible.'
    }
  })

  var response = {status: isAvailible, message: message}

  return response;
  
}

Client Side Codes:

M.FormSelect.init(document.querySelectorAll('select'));

var dps = document.querySelectorAll('.datepicker');
var dpsInstances = M.Datepicker.init(dps, {
  showClearBtn: true,
  autoClose: true
});

dpsInstances[0].options.onSelect = function(day) {
  dpsInstances[1].setDate(day);
  endDate.value = dpsInstances[1].toString()
  M.updateTextFields();
}

var tps = document.querySelectorAll('.timepicker');
var tpsInstances = M.Timepicker.init(tps, {
  twelveHour: false,
  showClearBtn: true,
  autoClose: true
});

function formSubmit(data) {

  var startDate = new Date(data.startDate.value);
  var startTime = data.startTime.value.split(':');
  startDate.setHours(startTime[0]);
  startDate.setMinutes(startTime[1]);

  var endDate = new Date(data.endDate.value);
  var endTime = data.endTime.value.split(':')
  endDate.setHours(endTime[0]);
  endDate.setMinutes(endTime[1]);
  
  if (startDate.valueOf() >= endDate.valueOf()) {
    M.toast({html: 'End date should be greater then start date.'})
    return;
  }
  
  var dataToSubmit = {
    ssCode: data.ssCode.value,
    startDateTime: startDate.valueOf(),
    endDateTime: endDate.valueOf()
  }

  google.script.run.withSuccessHandler(function(response) {
    if (response.status) {
      google.script.run.withSuccessHandler(function() {
        myForm.reset()
        M.toast({
          html: "Successful"
        })
      }).submitData(dataToSubmit)
    } else {
      M.toast({
        html: response.message
      })
    }

  }).isAvailible(dataToSubmit.startDateTime, dataToSubmit.endDateTime)


}
body {
  background: rgb(244, 235, 234)
}

.outer-field {
  border-radius: 15px;
  background: white;
  height: 150px;
  margin: 10px;
  padding: 20px;
}

.title {
  padding-left: 2%;
  font-weight: bold;
}
<html>
  <head>
    <base target="_top">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

  </head>
  <body>
  
    <div class="row">
      <div class="col s10 offset-s1 l4 offset-l4">
        <div class="outer-field" style="height: 100px">
          <h4>Slot Timings</h4>
        </div>
        <form id="myForm" onsubmit="event.preventDefault(); formSubmit(this) ">
          <div class="outer-field">
            <div class="title">Subscriber Code</div>
            <div class="input-field col s6 l6">
              <select form="myForm" name="ssCode" required>
                <option value="001">001</option>
                <option value="002">002</option>
                <option value="003">003</option>
              </select>
            </div>
          </div>
          <div class="outer-field">
            <div class="title">From</div>
            <div class="col s6 l6">
              <div class="input-field">
                <input type="text" id="startDate" name="startDate" class="datepicker validate" form="myForm" required>
                <label for="startDate">Date</label>
              </div>
            </div>
            <div class="col s6 l6">
              <div class="input-field">
                <input type="text" id="startTime" name="startTime" class="timepicker validate" form="myForm" required>
                <label for="startTime">Time</label>
              </div>
            </div>
          </div>
          <div class="outer-field">
            <div class="title">To</div>
            <div class="col s6 l6">
              <div class="input-field">
                <input type="text" id="endDate" name="endDate" class="datepicker" form="myForm" required>
                <label for="endDate">Date</label>
              </div>
            </div>
            <div class="col s6 l6">
              <div class="input-field">
                <input type="text" id="endTime" name="endTime" class="timepicker" form="myForm" required>
                <label for="endTime">Time</label>
              </div>
            </div>
          </div>
          <button class="btn waves-effect waves-light" type="submit" name="action" style="margin-left: 3%" >Submit
            <i class="material-icons right">send</i>
          </button>
        </form>
        
      </div>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
  </body>
</html>

Sign up to request clarification or add additional context in comments.

3 Comments

Thats great! I posted a sample and the code works perfectly and gives the desired result the way I wanted. Appreciate your time and efforts. Which programming language is used in google web apps? I am fairy new to app scripts. Do you freelance? I wanted to add few more fields, validations and security features like giving specific access to users. If I decide to do it myself, it will take a lot of time. I dont mind paying you for making a small project for me. Kindly revert how can we take it further. Thanks
I'm glad it worked for you. | You can simply make a copy of the sheet (File > Make a copy), then the script file also will be copied to your Drive. To go to the script, you have to click Tools > Script Editor. Google will ask your permission for once only, because the code will access your spreadsheet. Don't worry about that.
:) you served it on a platter and i was looking elsewhere. When I tried, the subscriber selection dropdown and date pickers were not coming as I had put the script on top inside head. Now I made a copy of your script as you suggested and it worked. Thank you so much. Well, I am awaiting your reply on my previous post if you are willing to freelance as I may spend a very long time to finish my small project.
2

Another idea you can try on your current setup is that you could use an onFormSubmit trigger. And once the response is submitted, it is compared to the previous rows that were added to the sheet to check if there have been any conflict with your data.

Upon confirming there is, just remove the last response. If no conflict, then let it be and end the script there.

Sheet Script:

function checkConflict(e) {
  var sheet = e.range.getSheet();
  var lastRow = sheet.getLastRow();
  // exclude the last submitted response
  var timeSlots = sheet.getRange(2, 3, lastRow - 2, 2).getValues();
  var lastResponse = e.values;
  var lastStart = new Date(lastResponse[2]).getTime();
  var lastEnd = new Date(lastResponse[3]).getTime();

  // convert timeslots to comparable data type
  timeSlots = timeSlots.map(timeSlot => [timeSlot[0].getTime(), timeSlot[1].getTime()]);
  // get conflicts
  var conflict = timeSlots.filter(timeSlot => (timeSlot[0] <= lastStart && timeSlot[1] >= lastStart) || 
                                              (timeSlot[0] <= lastEnd && timeSlot[1] >= lastEnd));
  // remove lastRow if conflict is 1
  if(conflict.length) {
    sheet.deleteRow(lastRow);
    MailApp.sendEmail(lastResponse[4], 
                      "Time Slot conflict", 
                      "Your recent response was removed due to a conflict. Your submitted time slot is the following:\n" + 
                      "From: " + new Date(lastStart) + "\n" + 
                      "To: " + new Date(lastEnd));
  }
  else {
    MailApp.sendEmail(lastResponse[4], 
                      "Time Slot confirmed", 
                      "Your time slot is now confirmed. Confirmed time slot is the following:\n " + 
                      "From: " + new Date(lastStart) + "\n" + 
                      "To: " + new Date(lastEnd));
  }
}

Sample Data:

sample

Trigger:

trigger

Emails confirm:

confirm

Emails conflict:

conflict

Output:

output

Note:

  • This approach doesn't check your input before the submission. It just removes the last response afterwards IF there have been conflicts with the previous slot timings to that one.
  • Also, the one who sent the response wouldn't know that his slot was confirmed or not, not unless you ask for his email in the form and send confirmation afterwards the checking (whether his submitted response was removed due to conflict, or approved due to it having no conflict)
  • If you have a time slot of 9:00 - 10:00, it won't accept another response with 9:00 in it since (>=, <=) is inclusive. What you can do is to pick timeslots where starting has 1/31 minute (e.g. 9:01-10:00, 8:31-9:30) or the ending slots has 59/29 minutes (e.g. 9:00-9:59, 8:30-9:29).
  • If that is a hassle, then you need to adjust the conditional on checking conflicts. You should be able to figure it out.

1 Comment

Thanks @NaziA for your kind assistance deserves an upvote. The email feature looks cool and the approach towards the query is also logical. However, I wanted a live interaction which would stop the user there itself to select a proper and available slot (before the submit button is clicked). Once again thanks for looking into my issue. I really appreciate

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.