/*
* jQuery UI wizard plug-in 0.9.5
*
*
* Copyright (c) 2009 Jan Sundman (jan.sundman[at]aland.net)
* Copyright (c) 2009 James M. Curran (jamescurran[at]mvps.org)
* Licensed under the MIT licens:
* http://www.opensource.org/licenses/mit-license.php
*
*
* Changelog:
* version 0.9.5a
* Fork of Jan's original by James. Principal changes:
* - Changed API to confirm to jQuery UI standards. Notably, it's
* now called wizard() instead of formwizard(). (a formwizard()
* function remains which adapts the old-style calls to the new API)
* - Added jQuery UI-ish CSS classes for styling.
* - Refactored some code.
* - added three new options:
* o validationOptions: A set of key/value pairs to set as configuration
* properties for the validation plugin. (formerly a separate
* parameter in formwizard)
* o formOptions: A set of key/value pairs to set as configuration
* properties for the form plugin. (formerly a separate parameter
* in formwizard)
* o autoDisableNext: When set to true, the "Next" button is automatically
* disabled, requiring it be manually enable once the user has
* completed the step.
* - added five new methods
* o enableNext() -
* o disableNext() -
* o enableBack() -
* o disableBack() -
* o gotoStep(step) - navigates to the given step. step can be either a 0-based index, or a jQuery selector.
* - added one new callback
* o show() - called each time a new panel is displayed.
* function show(evnt, args)
* event - standard jQuery UI event object.
* args - object with fields:
* step - jQuery wrapped object of the DIV being displayed.
* stepInx - 0-based index of the DIV.
* backwards - true if reached by clicking "back", false otherwise.
* (callback afterNext & afterBack remain, but are depreciated)
*
* version 0.9.5
* -------------
* - Fix for enabling optional validation
*
* version 0.9.4
* -------------
* - Performance fixes for validation of the steps
* - Performance fixes for rendering of the steps
* - Introduces a need for input fields in the wizard to be disabled in the html
*
* version 0.9.3
* -------------
* - Fixed the continueToNextStep and backButton.click callback to handle navigation correctly when the
* history plugin is not used
*
* version 0.9.2
* - A check was added to see if there are multiple links on one step. In the
* case there are we assume they are radio buttons or checkboxes. Only the
* one that is checked is considered a valid link. This fixes a bug where links
* in the form of radio buttons do not work. Credits to adnanshareef for
* reporting the bug.
*
* - Added initial functionality for doing server-side validation
*
* version 0.9.1
* -------------
* - Addition of afterNext and afterBack callbacks, can be used to do stuff after
* the rendering of a step has been completed
*
* version 0.9.0
* -------------
* - Initial release
*
*/
(function ($) {
$.widget("ui.wizard",
{
options:
{
animated: 'fadeIn',
historyEnabled: undefined,
validationEnabled: undefined,
formPluginEnabled: undefined,
linkClass: ".link",
submitStepClass: ".submit_step",
back: ".wizard_back",
next: ".wizard_next",
help: ".wizard_help",
textSubmit: 'Submit',
textNext: 'Next',
textBack: 'Back',
afterNext: undefined,
afterBack: undefined,
show: undefined,
autoNextDisabled: true,
serverSideValidationUrls: undefined,
formOptions: undefined,
validationOptions: {}
},
_init: function () {
// if historyEnabled not explicit defined, set it based on presence/absence of jquery.history.js plugin
if (this.options.historyEnabled == undefined)
this.options.historyEnabled = ($.historyInit != undefined);
// if formPluginEnabled not explicitly set, set it based on the presents/absence of formOptions
if (typeof (this.options.formPluginEnabled) == "undefined")
this.options.formPluginEnabled = (this.options.formOptions == null);
if (this.options.formPluginEnabled) {
this.options.formOptions = $.extend({ reset: true, success: function (data) { alert("success"); } }, this.options.formOptions);
var formOptionsSuccess = this.options.formOptions.success;
var formSettings = $.extend(this.options.formOptions, { success: function (data) {
if (formOptions.resetForm) {
_navigate(0);
if (this.options.historyEnabled) {
$.historyLoad(0);
}
else {
_renderStep();
}
}
formOptionsSuccess(data);
}
});
}
else {
if (typeof (this.options.formOptions) == "undefined")
this.options.formOptions = { success: function () { } };
else
this.options.formOptions.success = function () { };
}
/**
* Initialization
*/
this.element.addClass("ui-wizard ui-widget ui-helper-reset");
this.steps = this.element.find(".step");
this.steps.addClass("ui-wizard-content ui-helper-reset ui-widget-content ui-corner-all")
this.currentStep = 0;
this.previousStep = undefined;
this.backButton = this.element.find(this.options.back);
this.nextButton = this.element.find(this.options.next);
this.helpButton = this.element.find(this.options.help);
this.backButton.addClass("ui-wizard-content ui-helper-reset ui-state-default ui-state-hover ui-state-active");
this.nextButton.addClass("ui-wizard-content ui-helper-reset ui-state-default ui-state-hover ui-state-active");
this.helpButton.addClass("ui-wizard-content ui-helper-reset ui-state-default ui-button ui-icon ui-icon-help")
.hide();
jQuery(".help").hide();
this.activatedSteps = new Array();
this.isLastStep = false;
var wizard = this; // for closures in the next lines.
this.helpButton.click(function () {
jQuery(".help", wizard.steps[wizard.currentStep])
.clone()
.dialog({
resizable: false,
autoOpen: true,
height: 250,
width: 500,
modal: true,
overlay:
{
backgroundColor: '#000',
opacity: 0.5
}
});
});
if (this.options.historyEnabled) {
if ($.historyInit == undefined) {
this.options.historyEnabled = false;
alert("the history plugin needs to be included");
}
else {
location.hash = "";
$.historyInit(function (hash) { wizard._handleHistory(hash); });
}
}
else {
this._handleHistory(0);
}
if (this.options.validationEnabled == undefined)
this.options.validationEnabled = (this.options.validationSettings == {});
if (jQuery().validate == undefined) {
if (this.options.validationEnabled) {
this.options.validationEnabled = false;
alert("the validation plugin needs to be included");
}
}
else if (this.options.validationEnabled) {
this.element.validate(this.options.validationSettings);
}
if (this.options.formPluginEnabled && jQuery().ajaxSubmit == undefined) {
this.options.formPluginEnabled = false;
alert("the form plugin needs to be included");
}
/**
* Navigation event callbacks
*/
var wizard = this; // for closures in the next lines.
this.nextButton.click(function () { return wizard._nextButton_click(this); });
this.backButton.click(function () { return wizard._backButton_click(this); });
this.backButton.val(this.options.textBack).text(this.options.textBack);
$("input", this.element).attr("disabled", "disabled");
this.steps.hide();
this._renderStep();
return $(this);
},
destroy: function () {
this.element.removeClass("ui-formwizard ui-widget ui-helper-reset");
this.steps.removeClass("ui-wizard-content ui-helper-reset ui-widget-content ui-corner-all")
this.backButton.removeClass("ui-wizard-content ui-helper-reset ui-state-default ui-state-hover ui-state-active ui-state-disabled")
.unbind("click");
this.nextButton.removeClass("ui-wizard-content ui-helper-reset ui-state-default ui-state-hover ui-state-active ui-state-disabled")
.unbind("click");
},
/*
***********************************
*/
activate: function (step) {
gotoStep(step);
},
/*
***********************************
*/
_backButton_click: function (btn) {
if (this.activatedSteps.length > 0) {
if (this.options.historyEnabled) {
history.back();
}
else {
this._handleHistory(this.activatedSteps[this.activatedSteps.length - 2]);
}
}
this._trigger("afterBack");
return false;
},
/**
* Checks if the step is the last step in a wizard route
*
* @name checkIflastStep
* @type undefined
* @param Number step The step to check.
*/
_checkIflastStep: function (step) {
var link = this._getLink($(this.steps[step]));
this.isLastStep = false;
if ((("." + link) == this.options.submitStepClass) || (link == undefined && (step * 1) == this.steps.length - 1)) {
this.isLastStep = true;
}
},
/**
* Continues to the next step in the wizard
*/
_continueToNextStep: function () {
this._navigate(this.currentStep);
this._renderStep();
if (this.options.historyEnabled) {
$.historyLoad(this.currentStep);
}
else {
this._handleHistory(this.currentStep);
}
this._trigger("afterNext");
},
/*
***********************************
*/
disableBack: function () {
this.backButton.attr('disabled', 'disabled').addClass("ui-state-disabled");
},
/*
***********************************
*/
disableNext: function () {
this.nextButton.attr('disabled', 'disabled').addClass("ui-state-disabled");
},
/*
***********************************
*/
enableBack: function () {
this.backButton.removeAttr("disabled").removeClass("ui-state-disabled");
},
/*
***********************************
*/
enableNext: function () {
this.nextButton.removeAttr("disabled").removeClass("ui-state-disabled");
},
/**
* Finds the valid link for the step (if there is one)
*
* @name getLink
* @type String
* @param Number step The step to search for valid links
*/
_getLink: function (step) {
var link = undefined;
var links = step.find(this.options.linkClass);
if (links.length == 1) {
link = links.val();
}
else if (links.length > 1) {
// assume that the link is a radio button or checkbox
link = step.find(this.options.linkClass + ":checked").val();
}
return link;
},
gotoStep: function (step) {
var stepInx;
if (typeof (step) == "string")
stepInx = this.steps.index($(step));
else
stepInx = step;
// If in range, go there. Otherwise, silently exit.
if (stepInx >= 0 && stepInx < this.steps.length) {
jQuery(this.steps[this.currentStep]).hide();
this._handleHistory(stepInx);
}
},
/**
* Handles back navigation (and browser back and forward buttons if history is enabled)
*
* @name handleHistory
* @type undefined
* @param String hash The hash used in the browser history
If hash ==previous step, remove current step from activatedSteps, and then goto previous.
Otherwise, add hash to end of activatedSteps, and go there.
*/
_handleHistory: function (hash) {
var direction = false;
if (!hash) {
hash = 0;
}
if (this.activatedSteps[this.activatedSteps.length - 2] == hash) {
this.activatedSteps.pop();
direction = true;
}
else {
this.activatedSteps.push(hash);
}
this.previousStep = this.currentStep;
this.currentStep = hash;
this._checkIflastStep(hash);
this._renderStep();
if (this.options.autoNextDisabled)
this.disableNext();
this._trigger("show", null, { step: $(this.steps[this.currentStep]), stepInx: this.currentStep, backward: direction });
},
moveNext: function () {
this._nextButton_click(null);
},
/**
* Decides and sets the current step in the wizard
*
* @name navigate
* @type undefined
* @param Number step The step to navigate from.
*/
_navigate: function (stepInx) {
var step = $(this.steps[stepInx]);
var link = this._getLink(step);
if (link) {
var navigationTarget = this.steps.index($("#" + link));
if (navigationTarget.length == 0) {
return;
}
else {
this.previousStep = this.currentStep;
this.currentStep = navigationTarget;
}
this._checkIflastStep(step);
}
else if (!this.isLastStep) {
this.previousStep = this.currentStep;
this.currentStep++
this._checkIflastStep(this.currentStep);
}
},
/*
***********************************
*/
_nextButton_click: function (btn) {
if (this.options.validationEnabled) {
var valid = true;
var form = this.element;
$.each(form.find("input:enabled, select:enabled"), function () {
if (form.validate().element($(this)) == false)
valid = false;
})
if (!valid) return false;
}
if (this.isLastStep) {
if (this.options.formPluginEnabled) {
this.element.ajaxSubmit(this.options.formSettings);
return false;
}
this.element.submit();
return false;
}
// Doing server side validation for the steps
if (this.options.serverSideValidationUrls) {
var url = "";
var errorCallback = undefined;
$.each(this.options.serverSideValidationUrls, function () {
if (this.validation.step == this.currentStep) {
url = this.validation.url;
errorCallback = this.validation.error;
}
});
if (url != "") {
this.element.ajaxSubmit(
{ url: url,
success: function () { this._continueToNextStep(); },
error: function () { errorCallback(); }
});
alert("server side done");
return false;
}
}
this._continueToNextStep();
return false;
},
/**
* Renders the current step and disables the input fields in other steps
*
* @name renderStep
* @type undefined
*/
_renderStep: function () {
this.enableBack();
this.nextButton.val(this.options.textNext).text(this.options.textNext);
// var steps = this._getData('steps');
if (this.previousStep != undefined) {
this.steps.eq(this.previousStep).hide()
.find("input")
.attr("disabled", "disabled");
}
var effect = this.options.animated;
this.steps.eq(this.currentStep)[effect]()
.find("input").removeAttr("disabled");
if (this.isLastStep) {
for (var i = 0; i < this.activatedSteps.length; i++) {
this.steps.eq(this.activatedSteps[i]).find("input").removeAttr("disabled");
}
this.nextButton.val(this.options.textSubmit).text(this.options.textSubmit);
}
else if (this.currentStep == 0) {
this.disableBack();
}
this._showHelp();
},
_showHelp: function () {
if (jQuery(".help", this.steps[this.currentStep]).length > 0) {
this.helpButton.show();
}
else {
this.helpButton.hide();
}
}
});
/**
* Creates a wizard of all matched elements
*
* @constructor
* @name $.formwizard
* @param Hash wizardSettings A set of key/value pairs to set as configuration properties for the wizard plugin.
* @param Hash validationSettings A set of key/value pairs to set as configuration properties for the validation plugin.
* @param Hash formOptions A set of key/value pairs to set as configuration properties for the form plugin.
*/
$.fn.formwizard = function (wizardSettings, validationSettings, formOptions) {
return this.formwizard($.extend(wizardSettings, { back: ":reset", next: ":submit", autoNextDisabled: false, validationOptions: validationSettings, formOptions: formOptions }));
}
}
)(jQuery);