Reputation: 77
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.
Upvotes: 3
Views: 3801
Reputation: 589
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>
Upvotes: 4
Reputation: 11194
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.
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));
}
}
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
).Upvotes: 2