Skip to content

Instantly share code, notes, and snippets.

@devuri
Created November 10, 2018 03:25
Show Gist options
  • Select an option

  • Save devuri/c5b624660d82f25c0c4a2740ba534a31 to your computer and use it in GitHub Desktop.

Select an option

Save devuri/c5b624660d82f25c0c4a2740ba534a31 to your computer and use it in GitHub Desktop.
Secure Form Builder
<div class="container">
<div class="row">
<div class="col-sm-12">
<h1>Secure Form Builder</h1>
</div>
</div>
<div class="sf-message-wrap"><div class="sf-message"></div></div>
<div class="form-builder">
</div>
<hr />
<div class="row sf-action-buttons">
<div class="col-sm-4">
<a href="#" class="sf-add-field btn btn-primary btn-block">Add Field</a>
</div>
<div class="col-sm-4">
<a href="#" class="sf-add-heading btn btn-primary btn-block">Add Header</a>
</div>
<div class="col-sm-4">
<a href="#" class="sf-add-text btn btn-primary btn-block">Add Text</a>
</div>
</div>
<br /><br />
<div class="form-preview">
</div>
</div>
$(function() {
// Pre-populate the form builder with previously saved data
if (localStorage.getItem("formBuilder") !== null) {
var formBuilderData = JSON.parse(localStorage.getItem("formBuilder"));
for (var i = 0; i < formBuilderData.length; i++) {
var thisCardData = formBuilderData[i];
// For each field object in storage, append a card to the form builder
if (thisCardData.cardType == "field") {
addFormFieldCard();
} else if (thisCardData.cardType == "heading") {
addHeadingCard();
} else if (thisCardData.cardType == "text") {
addTextCard();
}
// Populate the values of the fields by matching the field name with the key of the localStorage json
// [{"fieldLabel: "First Name",...}]
// $("[name='fieldLabel']").val("First Name")
var formFieldKeys = Object.keys(thisCardData),
currentCard = $(".form-builder .sf-form-builder-card").eq(i);
for (var k = 0; k < formFieldKeys.length; k++) {
var formFieldName = formFieldKeys[k];
if (formFieldName != "cardType") {
if (formFieldName == "fieldOptions") {
// Build field options
for (var o = 0; o < thisCardData.fieldOptions.length; o++) {
var fieldOption = thisCardData["fieldOptions"][o],
fieldOptionKeys = Object.keys(thisCardData["fieldOptions"][o]),
displayTextFieldName = fieldOptionKeys[0],
optionValueFieldName = fieldOptionKeys[1];
// Repopulate saved field option
newFieldOption(currentCard, fieldOption["displayText" + (o + 1)], fieldOption["optionValue" + (o + 1)]);
}
if (thisCardData.fieldType != "text" && thisCardData.fieldType != "textarea") {
currentCard.find(".field-options-wrap").show();
}
} else {
var fieldElement = currentCard.find("[name='"+formFieldName+"']"),
fieldType = getFieldType(fieldElement);
if (fieldType == "checkbox" && thisCardData[formFieldName] == fieldElement.val()) {
fieldElement.attr("checked", true);
} else {
fieldElement.val(thisCardData[formFieldName]);
}
}
}
}
}
} else {
// If nothing is found in storage, start a new form builder
addFormFieldCard();
}
function saveFormBuilder() {
var formFieldsArray = [],
validFormBuilder = true;
//loop on each card
$(".form-builder .sf-form-builder-card").each(function() {
var thisCard = $(this),
thisCardType = thisCard.data("card-type"),
thisCardObj = {},
formFieldOptionsArray = [],
fieldOptionCount = 1;
thisCardObj.cardType = thisCardType;
// loop on each card group
thisCard.find(".sf-field-group").each(function(i,item) {
var thisFieldGroup = $(this),
formFieldOptionObj = {};
// loop on each field inside field group
thisFieldGroup.find("input, textarea, select").each(function() {
var thisField = $(this),
fieldName = thisField.attr("name"),
fieldValue = thisField.val(),
fieldType = getFieldType(thisField);
// Check if field is a form field option
if (fieldName.indexOf("displayText") > -1) {
formFieldOptionObj["displayText" + fieldOptionCount] = fieldValue;
} else if (fieldName.indexOf("optionValue") > -1) {
// If this is a field option, add to fieldOptions object
formFieldOptionObj["optionValue" + fieldOptionCount] = fieldValue;
fieldOptionCount++; // increase count of field option when group is complete
} else if (fieldType == "checkbox" && !thisField.is(":checked")) {
// Don't save unchecked checkboxes
} else {
thisCardObj[fieldName] = fieldValue;
}
});
// Add field options to card object
if (!$.isEmptyObject(formFieldOptionObj)) {
formFieldOptionsArray.push(formFieldOptionObj);
thisCardObj["fieldOptions"] = formFieldOptionsArray;
}
});
// push form field data to final array
formFieldsArray.push(thisCardObj);
});
// Store the form field data
localStorage.setItem("formBuilder", JSON.stringify(formFieldsArray));
$(".sf-message").text("Form Builder Saved Successfully!").show();
setTimeout(function() {
$(".sf-message").fadeOut();
},500);
}
function getFieldType(thisField) {
var thisFieldTag = thisField.prop("tagName").toLowerCase();
if (thisFieldTag == "input") {
return thisField.attr("type").toLowerCase();
} else {
return thisFieldTag;
}
}
function refreshSortable() {
$('.form-builder').sortable({ connectWith: '.form-builder' });
$('.field-options').sortable({ connectWith: '.field-options' });
}
// adds new field options for selects/radios/etc.
function newFieldOption(currentCard,displayText,optionValue) {
if (displayText == undefined || optionValue == undefined) {
var displayText = "",
optionValue = "",
newOption = true;
}
var fieldOptionsDiv = currentCard.find(".field-options"),
fieldOptionIndex = fieldOptionsDiv.find(".field-option-row").length + 1;
fieldOptionsDiv.append("<div class='field-option-row sf-field-group clearfix' data-index='"+fieldOptionIndex+"'>"
+ "<div class='col-sm-5 form-group'>"
+ "<label>Display Text</label>"
+ "<input class='form-control' type='text' name='displayText"+fieldOptionIndex+"' value='"+displayText+"'>"
+ "</div>"
+ "<div class='col-sm-5 form-group'>"
+ "<label>Option Value</label>"
+ "<input class='form-control' type='text' name='optionValue"+fieldOptionIndex+"' value='"+optionValue+"'>"
+ "</div>"
+ "<div class='col-xs-6 col-sm-1 field-options-action form-group text-center'>"
+ "<a href='#' class='delete-option' title='Delete'><i class='glyphicon glyphicon-remove'></i></a>"
+ "</div>"
+ "<div class='col-xs-6 col-sm-1 field-options-action form-group text-center'>"
+ "<a href='#' title='Move' class='handle2'><i class='glyphicon glyphicon-move'></i></a>"
+ "</div>"
+ "</div>");
refreshSortable();
if (newOption == true) {
setTimeout(function() {
fieldOptionsDiv.find(".field-option-row:last-child input:first").focus();
},10);
}
}
function addFormFieldCard() {
$(".form-builder").append("<div class='row sf-form-builder-card' data-card-type='field'>"
+ "<div class='col-sm-12'>"
+ "<div class='sf-form-builder-card-inner'>"
+ "<div class='card-options'>"
+ "<div class='move-card'>"
+ "<a href='#' title='Move' class='handle'><i class='glyphicon glyphicon-move'></i></a>"
+ "</div>"
+ "<div class='expand-collapse-card'>"
+ "<a href='#' title='Expand/Collapse'><i class='glyphicon glyphicon-chevron-up'></i></a>"
+ "</div>"
+ "</div>"
+ "<div class='row'>"
+ "<div class='col-sm-12 form-group sf-field-group clear-mobile'>"
+ "<label class='red'>Label *</label>"
+ "<input class='form-control' type='text' name='fieldLabel' required>"
+ "</div>"
+ "</div>"
+ "<div class='collapsible'>"
+ "<div class='row'>"
+ "<div class='col-sm-6 form-group sf-field-group'>"
+ "<label class='red'>Report Name *</label>"
+ "<input class='form-control' type='text' name='fieldReportName' required>"
+ "</div>"
+ "<div class='col-sm-6 form-group sf-field-group'>"
+ "<label class='red'>Field Type *</label>"
+ "<select class='form-control' name='fieldType' required>"
+ "<option value=''>Please Select</option>"
+ "<option value='text'>Text</option>"
+ "<option value='textarea'>Textarea</option>"
+ "<option value='select'>Select</option>"
+ "<option value='select-multiple'>Select (Multiple)</option>"
+ "<option value='checkbox'>Checkbox</option>"
+ "<option value='radio'>Radio</option>"
+ "<option value='file'>File Upload</option>"
+ "<option value='hidden'>Hidden</option>"
+ "</select>"
+ "</div>"
+ "<div class='col-sm-6 form-group sf-field-group'>"
+ "<label>Validate As</label>"
+ "<select class='form-control' name='fieldValidateAs'>"
+ "<option value='none'>No Validation</option>"
+ "<option value='text'>Text</option>"
+ "<option value='number'>Number</option>"
+ "<option value='phone'>Phone Number</option>"
+ "<option value='email'>Email</option>"
+ "<option value='zip'>Zip Code</option>"
+ "</select>"
+ "</div>"
+ "<div class='col-sm-6 form-group sf-field-group'>"
+ "<label>Placeholder Text</label>"
+ "<input class='form-control' type='text' name='fieldPlaceholder'>"
+ "</div>"
+ "<div class='col-sm-6 checkbox sf-field-group'>"
+ "<label>"
+ "<input type='checkbox' name='fieldRequired' value='Yes'>"
+ "Required Field?"
+ "</label>"
+ "</div>"
+ "</div>"
+ "<div class='row field-options-wrap'>"
+ "<hr />"
+ "<div class='col-sm-12 form-group'>"
+ "<p class='h4'>Field Options</p>"
+ "</div>"
+ "<div class='field-options'>"
+ "</div>"
+ "<div class='col-sm-12 form-group'>"
+ "<a href='#' class='add-option'><i class='glyphicon glyphicon-plus'></i> Add option</a>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>");
refreshSortable();
}
function addHeadingCard() {
$(".form-builder").append("<div class='row sf-form-builder-card' data-card-type='heading'>"
+ "<div class='col-sm-12'>"
+ "<div class='sf-form-builder-card-inner'>"
+ "<div class='row'>"
+ "<div class='card-options'>"
+ "<div class='move-card'>"
+ "<a href='' class='handle'><i class='glyphicon glyphicon-move'></i></a>"
+ "</div>"
+ "<div class='delete-card'>"
+ "<a href='#'><i class='glyphicon glyphicon-remove'></i></a>"
+ "</div>"
+ "</div>"
+ "<div class='col-sm-7 form-group sf-field-group'>"
+ "<label><span class='red'>Heading Text *</span></label>"
+ "<input class='form-control' type='text' name='headingText' required>"
+ "</div>"
+ "<div class='col-sm-3 form-group sf-field-group'>"
+ "<label><span class='red'>Font Size *</span></label>"
+ "<select class='form-control' name='headingSize' required>"
+ "<option value=''>Please Select</option>"
+ "<option value='h1'>H1</option>"
+ "<option value='h2'>H2</option>"
+ "<option value='h3'>H3</option>"
+ "<option value='h4'>H4</option>"
+ "<option value='h5'>H5</option>"
+ "<option value='h6'>H6</option>"
+ "</select>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>");
refreshSortable();
}
function addTextCard() {
$(".form-builder").append("<div class='row sf-form-builder-card' data-card-type='text'>"
+ "<div class='col-sm-12'>"
+ "<div class='sf-form-builder-card-inner'>"
+ "<div class='row'>"
+ "<div class='card-options'>"
+ "<div class='move-card'>"
+ "<a href='#' class='handle'><i class='glyphicon glyphicon-move'></i></a>"
+ "</div>"
+ "<div class='delete-card'>"
+ "<a href='#'><i class='glyphicon glyphicon-remove'></i></a>"
+ "</div>"
+ "</div>"
+ "<div class='col-sm-7 form-group sf-field-group'>"
+ "<label><span class='red'>Content *</span></label>"
+ "<textarea class='form-control' name='textContent' required></textarea>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>"
+ "</div>");
refreshSortable();
}
/************* USER INTERACTIONS ******************/
// Delete card
$(".form-builder").on("click", ".delete-card a", function(e) {
e.preventDefault();
if (confirm("Are you sure you would like to delete this card?") == true) {
$(this).closest(".sf-form-builder-card").remove();
saveFormBuilder();
refreshSortable();
}
});
// collpase/expand card
$(".form-builder").on("click", ".expand-collapse-card", function(e) {
e.preventDefault();
var currentCard = $(this).closest(".sf-form-builder-card");
if (!currentCard.hasClass("collapsed")) {
currentCard.addClass("collapsed");
//$(this).find("span").text("Expand");
$(this).find("i").removeClass("glyphicon-chevron-up").addClass("glyphicon-chevron-down");
} else {
currentCard.removeClass("collapsed");
//$(this).find("span").text("Collapse");
$(this).find("i").removeClass("glyphicon-chevron-down").addClass("glyphicon-chevron-up");
}
});
// add new standard field
$(".sf-add-field").on("click", function(e) {
e.preventDefault();
addFormFieldCard();
// Focus on new card's first input
$(".form-builder > .sf-form-builder-card:last-child").find("input:first").focus();
});
// add new heading field
$(".sf-add-heading").on("click", function(e) {
e.preventDefault();
addHeadingCard();
});
// add new text field
$(".sf-add-text").on("click", function(e) {
e.preventDefault();
addTextCard();
});
// detect form field changes - save form builder and other stuff
$(".form-builder").on("change", "input, textarea, select", function(e) {
var currentCard = $(this).closest(".sf-form-builder-card");
// Show field options if select/radio/checkbox/etc is selected
if ($(this).attr("name") == "fieldType") {
var fieldOptionTriggers = ["select", "select-multiple", "radio", "checkbox"],
fieldOptionsDiv = currentCard.find(".field-options-wrap");
if (fieldOptionTriggers.indexOf($(this).val()) > -1) {
// Disable validate field
currentCard.find("[name='fieldValidateAs']").attr("disabled", "disabled");
if (fieldOptionsDiv.is(":hidden")) {
// Only add new field option if none exist
if (fieldOptionsDiv.find(".field-option-row").length == 0) {
newFieldOption(currentCard);
}
fieldOptionsDiv.show();
} else {
fieldOptionsDiv.hide();
}
} else {
// Re-enable validate field
currentCard.find("[name='fieldValidateAs']").removeAttr("disabled");
}
}
saveFormBuilder();
});
$(".form-builder").on("click", ".add-option", function(e) {
e.preventDefault();
var currentCard = $(this).closest(".sf-form-builder-card");
newFieldOption(currentCard);
});
// Delete field option
$(".form-builder").on("click", ".delete-option", function(e) {
e.preventDefault();
if (confirm("Are you sure you would like to delete this option?") == true) {
$(this).closest(".field-option-row").remove();
saveFormBuilder();
refreshSortable();
}
});
/*************** INITALIZE DRAG AND DROP SORTING ***************/
// sort cards
$(".form-builder").sortable({
handle: '.handle',
containment: "parent",
update: function(event, ui) {
saveFormBuilder();
}
}).disableSelection();
// sort field options
$(".field-options").sortable({
handle: '.handle2',
containment: "parent",
update: function(event, ui) {
saveFormBuilder();
}
}).disableSelection();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
body {
background: #eee;
}
h1 {
font-size: 28px;
}
.sf-form-builder-card {
margin-bottom: 25px;
}
.sf-form-builder-card-inner {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,.2);
padding: 15px;
}
.sf-form-builder-card hr {
margin-top: 10px;
margin-bottom: 10px;
}
.move-card, .expand-collapse-card, .delete-card {
float: right;
text-align: center;
}
.handle, .handle2 {
cursor: move;
}
.sf-action-buttons .btn {
margin-bottom: 15px;
}
.card-fields {
border-right: 1px solid #eee;
}
.sf-form-builder-card {
transition: .3s;
}
.sf-form-builder-card.collapsed .collapsible {
display: none;
}
.red {
color: maroon;
}
.field-options-action .delete-option {
color: maroon;
}
.field-options-action a {
background: #ddd;
height: 30px;
width: 30px;
line-height: 30px;
border-radius: 50%;
display: inline-block;
margin-top: 25px;
}
.field-options-wrap {
display: none;
}
.sf-message {
position: fixed;
float: right;
bottom: 0;
right: 15px;
background: green;
padding: 8px 20px;
z-index: 100;
color: #fff;
display: none;
box-shadow: 0 6px 12px rgba(0,0,0,.2);
border-radius: 4px 4px 0 0;
}
.card-options {
position: absolute;
top: -10px;
right: 30px;
}
.card-options a {
display: block;
margin-left: 15px;
border-radius: 50%;
background: #ddd;
line-height: 30px;
height: 30px;
width: 30px;
}
.card-options .delete-card a {
color: maroon;
}
@media all and (max-width: 767px) {
.card-options {
float: none;
}
.clear-mobile {
clear: both;
}
.field-options-action {
border-bottom: 1px solid #eee;
padding-bottom: 15px;
}
.field-options-action a {
margin-top: 0;
}
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment