diff --git a/css/jquery.steps.css b/css/jquery.steps.css new file mode 100644 index 0000000..2b59b58 --- /dev/null +++ b/css/jquery.steps.css @@ -0,0 +1,382 @@ +/* + Common +*/ + +.wizard, +.tabcontrol +{ + display: block; + width: 100%; + overflow: hidden; +} + +.wizard a, +.tabcontrol a +{ + outline: 0; +} + +.wizard ul, +.tabcontrol ul +{ + list-style: none !important; + padding: 0; + margin: 0; +} + +.wizard ul > li, +.tabcontrol ul > li +{ + display: block; + padding: 0; +} + +/* Accessibility */ +.wizard > .steps .current-info, +.tabcontrol > .steps .current-info +{ + position: absolute; + left: -999em; +} + +.wizard > .content > .title, +.tabcontrol > .content > .title +{ + position: absolute; + left: -999em; +} + + + +/* + Wizard +*/ + +.wizard > .steps +{ + position: relative; + display: block; + width: 100%; +} + +.wizard.vertical > .steps +{ + display: inline; + float: left; + width: 30%; +} + +.wizard > .steps .number +{ + font-size: 1.429em; +} + +.wizard > .steps > ul > li +{ + width: 25%; +} + +.wizard > .steps > ul > li, +.wizard > .actions > ul > li +{ + float: left; +} + +.wizard.vertical > .steps > ul > li +{ + float: none; + width: 100%; +} + +.wizard > .steps a, +.wizard > .steps a:hover, +.wizard > .steps a:active +{ + display: block; + width: auto; + margin: 0 0.5em 0.5em; + padding: 1em 1em; + text-decoration: none; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.wizard > .steps .disabled a, +.wizard > .steps .disabled a:hover, +.wizard > .steps .disabled a:active +{ + background: #eee; + color: #aaa; + cursor: default; +} + +.wizard > .steps .current a, +.wizard > .steps .current a:hover, +.wizard > .steps .current a:active +{ + background: #2184be; + color: #fff; + cursor: default; +} + +.wizard > .steps .done a, +.wizard > .steps .done a:hover, +.wizard > .steps .done a:active +{ + background: #9dc8e2; + color: #fff; +} + +.wizard > .steps .error a, +.wizard > .steps .error a:hover, +.wizard > .steps .error a:active +{ + background: #ff3111; + color: #fff; +} + +.wizard > .content +{ + background: #eee; + display: block; + margin: 0.5em; + min-height: 35em; + overflow: hidden; + position: relative; + width: auto; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.wizard.vertical > .content +{ + display: inline; + float: left; + margin: 0 2.5% 0.5em 2.5%; + width: 65%; +} + +.wizard > .content > .body +{ + float: left; + position: absolute; + width: 95%; + height: 95%; + padding: 2.5%; +} + +.wizard > .content > .body ul +{ + list-style: disc !important; +} + +.wizard > .content > .body ul > li +{ + display: list-item; +} + +.wizard > .content > .body > iframe +{ + border: 0 none; + width: 100%; + height: 100%; +} + +.wizard > .content > .body input +{ + display: block; + border: 1px solid #ccc; +} + +.wizard > .content > .body input[type="checkbox"] +{ + display: inline-block; +} + +.wizard > .content > .body input.error +{ + background: rgb(251, 227, 228); + border: 1px solid #fbc2c4; + color: #8a1f11; +} + +.wizard > .content > .body label +{ + display: inline-block; + margin-bottom: 0.5em; +} + +.wizard > .content > .body label.error +{ + color: #8a1f11; + display: inline-block; + margin-left: 1.5em; +} + +.wizard > .actions +{ + position: relative; + display: block; + text-align: right; + width: 100%; +} + +.wizard.vertical > .actions +{ + display: inline; + float: right; + margin: 0 2.5%; + width: 95%; +} + +.wizard > .actions > ul +{ + display: inline-block; + text-align: right; +} + +.wizard > .actions > ul > li +{ + margin: 0 0.5em; +} + +.wizard.vertical > .actions > ul > li +{ + margin: 0 0 0 1em; +} + +.wizard > .actions a, +.wizard > .actions a:hover, +.wizard > .actions a:active +{ + background: #2184be; + color: #fff; + display: block; + padding: 0.5em 1em; + text-decoration: none; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +.wizard > .actions .disabled a, +.wizard > .actions .disabled a:hover, +.wizard > .actions .disabled a:active +{ + background: #eee; + color: #aaa; +} + +.wizard > .loading +{ +} + +.wizard > .loading .spinner +{ +} + + + +/* + Tabcontrol +*/ + +.tabcontrol > .steps +{ + position: relative; + display: block; + width: 100%; +} + +.tabcontrol > .steps > ul +{ + position: relative; + margin: 6px 0 0 0; + top: 1px; + z-index: 1; +} + +.tabcontrol > .steps > ul > li +{ + float: left; + margin: 5px 2px 0 0; + padding: 1px; + + -webkit-border-top-left-radius: 5px; + -webkit-border-top-right-radius: 5px; + -moz-border-radius-topleft: 5px; + -moz-border-radius-topright: 5px; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.tabcontrol > .steps > ul > li:hover +{ + background: #edecec; + border: 1px solid #bbb; + padding: 0; +} + +.tabcontrol > .steps > ul > li.current +{ + background: #fff; + border: 1px solid #bbb; + border-bottom: 0 none; + padding: 0 0 1px 0; + margin-top: 0; +} + +.tabcontrol > .steps > ul > li > a +{ + color: #5f5f5f; + display: inline-block; + border: 0 none; + margin: 0; + padding: 10px 30px; + text-decoration: none; +} + +.tabcontrol > .steps > ul > li > a:hover +{ + text-decoration: none; +} + +.tabcontrol > .steps > ul > li.current > a +{ + padding: 15px 30px 10px 30px; +} + +.tabcontrol > .content +{ + position: relative; + display: inline-block; + width: 100%; + height: 35em; + overflow: hidden; + border-top: 1px solid #bbb; + padding-top: 20px; +} + +.tabcontrol > .content > .body +{ + float: left; + position: absolute; + width: 95%; + height: 95%; + padding: 2.5%; +} + +.tabcontrol > .content > .body ul +{ + list-style: disc !important; +} + +.tabcontrol > .content > .body ul > li +{ + display: list-item; +} \ No newline at end of file diff --git a/index.html b/index.html index 4258189..8e61ec3 100644 --- a/index.html +++ b/index.html @@ -1,27 +1,75 @@ + - - + + + +Demo + + + + + + - +
+

➺ ffmproviser ❥

+ + + +
+

First Question

+
+
+

Question

+
+ Intro question + + + + + + + +

(*) Mandatory

+
+
+ +

Second Step

+
+

Profile

+
+ + +
+
+ +

Third Step

+
+
+ +

Forth Step

+
+
+
+
- - - - - - - + \ No newline at end of file diff --git a/js/jquery.steps.js b/js/jquery.steps.js new file mode 100755 index 0000000..4eb5126 --- /dev/null +++ b/js/jquery.steps.js @@ -0,0 +1,2015 @@ +/*! + * jQuery Steps v1.0.7 - 05/07/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 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); + + 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(); + } + }); +} + +/** + * 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 currentStep = getStep(wizard, state.currentIndex); + + if (!options.enableContentCache || !currentStep.contentLoaded) + { + switch (getValidEnumValue(contentMode, currentStep.contentMode)) + { + case contentMode.iframe: + wizard.find(".content > .body").eq(state.currentIndex).empty() + .html("