Skip to content

Instantly share code, notes, and snippets.

@jeffreybergier
Created January 4, 2017 22:34
Show Gist options
  • Save jeffreybergier/295c57cea832638c5c7b1b36dabeb15e to your computer and use it in GitHub Desktop.
Save jeffreybergier/295c57cea832638c5c7b1b36dabeb15e to your computer and use it in GitHub Desktop.
"use strict";
//
//
// // This file should be multiple files
// // But because multiple JS files take multiple round trips
// // Putting it in one for now
//
//
//
//
// //
// // Bootstrap DOM Generator .js
// // This includes all the object definitions and
// // Functions needed to convert JSON
// // Into a complete DOM tree that can be inserted into the Document
// //
//
//
//
// Global Function to verify types that come from DOM or JSON
//
function isNumber(input) {
if ((input != null && input != undefined) && (typeof input === 'number')) {
return true;
} else {
return false;
}
}
function isStringOrNumberAndNotEmptyString(input) {
if (isStringOrNumber(input) && input != "") {
return true;
} else {
return false;
}
}
function isFunction(input) {
if ((input != null && input != undefined) && (typeof input === 'function')) {
return true;
} else {
return false;
}
}
function isBootstrapDOMObject(input) {
if ((input != null && input != undefined) && (input instanceof BootstrapDOM)) {
return true;
} else {
return false;
}
}
function isDOMElement(input) {
if ((input != null && input != undefined) && (input instanceof Element)) {
return true;
} else {
return false;
}
}
function isString(input) {
if (input != null && input != undefined && typeof input == 'string') {
return true;
} else {
return false;
}
}
function isArray(input) {
if ((input != null && input != undefined) && (Array.isArray(input))) {
return true;
} else {
return false;
}
}
function isObject(input) {
if ((input != null && input != undefined) && (typeof input == 'object')) {
return true;
} else {
return false;
}
}
function isStringOrNumber(input) {
if ((input != null && input != undefined) && (typeof input == 'string' || typeof input == 'number')) {
return true;
} else {
return false;
}
}
function throwIfNotNumber(input) {
if (isNumber(input)) {
return true;
} else {
throw "Exception: throwIfNotNumber: input was not an a number: (" + input + ")";
return false;
}
}
function throwIfNotFunction(input) {
if (isFunction(input)) {
return true;
} else {
throw "Exception: throwIfNotFunction: input was not an a function: (" + input + ")";
return false;
}
}
function throwIfNotStringOrNumberAndNotEmptyString(input) {
if (isStringOrNumberAndNotEmptyString(input)) {
return true;
} else {
throw "Exception: throwIfNotStringOrNumberAndNotEmptyString: input was not a String or Number or was Empty String: (" + input + ")";
return false;
}
}
function throwIfNotBootstrapDOMObject(input) {
if (isBootstrapDOMObject(input)) {
return true;
} else {
throw "Exception: throwIfNotBootstrapDOMObject: input was not an a bootstrapDom object: (" + input + ")";
return false;
}
}
function throwIfNotObject(input) {
if ((input != null && input != undefined) && (typeof input == 'object')) {
return true;
} else {
throw "Exception: throwIfNotObject: input was not an object: (" + input + ")";
return false;
}
}
function throwIfNotObjectOrNull(input) {
if (input == null || input == undefined || isObject(input)) {
return true;
} else {
throw "Exception: throwIfNotObjectOrNull: input was not an object and it was also not null: (" + input + ")";
return false;
}
}
function throwIfNotDOMElement(input) {
if (isDOMElement(input)) {
return true;
} else {
throw "Exception: throwIfNotDOMElement: input was not DOM element object: (" + input + ")";
return false;
}
}
function throwIfNotStringOrNumberOrNull(input) {
if (input == null || input == undefined || typeof input == 'string' || typeof input == 'number') {
return true;
} else {
throw "Exception: throwIfNotStringOrNumberOrNull: input was not string or number or null: (" + input + ")";
return false;
}
}
function throwIfNotStringOrNumber(input) {
if (isStringOrNumber(input)) {
return true;
} else {
throw "Exception: throwIfNotStringOrNumber: input was not string or number: (" + input + ")";
return false;
}
}
function throwIfNotString(input) {
if (isString(input)) {
return true;
} else {
throw "Exception: throwIfNotString: input was not string: (" + input + ")";
return false;
}
}
function throwIfNotArray(input) {
if (isArray) {
return true;
} else {
throw "Exception: throwIfNotArray: input was not array: (" + input + ")";
return false;
}
}
//
//
// // Bootstrap DOM object generators
// // Each object has a constructor, a set of properties, and a generateDomElement() function
// // Each object checks its types so it can be constructed improperly
//
//
function BootstrapDOM(rootDOMObject, formOnsubmitHandler, panelOnClickHandler) {
var columnSizeStringConstant = "col-xs-12";
function NavigationBar(pageTitleString) {
var title = pageTitleString;
throwIfNotStringOrNumber(title);
this.title = title;
this.generateDomElement = function() {
var panel = document.createElement("div");
panel.className = "panel panel-default container-fluid";
var panelHeading = document.createElement("div");
panelHeading.className = "panel-heading row";
var textCenter = document.createElement("div");
textCenter.className = columnSizeStringConstant + " " + "text-center";
var heading4 = document.createElement("h4");
var textElement = document.createTextNode(this.title);
heading4.appendChild(textElement);
textCenter.appendChild(heading4);
panelHeading.appendChild(textCenter);
panel.appendChild(panelHeading);
return panel;
};
}
function Message(messageObject) {
function isValidMessageKind(input) {
if (isString(input)) {
var lowerInput = input.toLowerCase();
if (lowerInput == "success" || lowerInput == "info" || lowerInput == "warning" || lowerInput == "danger") {
return true;
} else {
return false;
}
} else {
return false;
}
}
function throwIfNotValidMessageKind(input) {
if (isValidMessageKind(input)) {
return true;
} else {
throw "Exception: throwIfNotValidMessageKind: message kind was not valid: (" + input + ")";
return false;
}
}
var kind = messageObject["kind"];
var text = messageObject["text"];
throwIfNotStringOrNumber(text); throwIfNotValidMessageKind(kind);
this.kind = kind;
this.text = text;
this.generateDomElement = function() {
var containerElement = document.createElement("div");
containerElement.className = "container-fluid";
var alertElement = document.createElement("div");
alertElement.className = "alert alert-" + this.kind + " " + "text-center";
alertElement.setAttribute("role", "alert");
var h5Element = document.createElement("h5");
h5Element.innerHTML = this.text;
alertElement.appendChild(h5Element);
containerElement.appendChild(alertElement);
return containerElement;
};
}
function Panel(panelObject, onclickHandler) {
function Row(rowObject, onclickHandler) {
var primaryText = rowObject["primaryText"];
var secondaryText = rowObject["secondaryText"];
var id = rowObject["selectionID"];
throwIfNotStringOrNumberOrNull(id); throwIfNotStringOrNumber(primaryText); throwIfNotStringOrNumberOrNull(secondaryText); throwIfNotFunction(onclickHandler);
this.primaryText = primaryText; // String or Number
this.secondaryText = secondaryText; // Optional String or Number
this.id = id; // Optional String or Number
this.onclickHandler = onclickHandler; // function
this.generateDomElement = function() {
var buttonElement = document.createElement("button");
buttonElement.className = "list-group-item";
buttonElement.setAttribute("type", "button");
if (isStringOrNumber(this.id)) {
buttonElement.setAttribute("selectionID", encodeURIComponent(this.id));
buttonElement.onclick = this.onclickHandler;
}
var primaryTextNode = document.createTextNode(this.primaryText);
buttonElement.appendChild(primaryTextNode);
if (isStringOrNumber(this.secondaryText)) {
var badgeElement = document.createElement("span");
badgeElement.className = "badge";
var badgeTextNode = document.createTextNode(this.secondaryText);
badgeElement.appendChild(badgeTextNode);
buttonElement.appendChild(badgeElement);
}
return buttonElement;
};
}
// map rows into array
var rawRows = panelObject["rows"];
var rows = [];
if (isArray(rawRows)) {
for (var i=0; i<rawRows.length; i++) {
var row = new Row(rawRows[i], onclickHandler);
rows.push(row);
}
}
var title = panelObject["title"];
var formData = panelObject["formData"];
throwIfNotStringOrNumber(title); throwIfNotObjectOrNull(formData);
this.title = title; // String or Number
this.formData = formData; // Optional JSON Object not stringified
this.rows = rows; // [Row]
this.generateDomElement = function() {
var parentElement = document.createElement("div");
parentElement.className = columnSizeStringConstant + " " + "center-block";
var panelElement = document.createElement("div");
panelElement.className = "panel panel-default";
var panelHeadingElement = document.createElement("div");
panelHeadingElement.className = "panel-heading";
var heading5 = document.createElement("h5");
heading5.className = "panel-title";
var headingTextNode = document.createTextNode(this.title);
heading5.appendChild(headingTextNode);
panelHeadingElement.appendChild(heading5);
panelElement.appendChild(panelHeadingElement);
var listGroupElement = document.createElement("div");
listGroupElement.className = "list-group";
if (isObject(this.formData)) {
listGroupElement.setAttribute("formData", encodeURIComponent(JSON.stringify(this.formData)));
}
for (var i=0; i<this.rows.length; i++) {
listGroupElement.appendChild(this.rows[i].generateDomElement());
}
panelElement.appendChild(listGroupElement);
parentElement.appendChild(panelElement);
return parentElement;
};
}
function Form(formObject, onsubmitHandler) {
function Button(buttonTitle) {
var title = buttonTitle;
throwIfNotStringOrNumber(title);
this.title = title;
this.generateDomElement = function() {
var buttonElement = document.createElement("button");
buttonElement.className = "btn btn-default";
buttonElement.setAttribute("type", "submit");
var buttonText = document.createTextNode(this.title);
buttonElement.appendChild(buttonText);
return buttonElement;
};
}
function Field(formFieldJSON) {
var formFieldObject = formFieldJSON;
var label = formFieldObject["label"], type = formFieldObject["type"], id = formFieldObject["selectionID"];
throwIfNotStringOrNumber(label); throwIfNotStringOrNumber(type); throwIfNotStringOrNumber(id);
var value = formFieldObject["value"]; // can be null
this.label = label;
this.type = type;
this.id = id;
this.value = value;
this.generateDomElement = function() {
var formGroupElement = document.createElement("div");
formGroupElement.className = "form-group";
var formGroupLabelElement = document.createElement("label");
formGroupLabelElement.setAttribute("for", this.id);
var formGroupLabelElementTextNode = document.createTextNode(this.label);
var formGroupInputElement = document.createElement("input");
formGroupInputElement.className = "form-control";
formGroupInputElement.id = this.id;
if (isString(this.value)) {
formGroupInputElement.value = this.value;
}
formGroupInputElement.setAttribute("type", this.type);
formGroupInputElement.setAttribute("selectionID", encodeURIComponent(this.id));
if (this.id === "login_time_zone_offset") { // special case for the time element. it needs to be filled in by JS
formGroupInputElement.value = new Date().getTimezoneOffset() * -1;
}
if (this.type == "hidden") { // if the field has type of hidden, hide its whole label and row in the DOM
formGroupElement.style.display = "none";
}
formGroupLabelElement.appendChild(formGroupLabelElementTextNode);
formGroupElement.appendChild(formGroupLabelElement);
formGroupElement.appendChild(formGroupInputElement);
return formGroupElement;
};
}
function Header(title) {
throwIfNotStringOrNumber(title);
this.title = title;
this.generateDomElement = function() {
var panelHeadingElement = document.createElement("div");
panelHeadingElement.className = "panel-heading";
var panelTitleElement = document.createElement("h5");
panelTitleElement.className = "panel-title";
var panelTitleText = document.createTextNode(this.title);
panelTitleElement.appendChild(panelTitleText);
panelHeadingElement.appendChild(panelTitleElement);
return panelHeadingElement;
};
}
var button = new Button(formObject["buttonTitle"]);
var formData = formObject["formData"];
var formFieldObjects = formObject["fields"];
throwIfNotArray(formFieldObjects); throwIfNotObject(formData); throwIfNotFunction(onsubmitHandler);
var formFields = [];
for (var i = 0; i < formFieldObjects.length; i++) {
var field = new Field(formFieldObjects[i]);
formFields.push(field);
}
this.header = new Header(formObject["title"]); // Form.Header
this.button = button; // Form.Button
this.fields = formFields; // [Form.Field]
this.formData = formData; // JSON Object not stringified
this.onsubmitHandler = onsubmitHandler;
this.generateDomElement = function() {
var panelContainerElement = document.createElement("div");
panelContainerElement.className = columnSizeStringConstant + " " + "center-block";
var panelElement = document.createElement("div");
panelElement.className = "panel panel-default";
panelElement.appendChild(this.header.generateDomElement());
panelContainerElement.appendChild(panelElement);
var panelBody = document.createElement("div");
panelBody.className = "panel-body";
var panelListElement = document.createElement("div");
panelListElement.className = "list-group";
var formElement = document.createElement("form");
formElement.className = columnSizeStringConstant + " " + "center-block";
formElement.setAttribute("formData", encodeURIComponent(JSON.stringify(this.formData)));
formElement.onsubmit = this.onsubmitHandler;
panelListElement.appendChild(formElement);
panelBody.appendChild(panelListElement);
panelElement.appendChild(panelBody);
for (var i = 0; i<this.fields.length; i++) {
var field = this.fields[i];
formElement.appendChild(field.generateDomElement());
}
formElement.appendChild(this.button.generateDomElement());
return panelContainerElement;
};
}
function Button(buttonObject) {
var text = buttonObject["text"];
var url = buttonObject["url"];
throwIfNotStringOrNumber(text); throwIfNotString(url);
this.text = text;
this.url = url;
this.generateDomElement = function() {
// <div class="row col-xs-12 center-block">
// <a href="link" class="btn btn-default btn-block btn-lg">text</a>
var containerElement = document.createElement("div");
containerElement.className = "row" + " " + columnSizeStringConstant + " " + "center-block";
var linkElement = document.createElement("a");
linkElement.href = this.url;
linkElement.className = "btn btn-default btn-block btn-lg";
var textNode = document.createTextNode(this.text);
var breakElement = document.createElement("br");
linkElement.appendChild(textNode);
containerElement.appendChild(linkElement);
containerElement.appendChild(breakElement);
return containerElement;
};
}
function Well(textString) {
throwIfNotStringOrNumber(textString);
this.text = textString;
this.generateDomElement = function() {
var container = document.createElement("div");
container.className = columnSizeStringConstant + " " + "center-block";
var well = document.createElement("div");
well.className = "well well-sm";
var pElement = document.createElement("p");
pElement.className = "small";
pElement.innerHTML = this.text;
well.appendChild(pElement)
container.appendChild(well);
return container;
};
}
// map the messages into an array
var rawMessages = rootDOMObject["messages"];
var messages = [];
if (isArray(rawMessages)) {
for (var i=0; i<rawMessages.length; i++) {
var message = new Message(rawMessages[i]);
messages.push(message);
}
}
// map the panels into an array
var rawPanels = rootDOMObject["panels"];
var panels = [];
if (isArray(rawPanels)) {
for (var i=0; i<rawPanels.length; i++) {
var panel = new Panel(rawPanels[i], panelOnClickHandler);
panels.push(panel);
}
}
// map the forms into an array
var rawForms = rootDOMObject["forms"];
var forms = [];
if (isArray(rawForms)) {
for (var i=0; i<rawForms.length; i++) {
var bootstrapForm = new Form(rawForms[i], formOnsubmitHandler);
forms.push(bootstrapForm);
}
}
// map the buttons into an array
var rawButtons = rootDOMObject["buttons"];
var buttons = [];
if (isArray(rawButtons)) {
for (var i=0; i<rawButtons.length; i++) {
var button = new Button(rawButtons[i]);
buttons.push(button);
}
}
// map the wells into an array
var wellStrings = rootDOMObject["wells"];
var wells = [];
if (isArray(wellStrings)) {
for (var i=0; i<wellStrings.length; i++) {
var well = new Well(wellStrings[i]);
wells.push(well);
}
}
this.navbar = new NavigationBar(rootDOMObject["title"]);
this.messages = messages; // [Message]
this.panels = panels; // [Panel]
this.forms = forms; // [Form]
this.buttons = buttons; // [Button]
this.wells = wells; // [Well]
this.generateDomElement = function() {
var parentElement = document.createElement("div");
parentElement.appendChild(this.navbar.generateDomElement());
for (var i=0; i<this.messages.length; i++) {
parentElement.appendChild(this.messages[i].generateDomElement());
}
for (var i=0; i<this.panels.length; i++) {
parentElement.appendChild(this.panels[i].generateDomElement());
}
for (var i=0; i<this.forms.length; i++) {
parentElement.appendChild(this.forms[i].generateDomElement());
}
for (var i=0; i<this.buttons.length; i++) {
parentElement.appendChild(this.buttons[i].generateDomElement());
}
for (var i=0; i<this.wells.length; i++) {
parentElement.appendChild(this.wells[i].generateDomElement());
}
return parentElement;
};
}
//
//
// //
// // SteelReserve.js
// // This is custom app code that handles ASYNC calls
// // It calls to the server, downloads JSON
// // Then uses the Bootstrap generator functions to generate a new DOM
// // Lastly, it handles user input and POST calls back to the server
//
//
//
//
// Handle ASYNC POST Requests
//
//
function postRequestWith(postBody, completionHandler) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200) {
completionHandler(request.responseText);
} else {
var errorResponse = {
"wells": [
"SteelReserve was hand-crafted during the July 2016 Hackathon by <a href=\"mailto:[email protected]\">Jeffrey Bergier<\/a>, <a href=\"mailto:[email protected]\">Josh Castaneda<\/a>, and <a href=\"mailto:[email protected]\">Tyler Anderson."
],
"title": "Error",
"messages" : [
{
"kind" : "danger",
"text" : "Your device appears to have lost its internet connection."
}
]
};
var errorResonseString = JSON.stringify(errorResponse);
completionHandler(errorResonseString);
}
}
}
request.open("POST", "/", true);
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
request.send(postBody);
}
function performPostAndRenderNewDomWithObjectAndLoadingText(object, loadingText) {
throwIfNotObject(object); throwIfNotStringOrNumber(loadingText);
// this whole function goes a little bit in reverse because
// the modal window should only be shown if there is a delay of more than 1/3 of a second
var showingModal = false;
var objectString = JSON.stringify(object);
var downloadedJSONObject;
// this gets run whether the modal is showing or not
// if the modal is showing, it gets run when the modal hides
// if the modal is not showing, it gets run by the networking completion handler
var completionHandler = function() {
showingModal = false;
throwIfNotObject(downloadedJSONObject);
var downloadedDOM = new BootstrapDOM(downloadedJSONObject, sr_loginFormSubmitHandler, sr_panelOnClickHandler);
var renderedDom = downloadedDOM.generateDomElement();
document.body.innerHTML = "";
document.body.appendChild(renderedDom);
history.pushState(downloadedJSONObject, downloadedDOM.navbar.title, "/");
};
// this timeout shows the modal after 1/3 of a second
// it is cancelled by the network completion handler whether it has executed or not
var timeout = setTimeout(function(){
showingModal = true;
waitingDialog.show(loadingText, {
onShow: function() { },
onHide: function() {
completionHandler();
}
});
}, 333);
// this executes the network request
// then parses the data
// then checks if the modal is showing
// if the modal is showing, it hides the modal (then the modal executes the completionHandler above)
// if the modal is not showing, the completion handler is executed directly.
postRequestWith(objectString, function(response) {
clearTimeout(timeout);
downloadedJSONObject = JSON.parse(response);
if (showingModal == true) {
waitingDialog.hide();
} else {
completionHandler();
}
});
}
//
//
// Handle onsubmit action from login forms
//
//
function sr_loginFormSubmitHandler() {
throwIfNotDOMElement(this);
// get the formData of this form
var formDataEncodedString = this.hasAttribute("formData") ? this.getAttribute("formData") : null;
throwIfNotStringOrNumber(formDataEncodedString);
var formDataString = decodeURIComponent(formDataEncodedString);
throwIfNotStringOrNumberAndNotEmptyString(formDataString);
// create a JSON object to POST back to the server
var submitObject = JSON.parse(formDataString);
var inputElements = this.getElementsByTagName("input");
for (var i=0; i<inputElements.length; i++) {
var inputElement = inputElements[i];
var selectionIDEncodedString = inputElement.hasAttribute("selectionID") ? inputElement.getAttribute("selectionID") : null;
throwIfNotStringOrNumber(selectionIDEncodedString);
var selectionID = decodeURIComponent(selectionIDEncodedString);
throwIfNotStringOrNumber(selectionID);
var inputElementValue = inputElement.value;
throwIfNotStringOrNumber(inputElementValue);
// add each field in the form into the JSON
submitObject[selectionID] = inputElementValue;
}
// POST Data to the server and add the response to the History Array
performPostAndRenderNewDomWithObjectAndLoadingText(submitObject, "Logging In");
return false;
}
function sr_panelOnClickHandler() {
throwIfNotDOMElement(this); throwIfNotDOMElement(this.parentElement);
// get the formData of this form
var formDataEncodedString = this.parentElement.hasAttribute("formData") ? this.parentElement.getAttribute("formData") : null;
var selectionIDEncodedString = this.hasAttribute("selectionID") ? this.getAttribute("selectionID") : null;
throwIfNotStringOrNumber(formDataEncodedString); throwIfNotStringOrNumber(selectionIDEncodedString);
var formDataString = decodeURIComponent(formDataEncodedString);
var selectionID = decodeURIComponent(selectionIDEncodedString);
throwIfNotStringOrNumberAndNotEmptyString(formDataString); throwIfNotStringOrNumber(selectionID);
var submitObject = JSON.parse(formDataString);
throwIfNotObject(submitObject);
submitObject["selectionID"] = selectionID;
// Check the current step in order to customize the loading text
var currentStep = submitObject["currentStep"];
var loadingText = "Loading";
if (currentStep === "chooseDuration") {
loadingText = "Finding Buildings";
} else if (currentStep === "chooseBuilding") {
loadingText = "Checking Rooms";
} else if (currentStep === "chooseRoom") {
loadingText = "Reserving Room";
}
// POST Data to the server and add the response to the History Array
performPostAndRenderNewDomWithObjectAndLoadingText(submitObject, loadingText);
return false;
}
//
//
// Handle Initial Page Load
//
//
window.onload = function() {
window.onpopstate = function(event) {
var state = event.state;
if (isObject(state)) {
var bootstrapDom = new BootstrapDOM(state, sr_loginFormSubmitHandler, sr_panelOnClickHandler);
var renderedDom = bootstrapDom.generateDomElement();
document.body.innerHTML = "";
document.body.appendChild(renderedDom);
return false;
} else {
return true;
}
};
// clear out the javascript warning immediately
document.body.innerHTML = "";
// POST Data to the server and add the response to the History Array
performPostAndRenderNewDomWithObjectAndLoadingText({}, "Loading");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment