Created
August 8, 2018 00:08
-
-
Save jgrant41475/9e522a809377acfee393a133fa1d4751 to your computer and use it in GitHub Desktop.
Attendant
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html class="no-js" lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Foundation | Welcome</title> | |
<link rel="stylesheet" href="css/foundation.css" /> | |
<script src="js/vendor/modernizr.js"></script> | |
<script src="js/vendor/jquery.js"></script> | |
<script src="js/foundation.min.js"></script> | |
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" | |
crossorigin="anonymous"> | |
<script defer src="https://use.fontawesome.com/releases/v5.1.0/js/all.js" integrity="sha384-3LK/3kTpDE/Pkp8gTNp2gR/2gOiwQ6QaO7Td0zV76UFJVhqLl4Vl3KL1We6q6wR9" | |
crossorigin="anonymous"></script> | |
<style> | |
@media screen and (max-width: 360px) { | |
#Attendant { | |
display: none; | |
} | |
} | |
#QuestionQuestionBox>* { | |
display: none; | |
} | |
#Attendant { | |
border: 3px solid #000; | |
background-color: #FFF; | |
position: fixed; | |
bottom: 10px; | |
right: 35px; | |
min-height: 300px; | |
min-width: 250px; | |
} | |
#questionHeader { | |
font-weight: bold; | |
border-bottom: 2px solid black; | |
margin-bottom: 10px; | |
} | |
.AttendantWindow { | |
text-align: center; | |
} | |
#QuestionBox { | |
text-align: left; | |
margin: 5px 5px; | |
min-height: 250px; | |
} | |
#BackButton { | |
margin: 5px auto; | |
} | |
.question { | |
border: 3px solid #00F; | |
border-radius: 25px; | |
padding: 10px 5px; | |
margin: 5px auto; | |
text-align: center; | |
font-weight: bold; | |
width: 80%; | |
} | |
.requiredInput { | |
border: 2px solid #F00 !important; | |
} | |
.default_form_submit { | |
float: right; | |
} | |
.fieldsContainer { | |
display: none; | |
} | |
.attendantCloseButton { | |
cursor: pointer; | |
position: absolute; | |
top: -25px; | |
right: 0px; | |
} | |
#attendantMinimized { | |
display: none; | |
position: fixed; | |
right: 35px; | |
bottom: 10px; | |
background-color: #008cba !important; | |
} | |
#Question-1-1 { | |
width: 70%; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- Attendant element --> | |
<div id="Attendant"></div> | |
<div id="attendantMinimized">Click for attendant!</div> | |
<div id="customFormElement1" style="display:none;"> | |
<div id="serviceAreaCheck"> | |
<div>Input:</div> | |
<input type="text" id="SAInput" /> | |
</div> | |
<script> | |
$(document).on("widgetLoad", function () { | |
if (document.getElementById("welcomeMessage") == null) | |
$("#serviceAreaCheck").append("<div id='welcomeMessage'></div>"); | |
let welcomeMessageElem = $("#welcomeMessage"); | |
welcomeMessageElem.text("Welcome, " + (self.attendant.getFieldFromLocal("visitor_name") || "friend") + "!"); | |
$("#SAInput").val(self.attendant.getFieldFromLocal("visitor_address")); | |
// $(document).off("widgetLoad"); | |
}) | |
</script> | |
</div> | |
<!-- Custom attendant selection --> | |
<div id="servicesProvided" style="display:none;"> | |
<ul id="servicesProvidedList"></ul> | |
<script> | |
$(document).on("servicesProvidedLoaded", function () { | |
console.log("fish") | |
let servicesList = ["A/C", "Heating", "Duct Cleaning"], | |
servicesListElem = $("#servicesProvidedList"); | |
servicesList.forEach(function (service) { | |
servicesListElem.append($("<li></li>").text(service)); | |
}) | |
$(document).off("servicesProvidedLoaded"); | |
}) | |
</script> | |
</div> | |
<script> | |
// The JSON object that the attendant maps to | |
var assistantTree = { | |
id: "1", | |
revision: 1, | |
header: "Is this an emergency?", | |
options: [ | |
{ | |
id: "1-1", | |
question: "Yes!", | |
header: "Call Now!", | |
hasFields: true, | |
options: [ | |
{ | |
customContainer: '<div class="row text-center"><a href="#" style="color: #FFF;"><div class="small-6 columns small-centered button highlight">Click to Call!</div></a></div><br><div class="text-center">This is a custom element!</div>' | |
} | |
] | |
}, | |
{ | |
id: "1-2", | |
question: "Nope!", | |
header: "Visitor Information", | |
hasFields: true, | |
options: [ | |
{ | |
id: "1-2-1", | |
header: "Select an Option.", | |
onSubmit: true, | |
options: [ | |
{ | |
id: "1-2-1-1", | |
question: "Schedule", | |
options: null | |
}, | |
{ | |
id: "1-2-1-2", | |
question: "Cost of...", | |
redirectTo: "1-3", | |
options: [] | |
}, | |
{ | |
id: "1-2-1-3", | |
question: "See Services", | |
redirectTo: "1-3", | |
options: [] | |
} | |
] | |
}, | |
{ | |
question: "What is your name?", | |
inputElem: '<input type="text" id="visitor_name" value="" />', | |
validation: new SimpleValidators().isNotBlank | |
}, | |
{ | |
question: "What is your phone number?", | |
inputElem: '<input type="text" id="visitor_phone" value="" />', | |
validation: new SimpleValidators().isValidPhone | |
}, | |
{ | |
question: "What is your email?", | |
inputElem: '<input type="text" id="visitor_email" value="" />', | |
validation: new SimpleValidators().isValidEmail | |
}, { defaultSubmit: true } | |
] | |
}, | |
{ | |
id: "1-3", | |
header: "Services Provided", | |
hasFields: true, | |
hidden: true, | |
options: [ | |
{ | |
useExternalId: "servicesProvided", | |
useTrigger: "servicesProvidedLoaded" | |
} | |
] | |
} | |
] | |
} | |
var actualTree = { | |
id: "1", | |
revision: 1, | |
header: "Is this an emergency?", | |
options: [ | |
{ | |
id: "1-1", | |
question: "Yes!", | |
header: "Call Now!", | |
hasFields: true, | |
options: [ | |
{ | |
customContainer: '<div class="row text-center"><a href="#" style="color: #FFF;"><div class="small-6 columns small-centered button highlight">Click to Call!</div></a></div><br><div class="text-center">This is a custom element!</div>' | |
} | |
] | |
}, | |
{ | |
id: "1-2", | |
question: "Nope!", | |
header: "Visitor Information", | |
hasFields: true, | |
options: [ | |
{ | |
id: "1-2-1", | |
header: "Select an Option.", | |
onSubmit: true, | |
options: [ | |
{ | |
id: "1-2-1-1", | |
question: "Schedule", | |
header: "Schedule a Service", | |
options: [] | |
}, | |
{ | |
id: "1-2-1-2", | |
question: "Cost of...", | |
options: null | |
}, | |
{ | |
id: "1-2-1-3", | |
question: "See Services:", | |
redirectTo: "1-3", | |
options: [] | |
} | |
] | |
}, | |
{ | |
question: "What is your name?", | |
inputElem: '<input type="text" id="visitor_name" value="" />', | |
validation: new SimpleValidators().isNotBlank | |
}, | |
{ | |
question: "What is your phone number?", | |
inputElem: '<input type="text" id="visitor_phone" value="" />', | |
validation: new SimpleValidators().isValidPhone | |
}, | |
{ | |
question: "What is your email?", | |
inputElem: '<input type="text" id="visitor_email" value="" />', | |
validation: new SimpleValidators().isValidEmail | |
}, { defaultSubmit: true } | |
] | |
}, | |
{ | |
id: "1-3", | |
header: "Services Provided", | |
hasFields: true, | |
hidden: true, | |
options: [ | |
{ | |
useExternalId: "servicesProvided", | |
useTrigger: "servicesProvidedLoaded" | |
} | |
] | |
}, | |
{ | |
id: "1-4", | |
question: "Service Area Confirmation", | |
header: "Service Area Confirmation", | |
hasFields: true, | |
options: [ | |
{ | |
id: "1-4-1", | |
header: "Checking...", | |
onSubmit: true, | |
hasFields: true, | |
options: [ | |
{ | |
useExternalId: "customFormElement1", | |
useTrigger: "widgetLoad" | |
} | |
] | |
}, | |
{ | |
question: "What is your address?", | |
inputElem: '<input type="text" id="visitor_address" value="" />', | |
validation: new SimpleValidators().isNotBlank | |
}, { defaultSubmit: true } | |
] | |
} | |
] | |
}; | |
var realTree = { | |
id: "1", | |
header: "Is this an emergency?", | |
options: [ | |
{ | |
id: "1-1", | |
question: "Yes!", | |
header: "Call Now!", | |
hasFields: true, | |
options: [ | |
{ | |
customContainer: '<div class="row text-center"><a href="#" style="color: #FFF;"><div class="small-6 columns small-centered button highlight">Click to Call!</div></a></div>' | |
} | |
] | |
}, | |
{ | |
id: "1-2", | |
header: "Visitor Information", | |
question: "Nope!", | |
hasFields: true, | |
options: [ | |
{ | |
id: "1-2-1", | |
header: "Hello!", | |
onSubmit: true, | |
options: [ | |
{ | |
id: "1-2-1-1", | |
question: "Email", | |
header: "Wabbajack", | |
options: [ | |
{ | |
id: "1-2-1-1-1", | |
question: "Salami", | |
option: [] | |
} | |
] | |
} | |
] | |
}, | |
{ | |
question: "What is your name?", | |
inputElem: '<input type="text" id="visitor_name" value="" />', | |
validation: function (elem) { | |
let inputField = $(elem).find("input"); | |
if (inputField.val() == "") { | |
inputField.addClass("requiredInput"); | |
return false; | |
} | |
inputField.removeClass("requiredInput"); | |
return { qid: inputField.attr("id"), data: inputField.val() }; | |
}, | |
}, | |
{ | |
question: "What is your email address?", | |
inputElem: '<input type="text" id="visitor_email" value="" />', | |
validation: function (elem) { | |
let inputField = $("#visitor_email"); | |
if (inputField.val() == "") { | |
inputField.addClass("requiredInput"); | |
return false; | |
} | |
inputField.removeClass("requiredInput"); | |
return { qid: "visitor_email", data: inputField.val() }; | |
} | |
}, { defaultSubmit: true } | |
] | |
} | |
] | |
} | |
var tree = { | |
id: "1", | |
revision: 2, | |
header: "Question 1!", | |
question: "Question 1", | |
options: [ | |
{ | |
id: "1-1", | |
header: "Selected 1-1!", | |
question: "Question 1-1", | |
options: [ | |
{ | |
id: "1-1-1", | |
header: "Selected 1-1-1!", | |
question: "Question 1-1-1", | |
options: [] | |
} | |
] | |
}, | |
{ | |
id: "1-2", | |
header: "Selected 1-2!", | |
question: "Question 1-2", | |
options: [ | |
{ | |
id: "1-2-1", | |
header: "Selected 1-2-1!", | |
question: "Question 1-2-1", | |
options: [ | |
{ | |
id: "1-2-1-1", | |
header: "Selected 1-2-1-1!", | |
question: "Question 1-2-1-1", | |
hasFields: true, | |
options: [ | |
{ | |
id: "1-2-1-1-1", | |
header: "Pepparoni Cows", | |
question: "Hot Spice", | |
options: [], | |
onSubmit: true | |
}, | |
{ | |
question: "What is your first name?", | |
inputElem: '<input type="text" id="first_name" value="" />', | |
validation: new SimpleValidators().isNotBlank | |
}, | |
{ | |
question: "What is your last name?", | |
inputElem: '<input type="text" id="last_name" value="" />' | |
}, { defaultSubmit: true } | |
] | |
}, | |
{ | |
id: "1-2-1-2", | |
header: "Selected 1-2-1-2!", | |
question: "Question 1-2-1-2", | |
options: null | |
} | |
] | |
}, | |
{ | |
id: "1-2-2", | |
header: "Selected 1-2-2!", | |
question: "Question 1-2-2", | |
operation: function () { | |
alert("Hello"); | |
}, | |
options: null | |
} | |
] | |
} | |
] | |
}; | |
// The actual Attendant function declaration | |
let Attendant = function (inputTree, hist) { | |
this.init = function () { | |
if (self.storageAvailable()) { | |
let storage = self.getStorage(), revision = storage.getItem("revision_id"); | |
if (revision != self.tree.revision) { | |
storage.clear(); | |
self.history = [self.tree.id]; | |
storage.setItem("revision_id", self.tree.revision) | |
} | |
} | |
self.parseTree(this.tree.options); | |
this.selectQuestion((this.history.length == 0) ? this.tree.id : this.history.pop()); | |
$("#attendantMinimized, #attendantMinimized *").click(function () { self.toggleAttendant(); }); | |
$(".attendantCloseButton").click(function () { self.toggleAttendant(); }); | |
$(".question").click(function () { self.selectQuestion(this.id); }); | |
$("#BackButton").click(function () { self.selectPrevious(); }); | |
$(".form_submit").click(function () { | |
let $this = $(this), id = $this.data("submitfor"); | |
if (id != null && self.validateForm($this.data("parentid")) != false) | |
self.selectQuestion(id); | |
}); | |
}; | |
this.selectQuestion = function (id) { | |
if (id.slice(0, 9) == "Question-") | |
id = id.slice(9); | |
let treeRef = this.getID(id); | |
if (treeRef == null) | |
return; | |
if (treeRef.redirectTo != null) | |
return self.selectQuestion(treeRef.redirectTo); | |
if (treeRef.operation != null) | |
treeRef.operation(); | |
if (treeRef.options != null) { | |
this.history.push(id); | |
$(".question").hide(); | |
$(".fieldContainer").hide(); | |
$("#questionHeader").text((treeRef.header != null) ? treeRef.header : ""); | |
$(".question[id*=Question-" + id + "]").each(function (index, elem) { | |
let i = elem.id.slice(9); | |
if (i != id && i.slice(id.length + 1).indexOf("-") == -1) | |
$(elem).show(); | |
}); | |
treeRef.options.filter(function (x) { return x.hidden == true; }).forEach(function (x) { $("#Question-" + x.id).hide(); }); | |
treeRef.options.filter(function (x) { return x.useTrigger != null; }).forEach(function (x) { $(document).trigger(x.useTrigger, self); }); | |
} | |
if (treeRef.hasFields == true) { | |
$(".fieldContainer[data-fieldfor=" + treeRef.id + "]").show(); | |
treeRef.options.filter(function (x) { return x.id != null; }).forEach(function (x) { $("#Question-" + x.id).hide(); }); | |
} | |
self.updateStorage(); | |
}; | |
this.getID = function (idString) { | |
if (idString.slice(0, 9) == "Question-") | |
idString = idString.slice(9); | |
let idStringList = idString.split("-"), | |
temp = this.tree, | |
getTreeFromID = function getTreeFromID(id, inputTree) { | |
if (inputTree.options != null && inputTree.options.length >= id && id > 0) | |
return inputTree.options[id - 1]; | |
else return null; | |
}; | |
for (let i = 1, max = idStringList.length; i < max; i++) { | |
if (temp == null) { | |
temp = this.tree; | |
break; | |
} | |
temp = getTreeFromID(idStringList[i], temp); | |
} | |
return temp; | |
}; | |
this.createQuestionFromRef = function (ref) { | |
if (ref.id == null) | |
return; | |
let elem = $(document.createElement("div")); | |
elem.addClass("question").attr({ "id": "Question-" + ref.id }); | |
elem.text(ref.question); | |
if (ref.hasFields == true) | |
$("#QuestionBox").append(self.createFieldsFromRef(ref)); | |
return elem; | |
}; | |
this.createFieldsFromRef = function (ref) { | |
if (ref.id == null) | |
throw new Error("Invalid ID"); | |
let fieldsContainer = $('<div class="fieldContainer" data-fieldfor="' + ref.id + '"></div>'), submitId = null; | |
ref.options.forEach(function (field) { | |
let tempContainer = $('<div class="Field"></div>'); | |
if (field.onSubmit == true) { | |
submitId = field.id; | |
self.parseTree(ref.options); | |
} | |
else if (field.useExternalId != null) { | |
tempContainer = $("#" + field.useExternalId); | |
tempContainer.show(); | |
} | |
else if (field.customContainer != null) | |
tempContainer = field.customContainer; | |
else if (field.defaultSubmit == true) | |
tempContainer.append($('<input type="button" class="form_submit default_form_submit" data-parentID="' + ref.id + '" data-submitfor="' + submitId + '" value="Next" />')); | |
else { | |
let textElem, inputElem; | |
if (field.customQuestion != null) | |
textElem = $(field.customQuestion); | |
else { | |
textElem = $('<div class="fieldText"></div>'); | |
textElem.text(field.question); | |
} | |
if (field.customInput) | |
inputElem = $(field.customInput); | |
else | |
inputElem = $(field.inputElem); | |
let answer = self.getFieldFromLocal(inputElem.attr("id")); | |
if (answer != null) | |
inputElem.val(answer); | |
tempContainer.append(textElem); | |
tempContainer.append(inputElem); | |
} | |
fieldsContainer.append(tempContainer); | |
}); | |
return fieldsContainer; | |
}; | |
this.selectPrevious = function () { | |
if (this.history != null && this.history.length > 1) { | |
this.history.pop(); | |
this.selectQuestion(this.history.pop()); | |
} | |
}; | |
this.getStorage = function () { return (self.storageAvailable(self.storageType) == true) ? window[self.storageType] : null; }; | |
this.updateStorage = function () { | |
if (!self.storageAvailable(self.storageType)) | |
return; | |
let storage = self.getStorage(); | |
if (self.history.length == null) | |
storage.removeItem(self.storageKey); | |
else | |
storage.setItem(self.storageKey, self.history.join("\t")); | |
}; | |
this.getHistory = function () { | |
if (!self.storageAvailable(self.storageType)) | |
return; | |
let storage = self.getStorage(); | |
let local = storage.getItem(self.storageKey); | |
return (local != null) ? local.split("\t") : []; | |
}; | |
this.parseTree = function parseTree(arr) { | |
let container = $("#QuestionBox"), cur = null; | |
for (let i in arr) { | |
cur = arr[i]; | |
if (cur.id != undefined) | |
container.append(self.createQuestionFromRef(cur)); | |
if (cur.options != null && cur.hasFields != true) | |
parseTree(cur.options); | |
} | |
} | |
this.validateForm = function (id) { | |
let ref = self.getID(id); | |
let container = $('.fieldContainer[data-fieldfor="' + ref.id + '"]').children(); | |
if (ref == null || ref.hasFields != true) | |
return false; | |
if (ref.options.length != container.length) | |
return false; | |
let rValue = true; | |
ref.options.forEach(function (refElem, index) { | |
if (refElem.validation != null) { | |
let validated = refElem.validation(container[index]); | |
if (validated == false) | |
rValue = false; | |
else if (validated.qid != null && validated.data != null) | |
self.saveFieldToLocal(validated.qid, validated.data); | |
} | |
}); | |
return rValue; | |
}; | |
this.saveFieldToLocal = function (id, data) { | |
if (!self.storageAvailable(self.storageType)) | |
return; | |
let storage = self.getStorage(); | |
storage.setItem("Attendant-" + id, data); | |
}; | |
this.getFieldFromLocal = function (id) { | |
if (!self.storageAvailable(self.storageType)) | |
return; | |
let storage = self.getStorage(); | |
return storage.getItem("Attendant-" + id); | |
} | |
this.storageAvailable = function (type) { | |
if (type == null) | |
type = self.storageType; | |
try { | |
let storage = window[type], | |
x = '__storage_test__'; | |
storage.setItem(x, x); | |
storage.removeItem(x); | |
return true; | |
} | |
catch (e) { | |
return e instanceof DOMException && ( | |
// everything except Firefox | |
e.code === 22 || | |
// Firefox | |
e.code === 1014 || | |
// test name field too, because code might not be present | |
// everything except Firefox | |
e.name === 'QuotaExceededError' || | |
// Firefox | |
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') && | |
// acknowledge QuotaExceededError only if there's something already stored | |
storage.length !== 0; | |
} | |
}; | |
this.toggleAttendant = function () { | |
$("#Attendant").toggle(); | |
$("#attendantMinimized").toggle(); | |
}; | |
let self = this; | |
$("div#Attendant").append($('<div class="AttendantWindow"></div>')) | |
.find(".AttendantWindow") | |
.append($('<div id="questionHeader"></div>')) | |
.append($('<div id="QuestionBox"></div>')) | |
.append($('<div class="button highlight" id="BackButton">Back</div>')) | |
.append($('<div class="attendantCloseButton"><i class="fas fa-window-close"></i></div>')); | |
this.tree = inputTree; | |
this.storageType = "localStorage" | |
this.storageKey = "Attendant_History"; | |
this.history = self.getHistory(); | |
} | |
// Provides default methods of validation for simple cases | |
function SimpleValidators() { | |
this.isNotBlank = function (elem) { | |
let inputField = $(elem).find("input"); | |
if (inputField.val().trim() == "") { | |
inputField.addClass("requiredInput"); | |
return false; | |
} | |
inputField.removeClass("requiredInput"); | |
return { qid: inputField.attr("id"), data: inputField.val() }; | |
}; | |
this.isValidPhone = function (elem) { | |
let inputField = $(elem).find("input"); | |
let inputVal = inputField.val(); | |
if (typeof inputVal != "string" || inputVal.match(/^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/) == null) { | |
inputField.addClass("requiredInput"); | |
return false; | |
} | |
inputField.removeClass("requiredInput"); | |
return { qid: inputField.attr("id"), data: inputVal }; | |
}; | |
this.isValidEmail = function (elem) { | |
let inputField = $(elem).find("input"); | |
let inputVal = inputField.val(); | |
if (typeof inputVal != "string" || inputVal.match(/^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/) == null) { | |
inputField.addClass("requiredInput"); | |
return false; | |
} | |
inputField.removeClass("requiredInput"); | |
return { qid: inputField.attr("id"), data: inputVal }; | |
} | |
} | |
// Initilialize the attendant | |
var attendant = new Attendant(actualTree); | |
attendant.init(); | |
</script> | |
<script>$(document).foundation();</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment