/*! * 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}\">".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 finishStep1(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}".format(options.headerTag, step.title)), body = $("<{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("