Hello everyone,
I created this function that can create a form with as many input fields as you need. Any ideas on how to improve it are welcome.
I was tired of multiple setAttribute or appendChild calls so I figured why not defined all inputs as objects, pass them into a function and have it return a well-structured form contained in a section tag.
A call to createForm(title,fieldArray)
will return
<section id=title>
<h2>title</h2>
<form>...</form>
</section>
Then ofcourse the <section>
object returned can be appended anywhere in your web page.
There are some conditions on how to use the function and a detailed explanation can be found at this https://raw.githubusercontent.com/jacob-nuwamanya/createform/master/README.md
for now here is the code and below an example that you can run to see how it works and from it all suggestions are welcome which will make us all better for it.
function isPropDefined(attrObj){
//checks any number of props. Add as many props of type string and
//it will check for availability on the object.
let propsArr=[].slice.call(arguments,1);
propsArr.map(prop=>{
if(!attrObj.hasOwnProperty(prop))
throw new Error(`The object corresponding with id ${attrObj.id} must have a ${prop} property`);
});
};
const isObject=(attrObj)=>(Object.prototype.toString.call(attrObj)==='[object Object]')?true:false,
createElement=(type)=>document.createElement(type),
wrapInputTag=(id,inputTag,label)=>{
//returns a containing tag with a unique id
//adds label tag if the text is provided using the label property
let wrapperElem=createElement('p'),
dashedID=id.toLowerCase().replace(/ /g,'-'),
type=(inputTag.getAttribute('type')!==null)?inputTag.getAttribute('type').toLowerCase():'undefined',
chkBoxOrRadio=(type=='checkbox'||type=='radio')?true:false;
wrapperElem.setAttribute('id',dashedID);
if(!chkBoxOrRadio && label){
//if type is not checkbox append label first & input last
let labelTag=createElement('label'),
text=document.createTextNode(label);
labelTag.appendChild(text);
wrapperElem.appendChild(labelTag);
wrapperElem.appendChild(inputTag);
}else if(chkBoxOrRadio && label){
//if type is checkbox or radio append input first and label last
let labelTag=createElement('label'),
text=document.createTextNode(label);
labelTag.appendChild(text);
wrapperElem.appendChild(inputTag);
wrapperElem.appendChild(labelTag);
}else{
wrapperElem.appendChild(inputTag);
}
return wrapperElem;
},
setInputAttributes=(inputTag,attrObj)=>{
for(let props in attrObj){
if(props!=='id'&& props!=='label'){
inputTag.setAttribute(props,attrObj[props].toLowerCase());
}
}
return inputTag;
},
createInputTag=(attrObj,type='input')=>{
isPropDefined(attrObj,'id');
let inputTag=createElement(type),
inputWithAttr=setInputAttributes(inputTag,attrObj);
return (attrObj['label'])?wrapInputTag(attrObj['id'],inputWithAttr,attrObj['label'])
:wrapInputTag(attrObj['id'],inputWithAttr);
},
createChkBoxOrRadio=(attrObj)=>{
let type=attrObj.type.toLowerCase(),
terminate=()=>{throw new Error('Object not of type checkbox or radio')};
(type=='checkbox')?isPropDefined(attrObj,'id','legend','group'):
(type=='radio')?isPropDefined(attrObj,'id','legend','group','name'):terminate();
if(!Array.isArray(attrObj.group)) throw new Error('prop group should be an array of objects');
let wrapTag=createElement('p'),
legendTag=createElement('legend'),
legendTxt=document.createTextNode(attrObj.legend);
legendTag.appendChild(legendTxt);
wrapTag.appendChild(legendTag);
wrapTag.setAttribute('id',attrObj.id);
//the type is only defined once so we make sure it is on every object stored in the group prop
//forexample let colors={id:'lovely colors',type:radio,name:'my colors',legend:'choose one color',
// group:[{id:'green',value:'green',label:'green'},{id:'red',value:'red',label:'red'}]}
//to avoid repition we define type once and the next step ensures it trickles down to individual tags same goes for
//the name prop in the case of radios because name is how radios are grouped together
attrObj.group.map(obj=>{
if(type=='checkbox'){
obj.type=attrObj['type'];
}else{
obj.type=attrObj['type'];
obj.name=attrObj['name'];
}
return obj;
}).map(obj=>createInputTag(obj)).map(chkOrRadio=>wrapTag.appendChild(chkOrRadio));
return wrapTag;
},
createSelectTag=(attrObj)=>{
isPropDefined(attrObj,'id','options');
let selectTag=createElement('select'),
optionsArr=attrObj['options'];
for(let props in attrObj){
if(props!=='options'&& props!=='id'&& props!=='tag'&&props!=='label'){
selectTag.setAttribute(props,attrObj[props].toLowerCase());
}
}
optionsArr.map(menuOption=>{
let optionTag=createElement('option');
//{options:[{value:'#ff0000',text:'red'},{value:'#00ff00',text:'green'}]}
//if what you want to display to the user is different from what u want to use later in your code
//use text to set what the user will see on screen otherwise simply use only value
//{options:[{value:'red'},{value:'green'}]}
if(menuOption.hasOwnProperty('text')){
let text=document.createTextNode(menuOption['text']);
optionTag.appendChild(text);
for(let props in menuOption){
if(props!=='text' && props!=='id'){
optionTag.setAttribute(props,menuOption[props].toLowerCase());
}
if(props=='id'){
let dashedID=menuOption[props].toLowerCase().replace(/ /g,'-');
optionTag.setAttribute(props,dashedID);
}
}
return optionTag;
}else{
if(menuOption.hasOwnProperty('value')){
let text=document.createTextNode(menuOption['value']);
optionTag.appendChild(text);
for(let props in menuOption){
if(props!=='id'){
optionTag.setAttribute(props,menuOption[props].toLowerCase());
}
if(props=='id'){
let dashedID=menuOption[props].toLowerCase().replace(/ /g,'-');
}
}
}
return optionTag;
}
}).map(optObj=>{selectTag.appendChild(optObj)});
return (attrObj['label'])?wrapInputTag(attrObj['id'],selectTag,attrObj['label'])
:wrapInputTag(attrObj['id'],selectTag);
},
createFormInput=(attrObj)=>{
let tagType=(attrObj.hasOwnProperty('tag'))?attrObj['tag'].toLowerCase():'input';
if(tagType=='select') return createSelectTag(attrObj);
if(attrObj.hasOwnProperty('type')){
let type=attrObj.type.toLowerCase();
if(type=='checkbox'||type=='radio')return createChkBoxOrRadio(attrObj);
}
return createInputTag(attrObj,tagType);
},
createForm=(title='untitled list',attArr)=>{
if(!Array.isArray(attArr)){
throw new Error('inputs should be contained in an array');
}
let section=createElement('section'),
form=createElement('form'),
heading=createElement('h2');
section.setAttribute('id',title.toLowerCase().replace(/ /g,'-'));
heading.appendChild(document.createTextNode(title));
section.appendChild(heading);
attArr.map(input=>{
if(!isObject(input)){
console.log(Object.prototype.toString.call(input))
throw new Error('Inputs not objects');
}
return createFormInput(input);
}).map(inputTag=>form.appendChild(inputTag));
section.appendChild(form);
return section;
};
window.onload=function(){
let name={id:'tenant name',label:'Tenant Name',type:'text',placeholder:'Type tenant name...'},
location={id:'location',label:'Location',tag:'select',
options:[{value:'California'},{value:'Washington DC'},{value:'Gotham'}]},
apartment={id:'apartment number',label:'Apartment',tag:'select',
options:[{value:'K1'},{value:'K2'},{value:'K3'},{value:'K4'},{value:'K5'},{value:'K6'},
{value:'M1'},{value:'M2'},{value:'M4'},{value:'M5'},{value:'M6'}]},
date={id:'due date',label:'Next Due',type:'date'},
remarks={id:'remarks',tag:'textarea',cols:'20',rows:'10',label:'Additional Information'},
check={id:'check',legend:'Check your life goals',type:'checkbox',
group:[{id:'chkPeace',value:'peace',label:'World Peace'},
{id:'chkHarmony',value:'Harmony',label:'Harmony & Brotherhood'},
{id:'chkCash',value:'cash',label:'Cash'}]},
radio={id:'radio',legend:'Check your life goals',type:'radio', name:'life goals',
group:[{id:'chkPeace',value:'peace',label:'World Peace'},
{id:'chkHarmony',value:'Harmony',label:'Harmony & Brotherhood'},
{id:'chkCash',value:'cash',label:'Cash'}]},
button={id:'save form',type:'button',value:'register tenant'};
document.getElementsByTagName('body')[0].
appendChild(createForm('Tenant Registration Form',[name,location,apartment,date,remarks,check,radio,button]));
}
copy, paste and open file in browser