/*jQuery Form to Form Wizard (Initial: Oct 1st, 2010) * This notice must stay intact for usage * Author: Dynamic Drive at http://www.dynamicdrive.com/ * Visit http://www.dynamicdrive.com/ for full source code */ //Oct 21st, 2010: Script updated to v1.1, which adds basic form validation functionality, triggered each time the user goes from one page to the next, or tries to submit the form. //jQuery.noConflict() function formtowizard(options){ this.setting=jQuery.extend({persistsection:false, revealfx:['slide', 500], oninit:function(){}, onpagechangestart:function(){}}, options) this.currentsection=-1 this.init(this.setting) } formtowizard.prototype={ createfieldsets:function($theform, arr){ //reserved function for future version (dynamically wraps form elements with a fieldset element) $theform.find('fieldset.sectionwrap').removeClass('sectionwrap') //make sure no fieldsets carry 'sectionwrap' before proceeding var $startelement=$theform.find(':first-child') //reference first element inside form for (var i=0; i<arr.length; i++){ //loop thru "break" elements var $fieldsetelements=$startelement.nextUntil('#'+arr[i].breakafter+', *[name='+arr[i].breakafter+']').andSelf() //reference all elements from start element to break element (nextUntil() is jQuery 1.4 function) $fieldsetelements.add($fieldsetelements.next()).wrapAll('<fieldset class="sectionwrap" />') //wrap these elements with fieldset element $startelement=$theform.find('fieldset.sectionwrap').eq(i).prepend('<legend class="legendStep">'+arr[i].legend+'</legend>').next() //increment startelement to begin at the end of the just inserted fieldset element } }, loadsection:function(rawi, bypasshooks){ var thiswizard=this //doload Boolean checks to see whether to load next section (true if bypasshooks param is true or onpagechangestart() event handler doesn't return false) var doload=bypasshooks || this.setting.onpagechangestart(jQuery, this.currentsection, this.sections.$sections.eq(this.currentsection)) doload=(doload===false)? false : true //unless doload is explicitly false, set to true if (!bypasshooks && this.setting.validate){ var outcome=this.validate(this.currentsection) if (outcome===false) doload=false } var i=(rawi=="prev")? this.currentsection-1 : (rawi=="next")? this.currentsection+1 : parseInt(rawi) //get index of next section to show i=(i<0)? this.sections.count-1 : (i>this.sections.count-1)? 0 : i //make sure i doesn't exceed min/max limit if (i<this.sections.count && doload){ //if next section to show isn't the same as the current section shown this.$thesteps.eq(this.currentsection).addClass('disabledstep').end().eq(i).removeClass('disabledstep') //dull current "step" text then highlight next "step" text if (this.setting.revealfx[0]=="slide"){ this.sections.$sections.css("visibility", "visible") this.sections.$outerwrapper.stop().animate({height: this.sections.$sections.eq(i).outerHeight()}, this.setting.revealfx[1]) //animate fieldset wrapper's height to accomodate next section's height this.sections.$innerwrapper.stop().animate({left:-i*this.maxfieldsetwidth}, this.setting.revealfx[1], function(){ //slide next section into view thiswizard.sections.$sections.each(function(thissec){ if (thissec!=i) //hide fieldset sections currently not in veiw, so tabbing doesn't go to elements within them (and mess up layout) thiswizard.sections.$sections.eq(thissec).css("visibility", "hidden") }) }) } else if (this.setting.revealfx[0]=="fade"){ //if fx is "fade" this.sections.$sections.eq(this.currentsection).hide().end().eq(i).fadeIn(this.setting.revealfx[1], function(){ if (document.all && this.style && this.style.removeAttribute) this.style.removeAttribute('filter') //fix IE clearType problem }) } else{ this.sections.$sections.eq(this.currentsection).hide().end().eq(i).show() } this.paginatediv.$status.text("Page "+(i+1)+" of "+this.sections.count) //update current page status text this.paginatediv.$navlinks.css('visibility', 'visible') if (i==0) //hide "prev" link this.paginatediv.$navlinks.eq(0).css('visibility', 'hidden') else if (i==this.sections.count-1) //hide "next" link this.paginatediv.$navlinks.eq(1).css('visibility', 'hidden') if (this.setting.persistsection) //enable persistence? formtowizard.routines.setCookie(this.setting.formid+"_persist", i) this.currentsection=i if(i === 0) { setTimeout(function() { $('#nameToSearch').focus(); }, 250); } } }, addvalidatefields:function(){ var $=jQuery, setting=this.setting, theform=this.$theform.get(0), validatefields=[] var validatefields=setting.validate //array of form element ids to validate for (var i=0; i<validatefields.length; i++){ var el=theform.elements[validatefields[i]] //reference form element if (el){ //if element is defined var $section=$(el).parents('fieldset.sectionwrap:eq(0)') //find fieldset.sectionwrap this form element belongs to if ($section.length==1){ //if element is within a fieldset.sectionwrap element $section.data('elements').push(el) //cache this element inside corresponding section } } } }, validate:function(section){ var elements=this.sections.$sections.eq(section).data('elements') //reference elements within this section that should be validated var validated=true, invalidtext=["Please fill out the following fields:\n"] function invalidate(el){ validated=false invalidtext.push("- "+ (el.id || el.name)) } for (var i=0; i<elements.length; i++){ if (/(text)/.test(elements[i].type) && elements[i].value==""){ //text and textarea elements invalidate(elements[i]) } else if (/(select)/.test(elements[i].type) && (elements[i].selectedIndex==-1 || elements[i].options[elements[i].selectedIndex].text=="")){ //select elements invalidate(elements[i]) } else if (elements[i].type==undefined && elements[i].length>0){ //radio and checkbox elements var onechecked=false for (var r=0; r<elements[i].length; r++){ if (elements[i][r].checked==true){ onechecked=true break } } if (!onechecked){ invalidate(elements[i][0]) } } } if (!validated) alert(invalidtext.join('\n')) return validated }, init:function(setting){ var thiswizard=this jQuery(function($){ //on document.ready var $theform=$('#'+setting.formid) if ($theform.length==0) //if form with specified ID doesn't exist, try name attribute instead $theform=$('form[name='+setting.formid+']') if (setting.manualfieldsets && setting.manualfieldsets.length>0) thiswizard.createfieldsets($theform, setting.manualfieldsets) var $stepsguide=$('<div class="stepsguide" />') //create Steps Container to house the "steps" text var $sections=$theform.find('fieldset.sectionwrap').hide() //find all fieldset elements within form and hide them initially if (setting.revealfx[0]=="slide"){ //create outer DIV that will house all the fieldset.sectionwrap elements $sectionswrapper=$('<div style="position:relative;overflow:hidden;"></div>').insertBefore($sections.eq(0)) //add DIV above the first fieldset.sectionwrap element $sectionswrapper_inner=$('<div style="position:absolute;left:0;top:0;"></div>') //create inner DIV of $sectionswrapper that will scroll to reveal a fieldset element } var maxfieldsetwidth=$sections.eq(0).outerWidth() //variable to get width of widest fieldset.sectionwrap $sections.slice(1).each(function(i){ //loop through $sections (starting from 2nd one) maxfieldsetwidth=Math.max($(this).outerWidth(), maxfieldsetwidth) }) maxfieldsetwidth+=2 //add 2px to final width to reveal fieldset border (if not removed via CSS) thiswizard.maxfieldsetwidth=maxfieldsetwidth $sections.each(function(i){ //loop through $sections again var $section=$(this) if (setting.revealfx[0]=="slide"){ $section.data('page', i).css({position:'absolute', top:0, left:maxfieldsetwidth*i}).appendTo($sectionswrapper_inner) //set fieldset position to "absolute" and move it to inside sectionswrapper_inner DIV } $section.data('elements', []) //empty array to contain elements within this section that should be validated for data (applicable only if validate option is defined) //create each "step" DIV and add it to main Steps Container: var $thestep=$('<div class="step disabledstep" />').data('section', i).html('Step '+(i+1)+'<div class="smalltext">'+$section.find('legend:eq(0)').text()+'<p></p></div>').appendTo($stepsguide) $thestep.click(function(){ //assign behavior to each step div thiswizard.loadsection($(this).data('section')) }) }) if (setting.revealfx[0]=="slide"){ $sectionswrapper.width(maxfieldsetwidth) //set fieldset wrapper to width of widest fieldset $sectionswrapper.append($sectionswrapper_inner) //add $sectionswrapper_inner as a child of $sectionswrapper } $theform.prepend($stepsguide) //add $thesteps div to the beginning of the form //$stepsguide.insertBefore($sectionswrapper) //add Steps Container before sectionswrapper container var $thesteps=$stepsguide.find('div.step') //create pagination DIV and add it to end of form: var $paginatediv=$('<div class="formpaginate" style="overflow:hidden;"><span class="prev" style="float:left">Prev</span> <span class="status">Step 1 of </span> <span class="next" style="float:right">Next</span></div>') $theform.append($paginatediv) thiswizard.$theform=$theform if (setting.revealfx[0]=="slide"){ thiswizard.sections={$outerwrapper:$sectionswrapper, $innerwrapper:$sectionswrapper_inner, $sections:$sections, count:$sections.length} //remember various parts of section container thiswizard.sections.$sections.show() } else{ thiswizard.sections={$sections:$sections, count:$sections.length} //remember various parts of section container } thiswizard.$thesteps=$thesteps //remember this ref thiswizard.paginatediv={$main:$paginatediv, $navlinks:$paginatediv.find('span.prev, span.next'), $status:$paginatediv.find('span.status')} //remember various parts of pagination DIV thiswizard.paginatediv.$main.click(function(e){ //assign behavior to pagination buttons if (/(prev)|(next)/.test(e.target.className)) thiswizard.loadsection(e.target.className) }) var i=(setting.persistsection)? formtowizard.routines.getCookie(setting.formid+"_persist") : 0 thiswizard.loadsection(i||0, true) //show the first section thiswizard.setting.oninit($, i, $sections.eq(i)) //call oninit event handler if (setting.validate){ //if validate array defined thiswizard.addvalidatefields() //seek out and cache form elements that should be validated thiswizard.$theform.submit(function(){ var returnval=true for (var i=0; i<thiswizard.sections.count; i++){ if (!thiswizard.validate(i)){ thiswizard.loadsection(i, true) returnval=false break } } return returnval //allow or disallow form submission }) } }) } } formtowizard.routines={ getCookie:function(Name){ var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair if (document.cookie.match(re)) //if cookie found return document.cookie.match(re)[0].split("=")[1] //return its value return null }, setCookie:function(name, value){ document.cookie = name+"=" + value + ";path=/" } }