-
Aug 19th, 2021, 07:33 PM
#1
[Javascript] [ES6] Bootstrap Form Builder
Updated Code
See post #2: https://www.vbforums.com/showthread....=1#post5537284
Bootstrap Form Builder
A simple JavaScript solution for building Bootstrap forms on the fly.
Demo
Fiddle: https://jsfiddle.net/ux84votz/
Repository
https://github.com/dday9/Bootstrap-Form-Builder
Usage
Code:
const contactForm = formBuilder.buildForm({
attributes: {
enctype: 'application/x-www-form-urlencoded',
method: 'post',
},
fieldsets: [
{
legend: 'Name',
fields: [
{
attributes: {
name: 'firstName',
placeholder: 'John'
},
label: 'First Name'
},
{
attributes: {
name: 'lastName',
placeholder: 'Doe'
},
label: 'Last Name'
},
{
attributes: {
name: 'email'
},
label: 'Email Address',
type: 'email'
}
]
},
{
legend: 'Address',
fields: [
{
attributes: {
name: 'street1',
placeholder: '123 Main St'
},
label: 'Street'
},
{
attributes: {
name: 'city',
placeholder: 'Anytown'
},
label: 'City'
},
{
attributes: {
list: 'datalist-states',
name: 'state'
},
datalist: {
id: 'datalist-states',
values: [ 'AK','AL','AR','AS','AZ','CA','CO','CT','DC','DE','FL','GA','GU','HI','IA','ID','IL','IN','KS','KY','LA','MA','MD','ME','MI','MN','MO','MS','MT','NC','ND','NE','NH','NJ','NM','NV','NY','OH','OK','OR','PA','PR','RI','SC','SD','TN','TX','UT','VA','VI','VT','WA','WI','WV','WY']
},
label: 'State',
},
{
attributes: {
name: 'zip',
pattern: '\\d{5}',
placeholder: '12345'
},
label: 'Zip Code',
type: 'tel'
}
]
}
],
submit: (e) => {
e.preventDefault();
console.log('submit');
}
});
Source Code
Code:
const formBuilder = {};
formBuilder.buildForm = (options) => {
// validate the options parameter
options = options || {};
if (typeof options !== 'object' || options == null) {
console.error('options is not a valid object');
return null;
}
// validate the options.fieldsets parameter
options.fieldsets = options.fieldsets || [];
if (!Array.isArray(options.fieldsets)) {
console.error('options.fieldset is not an array');
return null;
}
// validate the options.attributes
options.attributes = options.attributes || {};
if (typeof options.attributes !== 'object' || options.attributes == null) {
console.error('options.attributes is not a valid object');
return null;
}
// create the form
const form = document.createElement('form');
// create a random id
const rightNow = new Date();
form.id = rightNow.getTime();
// optionally bind to the submit event
if (options.submit) {
if ({}.toString.call(options.submit) !== '[object Function]') {
console.error('options.submit is not a valid function');
return;
}
// add the event listenter to document
document.addEventListener('submit', (e) => {
// check if the target is the form
if (e.target && e.target.id === form.id) {
// call the submit callback, passing e
options.submit(e);
}
});
}
// iterate over every attribute to set the form attribute
const attributes = Object.keys(options.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidFormAttribute(attribute)) {
form.setAttribute(attribute, options.attributes[attribute]);
} else {
console.log(`'${attribute}' is an invalid form attribute.`);
}
});
// iterate over every fieldset
options.fieldsets.forEach(fieldset => {
// create and append the fieldset to the form
const fieldsetElement = formBuilder.buildFieldset(fieldset);
if (fieldsetElement) {
form.append(fieldsetElement);
}
});
// return the form
return form;
};
formBuilder.buildDatalist = (datalist) => {
// validate the datalist parameter
if (typeof datalist !== 'object' || datalist == null) {
return null;
}
// require an id property
if (!datalist.hasOwnProperty('id')) {
console.error('datalist is missing the required property \'id\'');
return null;
}
// require a values property
if (!datalist.hasOwnProperty('values')) {
console.error('datalist is missing the required property \'values\'');
return null;
}
// require that the values property be an array
if (!Array.isArray(datalist.values)) {
console.error('datalist.values is not an array');
return null;
}
// create the datalist
const datalistElement = document.createElement('datalist');
datalistElement.id = datalist.id;
// iterate over the values to create an option with a value
datalist.values.forEach(value => {
const option = document.createElement('option');
option.value = value;
datalistElement.append(option);
});
// return the datalist
return datalistElement;
};
formBuilder.buildFieldset = (fieldset) => {
// validate the fieldset parameter
if (typeof fieldset !== 'object' || fieldset == null) {
console.error('fieldset is not a valid object');
return null;
}
// validate the fieldset.fields parameter
fieldset.fields = fieldset.fields || [];
if (!Array.isArray(fieldset.fields)) {
console.error('fieldset.fields is not an array');
return null;
}
// create the element
const fieldsetElement = document.createElement('fieldset');
// optionally create a legend
if (fieldset.legend) {
if (Object.prototype.toString.call(fieldset.legend) !== "[object String]") {
// validate the legend property
console.log('fieldset.legend is not a string');
} else {
// create the legend element and append it to the fieldset
const legend = document.createElement('legend');
legend.innerHTML = fieldset.legend;
fieldsetElement.append(legend);
}
}
// iterate over each field
fieldset.fields.forEach(field => {
// create the div container
const divContainer = document.createElement('div');
divContainer.setAttribute('class', 'mb-3');
// validate the field type or default to text
const fieldType = (formBuilder.isValidFieldType(field) ? field.type : 'text');
// optionally create the label
if (field.label) {
if (Object.prototype.toString.call(field.label) !== "[object String]") {
// validate the legend property
console.log('field.label is not a string');
} else {
// create the label and append it to the div container
const label = formBuilder.buildLabel(field.label);
if (label) {
divContainer.append(label);
}
}
}
// create the input and append it to the div container
const input = formBuilder.buildInput(field, fieldType);
if (input) {
divContainer.append(input);
}
// optionally create the datalist
if (field.hasOwnProperty('datalist') && typeof field.datalist === 'object' && field.datalist !== null) {
const datalist = formBuilder.buildDatalist(field.datalist);
if (datalist) {
divContainer.append(datalist);
}
}
// append the div container
fieldsetElement.append(divContainer);
});
// return the fieldset
return fieldsetElement;
};
formBuilder.buildInput = (field, fieldType) => {
// valdiate the field parameter
if (typeof field !== 'object' || field == null) {
return null;
}
// validate the fieldType parameter
if (Object.prototype.toString.call(fieldType) !== "[object String]") {
return null;
}
// create the input
const input = document.createElement('input');
input.setAttribute('type', fieldType);
// iterate over each attribute to set the input attribute
const attributes = Object.keys(field.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidInputAttribute(fieldType, attribute)) {
input.setAttribute(attribute, field.attributes[attribute]);
} else {
console.log(`'${attribute}' is an invalid attribute for ${fieldType}.`);
}
});
if (attributes.indexOf('class') < 0 || attributes.class.indexOf('form-control') < 0) {
input.classList.add('form-control');
}
// return the input
return input;
};
formBuilder.buildLabel = (label) => {
// validate the label parameter
if (Object.prototype.toString.call(label) !== "[object String]") {
return null;
}
// create the label
const labelElement = document.createElement('label');
labelElement.setAttribute('class', 'form-label');
labelElement.innerHTML = label;
// return the label
return labelElement;
};
formBuilder.isValidFieldType = (field) => {
// validate the field parameter
if (typeof field !== 'object' || field == null || !field.hasOwnProperty('type')) {
return false;
}
// enumerate the acceptable types
const acceptedTypes = [
'checkbox',
'color',
'date',
'datetime-local',
'email',
'file',
'hidden',
'month',
'number',
'password',
'radio',
'range',
'search',
'tel',
'text',
'time',
'url',
'week'
];
// return if the field.type is one of the acceptable types
return acceptedTypes.indexOf(field.type) > -1;
};
formBuilder.isValidFormAttribute = (attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const attributes = [
'accept',
'accept-charset',
'accesskey',
'action',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'draggable',
'enctype',
'enterkeyhint',
'exportparts',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'method',
'name',
'nonce',
'novalidate',
'part',
'rel',
'slot',
'spellcheck',
'style',
'tabindex',
'target',
'title',
'translate'
];
if (attributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
return false;
};
formBuilder.isValidInputAttribute = (type, attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const allTypeAttributes = [
'accesskey',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'disabled',
'draggable',
'enterkeyhint',
'exportparts',
'form',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'list',
'name',
'nonce',
'part',
'readonly',
'required',
'slot',
'spellcheck',
'style',
'tabindex',
'title',
'translate',
'value'
];
if (allTypeAttributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
if (type === 'checkbox') {
const checkboxAttributes = [
'checked'
];
return checkboxAttributes.indexOf(attribute) > -1;
}
if (type === 'color') {
const colorAttributes = [
'list'
];
return colorAttributes.indexOf(attribute) > -1;
}
if (type === 'date') {
const dateAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return dateAttributes.indexOf(attribute) > -1;
}
if (type === 'datetime-local') {
const datetimeLocalAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return datetimeLocalAttributes.indexOf(attribute) > -1;
}
if (type === 'email') {
const emailAttributes = [
'list',
'readonly',
'required'
];
return emailAttributes.indexOf(attribute) > -1;
}
if (type === 'file') {
const fileAttributes = [
'accept',
'capture',
'multiple',
'required'
];
return fileAttributes.indexOf(attribute) > -1;
}
if (type === 'hidden') {
return false;
}
if (type === 'month') {
const monthAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return monthAttributes.indexOf(attribute) > -1;
}
if (type === 'number') {
const numberAttributes = [
'max',
'min',
'required',
'step'
];
return numberAttributes.indexOf(attribute) > -1;
}
if (type === 'password') {
const passwordAttributes = [
'maxlength',
'minlength',
'readonly',
'required',
'pattern',
'placeholder',
'size'
];
return passwordAttributes.indexOf(attribute) > -1;
}
if (type === 'radio') {
const radioAttributes = [
'checked'
];
return radioAttributes.indexOf(attribute) > -1;
}
if (type === 'range') {
const rangeAttributes = [
'list'
];
return rangeAttributes.indexOf(attribute) > -1;
}
if (type === 'search') {
const searchAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return searchAttributes.indexOf(attribute) > -1;
}
if (type === 'tel') {
const telAttributes = [
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return telAttributes.indexOf(attribute) > -1;
}
if (type === 'text') {
const textAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return textAttributes.indexOf(attribute) > -1;
}
if (type === 'time') {
const timeAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'url') {
const timeAttributes = [
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'week') {
const weekAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return weekAttributes.indexOf(attribute) > -1;
}
return false;
};
Last edited by dday9; Sep 22nd, 2021 at 12:04 PM.
-
Sep 22nd, 2021, 12:03 PM
#2
Re: [Javascript] [ES6] Bootstrap Form Builder
Updated to include support for <select /> elements.
Demo
https://jsfiddle.net/Lr2fh9p6/
Usage
Code:
window
.addEventListener('load', function () {
const contactForm = formBuilder.buildForm({
attributes: {
enctype: 'application/x-www-form-urlencoded',
method: 'post'
},
fieldsets: [
{
legend: 'Name',
fields: [
{
attributes: {
name: 'firstName',
placeholder: 'John'
},
label: 'First Name'
}, {
attributes: {
name: 'lastName',
placeholder: 'Doe'
},
label: 'Last Name'
}, {
attributes: {
name: 'email'
},
label: 'Email Address',
type: 'email'
}
]
}, {
legend: 'Address',
fields: [
{
attributes: {
name: 'street1',
placeholder: '123 Main St'
},
label: 'Street'
}, {
attributes: {
name: 'city',
placeholder: 'Anytown'
},
label: 'City'
}, {
attributes: {
name: 'state',
options: [{optgroup:{label:'A',options:[{text:'Alabama',value:'AL'},{text:'Alaska',value:'AK'},{text:'Arizona',value:'AZ'},{text:'Arkansas',value:'AR'}]}},{optgroup:{label:'C',options:[{text:'California',value:'CA'},{text:'Colorado',value:'CO'},{text:'Connecticut',value:'CT'}]}},{optgroup:{label:'D',options:[{text:'Delaware',value:'DE'},{text:'District of Columbia',value:'DC'}]}},{text:'Florida',value:'FL'},{text:'Georgia',value:'GA'},{text:'Hawaii',value:'HI'},{optgroup:{label:'I',options:[{text:'Idaho',value:'ID'},{text:'Illinois',value:'IL'},{text:'Indiana',value:'IN'},{text:'Iowa',value:'IA'}]}},{optgroup:{label:'K',options:[{text:'Kansas',value:'KS'},{text:'Kentucky',value:'KY'}]}},{text:'Louisiana',value:'LA'},{optgroup:{label:'M',options:[{text:'Maine',value:'ME'},{text:'Maryland',value:'MD'},{text:'Massachusetts',value:'MA'},{text:'Michigan',value:'MI'},{text:'Minnesota',value:'MN'},{text:'Mississippi',value:'MS'},{text:'Missouri',value:'MO'},{text:'Montana',value:'MT'}]}},{optgroup:{label:'N',options:[{text:'Nebraska',value:'NE'},{text:'Nevada',value:'NV'},{text:'New Hampshire',value:'NH'},{text:'New Jersey',value:'NJ'},{text:'New Mexico',value:'NM'},{text:'New York',value:'NY'},{text:'North Carolina',value:'NC'},{text:'North Dakota',value:'ND'}]}},{optgroup:{label:'O',options:[{text:'Ohio',value:'OH'},{text:'Oklahoma',value:'OK'},{text:'Oregan',value:'OR'}]}},{text:'Pennsilvania',value:'PA'},{text:'Rhode Island',value:'RI'},{optgroup:{label:'S',options:[{text:'South Carolina',value:'SC'},{text:'South Dakota',value:'SD'}]}},{optgroup:{label:'T',options:[{text:'Tennessee',value:'TN'},{text:'Texas',value:'TX'}]}},{text:'Utah',value:'UT'},{optgroup:{label:'V',options:[{text:'Vermont',value:'VT'},{text:'Virginia',value:'VA'}]}},{optgroup:{label:'W',options:[{text:'Washington',value:'WA'},{text:'West Virginia',value:'WV'},{text:'Wisconsin',value:'WI'},{text:'Wyoming',value:'WY'}]}}]
},
label: 'State',
type: 'select'
}, {
attributes: {
name: 'zip',
pattern: '\\d{5}',
placeholder: '12345'
},
label: 'Zip Code',
type: 'tel'
}
]
}
],
submit: (e) => {
e.preventDefault();
console.log('submit');
}
});
const input = document.createElement('input');
input.setAttribute('class', 'btn btn-primary');
input.setAttribute('type', 'submit');
input.value = 'Submit';
contactForm.append(input);
document
.getElementById('container')
.append(contactForm);
}, false);
Source Code
Code:
const formBuilder = {};
formBuilder.buildForm = (options) => {
// validate the options parameter
options = options || {};
if (typeof options !== 'object' || options == null) {
console.error('options is not a valid object');
return null;
}
// validate the options.fieldsets parameter
options.fieldsets = options.fieldsets || [];
if (!Array.isArray(options.fieldsets)) {
console.error('options.fieldset is not an array');
return null;
}
// validate the options.attributes
options.attributes = options.attributes || {};
if (typeof options.attributes !== 'object' || options.attributes == null) {
console.error('options.attributes is not a valid object');
return null;
}
// create the form
const form = document.createElement('form');
// create a random id
const rightNow = new Date();
form.id = rightNow.getTime();
// optionally bind to the submit event
if (options.submit) {
if ({}.toString.call(options.submit) !== '[object Function]') {
console.error('options.submit is not a valid function');
return;
}
// add the event listenter to document
document.addEventListener('submit', (e) => {
// check if the target is the form
if (e.target && e.target.id === form.id) {
// call the submit callback, passing e
options.submit(e);
}
});
}
// iterate over every attribute to set the form attribute
const attributes = Object.keys(options.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidFormAttribute(attribute)) {
form.setAttribute(attribute, options.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid form attribute.`);
}
});
// iterate over every fieldset
options.fieldsets.forEach(fieldset => {
// create and append the fieldset to the form
const fieldsetElement = formBuilder.buildFieldset(fieldset);
if (fieldsetElement) {
form.append(fieldsetElement);
}
});
// return the form
return form;
};
formBuilder.buildDatalist = (datalist) => {
// validate the datalist parameter
if (typeof datalist !== 'object' || datalist == null) {
return null;
}
// require an id property
if (!datalist.hasOwnProperty('id')) {
console.error('datalist is missing the required property \'id\'');
return null;
}
// require a values property
if (!datalist.hasOwnProperty('values')) {
console.error('datalist is missing the required property \'values\'');
return null;
}
// require that the values property be an array
if (!Array.isArray(datalist.values)) {
console.error('datalist.values is not an array');
return null;
}
// create the datalist
const datalistElement = document.createElement('datalist');
datalistElement.id = datalist.id;
// iterate over the values to create an option with a value
datalist.values.forEach(value => {
const option = document.createElement('option');
option.value = value;
datalistElement.append(option);
});
// return the datalist
return datalistElement;
};
formBuilder.buildFieldset = (fieldset) => {
// validate the fieldset parameter
if (typeof fieldset !== 'object' || fieldset == null) {
console.error('fieldset is not a valid object');
return null;
}
// validate the fieldset.fields parameter
fieldset.fields = fieldset.fields || [];
if (!Array.isArray(fieldset.fields)) {
console.error('fieldset.fields is not an array');
return null;
}
// create the element
const fieldsetElement = document.createElement('fieldset');
// optionally create a legend
if (fieldset.legend) {
if (Object.prototype.toString.call(fieldset.legend) !== "[object String]") {
// validate the legend property
console.warn('fieldset.legend is not a string');
} else {
// create the legend element and append it to the fieldset
const legend = document.createElement('legend');
legend.innerHTML = fieldset.legend;
fieldsetElement.append(legend);
}
}
// iterate over each field
fieldset.fields.forEach(field => {
// create the div container
const divContainer = document.createElement('div');
divContainer.setAttribute('class', 'mb-3');
// validate the field type or default to text
let fieldType = 'text';
if (formBuilder.isValidFieldType(field)) {
fieldType = field.type;
} else {
if (typeof field.type !== 'undefined') {
console.warn(`${field.type} is not a valid input type. Defaulting to text.`);
}
}
// optionally create the label
if (field.label) {
if (Object.prototype.toString.call(field.label) !== "[object String]") {
// validate the legend property
console.warn('field.label is not a string');
} else {
// create the label and append it to the div container
const label = formBuilder.buildLabel(field.label);
if (label) {
divContainer.append(label);
}
}
}
// create the input and append it to the div container
const input = formBuilder.buildInput(field, fieldType);
if (input) {
divContainer.append(input);
}
// optionally create the datalist
if (field.hasOwnProperty('datalist') && typeof field.datalist === 'object' && field.datalist !== null) {
const datalist = formBuilder.buildDatalist(field.datalist);
if (datalist) {
divContainer.append(datalist);
}
}
// append the div container
fieldsetElement.append(divContainer);
});
// return the fieldset
return fieldsetElement;
};
formBuilder.buildInput = (field, fieldType) => {
// valdiate the field parameter
if (typeof field !== 'object' || field == null) {
return null;
}
// validate the fieldType parameter
if (Object.prototype.toString.call(fieldType) !== "[object String]") {
return null;
}
// special case for <select /> elements
if (fieldType === 'select') {
return formBuilder.buildSelect(field);
}
// create the input
const input = document.createElement('input');
input.setAttribute('type', fieldType);
// iterate over each attribute to set the input attribute
const attributes = Object.keys(field.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidInputAttribute(fieldType, attribute)) {
input.setAttribute(attribute, field.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid attribute for ${fieldType}.`);
}
});
if (attributes.indexOf('class') < 0 || attributes.class.indexOf('form-control') < 0) {
input.classList.add('form-control');
}
// return the input
return input;
};
formBuilder.buildLabel = (label) => {
// validate the label parameter
if (Object.prototype.toString.call(label) !== "[object String]") {
return null;
}
// create the label
const labelElement = document.createElement('label');
labelElement.setAttribute('class', 'form-label');
labelElement.innerHTML = label;
// return the label
return labelElement;
};
formBuilder.buildOption = (option) => {
if (Object.prototype.toString.call(option) === "[object String]") {
const optionElement = document.createElement('option');
optionElement.setAttribute('value', option);
optionElement.innerHTML = option;
return optionElement;
} else if (typeof option === 'object') {
const optionElement = document.createElement('option');
const optionKeys = Object.keys(option);
if (optionKeys.indexOf('optgroup') > -1) {
const optGroupKeys = Object.keys(option.optgroup);
if (optGroupKeys.indexOf('label') < 0) {
console.error('\'optgroup\' requires a \'label\' attribute.');
return;
}
const optGroupElement = document.createElement('optgroup');
optGroupElement.setAttribute('label', option.optgroup.label);
if (optGroupKeys.indexOf('options') > -1) {
// check that the options attribute is an array
if (!Array.isArray(option.optgroup.options)) {
console.error('\'optgroup\' options is invalid. Expected type is an array.');
return null;
}
// loop over each option
option.optgroup.options.forEach(innerOption => {
const innerOptionElement = formBuilder.buildOption(innerOption);
if (innerOptionElement) {
optGroupElement.append(innerOptionElement);
}
});
}
return optGroupElement;
} else {
if (optionKeys.indexOf('text') > -1) {
optionElement.innerHTML = option.text;
}
if (optionKeys.indexOf('value') > -1) {
optionElement.setAttribute('value', option.value);
}
return optionElement;
}
} else {
console.error('\'option\' is invalid. Expected type is string or object.');
return null;
}
}
formBuilder.buildSelect = (field) => {
const attributes = Object.keys(field.attributes);
// check for the options attribute
if (attributes.indexOf('options') < 0) {
console.error('\'select\' types must have an \'options\' attribute.');
return null;
}
// check that the options attribute is an array
if (!Array.isArray(field.attributes.options)) {
console.error('\'select\' types must have an \'options\' attribute that is an array.');
return null;
}
// create the select
const select = document.createElement('select');
// iterate over each attribute to set the select attribute
attributes.forEach(attribute => {
if (attribute === 'options') {
return;
} else if (formBuilder.isValidInputAttribute('select', attribute)) {
select.setAttribute(attribute, field.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid attribute for ${fieldType}.`);
}
});
// iterate over each option
field.attributes.options.forEach(option => {
const optionElement = formBuilder.buildOption(option);
if (optionElement) {
select.append(optionElement);
}
});
if (attributes.indexOf('class') < 0 || attributes.class.indexOf('form-select') < 0) {
select.classList.add('form-select');
}
return select;
}
formBuilder.isValidFieldType = (field) => {
// validate the field parameter
if (typeof field !== 'object' || field == null || !field.hasOwnProperty('type')) {
return false;
}
// enumerate the acceptable types
const acceptedTypes = [
'checkbox',
'color',
'date',
'datetime-local',
'email',
'file',
'hidden',
'month',
'number',
'password',
'radio',
'range',
'search',
'select',
'tel',
'text',
'time',
'url',
'week'
];
// return if the field.type is one of the acceptable types
return acceptedTypes.indexOf(field.type) > -1;
};
formBuilder.isValidFormAttribute = (attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const attributes = [
'accept',
'accept-charset',
'accesskey',
'action',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'draggable',
'enctype',
'enterkeyhint',
'exportparts',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'method',
'name',
'nonce',
'novalidate',
'part',
'rel',
'slot',
'spellcheck',
'style',
'tabindex',
'target',
'title',
'translate'
];
if (attributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
return false;
};
formBuilder.isValidInputAttribute = (type, attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const allTypeAttributes = [
'accesskey',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'disabled',
'draggable',
'enterkeyhint',
'exportparts',
'form',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'list',
'name',
'nonce',
'part',
'readonly',
'required',
'slot',
'spellcheck',
'style',
'tabindex',
'title',
'translate',
'value'
];
if (allTypeAttributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
if (type === 'checkbox') {
const checkboxAttributes = [
'checked'
];
return checkboxAttributes.indexOf(attribute) > -1;
}
if (type === 'color') {
const colorAttributes = [
'list'
];
return colorAttributes.indexOf(attribute) > -1;
}
if (type === 'date') {
const dateAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return dateAttributes.indexOf(attribute) > -1;
}
if (type === 'datetime-local') {
const datetimeLocalAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return datetimeLocalAttributes.indexOf(attribute) > -1;
}
if (type === 'email') {
const emailAttributes = [
'list',
'readonly',
'required'
];
return emailAttributes.indexOf(attribute) > -1;
}
if (type === 'file') {
const fileAttributes = [
'accept',
'capture',
'multiple',
'required'
];
return fileAttributes.indexOf(attribute) > -1;
}
if (type === 'hidden') {
return false;
}
if (type === 'month') {
const monthAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return monthAttributes.indexOf(attribute) > -1;
}
if (type === 'number') {
const numberAttributes = [
'max',
'min',
'required',
'step'
];
return numberAttributes.indexOf(attribute) > -1;
}
if (type === 'password') {
const passwordAttributes = [
'maxlength',
'minlength',
'readonly',
'required',
'pattern',
'placeholder',
'size'
];
return passwordAttributes.indexOf(attribute) > -1;
}
if (type === 'radio') {
const radioAttributes = [
'checked'
];
return radioAttributes.indexOf(attribute) > -1;
}
if (type === 'range') {
const rangeAttributes = [
'list'
];
return rangeAttributes.indexOf(attribute) > -1;
}
if (type === 'search') {
const searchAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return searchAttributes.indexOf(attribute) > -1;
}
if (type === 'select') {
const selectAttributes = [
'multiple',
'size'
];
}
if (type === 'tel') {
const telAttributes = [
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return telAttributes.indexOf(attribute) > -1;
}
if (type === 'text') {
const textAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return textAttributes.indexOf(attribute) > -1;
}
if (type === 'time') {
const timeAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'url') {
const timeAttributes = [
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'week') {
const weekAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return weekAttributes.indexOf(attribute) > -1;
}
return false;
};
-
Jan 26th, 2022, 10:22 PM
#3
Re: [Javascript] [ES6] Bootstrap Form Builder
Updated to include support for Bootstrap grid system.
Demo
https://jsfiddle.net/aLh69q3k/
Usage
-removed because of forum limitations see fiddle-
Source Code
Code:
const formBuilder = {};
formBuilder.buildForm = (options) => {
// validate the options parameter
options = options || {};
if (typeof options !== 'object' || options == null) {
console.error('options is not a valid object');
return null;
}
// validate the options.fieldsets parameter
options.fieldsets = options.fieldsets || [];
if (!Array.isArray(options.fieldsets)) {
console.error('options.fieldset is not an array');
return null;
}
// validate the options.attributes
options.attributes = options.attributes || {};
if (typeof options.attributes !== 'object' || options.attributes == null) {
console.error('options.attributes is not a valid object');
return null;
}
// create the form
const form = document.createElement('form');
// create a random id
const rightNow = new Date();
form.id = rightNow.getTime();
// optionally bind to the submit event
if (options.submit) {
if ({}.toString.call(options.submit) !== '[object Function]') {
console.error('options.submit is not a valid function');
return;
}
// add the event listenter to document
document.addEventListener('submit', (e) => {
// check if the target is the form
if (e.target && e.target.id === form.id) {
// call the submit callback, passing e
options.submit(e);
}
});
}
// iterate over every attribute to set the form attribute
const attributes = Object.keys(options.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidFormAttribute(attribute)) {
form.setAttribute(attribute, options.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid form attribute.`);
}
});
// iterate over every fieldset
options.fieldsets.forEach(fieldset => {
// create and append the fieldset to the form
const fieldsetElement = formBuilder.buildFieldset(fieldset);
if (fieldsetElement) {
form.append(fieldsetElement);
}
});
// return the form
return form;
};
formBuilder.buildDatalist = (datalist) => {
// validate the datalist parameter
if (typeof datalist !== 'object' || datalist == null) {
return null;
}
// require an id property
if (!datalist.hasOwnProperty('id')) {
console.error('datalist is missing the required property \'id\'');
return null;
}
// require a values property
if (!datalist.hasOwnProperty('values')) {
console.error('datalist is missing the required property \'values\'');
return null;
}
// require that the values property be an array
if (!Array.isArray(datalist.values)) {
console.error('datalist.values is not an array');
return null;
}
// create the datalist
const datalistElement = document.createElement('datalist');
datalistElement.id = datalist.id;
// iterate over the values to create an option with a value
datalist.values.forEach(value => {
const option = document.createElement('option');
option.value = value;
datalistElement.append(option);
});
// return the datalist
return datalistElement;
};
formBuilder.buildFieldset = (fieldset) => {
// validate the fieldset parameter
if (typeof fieldset !== 'object' || fieldset == null) {
console.error('fieldset is not a valid object');
return null;
}
// validate the fieldset.fields parameter
fieldset.fields = fieldset.fields || [];
if (!Array.isArray(fieldset.fields)) {
console.error('fieldset.fields is not an array');
return null;
}
// create the element
const fieldsetElement = document.createElement('fieldset');
// optionally create a legend
if (fieldset.legend) {
if (Object.prototype.toString.call(fieldset.legend) !== "[object String]") {
// validate the legend property
console.warn('fieldset.legend is not a string');
} else {
// create the legend element and append it to the fieldset
const legend = document.createElement('legend');
legend.innerHTML = fieldset.legend;
fieldsetElement.append(legend);
}
}
const divRow = document.createElement('div');
divRow.classList.add('row');
// iterate over each field
fieldset.fields.forEach(field => {
// create the div container
const divContainer = document.createElement('div');
divContainer.setAttribute('class', 'mb-3');
// validate the field type or default to text
let fieldType = 'text';
if (formBuilder.isValidFieldType(field)) {
fieldType = field.type;
} else {
if (typeof field.type !== 'undefined') {
console.warn(`${field.type} is not a valid input type. Defaulting to text.`);
}
}
// optionally create the label
if (field.label) {
if (Object.prototype.toString.call(field.label) !== "[object String]") {
// validate the legend property
console.warn('field.label is not a string');
} else {
// optionally get the id for the 'for' attribute
let id = null;
if (field.attributes && field.attributes.id) {
id = field.attributes.id;
}
// create the label and append it to the div container
const label = formBuilder.buildLabel(field.label, id);
if (label) {
divContainer.append(label);
}
}
}
// create the input and append it to the div container
const input = formBuilder.buildInput(field, fieldType);
if (input) {
divContainer.append(input);
}
// optionally build the grid
if (field.grid) {
if (typeof field.grid === 'object') {
if (field.grid.xs) {
divContainer.classList.add(`col-xs-${field.grid.xs}`);
}
if (field.grid.sm) {
divContainer.classList.add(`col-sm-${field.grid.sm}`);
}
if (field.grid.md) {
divContainer.classList.add(`col-md-${field.grid.md}`);
}
if (field.grid.lg) {
divContainer.classList.add(`col-lg-${field.grid.lg}`);
}
if (field.grid.xl) {
divContainer.classList.add(`col-xl-${field.grid.xl}`);
}
if (field.grid.xxl) {
divContainer.classList.add(`col-xxl-${field.grid.xxl}`);
}
} else if (Object.prototype.toString.call(fieldType) !== "[object String]") {
divContainer.classList.add(field.grid);
} else {
console.warn('The field grid is not a valid string or object.');
}
}
// optionally create the datalist
if (field.hasOwnProperty('datalist') && typeof field.datalist === 'object' && field.datalist !== null) {
const datalist = formBuilder.buildDatalist(field.datalist);
if (datalist) {
divContainer.append(datalist);
input.setAttribute('list', field.datalist.id);
}
}
// append the div container
divRow.append(divContainer);
});
if (fieldset.grid) {
fieldsetElement.append(divRow);
} else {
const children = Array.from(divRow.children);
for (let child of children) {
fieldsetElement.append(child);
}
}
// return the fieldset
return fieldsetElement;
};
formBuilder.buildInput = (field, fieldType) => {
// valdiate the field parameter
if (typeof field !== 'object' || field == null) {
return null;
}
// validate the fieldType parameter
if (Object.prototype.toString.call(fieldType) !== "[object String]") {
return null;
}
// special case for <select /> elements
if (fieldType === 'select') {
return formBuilder.buildSelect(field);
}
// create the input
const input = document.createElement('input');
input.setAttribute('type', fieldType);
// iterate over each attribute to set the input attribute
const attributes = Object.keys(field.attributes);
attributes.forEach(attribute => {
if (formBuilder.isValidInputAttribute(fieldType, attribute)) {
input.setAttribute(attribute, field.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid attribute for ${fieldType}.`);
}
});
if (attributes.indexOf('class') < 0 || field.attributes['class'].indexOf('form-control') < 0) {
input.classList.add('form-control');
}
// return the input
return input;
};
formBuilder.buildLabel = (label, id = null) => {
// validate the label parameter
if (Object.prototype.toString.call(label) !== "[object String]") {
return null;
}
// create the label
const labelElement = document.createElement('label');
labelElement.setAttribute('class', 'form-label');
labelElement.innerHTML = label;
if (id) {
labelElement.setAttribute('for', id);
}
// return the label
return labelElement;
};
formBuilder.buildOption = (option) => {
if (Object.prototype.toString.call(option) === "[object String]") {
const optionElement = document.createElement('option');
optionElement.setAttribute('value', option);
optionElement.innerHTML = option;
return optionElement;
} else if (typeof option === 'object') {
const optionElement = document.createElement('option');
const optionKeys = Object.keys(option);
if (optionKeys.indexOf('optgroup') > -1) {
const optGroupKeys = Object.keys(option.optgroup);
if (optGroupKeys.indexOf('label') < 0) {
console.error('\'optgroup\' requires a \'label\' attribute.');
return;
}
const optGroupElement = document.createElement('optgroup');
optGroupElement.setAttribute('label', option.optgroup.label);
if (optGroupKeys.indexOf('options') > -1) {
// check that the options attribute is an array
if (!Array.isArray(option.optgroup.options)) {
console.error('\'optgroup\' options is invalid. Expected type is an array.');
return null;
}
// loop over each option
option.optgroup.options.forEach(innerOption => {
const innerOptionElement = formBuilder.buildOption(innerOption);
if (innerOptionElement) {
optGroupElement.append(innerOptionElement);
}
});
}
return optGroupElement;
} else {
if (optionKeys.indexOf('text') > -1) {
optionElement.innerHTML = option.text;
}
if (optionKeys.indexOf('value') > -1) {
optionElement.setAttribute('value', option.value);
}
return optionElement;
}
} else {
console.error('\'option\' is invalid. Expected type is string or object.');
return null;
}
}
formBuilder.buildSelect = (field) => {
const attributes = Object.keys(field.attributes);
// check for the options attribute
if (attributes.indexOf('options') < 0) {
console.error('\'select\' types must have an \'options\' attribute.');
return null;
}
// check that the options attribute is an array
if (!Array.isArray(field.attributes.options)) {
console.error('\'select\' types must have an \'options\' attribute that is an array.');
return null;
}
// create the select
const select = document.createElement('select');
// iterate over each attribute to set the select attribute
attributes.forEach(attribute => {
if (attribute === 'options') {
return;
} else if (formBuilder.isValidInputAttribute('select', attribute)) {
select.setAttribute(attribute, field.attributes[attribute]);
} else {
console.warn(`'${attribute}' is an invalid attribute for ${fieldType}.`);
}
});
// iterate over each option
field.attributes.options.forEach(option => {
const optionElement = formBuilder.buildOption(option);
if (optionElement) {
select.append(optionElement);
}
});
if (attributes.indexOf('class') < 0 || attributes.class.indexOf('form-select') < 0) {
select.classList.add('form-select');
}
return select;
}
formBuilder.isValidFieldType = (field) => {
// validate the field parameter
if (typeof field !== 'object' || field == null || !field.hasOwnProperty('type')) {
return false;
}
// enumerate the acceptable types
const acceptedTypes = [
'checkbox',
'color',
'date',
'datetime-local',
'email',
'file',
'hidden',
'month',
'number',
'password',
'radio',
'range',
'search',
'select',
'tel',
'text',
'time',
'url',
'week'
];
// return if the field.type is one of the acceptable types
return acceptedTypes.indexOf(field.type) > -1;
};
formBuilder.isValidFormAttribute = (attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const attributes = [
'accept',
'accept-charset',
'accesskey',
'action',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'draggable',
'enctype',
'enterkeyhint',
'exportparts',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'method',
'name',
'nonce',
'novalidate',
'part',
'rel',
'slot',
'spellcheck',
'style',
'tabindex',
'target',
'title',
'translate'
];
if (attributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
return false;
};
formBuilder.isValidInputAttribute = (type, attribute) => {
if (Object.prototype.toString.call(attribute) !== "[object String]") {
return false;
}
const allTypeAttributes = [
'accesskey',
'autocapitalize',
'autocomplete',
'autofocus',
'class',
'contenteditable',
'context-menu',
'dir',
'disabled',
'draggable',
'enterkeyhint',
'exportparts',
'form',
'hidden',
'id',
'inputmode',
'is',
'itemid',
'itemprop',
'itemref',
'itemscope',
'itemtype',
'lang',
'list',
'name',
'nonce',
'part',
'readonly',
'required',
'slot',
'spellcheck',
'style',
'tabindex',
'title',
'translate',
'value'
];
if (allTypeAttributes.indexOf(attribute) > -1) {
return true;
}
if (attribute.indexOf('data-') > 0 && attribute.length > 5) {
return true;
}
if (type === 'checkbox') {
const checkboxAttributes = [
'checked'
];
return checkboxAttributes.indexOf(attribute) > -1;
}
if (type === 'color') {
const colorAttributes = [
'list'
];
return colorAttributes.indexOf(attribute) > -1;
}
if (type === 'date') {
const dateAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return dateAttributes.indexOf(attribute) > -1;
}
if (type === 'datetime-local') {
const datetimeLocalAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return datetimeLocalAttributes.indexOf(attribute) > -1;
}
if (type === 'email') {
const emailAttributes = [
'list',
'placeholder',
'readonly',
'required'
];
return emailAttributes.indexOf(attribute) > -1;
}
if (type === 'file') {
const fileAttributes = [
'accept',
'capture',
'multiple',
'required'
];
return fileAttributes.indexOf(attribute) > -1;
}
if (type === 'hidden') {
return false;
}
if (type === 'month') {
const monthAttributes = [
'list',
'max',
'min',
'placeholder',
'readonly',
'required',
'step'
];
return monthAttributes.indexOf(attribute) > -1;
}
if (type === 'number') {
const numberAttributes = [
'max',
'min',
'placeholder',
'required',
'step'
];
return numberAttributes.indexOf(attribute) > -1;
}
if (type === 'password') {
const passwordAttributes = [
'maxlength',
'minlength',
'readonly',
'required',
'pattern',
'placeholder',
'size'
];
return passwordAttributes.indexOf(attribute) > -1;
}
if (type === 'radio') {
const radioAttributes = [
'checked'
];
return radioAttributes.indexOf(attribute) > -1;
}
if (type === 'range') {
const rangeAttributes = [
'list'
];
return rangeAttributes.indexOf(attribute) > -1;
}
if (type === 'search') {
const searchAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return searchAttributes.indexOf(attribute) > -1;
}
if (type === 'select') {
const selectAttributes = [
'multiple',
'placeholder',
'size'
];
}
if (type === 'tel') {
const telAttributes = [
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return telAttributes.indexOf(attribute) > -1;
}
if (type === 'text') {
const textAttributes = [
'dirname',
'list',
'maxlength',
'minlength',
'pattern',
'placeholder',
'readonly',
'required',
'size'
];
return textAttributes.indexOf(attribute) > -1;
}
if (type === 'time') {
const timeAttributes = [
'list',
'max',
'min',
'readonly',
'required',
'step'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'url') {
const timeAttributes = [
'list',
'maxlength',
'minlength',
'placeholder',
'readonly',
'required'
];
return timeAttributes.indexOf(attribute) > -1;
}
if (type === 'week') {
const weekAttributes = [
'list',
'max',
'min',
'placeholder',
'readonly',
'required',
'step'
];
return weekAttributes.indexOf(attribute) > -1;
}
return false;
};
Last edited by dday9; Jan 26th, 2022 at 10:25 PM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|