A Pen by Uriel Wilson Jr. on CodePen.
Created
November 10, 2018 03:25
-
-
Save devuri/c5b624660d82f25c0c4a2740ba534a31 to your computer and use it in GitHub Desktop.
Secure Form Builder
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
| <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> |
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
| $(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(); | |
| }); |
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
| <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> |
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
| 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; | |
| } | |
| } |
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
| <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