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.
//Configuration variables
var 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 variables
var 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 attribute
function getByName(n) {
return document.getElementsByName(n);
}
//required fields have the class required_field
var required = jQuery(".required_field");
//message div to display global form messages
var message = jQuery("#message");
//reference to the entire form itself
var form = jQuery("#form");
//form data
jQuery(form).data("required_empty",required.length);
//Add next and back buttons
jQuery(".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 buttons
var 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();
}
}
//Aliases
function 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");
}
};
}