I'd like some comments and any suggestions that you have for where to go next with this.I've been working on developing a JavaScript library that allows for dynamic forms by simply adding some extra HTML tags and attributes to your form.  It's brand new, so it doesn't have a lot of features, but it's based on the idea that you shouldn't need to write a bunch of JavaScript and have to deal with bugs and stuff just to get your form to have multiple pages or to add new fields when you click a button... stuff like that.All you have to do to make it work is include jQuery and this JavaScript in the page after all the form HTML....<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js'></script><script type='text/javascript' src='edomform.js'></script></body>I've got the current JavaScript in a Fiddle with some example HTML and CSS, and some basic documentation/guide material in a Google doc that you can comment on if you like.  Any comments and suggestions are welcome.You're also welcome to use this for whatever you want as-is or with any modifications you or anyone comes up with.FiddleeDomForm//Configuration variablesvar config = jQuery(edfconfig);function getConfigBoolean(attr) {    if (config != null)    return jQuery(config).attr(attr) != undefined;}function getConfigValue(attr) {    if (config != null)    return jQuery(config).attr(attr);}var noasterisks         = getConfigBoolean(noasterisks);var addafter            = getConfigBoolean(addafter);var doactions           = getConfigBoolean(doactions);var noappend            = getConfigBoolean(noappend);var requiredmessage     = getConfigValue(requiredmessage);var requiredmessageid   = getConfigValue(requiredmessageid);//eDomForm variablesvar attrs = jQuery(edfvars)[0];var variables = {};if (attrs != null)for (i=0;i<attrs.attributes.length;i++) {    variables[attrs.attributes[i].nodeName] = attrs.attributes[i].nodeValue;}//Function because jQuery doesn't have a selector for the name attributefunction getByName(n) {    return document.getElementsByName(n);}//required fields have the class required_fieldvar required = jQuery(.required_field);//message div to display global form messagesvar message = jQuery(#message);//reference to the entire form itselfvar form = jQuery(#form);//form datajQuery(form).data(required_empty,required.length);//Add next and back buttonsjQuery(.form_page).each(function(i){    jQuery(this).prepend('<button type=button class=back>Back</button> <button type=button class=next>Next</button>');});//Next and Back buttonsvar pages = jQuery(.form_page);var backButtons = jQuery(.back);var nextButtons = jQuery(.next);for (i=0;i<pages.length;i++) {    if (i != pages.length-1) {        nextButtons[i].onclick = function() {            jQuery(this).closest(div).fadeOut();            jQuery(this).closest(div).nextAll(:not(.disabled):first).fadeIn();        };    } else {        jQuery(nextButtons[i]).remove();    }    if (i != 0) {        backButtons[i].onclick = function(i) {            jQuery(this).closest(div).fadeOut();            jQuery(this).closest(div).prevAll(:not(.disabled):first).fadeIn();        };    } else {        jQuery(backButtons[i]).remove();    }}//Aliasesfunction getByAlias(a) {    return jQuery([alias=+a+]);}function hasClass(element, cls) {    return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;}var aliases = jQuery(.alias);for (i=0;i<aliases.length;i++) {    var alias = jQuery(aliases[i]).attr(alias);    var original = jQuery(.+alias);    for (j=0;j<original.length;j++) {        if (hasClass(original[j],required_field)) {            jQuery(aliases[i]).addClass(required_field);        }        original[j].onchange = aliasChange.bind(null,original[j],aliases[i]);        aliases[i].onchange = aliasChange.bind(null,aliases[i],original[j]);    }}function aliasChange(o,a) {    a.value = o.value;    a.onblur();}var radioGroupCounter = 1;jQuery(document).ready(function() {    //Prevent form submission if required fields have not been filled in    if (form[0] != null)    form[0].onsubmit = function() {        var required_fields = document.getElementsByClassName(required_field);        for (i=0;i<required_fields.length;i++) {            if (required_fields[i].value == ) {                jQuery(#+requiredmessageid).html(requiredmessage);                return false;            }        }        jQuery(#+requiredmessageid).html();        return true;    };    //Hidden pages    function handleHiddenPages() {        jQuery(.revealer).each(function(i){            var page = jQuery(this).attr(page);            jQuery(this).click(function(){                if (jQuery(this).is(:checked)) {                    jQuery(.+page).removeClass(disabled);                } else {                    jQuery(.+page).addClass(disabled);                }            });        });    }    handleHiddenPages();    //Switchers    function handleSwitchers() {        jQuery(.switcher).each(function(x){            var connections = jQuery(this).attr(connections);            connections = jQuery.parseJSON(connections);            var connectedSections = {};            for (var key in connections) {                //if something like a-b                if (connections[key].indexOf(-) > -1) {                    var nums = connections[key].split(-);                    var resultNums = [];                    for (i=0;i<nums.length;i++) {                        nums[i] = parseInt(nums[i]);                    }                    for (i=nums[0];i<=nums[nums.length-1];i++) {                        resultNums.push(i+);                        connectedSections[i] = key;                    }                } else {                    if (connections.hasOwnProperty(key))                    connectedSections[connections[key]] = key;                }            }            jQuery(this).change(function(){                for (var key in connectedSections) {                    jQuery(.+connectedSections[key]).hide();                }                jQuery(.+connectedSections[jQuery(this).val()]).show();            });        });    }    handleSwitchers();    //Displayers/Hiders    function handleDisplayers() {        jQuery(.displayer).each(function(x){            var connected = jQuery(this).attr(display);            var special = ;            var connecteds = [];            if (connected.indexOf( ) > -1) {                connecteds = connected.split( );                var special = connecteds[0];                connected = connecteds[1];            }            var name = jQuery(this).attr(name);            var group = getByName(name);            jQuery(group).each(function() {                jQuery(this).on(click,function() {                var button = this;                    if (jQuery(this).attr(display) != null) {                        if (special == ) {                            jQuery(.+connected).each(function() {                                jQuery(this).show();                            });                        }                        else if (special == next) {                            jQuery(.+connected).each(function() {                                if (button.compareDocumentPosition(this) == 4) {                                    jQuery(this).show();                                    return false;                                }                            });                        }                        else if (special == prev) {                            jQuery(jQuery(.+connected).get().reverse()).each(function(i) {                            if (button.compareDocumentPosition(this) == 2) {                                jQuery(this).show();                                return false;                            }                        });                        }                    }else {                        if (special == ) {                            jQuery(.+connected).each(function() {                                jQuery(this).hide();                            });                        }                        else if (special == next)                        jQuery(.+connected).each(function() {                            if (button.compareDocumentPosition(this) == 4) {                                jQuery(this).hide();                                return false;                            }                        });                        else if (special == prev)                        jQuery(jQuery(.+connected).get().reverse()).each(function(i) {                            if (button.compareDocumentPosition(this) == 2) {                                jQuery(this).hide();                                return false;                            }                        });                    }                });            });        });    }    handleDisplayers();    //findNext function from stackoverflow    /**     * Find the next element matching a certain selector. Differs from next() in     *  that it searches outside the current element's parent.     *       * @param selector The selector to search for     * @param steps (optional) The number of steps to search, the default is 1     * @param scope (optional) The scope to search in, the default is document wide      */    $.fn.findNext = function(selector, steps, scope)    {        // Steps given? Then parse to int         if (steps)        {            steps = Math.floor(steps);        }        else if (steps === 0)        {            // Stupid case :)            return this;        }        else        {            // Else, try the easy way            var next = this.next(selector);            if (next.length)                return next;            // Easy way failed, try the hard way :)            steps = 1;        }        // Set scope to document or user-defined        scope = (scope) ? $(scope) : $(document);        // Find kids that match selector: used as exclusion filter        var kids = this.find(selector);        // Find in parent(s)        hay = $(this);        while(hay[0] != scope[0])        {            // Move up one level            hay = hay.parent();                 // Select all kids of parent            //  - excluding kids of current element (next != inside),            //  - add current element (will be added in document order)            var rs = hay.find(selector).not(kids).add($(this));            // Move the desired number of steps            var id = rs.index(this) + steps;            // Result found? then return            if (id > -1 && id < rs.length)                return $(rs[id]);        }        // Return empty result        return $([]);    }    //Adding New Sections    function handleAdds() {        jQuery(.add).each(function(x){            var add = jQuery(this).attr(add);            if (add.indexOf( ) > -1) {                add = add.split( );            }            var to = jQuery(this).attr(to);            var radiogroup = jQuery(this).attr(radiogroup);            if (radiogroup != null)            radiogroup = radiogroup.split( );            var cpy = jQuery(<div />).append(jQuery(.+add).clone()).html();            if (to == null) {                jQuery(this).click(function() {                    var text = cpy;                    var counter = radioGroupCounter++;                    if (radiogroup != null)                    for (i=0;i<radiogroup.length;i++) {                        var re = new RegExp(radiogroup[i]+\\[\\d\\],g);                        text = text.replace(re,radiogroup[i]+[+(counter)+]);                    }                    if (addafter)                    jQuery(this).after(text);                    else                    jQuery(this).before(text);                    handleHiddenPages();                    handleDisplayers();                    handleSwitchers();                });            } else {                if (to.indexOf( ) > -1) {                    to = to.split( );                }                jQuery(this).click(function() {                    var text = cpy;                    var counter = radioGroupCounter++;                    if (radiogroup != null)                    for (i=0;i<radiogroup.length;i++) {                        var re = new RegExp(radiogroup[i]+\\[\\d\\],g);                        text = text.replace(re,radiogroup[i]+[+(counter)+]);                        console.log(text);                    }                    jQuery(#+to).append(text);                    handleHiddenPages();                    handleDisplayers();                    handleSwitchers();                });            }        });    }    handleAdds();    //Action tags    function handleAll() {        handleHiddenPages();        handleDisplayers();        handleSwitchers();        handleAdds();    }});required = jQuery(.required_field);//Loop through required fields, adding the onblur event//so that whenever the user deselects a required field,//if it is blank the asterisk will turn red.for (i=0;i<required.length;i++) {    jQuery(required[i]).after(<span>*</span>);    jQuery(required[i]).data(empty,true);    required[i].onblur = function() {        if (this.value == ) {            jQuery(this).next().css(color,#f00);        } else {            jQuery(this).next().css(color,#000);        }    };}
eDomForm - dynamic forms without writing any JavaScript
javascript;jquery;html;form;dom
On the wholeInteresting, from a philosophy perspective, you are now moving from code with potential JavaScript bugs to code with potential HTML 'configuration' bugs. Debugging configuration bugs is annoying since there is no dedicated tooling for it.Furthermore, you have some interesting design choices HTML wise;You have custom tags but you are not using registerElementYou have custom properties that are not compatible with $().data()This works, but it should be avoided when possible, and I believe it is possibleFrom a consistency perspective;You have a tag property requiredmessageYou have classes required_empty and required_fieldYour code is lowerCamelCaseI would synchronize everything to lowerCamelCase across properties, CSS classes and code, otherwise users will have to keep referring to documentation to see what the proper spelling is.Coding*Constants, most of these should have a well named constant name;You use .required_field and required_field several timesYou use .alias and alias several timesetc. etc.Comments:handleSwitchers <- really unclear what this is supposed to doOther than that, the commenting throughout the code is very unevenSome jQuery specific items:This:jQuery(.form_page).each(function(i){  jQuery(this).prepend('<button type=button class=back>Back</button> <button type=button class=next>Next</button>');});can bevar buttons = '<button type=button class=back>Back</button> <button type=button class=next>Next</button>';jQuery(.form_page).prepend( buttons );basically prepend works on every selected element.This:for (i=0;i<pages.length;i++) {    if (i != pages.length-1) {        nextButtons[i].onclick = function() {            jQuery(this).closest(div).fadeOut();            jQuery(this).closest(div).nextAll(:not(.disabled):first).fadeIn();        };    } else {        jQuery(nextButtons[i]).remove();    }    if (i != 0) {        backButtons[i].onclick = function(i) {            jQuery(this).closest(div).fadeOut();            jQuery(this).closest(div).prevAll(:not(.disabled):first).fadeIn();        };    } else {        jQuery(backButtons[i]).remove();    }}is basically trying to make every back and next button work, and remove the first back button and the last next button, the following code does the exact thing with fewer lines and more clarity of purpose:nextButtons.last().remove();  backButtons.first().remove();  nextButtons.click( function(){  $(this).closest(div).fadeOut()                      .nextAll(:not(.disabled):first).fadeIn();         });backButtons.click( function(){  $(this).closest(div).fadeOut()                        .prevAll(:not(.disabled):first).fadeIn();     })  Note also that I chained the two statement within the click handlers for efficiency.You use jQuery everywhere, but not here: var required_fields = document.getElementsByClassName(required_field); why ?Cache jQuery(this) into $this = jQuery(this) in all your listeners where you access jQuery(this) more than once.When you decide to add a class or remove a class based on a Boolean, consider toggleClass, this:jQuery(this).click(function(){  if (jQuery(this).is(:checked)) {    jQuery(.+page).removeClass(disabled);  } else {    jQuery(.+page).addClass(disabled);  }});could bejQuery(this).click(function(){  var $this = jQuery(this);  jQuery(.+page).toggleClass( 'disabled' , !$this.is(:checked) );});Not really jQuery related, but realize that ternaries can be your friend:required[i].onblur = function() {if (this.value == ) {        jQuery(this).next().css(color,#f00);    } else {        jQuery(this).next().css(color,#000);    }};can be ( you can put the assignment outside of the loop )required.blur( function() {    jQuery(this).next().css(color, (this.value == ) ? #f00 : #000);    });When you think about it, #f00 should be a constant, and probably you should assign a class instead of putting the color red.