Skip to content

Instantly share code, notes, and snippets.

@nicolasdao
Last active March 29, 2021 15:23
Show Gist options
  • Save nicolasdao/e5ba7a6cb919f08a100d4081f9ec09f1 to your computer and use it in GitHub Desktop.
Save nicolasdao/e5ba7a6cb919f08a100d4081f9ec09f1 to your computer and use it in GitHub Desktop.
html code that demos various forms to interact with your Google Docs. Build with jQuery, jQuery UI, jQuery Validation, spin.js and Google Apps Script CSS style guide
<!DOCTYPE html>
<html>
<head>
<!-- Used by Google Apps Script to use with iFrame (ref. https://developers.google.com/apps-script/migration/iframe) -->
<base target="_top">
<!-- Google Apps Script CCS STyle Guide (ref. https://developers.google.com/apps-script/add-ons/css) -->
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<!-- jQuery UI ThemeRoller (ref. http://jqueryui.com/themeroller/) - e.g. needed for calendar -->
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/themes/smoothness/jquery-ui.css">
<!-- Good ol' jQuery -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.9.1/jquery-ui.min.js"></script>
<script src="https://cdn.jsdelivr.net/jquery.validation/1.16.0/jquery.validate.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js"></script>
<script>
//-------------------------------------------------------------------------------- 1. START - EVENT HANDLERS
// Prevent forms from submitting.
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function validateInt(evt) {
var theEvent = evt || window.event;
var key = theEvent.keyCode || theEvent.which;
key = String.fromCharCode(key);
var regex = /[0-9]/;
if (!regex.test(key)) {
theEvent.returnValue = false;
if (theEvent.preventDefault) theEvent.preventDefault();
}
}
function submitHandler(form) {
var v = form["randomvalue"].value;
showSpinner('submit-spinning-wheel');
// Replace this code with a call to 'google.script.run.yourGScode()'
showDialog(
"Dummy Behavior",
"Replace this dialog with a call to 'google.script.run.yourGScode()'",
function() {
hideSpinner();
});
}
function handleFindUserSubmit(form) {
var age = getFormValue("#age", form);
var name = getFormValue("#name", form);
var email = getFormValue("#email", form);
showSpinner('find-spinning-wheel');
// Replace this code with a call to 'google.script.run.yourGScode()'
showDialog(
"Dummy Behavior",
"Replace this dialog with a call to 'google.script.run.yourGScode()'",
function() {
hideSpinner();
});
}
function handleSearchTripSubmit(form) {
var startdate = getFormValue("#startdate", form); // string
var enddate = getFormValue("#enddate", form); // string
var departDate = new Date(startdate);
var returnDate = new Date(enddate);
var priceRange = $( "#slider-range" ).slider( "values" ); // e.g. [75, 345]
var extraSources = getFormValues('input[name=travel-source]:checked', form);
var tripType = getFormValues('input[name=triptype]:checked', form);
showSpinner('search-spinning-wheel');
// WARNING - Javascript dates serialization issue
// As of March 2017, there is a weird bug when passing js date objects to
// 'google.script.run.yourGScode(yourInputWithJsDate)'. To cope with this
// issue, one possible solution is to serialize the js date object into a
// string using JSON.stringify(yourDateObj). On the server-side (i.e.) .gs
// code, you will then use JSON.parse to deserialize that string object.
// Replace this code with a call to 'google.script.run.yourGScode()'
showDialog(
"Dummy Behavior",
"Replace this dialog with a call to 'google.script.run.yourGScode()'",
function() {
hideSpinner();
});
}
function onCurrencyChange(obj) {
var currency = obj.value;
var currencySymbol = getCurrencySymbol(currency);
$("#slider-range").slider("option","slide",""); // unbind the previous callback method on the slide event
$("#slider-range").on( "slide", function( event, ui ) { // bind a new callback on the slide event
updateRangeValue(ui.values[0], ui.values[1], currencySymbol);
} );
updateRangeValue($("#slider-range").slider("values", 0), $("#slider-range").slider("values", 1), currencySymbol);
}
function updateDatepickers(obj){
var selection = obj.value;
if (selection == "return"){
$("#enddate").val(null);
$("#enddate-row").show();
}
else { // one way has been selected
$("#enddate-row").hide();
$("#enddate").val("01/01/2999"); // hack to 'disable' validation so we can submit without worrying about that field
}
}
//-------------------------------------------------------------------------------- 1. END - EVENT HANDLERS
//
//-------------------------------------------------------------------------------- 2. START - UTILITIES
function getFormValue(selector, form) {
return $($(form).find(selector)).val();
}
function getFormValues(selector, form) {
var results = $(form).find(selector);
var a = [];
for (var i = 0; i < results.length; i++) a.push(results[i].value);
return a;
}
function updateRangeValue(minValue, maxValue, currency){
$("#amount").val(currency + minValue + " - " + currency + maxValue);
}
function getCurrencySymbol(currency){
switch(currency.toUpperCase()){
case "USD":
return "$";
case "CAD":
return "$";
case "NZD":
return "$";
case "AUD":
return "$";
case "JPY":
return "¥";
case "EUR":
return "€";
default:
return "$";
}
}
//-------------------------------------------------------------------------------- 2. END - UTILITIES
//
//-------------------------------------------------------------------------------- 3. START - UI CONFIG
// Check out http://spin.js.org/ to visually adjust those params
var opts = {
lines: 9, // The number of lines to draw
length: 2, // The length of each line
width: 3, // The line thickness
radius: 6, // The radius of the inner circle
scale: 1, // Scales overall size of the spinner
corners: 1, // Corner roundness (0..1)
color: '#000', // #rgb or #rrggbb or array of colors
opacity: 0.35, // Opacity of the lines
rotate: 0, // The rotation offset
direction: 1, // 1: clockwise -1: counterclockwise
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
fps: 20, // Frames per second when using setTimeout() as a fallback for CSS
zIndex: 2e9, // The z-index (defaults to 2000000000)
className: 'spinner', // The CSS class to assign to the spinner
top: '50%', // Top position relative to parent
left: '50%', // Left position relative to parent
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
position: 'relative', // Element positioning
};
var spinner = {};
function showSpinner(selector) {
var spinningTarget = document.getElementById(selector);
spinner = new Spinner(opts).spin(spinningTarget);
}
function hideSpinner() {
spinner.stop();
}
function showDialog(header, msg, onCloseCb) {
$("#dialog")
.dialog("option", "title", header)
.html(msg)
.on("dialogclose", function(event, ui) {
onCloseCb();
})
.dialog("open");
}
//-------------------------------------------------------------------------------- 3. END - UI
</script>
</head>
<body>
<style>
.main {
padding-top: 10px;
padding-left: 20px
}
.row {
margin-bottom: 15px;
vertical-align: top;
}
.col-100-perc {
width: 230px;
}
.col-33-perc {
width: 70px;
}
.col-66-perc {
width: 144px;
}
.col-50-perc {
width: 105px;
}
.spinning-wheel-container {
max-height: 0px;
}
.spinning-wheel {
margin-top: 10px;
margin-left: 20px;
}
.slider {
margin-left: 0px !important
}
.group-checkbox {
margin-left: 10px;
margin-top: 5px
}
</style>
<div class="main">
<div id="dialog" title="Dialog Title"></div>
<form id="simplest-form" onsubmit="submitHandler(this)">
<h3>Example A - Simplest Form + No Validation</h3>
<div class="row form-group">
<label for="randomvalue">Enter any value</label>
<input id="randomvalue" type="text" name="randomvalue" class="col-100-perc" />
</div>
<div class="row inline form-group">
<input class="action" type="submit" value="Submit" />
<input type="button" value="Close" onclick="google.script.host.close()" />
</div>
<div class="row inline form-group spinning-wheel-container">
<div id="submit-spinning-wheel" class="spinning-wheel"></div>
</div>
</form>
<form id="user-form">
<h3>Example B - Find User + Validation</h3>
<div id="name-age">
<div class="row inline form-group">
<label for="name">Name</label>
<input id="name" type="text" name="name" class="col-66-perc" />
</div>
<div class="row inline form-group">
<label for="age">Age</label>
<input id="age" type="text" name="age" class="col-33-perc" onkeypress='validateInt(event)' />
</div>
</div>
<div id="name-error"></div>
<div id="age-error"></div>
<div class="row form-group">
<label for="email">Email (optional)</label>
<input id="email" type="text" name="email" class="col-100-perc" />
</div>
<div class="row inline form-group">
<input id="find-button" type="submit" value="Find" />
</div>
<div class="row inline form-group spinning-wheel-container">
<div id="find-spinning-wheel" class="spinning-wheel"></div>
</div>
</form>
<form id="filter-form">
<h3>Example C - Find Random Trip + Validation</h3>
<div class="row form-group">
<input type="radio" name="triptype" id="oneway" value="oneway" onClick="updateDatepickers(this)" > One way
<input type="radio" name="triptype" id="return" value="return" onClick="updateDatepickers(this)" checked="checked" style="margin: 0px 5px 0px 20px"> Return
</div>
<div class="row inline form-group">
<label for="startdate">Depart</label>
<input type="text" name="startdate" id="startdate" class="col-50-perc" />
</div>
<div id="enddate-row" class="row inline form-group">
<label for="enddate">Return</label>
<input type="text" name="enddate" id="enddate" class="col-50-perc" />
</div>
<div class="row inline form-group slider">
<span for="amount">Price Range:</span>
<input type="text" id="amount" readonly style="border:0; color:#f6931f; font-weight:bold;">
<div style="margin-top: 5px">
<div id="slider-range"></div>
</div>
</div>
<div class="row form-group" style="margin-top: 12px">
<label for="currency">Currency</label>
<select id="currency" name="currency" class="col-33-perc" onchange="onCurrencyChange(this)">
<option value="usd" selected>USD</option>
<option value="cad">CAD</option>
<option value="aud">AUD</option>
<option value="nzd">NZD</option>
<option value="eur">EUR</option>
<option value="jpy">JPY</option>
</select>
</div>
<div class="row form-group" style="margin-top: 12px">
<label for="travel-source">Included Extra Sources</label>
<div class="group-checkbox">
<input type="checkbox" name="travel-source" id="google" value="google"> Google Trips <br/>
<input type="checkbox" name="travel-source" id="kayak" value="kayak"> Kayak <br/>
<input type="checkbox" name="travel-source" id="skyscanner" value="skyscanner"> Skyscanner
</div>
</div>
<div class="row inline form-group">
<input class="share" type="submit" value="Search" />
</div>
<div class="row inline form-group spinning-wheel-container">
<div id="search-spinning-wheel" class="spinning-wheel"></div>
</div>
</form>
</div>
<script>
$().ready(function() {
//-------------------------------------------------------------------------------- 1. START - VALIDATION CONFIG
$("#user-form").validate({
rules: {
name: "required",
age: "required",
email: {
email: true
}
},
messages: {
age: "The Age field is required",
name: "The Name field is required"
},
submitHandler: handleFindUserSubmit,
invalidHandler: function(event, validator) {
// code being called if the form is being submitted, but the validation fails.
},
errorPlacement: function(error, element) {
switch (element.attr("name")) {
case "age":
error.insertAfter($("div#name-error"));
break;
case "name":
error.insertAfter($("div#age-error"));
break;
default:
error.insertAfter(element);
}
}
});
$("#filter-form").validate({
rules: {
startdate: "required",
enddate: "required"
},
messages: {
startdate: "Depart date required",
enddate: "Return date required"
},
submitHandler: handleSearchTripSubmit,
invalidHandler: function(event, validator) {
// code being called if the form is being submitted, but the validation fails.
}
});
//-------------------------------------------------------------------------------- 1. END - VALIDATION CONFIG
//
//-------------------------------------------------------------------------------- 2. START - JQUERY UI CONFIG
// Doc for the datepicker API here http://api.jqueryui.com/datepicker/
var today = new Date();
$("#startdate").datepicker({
minDate: today
})
.on('change', function(ev) {
$(this).valid(); // triggers the validation test
$("#enddate").datepicker("option", "minDate", new Date(this.value)); // make sure the end date never happens before the start date
});
$("#enddate").datepicker({
minDate: today
})
.on('change', function(ev) {
$(this).valid(); // triggers the validation test
});
$("#slider-range").slider({
range: true,
min: 0,
max: 2000,
values: [450, 1200],
slide: function(event, ui) {
updateRangeValue(ui.values[0], ui.values[1], "$");
}
});
updateRangeValue($("#slider-range").slider("values", 0), $("#slider-range").slider("values", 1), "$");
// You probably don't need this dialog code. It's just usefull to guide you through this demo
$("#dialog").dialog({
autoOpen: false,
width: 250
});
//-------------------------------------------------------------------------------- 2. END - JQUERY UI CONFIG
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment