|
|
/*! * jQuery Steps v1.1.0 - 09/04/2014 * Copyright (c) 2014 Rafael Staib (http://www.jquery-steps.com)
* Licensed under MIT http://www.opensource.org/licenses/MIT
*/;(function ($, undefined){$.fn.extend({ _aria: function (name, value) { return this.attr("aria-" + name, value); },
_removeAria: function (name) { return this.removeAttr("aria-" + name); },
_enableAria: function (enable) { return (enable == null || enable) ? this.removeClass("disabled")._aria("disabled", "false") : this.addClass("disabled")._aria("disabled", "true"); },
_showAria: function (show) { return (show == null || show) ? this.show()._aria("hidden", "false") : this.hide()._aria("hidden", "true"); },
_selectAria: function (select) { return (select == null || select) ? this.addClass("current")._aria("selected", "true") : this.removeClass("current")._aria("selected", "false"); },
_id: function (id) { return (id) ? this.attr("id", id) : this.attr("id"); }});
if (!String.prototype.format){ String.prototype.format = function() { var args = (arguments.length === 1 && $.isArray(arguments[0])) ? arguments[0] : arguments; var formattedString = this; for (var i = 0; i < args.length; i++) { var pattern = new RegExp("\\{" + i + "\\}", "gm"); formattedString = formattedString.replace(pattern, args[i]); } return formattedString; };}
/** * A global unique id count. * * @static * @private * @property _uniqueId * @type Integer **/var _uniqueId = 0;
/** * The plugin prefix for cookies. * * @final * @private * @property _cookiePrefix * @type String **/var _cookiePrefix = "jQu3ry_5teps_St@te_";
/** * Suffix for the unique tab id. * * @final * @private * @property _tabSuffix * @type String * @since 0.9.7 **/var _tabSuffix = "-t-";
/** * Suffix for the unique tabpanel id. * * @final * @private * @property _tabpanelSuffix * @type String * @since 0.9.7 **/var _tabpanelSuffix = "-p-";
/** * Suffix for the unique title id. * * @final * @private * @property _titleSuffix * @type String * @since 0.9.7 **/var _titleSuffix = "-h-";
/** * An error message for an "index out of range" error. * * @final * @private * @property _indexOutOfRangeErrorMessage * @type String **/var _indexOutOfRangeErrorMessage = "Index out of range.";
/** * An error message for an "missing corresponding element" error. * * @final * @private * @property _missingCorrespondingElementErrorMessage * @type String **/var _missingCorrespondingElementErrorMessage = "One or more corresponding step {0} are missing.";
/** * Adds a step to the cache. * * @static * @private * @method addStepToCache * @param wizard {Object} A jQuery wizard object * @param step {Object} The step object to add **/function addStepToCache(wizard, step){ getSteps(wizard).push(step);}
function analyzeData(wizard, options, state){ var stepTitles = wizard.children(options.headerTag), stepContents = wizard.children(options.bodyTag);
// Validate content
if (stepTitles.length > stepContents.length) { throwError(_missingCorrespondingElementErrorMessage, "contents"); } else if (stepTitles.length < stepContents.length) { throwError(_missingCorrespondingElementErrorMessage, "titles"); } var startIndex = options.startIndex;
state.stepCount = stepTitles.length;
// Tries to load the saved state (step position)
if (options.saveState && $.cookie) { var savedState = $.cookie(_cookiePrefix + getUniqueId(wizard)); // Sets the saved position to the start index if not undefined or out of range
var savedIndex = parseInt(savedState, 0); if (!isNaN(savedIndex) && savedIndex < state.stepCount) { startIndex = savedIndex; } }
state.currentIndex = startIndex;
stepTitles.each(function (index) { var item = $(this), // item == header
content = stepContents.eq(index), modeData = content.data("mode"), mode = (modeData == null) ? contentMode.html : getValidEnumValue(contentMode, (/^\s*$/.test(modeData) || isNaN(modeData)) ? modeData : parseInt(modeData, 0)), contentUrl = (mode === contentMode.html || content.data("url") === undefined) ? "" : content.data("url"), contentLoaded = (mode !== contentMode.html && content.data("loaded") === "1"), step = $.extend({}, stepModel, { title: item.html(), content: (mode === contentMode.html) ? content.html() : "", contentUrl: contentUrl, contentMode: mode, contentLoaded: contentLoaded });
addStepToCache(wizard, step); });}
/** * Triggers the onCanceled event. * * @static * @private * @method cancel * @param wizard {Object} The jQuery wizard object **/function cancel(wizard){ wizard.triggerHandler("canceled");}
function decreaseCurrentIndexBy(state, decreaseBy){ return state.currentIndex - decreaseBy;}
/** * Removes the control functionality completely and transforms the current state to the initial HTML structure. * * @static * @private * @method destroy * @param wizard {Object} A jQuery wizard object **/function destroy(wizard, options){ var eventNamespace = getEventNamespace(wizard);
// Remove virtual data objects from the wizard
wizard.unbind(eventNamespace).removeData("uid").removeData("options") .removeData("state").removeData("steps").removeData("eventNamespace") .find(".actions a").unbind(eventNamespace);
// Remove attributes and CSS classes from the wizard
wizard.removeClass(options.clearFixCssClass + " vertical");
var contents = wizard.find(".content > *");
// Remove virtual data objects from panels and their titles
contents.removeData("loaded").removeData("mode").removeData("url");
// Remove attributes, CSS classes and reset inline styles on all panels and their titles
contents.removeAttr("id").removeAttr("role").removeAttr("tabindex") .removeAttr("class").removeAttr("style")._removeAria("labelledby") ._removeAria("hidden");
// Empty panels if the mode is set to 'async' or 'iframe'
wizard.find(".content > [data-mode='async'],.content > [data-mode='iframe']").empty();
var wizardSubstitute = $("<{0} class=\"{1}\"></{0}>".format(wizard.get(0).tagName, wizard.attr("class")));
var wizardId = wizard._id(); if (wizardId != null && wizardId !== "") { wizardSubstitute._id(wizardId); }
wizardSubstitute.html(wizard.find(".content").html()); wizard.after(wizardSubstitute); wizard.remove();
return wizardSubstitute;}
/** * Triggers the onFinishing and onFinished event. * * @static * @private * @method finishStep * @param wizard {Object} The jQuery wizard object * @param state {Object} The state container of the current wizard **/function finishStep(wizard, state){ var currentStep = wizard.find(".steps li").eq(state.currentIndex);
if (wizard.triggerHandler("finishing", [state.currentIndex])) { currentStep.addClass("done").removeClass("error"); wizard.triggerHandler("finished", [state.currentIndex]); } else { currentStep.addClass("error"); }}
/** * Gets or creates if not exist an unique event namespace for the given wizard instance. * * @static * @private * @method getEventNamespace * @param wizard {Object} A jQuery wizard object * @return {String} Returns the unique event namespace for the given wizard */function getEventNamespace(wizard){ var eventNamespace = wizard.data("eventNamespace");
if (eventNamespace == null) { eventNamespace = "." + getUniqueId(wizard); wizard.data("eventNamespace", eventNamespace); }
return eventNamespace;}
function getStepAnchor(wizard, index){ var uniqueId = getUniqueId(wizard);
return wizard.find("#" + uniqueId + _tabSuffix + index);}
function getStepPanel(wizard, index){ var uniqueId = getUniqueId(wizard);
return wizard.find("#" + uniqueId + _tabpanelSuffix + index);}
function getStepTitle(wizard, index){ var uniqueId = getUniqueId(wizard);
return wizard.find("#" + uniqueId + _titleSuffix + index);}
function getOptions(wizard){ return wizard.data("options");}
function getState(wizard){ return wizard.data("state");}
function getSteps(wizard){ return wizard.data("steps");}
/** * Gets a specific step object by index. * * @static * @private * @method getStep * @param index {Integer} An integer that belongs to the position of a step * @return {Object} A specific step object **/function getStep(wizard, index){ var steps = getSteps(wizard);
if (index < 0 || index >= steps.length) { throwError(_indexOutOfRangeErrorMessage); }
return steps[index];}
/** * Gets or creates if not exist an unique id from the given wizard instance. * * @static * @private * @method getUniqueId * @param wizard {Object} A jQuery wizard object * @return {String} Returns the unique id for the given wizard */function getUniqueId(wizard){ var uniqueId = wizard.data("uid");
if (uniqueId == null) { uniqueId = wizard._id(); if (uniqueId == null) { uniqueId = "steps-uid-".concat(_uniqueId); wizard._id(uniqueId); }
_uniqueId++; wizard.data("uid", uniqueId); }
return uniqueId;}
/** * Gets a valid enum value by checking a specific enum key or value. * * @static * @private * @method getValidEnumValue * @param enumType {Object} Type of enum * @param keyOrValue {Object} Key as `String` or value as `Integer` to check for */function getValidEnumValue(enumType, keyOrValue){ validateArgument("enumType", enumType); validateArgument("keyOrValue", keyOrValue);
// Is key
if (typeof keyOrValue === "string") { var value = enumType[keyOrValue]; if (value === undefined) { throwError("The enum key '{0}' does not exist.", keyOrValue); }
return value; } // Is value
else if (typeof keyOrValue === "number") { for (var key in enumType) { if (enumType[key] === keyOrValue) { return keyOrValue; } }
throwError("Invalid enum value '{0}'.", keyOrValue); } // Type is not supported
else { throwError("Invalid key or value type."); }}
/** * Routes to the next step. * * @static * @private * @method goToNextStep * @param wizard {Object} The jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @return {Boolean} Indicates whether the action executed **/function goToNextStep(wizard, options, state){ return paginationClick(wizard, options, state, increaseCurrentIndexBy(state, 1));}
/** * Routes to the previous step. * * @static * @private * @method goToPreviousStep * @param wizard {Object} The jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @return {Boolean} Indicates whether the action executed **/function goToPreviousStep(wizard, options, state){ return paginationClick(wizard, options, state, decreaseCurrentIndexBy(state, 1));}
/** * Routes to a specific step by a given index. * * @static * @private * @method goToStep * @param wizard {Object} The jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param index {Integer} The position (zero-based) to route to * @return {Boolean} Indicates whether the action succeeded or failed **/function goToStep(wizard, options, state, index){ if (index < 0 || index >= state.stepCount) { throwError(_indexOutOfRangeErrorMessage); }
if (options.forceMoveForward && index < state.currentIndex) { return; }
var oldIndex = state.currentIndex; if (wizard.triggerHandler("stepChanging", [state.currentIndex, index])) { // Save new state
state.currentIndex = index; saveCurrentStateToCookie(wizard, options, state);
// Change visualisation
refreshStepNavigation(wizard, options, state, oldIndex); refreshPagination(wizard, options, state); loadAsyncContent(wizard, options, state); startTransitionEffect(wizard, options, state, index, oldIndex, function() { wizard.triggerHandler("stepChanged", [index, oldIndex]); }); } else { wizard.find(".steps li").eq(oldIndex).addClass("error"); }
return true;}
function increaseCurrentIndexBy(state, increaseBy){ return state.currentIndex + increaseBy;}
/** * Initializes the component. * * @static * @private * @method initialize * @param options {Object} The component settings **/function initialize(options){ /*jshint -W040 */ var opts = $.extend(true, {}, defaults, options);
return this.each(function () { var wizard = $(this); var state = { currentIndex: opts.startIndex, currentStep: null, stepCount: 0, transitionElement: null };
// Create data container
wizard.data("options", opts); wizard.data("state", state); wizard.data("steps", []);
analyzeData(wizard, opts, state); render(wizard, opts, state); registerEvents(wizard, opts);
// Trigger focus
if (opts.autoFocus && _uniqueId === 0) { getStepAnchor(wizard, opts.startIndex).focus(); }
wizard.triggerHandler("init", [opts.startIndex]); });}
/** * Inserts a new step to a specific position. * * @static * @private * @method insertStep * @param wizard {Object} The jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param index {Integer} The position (zero-based) to add * @param step {Object} The step object to add * @example * $("#wizard").steps().insert(0, { * title: "Title", * content: "", // optional
* contentMode: "async", // optional
* contentUrl: "/Content/Step/1" // optional
* }); * @chainable **/function insertStep(wizard, options, state, index, step){ if (index < 0 || index > state.stepCount) { throwError(_indexOutOfRangeErrorMessage); }
// TODO: Validate step object
// Change data
step = $.extend({}, stepModel, step); insertStepToCache(wizard, index, step); if (state.currentIndex !== state.stepCount && state.currentIndex >= index) { state.currentIndex++; saveCurrentStateToCookie(wizard, options, state); } state.stepCount++;
var contentContainer = wizard.find(".content"), header = $("<{0}>{1}</{0}>".format(options.headerTag, step.title)), body = $("<{0}></{0}>".format(options.bodyTag));
if (step.contentMode == null || step.contentMode === contentMode.html) { body.html(step.content); }
if (index === 0) { contentContainer.prepend(body).prepend(header); } else { getStepPanel(wizard, (index - 1)).after(body).after(header); }
renderBody(wizard, state, body, index); renderTitle(wizard, options, state, header, index); refreshSteps(wizard, options, state, index); if (index === state.currentIndex) { refreshStepNavigation(wizard, options, state); } refreshPagination(wizard, options, state);
return wizard;}
/** * Inserts a step object to the cache at a specific position. * * @static * @private * @method insertStepToCache * @param wizard {Object} A jQuery wizard object * @param index {Integer} The position (zero-based) to add * @param step {Object} The step object to add **/function insertStepToCache(wizard, index, step){ getSteps(wizard).splice(index, 0, step);}
/** * Handles the keyup DOM event for pagination. * * @static * @private * @event keyup * @param event {Object} An event object */function keyUpHandler(event){ var wizard = $(this), options = getOptions(wizard), state = getState(wizard);
if (options.suppressPaginationOnFocus && wizard.find(":focus").is(":input")) { event.preventDefault(); return false; }
var keyCodes = { left: 37, right: 39 }; if (event.keyCode === keyCodes.left) { event.preventDefault(); goToPreviousStep(wizard, options, state); } else if (event.keyCode === keyCodes.right) { event.preventDefault(); goToNextStep(wizard, options, state); }}
/** * Loads and includes async content. * * @static * @private * @method loadAsyncContent * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard */function loadAsyncContent(wizard, options, state){ if (state.stepCount > 0) { var currentIndex = state.currentIndex, currentStep = getStep(wizard, currentIndex);
if (!options.enableContentCache || !currentStep.contentLoaded) { switch (getValidEnumValue(contentMode, currentStep.contentMode)) { case contentMode.iframe: wizard.find(".content > .body").eq(state.currentIndex).empty() .html("<iframe src=\"" + currentStep.contentUrl + "\" frameborder=\"0\" scrolling=\"no\" />") .data("loaded", "1"); break;
case contentMode.async: var currentStepContent = getStepPanel(wizard, currentIndex)._aria("busy", "true") .empty().append(renderTemplate(options.loadingTemplate, { text: options.labels.loading }));
$.ajax({ url: currentStep.contentUrl, cache: false }).done(function (data) { currentStepContent.empty().html(data)._aria("busy", "false").data("loaded", "1"); wizard.triggerHandler("contentLoaded", [currentIndex]); }); break; } } }}
/** * Fires the action next or previous click event. * * @static * @private * @method paginationClick * @param wizard {Object} The jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param index {Integer} The position (zero-based) to route to * @return {Boolean} Indicates whether the event fired successfully or not **/function paginationClick(wizard, options, state, index){ var oldIndex = state.currentIndex;
if (index >= 0 && index < state.stepCount && !(options.forceMoveForward && index < state.currentIndex)) { var anchor = getStepAnchor(wizard, index), parent = anchor.parent(), isDisabled = parent.hasClass("disabled");
// Enable the step to make the anchor clickable!
parent._enableAria(); anchor.click();
// An error occured
if (oldIndex === state.currentIndex && isDisabled) { // Disable the step again if current index has not changed; prevents click action.
parent._enableAria(false); return false; }
return true; }
return false;}
/** * Fires when a pagination click happens. * * @static * @private * @event click * @param event {Object} An event object */function paginationClickHandler(event){ event.preventDefault();
var anchor = $(this), wizard = anchor.parent().parent().parent().parent(), options = getOptions(wizard), state = getState(wizard), href = anchor.attr("href");
switch (href.substring(href.lastIndexOf("#") + 1)) { case "cancel": cancel(wizard); break;
case "finish": finishStep(wizard, state); break;
case "next": goToNextStep(wizard, options, state); break;
case "previous": goToPreviousStep(wizard, options, state); break; }}
/** * Refreshs the visualization state for the entire pagination. * * @static * @private * @method refreshPagination * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard */function refreshPagination(wizard, options, state){ if (options.enablePagination) { var finish = wizard.find(".actions a[href$='#finish']").parent(), next = wizard.find(".actions a[href$='#next']").parent();
if (!options.forceMoveForward) { var previous = wizard.find(".actions a[href$='#previous']").parent(); previous._enableAria(state.currentIndex > 0); }
if (options.enableFinishButton && options.showFinishButtonAlways) { finish._enableAria(state.stepCount > 0); next._enableAria(state.stepCount > 1 && state.stepCount > (state.currentIndex + 1)); } else { finish._showAria(options.enableFinishButton && state.stepCount === (state.currentIndex + 1)); next._showAria(state.stepCount === 0 || state.stepCount > (state.currentIndex + 1)). _enableAria(state.stepCount > (state.currentIndex + 1) || !options.enableFinishButton); } }}
/** * Refreshs the visualization state for the step navigation (tabs). * * @static * @private * @method refreshStepNavigation * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param [oldIndex] {Integer} The index of the prior step */function refreshStepNavigation(wizard, options, state, oldIndex){ var currentOrNewStepAnchor = getStepAnchor(wizard, state.currentIndex), currentInfo = $("<span class=\"current-info audible\">" + options.labels.current + " </span>"), stepTitles = wizard.find(".content > .title");
if (oldIndex != null) { var oldStepAnchor = getStepAnchor(wizard, oldIndex); oldStepAnchor.parent().addClass("done").removeClass("error")._selectAria(false); stepTitles.eq(oldIndex).removeClass("current").next(".body").removeClass("current"); currentInfo = oldStepAnchor.find(".current-info"); currentOrNewStepAnchor.focus(); }
currentOrNewStepAnchor.prepend(currentInfo).parent()._selectAria().removeClass("done")._enableAria(); stepTitles.eq(state.currentIndex).addClass("current").next(".body").addClass("current");}
/** * Refreshes step buttons and their related titles beyond a certain position. * * @static * @private * @method refreshSteps * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param index {Integer} The start point for refreshing ids */function refreshSteps(wizard, options, state, index){ var uniqueId = getUniqueId(wizard);
for (var i = index; i < state.stepCount; i++) { var uniqueStepId = uniqueId + _tabSuffix + i, uniqueBodyId = uniqueId + _tabpanelSuffix + i, uniqueHeaderId = uniqueId + _titleSuffix + i, title = wizard.find(".title").eq(i)._id(uniqueHeaderId);
wizard.find(".steps a").eq(i)._id(uniqueStepId) ._aria("controls", uniqueBodyId).attr("href", "#" + uniqueHeaderId) .html(renderTemplate(options.titleTemplate, { index: i + 1, title: title.html() })); wizard.find(".body").eq(i)._id(uniqueBodyId) ._aria("labelledby", uniqueHeaderId); }}
function registerEvents(wizard, options){ var eventNamespace = getEventNamespace(wizard);
wizard.bind("canceled" + eventNamespace, options.onCanceled); wizard.bind("contentLoaded" + eventNamespace, options.onContentLoaded); wizard.bind("finishing" + eventNamespace, options.onFinishing); wizard.bind("finished" + eventNamespace, options.onFinished); wizard.bind("init" + eventNamespace, options.onInit); wizard.bind("stepChanging" + eventNamespace, options.onStepChanging); wizard.bind("stepChanged" + eventNamespace, options.onStepChanged);
if (options.enableKeyNavigation) { wizard.bind("keyup" + eventNamespace, keyUpHandler); }
wizard.find(".actions a").bind("click" + eventNamespace, paginationClickHandler);}
/** * Removes a specific step by an given index. * * @static * @private * @method removeStep * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param index {Integer} The position (zero-based) of the step to remove * @return Indecates whether the item is removed. **/function removeStep(wizard, options, state, index){ // Index out of range and try deleting current item will return false.
if (index < 0 || index >= state.stepCount || state.currentIndex === index) { return false; }
// Change data
removeStepFromCache(wizard, index); if (state.currentIndex > index) { state.currentIndex--; saveCurrentStateToCookie(wizard, options, state); } state.stepCount--;
getStepTitle(wizard, index).remove(); getStepPanel(wizard, index).remove(); getStepAnchor(wizard, index).parent().remove();
// Set the "first" class to the new first step button
if (index === 0) { wizard.find(".steps li").first().addClass("first"); }
// Set the "last" class to the new last step button
if (index === state.stepCount) { wizard.find(".steps li").eq(index).addClass("last"); }
refreshSteps(wizard, options, state, index); refreshPagination(wizard, options, state);
return true;}
function removeStepFromCache(wizard, index){ getSteps(wizard).splice(index, 1);}
/** * Transforms the base html structure to a more sensible html structure. * * @static * @private * @method render * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard **/function render(wizard, options, state){ // Create a content wrapper and copy HTML from the intial wizard structure
var wrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>", orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation), verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "", contentWrapper = $(wrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass, wizard.html())), stepsWrapper = $(wrapperTemplate.format(options.stepsContainerTag, "steps " + options.clearFixCssClass, "<ul role=\"tablist\"></ul>")), stepTitles = contentWrapper.children(options.headerTag), stepContents = contentWrapper.children(options.bodyTag);
// Transform the wizard wrapper and remove the inner HTML
wizard.attr("role", "application").empty().append(stepsWrapper).append(contentWrapper) .addClass(options.cssClass + " " + options.clearFixCssClass + verticalCssClass);
// Add WIA-ARIA support
stepContents.each(function (index) { renderBody(wizard, state, $(this), index); });
stepTitles.each(function (index) { renderTitle(wizard, options, state, $(this), index); });
refreshStepNavigation(wizard, options, state); renderPagination(wizard, options, state);}
/** * Transforms the body to a proper tabpanel. * * @static * @private * @method renderBody * @param wizard {Object} A jQuery wizard object * @param body {Object} A jQuery body object * @param index {Integer} The position of the body */function renderBody(wizard, state, body, index){ var uniqueId = getUniqueId(wizard), uniqueBodyId = uniqueId + _tabpanelSuffix + index, uniqueHeaderId = uniqueId + _titleSuffix + index;
body._id(uniqueBodyId).attr("role", "tabpanel")._aria("labelledby", uniqueHeaderId) .addClass("body")._showAria(state.currentIndex === index);}
/** * Renders a pagination if enabled. * * @static * @private * @method renderPagination * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard */function renderPagination(wizard, options, state){ if (options.enablePagination) { var pagination = "<{0} class=\"actions {1}\"><ul role=\"menu\" aria-label=\"{2}\">{3}</ul></{0}>", buttonTemplate = "<li><a href=\"#{0}\" role=\"menuitem\">{1}</a></li>", buttons = "";
if (!options.forceMoveForward) { buttons += buttonTemplate.format("previous", options.labels.previous); }
buttons += buttonTemplate.format("next", options.labels.next);
if (options.enableFinishButton) { buttons += buttonTemplate.format("finish", options.labels.finish); }
if (options.enableCancelButton) { buttons += buttonTemplate.format("cancel", options.labels.cancel); }
wizard.append(pagination.format(options.actionContainerTag, options.clearFixCssClass, options.labels.pagination, buttons));
refreshPagination(wizard, options, state); loadAsyncContent(wizard, options, state); }}
/** * Renders a template and replaces all placeholder. * * @static * @private * @method renderTemplate * @param template {String} A template * @param substitutes {Object} A list of substitute * @return {String} The rendered template */function renderTemplate(template, substitutes){ var matches = template.match(/#([a-z]*)#/gi);
for (var i = 0; i < matches.length; i++) { var match = matches[i], key = match.substring(1, match.length - 1);
if (substitutes[key] === undefined) { throwError("The key '{0}' does not exist in the substitute collection!", key); }
template = template.replace(match, substitutes[key]); }
return template;}
/** * Transforms the title to a step item button. * * @static * @private * @method renderTitle * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard * @param header {Object} A jQuery header object * @param index {Integer} The position of the header */function renderTitle(wizard, options, state, header, index){ var uniqueId = getUniqueId(wizard), uniqueStepId = uniqueId + _tabSuffix + index, uniqueBodyId = uniqueId + _tabpanelSuffix + index, uniqueHeaderId = uniqueId + _titleSuffix + index, stepCollection = wizard.find(".steps > ul"), title = renderTemplate(options.titleTemplate, { index: index + 1, title: header.html() }), stepItem = $("<li role=\"tab\"><a id=\"" + uniqueStepId + "\" href=\"#" + uniqueHeaderId + "\" aria-controls=\"" + uniqueBodyId + "\">" + title + "</a></li>"); stepItem._enableAria(options.enableAllSteps || state.currentIndex > index);
if (state.currentIndex > index) { stepItem.addClass("done"); }
header._id(uniqueHeaderId).attr("tabindex", "-1").addClass("title");
if (index === 0) { stepCollection.prepend(stepItem); } else { stepCollection.find("li").eq(index - 1).after(stepItem); }
// Set the "first" class to the new first step button
if (index === 0) { stepCollection.find("li").removeClass("first").eq(index).addClass("first"); }
// Set the "last" class to the new last step button
if (index === (state.stepCount - 1)) { stepCollection.find("li").removeClass("last").eq(index).addClass("last"); }
// Register click event
stepItem.children("a").bind("click" + getEventNamespace(wizard), stepClickHandler);}
/** * Saves the current state to a cookie. * * @static * @private * @method saveCurrentStateToCookie * @param wizard {Object} A jQuery wizard object * @param options {Object} Settings of the current wizard * @param state {Object} The state container of the current wizard */function saveCurrentStateToCookie(wizard, options, state){ if (options.saveState && $.cookie) { $.cookie(_cookiePrefix + getUniqueId(wizard), state.currentIndex); }}
function startTransitionEffect(wizard, options, state, index, oldIndex, doneCallback){ var stepContents = wizard.find(".content > .body"), effect = getValidEnumValue(transitionEffect, options.transitionEffect), effectSpeed = options.transitionEffectSpeed, newStep = stepContents.eq(index), currentStep = stepContents.eq(oldIndex);
switch (effect) { case transitionEffect.fade: case transitionEffect.slide: var hide = (effect === transitionEffect.fade) ? "fadeOut" : "slideUp", show = (effect === transitionEffect.fade) ? "fadeIn" : "slideDown";
state.transitionElement = newStep; currentStep[hide](effectSpeed, function () { var wizard = $(this)._showAria(false).parent().parent(), state = getState(wizard);
if (state.transitionElement) { state.transitionElement[show](effectSpeed, function () { $(this)._showAria(); }).promise().done(doneCallback); state.transitionElement = null; } }); break;
case transitionEffect.slideLeft: var outerWidth = currentStep.outerWidth(true), posFadeOut = (index > oldIndex) ? -(outerWidth) : outerWidth, posFadeIn = (index > oldIndex) ? outerWidth : -(outerWidth);
$.when(currentStep.animate({ left: posFadeOut }, effectSpeed, function () { $(this)._showAria(false); }), newStep.css("left", posFadeIn + "px")._showAria() .animate({ left: 0 }, effectSpeed)).done(doneCallback); break;
default: $.when(currentStep._showAria(false), newStep._showAria()) .done(doneCallback); break; }}
/** * Fires when a step click happens. * * @static * @private * @event click * @param event {Object} An event object */function stepClickHandler(event){ event.preventDefault();
var anchor = $(this), wizard = anchor.parent().parent().parent().parent(), options = getOptions(wizard), state = getState(wizard), oldIndex = state.currentIndex;
if (anchor.parent().is(":not(.disabled):not(.current)")) { var href = anchor.attr("href"), position = parseInt(href.substring(href.lastIndexOf("-") + 1), 0);
goToStep(wizard, options, state, position); }
// If nothing has changed
if (oldIndex === state.currentIndex) { getStepAnchor(wizard, oldIndex).focus(); return false; }}
function throwError(message){ if (arguments.length > 1) { message = message.format(Array.prototype.slice.call(arguments, 1)); }
throw new Error(message);}
/** * Checks an argument for null or undefined and throws an error if one check applies. * * @static * @private * @method validateArgument * @param argumentName {String} The name of the given argument * @param argumentValue {Object} The argument itself */function validateArgument(argumentName, argumentValue){ if (argumentValue == null) { throwError("The argument '{0}' is null or undefined.", argumentName); }}
/** * Represents a jQuery wizard plugin. * * @class steps * @constructor * @param [method={}] The name of the method as `String` or an JSON object for initialization * @param [params=]* {Array} Additional arguments for a method call * @chainable **/$.fn.steps = function (method){ if ($.fn.steps[method]) { return $.fn.steps[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === "object" || !method) { return initialize.apply(this, arguments); } else { $.error("Method " + method + " does not exist on jQuery.steps"); }};
/** * Adds a new step. * * @method add * @param step {Object} The step object to add * @chainable **/$.fn.steps.add = function (step){ var state = getState(this); return insertStep(this, getOptions(this), state, state.stepCount, step);};
/** * Removes the control functionality completely and transforms the current state to the initial HTML structure. * * @method destroy * @chainable **/$.fn.steps.destroy = function (){ return destroy(this, getOptions(this));};
/** * Triggers the onFinishing and onFinished event. * * @method finish **/$.fn.steps.finish = function (){ finishStep(this, getState(this));};
/** * Gets the current step index. * * @method getCurrentIndex * @return {Integer} The actual step index (zero-based) * @for steps **/$.fn.steps.getCurrentIndex = function (){ return getState(this).currentIndex;};
/** * Gets the current step object. * * @method getCurrentStep * @return {Object} The actual step object **/$.fn.steps.getCurrentStep = function (){ return getStep(this, getState(this).currentIndex);};
/** * Gets a specific step object by index. * * @method getStep * @param index {Integer} An integer that belongs to the position of a step * @return {Object} A specific step object **/$.fn.steps.getStep = function (index){ return getStep(this, index);};
/** * Inserts a new step to a specific position. * * @method insert * @param index {Integer} The position (zero-based) to add * @param step {Object} The step object to add * @example * $("#wizard").steps().insert(0, { * title: "Title", * content: "", // optional
* contentMode: "async", // optional
* contentUrl: "/Content/Step/1" // optional
* }); * @chainable **/$.fn.steps.insert = function (index, step){ return insertStep(this, getOptions(this), getState(this), index, step);};
/** * Routes to the next step. * * @method next * @return {Boolean} Indicates whether the action executed **/$.fn.steps.next = function (){ return goToNextStep(this, getOptions(this), getState(this));};
/** * Routes to the previous step. * * @method previous * @return {Boolean} Indicates whether the action executed **/$.fn.steps.previous = function (){ return goToPreviousStep(this, getOptions(this), getState(this));};
/** * Removes a specific step by an given index. * * @method remove * @param index {Integer} The position (zero-based) of the step to remove * @return Indecates whether the item is removed. **/$.fn.steps.remove = function (index){ return removeStep(this, getOptions(this), getState(this), index);};
/** * Sets a specific step object by index. * * @method setStep * @param index {Integer} An integer that belongs to the position of a step * @param step {Object} The step object to change **/$.fn.steps.setStep = function (index, step){ throw new Error("Not yet implemented!");};
/** * Skips an certain amount of steps. * * @method skip * @param count {Integer} The amount of steps that should be skipped * @return {Boolean} Indicates whether the action executed **/$.fn.steps.skip = function (count){ throw new Error("Not yet implemented!");};/** * Get number of steps in wizard * * @method getTotalSteps * @return {Integer} Total Steps in Wizard **/$.fn.steps.getTotalSteps = function () { var wizard = this; var steps = getSteps(wizard); return steps.length;};/** * An enum represents the different content types of a step and their loading mechanisms. * * @class contentMode * @for steps **/var contentMode = $.fn.steps.contentMode = { /** * HTML embedded content * * @readOnly * @property html * @type Integer * @for contentMode **/ html: 0,
/** * IFrame embedded content * * @readOnly * @property iframe * @type Integer * @for contentMode **/ iframe: 1,
/** * Async embedded content * * @readOnly * @property async * @type Integer * @for contentMode **/ async: 2};
/** * An enum represents the orientation of the steps navigation. * * @class stepsOrientation * @for steps **/var stepsOrientation = $.fn.steps.stepsOrientation = { /** * Horizontal orientation * * @readOnly * @property horizontal * @type Integer * @for stepsOrientation **/ horizontal: 0,
/** * Vertical orientation * * @readOnly * @property vertical * @type Integer * @for stepsOrientation **/ vertical: 1};
/** * An enum that represents the various transition animations. * * @class transitionEffect * @for steps **/var transitionEffect = $.fn.steps.transitionEffect = { /** * No transition animation * * @readOnly * @property none * @type Integer * @for transitionEffect **/ none: 0,
/** * Fade in transition * * @readOnly * @property fade * @type Integer * @for transitionEffect **/ fade: 1,
/** * Slide up transition * * @readOnly * @property slide * @type Integer * @for transitionEffect **/ slide: 2,
/** * Slide left transition * * @readOnly * @property slideLeft * @type Integer * @for transitionEffect **/ slideLeft: 3};
var stepModel = $.fn.steps.stepModel = { title: "", content: "", contentUrl: "", contentMode: contentMode.html, contentLoaded: false};
/** * An object that represents the default settings. * There are two possibities to override the sub-properties. * Either by doing it generally (global) or on initialization. * * @static * @class defaults * @for steps * @example * // Global approach
* $.steps.defaults.headerTag = "h3"; * @example * // Initialization approach
* $("#wizard").steps({ headerTag: "h3" }); **/var defaults = $.fn.steps.defaults = { /** * The header tag is used to find the step button text within the declared wizard area. * * @property headerTag * @type String * @default "h1" * @for defaults **/ headerTag: "h1",
/** * The body tag is used to find the step content within the declared wizard area. * * @property bodyTag * @type String * @default "div" * @for defaults **/ bodyTag: "div",
/** * The content container tag which will be used to wrap all step contents. * * @property contentContainerTag * @type String * @default "div" * @for defaults **/ contentContainerTag: "div",
/** * The action container tag which will be used to wrap the pagination navigation. * * @property actionContainerTag * @type String * @default "div" * @for defaults **/ actionContainerTag: "div",
/** * The steps container tag which will be used to wrap the steps navigation. * * @property stepsContainerTag * @type String * @default "div" * @for defaults **/ stepsContainerTag: "div",
/** * The css class which will be added to the outer component wrapper. * * @property cssClass * @type String * @default "wizard" * @for defaults * @example * <div class="wizard"> * ... * </div> **/ cssClass: "wizard",
/** * The css class which will be used for floating scenarios. * * @property clearFixCssClass * @type String * @default "clearfix" * @for defaults **/ clearFixCssClass: "clearfix",
/** * Determines whether the steps are vertically or horizontally oriented. * * @property stepsOrientation * @type stepsOrientation * @default horizontal * @for defaults * @since 1.0.0 **/ stepsOrientation: stepsOrientation.horizontal,
/* * Tempplates */
/** * The title template which will be used to create a step button. * * @property titleTemplate * @type String * @default "<span class=\"number\">#index#.</span> #title#" * @for defaults **/ titleTemplate: "<span class=\"number\">#index#.</span> #title#",
/** * The loading template which will be used to create the loading animation. * * @property loadingTemplate * @type String * @default "<span class=\"spinner\"></span> #text#" * @for defaults **/ loadingTemplate: "<span class=\"spinner\"></span> #text#",
/* * Behaviour */
/** * Sets the focus to the first wizard instance in order to enable the key navigation from the begining if `true`. * * @property autoFocus * @type Boolean * @default false * @for defaults * @since 0.9.4 **/ autoFocus: false,
/** * Enables all steps from the begining if `true` (all steps are clickable). * * @property enableAllSteps * @type Boolean * @default false * @for defaults **/ enableAllSteps: false,
/** * Enables keyboard navigation if `true` (arrow left and arrow right). * * @property enableKeyNavigation * @type Boolean * @default true * @for defaults **/ enableKeyNavigation: true,
/** * Enables pagination if `true`. * * @property enablePagination * @type Boolean * @default true * @for defaults **/ enablePagination: true,
/** * Suppresses pagination if a form field is focused. * * @property suppressPaginationOnFocus * @type Boolean * @default true * @for defaults **/ suppressPaginationOnFocus: true,
/** * Enables cache for async loaded or iframe embedded content. * * @property enableContentCache * @type Boolean * @default true * @for defaults **/ enableContentCache: true,
/** * Shows the cancel button if enabled. * * @property enableCancelButton * @type Boolean * @default false * @for defaults **/ enableCancelButton: false,
/** * Shows the finish button if enabled. * * @property enableFinishButton * @type Boolean * @default true * @for defaults **/ enableFinishButton: true,
/** * Not yet implemented. * * @property preloadContent * @type Boolean * @default false * @for defaults **/ preloadContent: false,
/** * Shows the finish button always (on each step; right beside the next button) if `true`. * Otherwise the next button will be replaced by the finish button if the last step becomes active. * * @property showFinishButtonAlways * @type Boolean * @default false * @for defaults **/ showFinishButtonAlways: false,
/** * Prevents jumping to a previous step. * * @property forceMoveForward * @type Boolean * @default false * @for defaults **/ forceMoveForward: false,
/** * Saves the current state (step position) to a cookie. * By coming next time the last active step becomes activated. * * @property saveState * @type Boolean * @default false * @for defaults **/ saveState: false,
/** * The position to start on (zero-based). * * @property startIndex * @type Integer * @default 0 * @for defaults **/ startIndex: 0,
/* * Animation Effect Configuration */
/** * The animation effect which will be used for step transitions. * * @property transitionEffect * @type transitionEffect * @default none * @for defaults **/ transitionEffect: transitionEffect.none,
/** * Animation speed for step transitions (in milliseconds). * * @property transitionEffectSpeed * @type Integer * @default 200 * @for defaults **/ transitionEffectSpeed: 200,
/* * Events */
/** * Fires before the step changes and can be used to prevent step changing by returning `false`. * Very useful for form validation. * * @property onStepChanging * @type Event * @default function (event, currentIndex, newIndex) { return true; } * @for defaults **/ onStepChanging: function (event, currentIndex, newIndex) { return true; },
/** * Fires after the step has change. * * @property onStepChanged * @type Event * @default function (event, currentIndex, priorIndex) { } * @for defaults **/ onStepChanged: function (event, currentIndex, priorIndex) { },
/** * Fires after cancelation. * * @property onCanceled * @type Event * @default function (event) { } * @for defaults **/ onCanceled: function (event) { },
/** * Fires before finishing and can be used to prevent completion by returning `false`. * Very useful for form validation. * * @property onFinishing * @type Event * @default function (event, currentIndex) { return true; } * @for defaults **/ onFinishing: function (event, currentIndex) { return true; },
/** * Fires after completion. * * @property onFinished * @type Event * @default function (event, currentIndex) { } * @for defaults **/ onFinished: function (event, currentIndex) { },
/** * Fires after async content is loaded. * * @property onContentLoaded * @type Event * @default function (event, index) { } * @for defaults **/ onContentLoaded: function (event, currentIndex) { },
/** * Fires when the wizard is initialized. * * @property onInit * @type Event * @default function (event) { } * @for defaults **/ onInit: function (event, currentIndex) { },
/** * Contains all labels. * * @property labels * @type Object * @for defaults **/ labels: { /** * Label for the cancel button. * * @property cancel * @type String * @default "Cancel" * @for defaults **/ cancel: "Cancel",
/** * This label is important for accessability reasons. * Indicates which step is activated. * * @property current * @type String * @default "current step:" * @for defaults **/ current: "current step:",
/** * This label is important for accessability reasons and describes the kind of navigation. * * @property pagination * @type String * @default "Pagination" * @for defaults * @since 0.9.7 **/ pagination: "Pagination",
/** * Label for the finish button. * * @property finish * @type String * @default "Finish" * @for defaults **/ finish: "Finish",
/** * Label for the next button. * * @property next * @type String * @default "Next" * @for defaults **/ next: "Next",
/** * Label for the previous button. * * @property previous * @type String * @default "Previous" * @for defaults **/ previous: "Previous",
/** * Label for the loading animation. * * @property loading * @type String * @default "Loading ..." * @for defaults **/ loading: "Loading ..." }};})(jQuery);
|