Commit b4fef4d4d5d2bef683b58d80267b721f27947d6c

Authored by Fernando Brito
1 parent 380a1960
Exists in master and in 2 other branches v2, wikilibras

Wizard prototype. File validation.

app/assets/javascripts/v_libras/requests/new.js 0 → 100644
... ... @@ -0,0 +1,93 @@
  1 +$(function () {
  2 + $("#vlibras-wizard").steps({
  3 + headerTag: "h2",
  4 + bodyTag: "section",
  5 + transitionEffect: "slideLeft",
  6 + stepsOrientation: "horizontal",
  7 + enablePagination: true,
  8 + forceMoveForward: true,
  9 +
  10 + onStepChanging: stepValidation,
  11 + onStepChanged: stepChanged,
  12 + onFinished: finished,
  13 +
  14 + labels: {
  15 + cancel: "Cancelar",
  16 + current: "etapa atual:",
  17 + pagination: "Pagination",
  18 + finish: "Finalizar",
  19 + next: "Próximo",
  20 + previous: "Anterior",
  21 + loading: "Carregando ..."
  22 + }
  23 + });
  24 +
  25 + function stepChanged(event, currentIndex, priorIndex) {
  26 + var totalSteps = $("#vlibras-wizard .content section").size();
  27 +
  28 + if ((currentIndex + 1) === totalSteps) {
  29 + $("#btn-next").text("Finalizar");
  30 + } else {
  31 + $("#btn-next").text("Próximo");
  32 + }
  33 +
  34 + deactivateNextButton();
  35 + }
  36 +
  37 + function finished(event, currentIndex) {
  38 + $("#vlibras-form").submit();
  39 + }
  40 +
  41 + function stepValidation(event, currentIndex, newIndex) {
  42 + return true;
  43 + }
  44 +
  45 + $('#menu #btn-next').click(function() {
  46 + // Number of steps
  47 + var totalSteps = $("#vlibras-wizard .content section").size();
  48 + var currentStep = $("#vlibras-wizard").steps('getCurrentIndex');
  49 +
  50 + if ((currentStep + 1) === totalSteps) {
  51 + $("#vlibras-wizard").steps('finish');
  52 + } else {
  53 + $("#vlibras-wizard").steps('next');
  54 + }
  55 + });
  56 +
  57 + function activateNextButton() {
  58 + $("#btn-next").prop('disabled', false);
  59 + }
  60 +
  61 + function deactivateNextButton() {
  62 + $("#btn-next").prop('disabled', true);
  63 + }
  64 +
  65 +
  66 +
  67 + /*
  68 + * Validates video and subtitle extension and activate the next button
  69 + */
  70 + $("#vlibras-wizard #subtitle-upload").change(function() {
  71 + var acceptedFileTypes = ["srt"];
  72 + validateFileWizard($(this), acceptedFileTypes);
  73 + });
  74 +
  75 + $("#vlibras-wizard #video-upload").change(function() {
  76 + var acceptedFileTypes = ["flv", "ts", "avi", "mp4", "mov", "webm", "wmv", "mkv"];
  77 + validateFileWizard($(this), acceptedFileTypes);
  78 + });
  79 +
  80 + function validateFileWizard(input, acceptedFileTypes) {
  81 + var isValidFile = checkType(input, acceptedFileTypes);
  82 +
  83 + if (isValidFile) {
  84 + activateNextButton();
  85 + } else {
  86 + deactivateNextButton();
  87 + input.val(null);
  88 + alert("Apenas os formatos abaixo são aceitos:\n\n" + acceptedFileTypes.join(", "));
  89 + }
  90 +
  91 + return true;
  92 + }
  93 +});
0 94 \ No newline at end of file
... ...
app/assets/javascripts/v_libras/requests/rapid.js
... ... @@ -17,4 +17,32 @@ $(function() {
17 17 if ($("#service-video")[0].checked) {
18 18 $("#service-video").click();
19 19 }
20   -});
21 20 \ No newline at end of file
  21 +});
  22 +
  23 +
  24 +/*
  25 + * File type verification
  26 + */
  27 +
  28 +$(function() {
  29 + $("#subtitle-upload").change(function() {
  30 + var acceptedFileTypes = ["srt"];
  31 + validateFile($(this), acceptedFileTypes);
  32 + });
  33 +
  34 + $("#video-upload").change(function() {
  35 + var acceptedFileTypes = ["flv", "ts", "avi", "mp4", "mov", "webm", "wmv", "mkv"];
  36 + validateFile($(this), acceptedFileTypes);
  37 + });
  38 +
  39 + function validateFile(input, acceptedFileTypes) {
  40 + var isValidFile = checkType(input, acceptedFileTypes);
  41 +
  42 + if (!isValidFile) {
  43 + input.val(null);
  44 + alert("Apenas os formatos abaixo são aceitos:\n\n" + acceptedFileTypes.join(", "));
  45 + }
  46 +
  47 + return true;
  48 + }
  49 +});
... ...
app/assets/javascripts/v_libras/requests/shared.js 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +function checkType(file, acceptedFileTypes) {
  2 + var ext = file.val().split('.').pop().toLowerCase();
  3 + var isValidFile = false;
  4 +
  5 + for (var i = 0; i < acceptedFileTypes.length; i++) {
  6 + if (ext == acceptedFileTypes[i]) {
  7 + isValidFile = true;
  8 + break;
  9 + }
  10 + }
  11 +
  12 + return isValidFile;
  13 +}
0 14 \ No newline at end of file
... ...
app/assets/stylesheets/application.css.scss
... ... @@ -41,10 +41,4 @@
41 41 color:#BBB;
42 42 text-shadow: 1px 1px #AAA;
43 43 }
44   -}
45   -
46   -@media (max-width: 980px) {
47   - .dropdown ul.dropdown-menu {
48   - display: block;
49   - }
50 44 }
51 45 \ No newline at end of file
... ...
app/assets/stylesheets/bootstrap.css.less
... ... @@ -50,16 +50,6 @@ footer {
50 50  
51 51 }
52 52  
53   -.upload{
54   - width: 100%;
55   - height: 480px;
56   - text-align:center;
57   -}
58   -
59   -.upload a{
60   - text-align:center;
61   -}
62   -
63 53 .center{
64 54 text-align:center;
65 55 }
... ... @@ -112,23 +102,26 @@ label {
112 102 font-size:18px;
113 103 }
114 104  
115   -#upload{
  105 +.upload{
116 106 margin-top:10px;
117 107 background-color:#DDD;
118 108 height:100px;
119   - text-transform:uppercase;
120 109 color:#000;
121   - text-align:center;
  110 + text-align: center;
122 111  
123 112 -moz-box-shadow: 0 0 1px 1px #888;
124   - -webkit-box-shadow: 0 0 1px 1px#888;
  113 + -webkit-box-shadow: 0 0 1px 1px #888;
125 114 box-shadow: 0 0 1px 1px #888;
126 115 }
127 116  
128   -input{
  117 +
  118 +
  119 +
  120 +
  121 +input {
129 122 display: inline-block;
130 123 *display: inline;
131   - padding: 4px 12px;
  124 + padding: 0px;
132 125 margin-bottom: 0;
133 126 *margin-left: .3em;
134 127 font-size: 14px;
... ... @@ -327,6 +320,10 @@ input[type=&quot;checkbox&quot;] {
327 320 width: auto;
328 321 }
329 322  
  323 +input[type="file"] {
  324 + width: 100%;
  325 +}
  326 +
330 327 select,
331 328 input[type="file"] {
332 329 height: 30px;
... ...
app/assets/stylesheets/bootstrap_overrides.css.scss
  1 +.container-fluid {
  2 + max-width: 1024px;
  3 + margin: auto;
  4 +}
  5 +
  6 +
1 7 .breadcrumb {
2 8 padding: 7px 14px;
3 9 margin: 0 0 18px;
... ... @@ -45,8 +51,16 @@
45 51 color: #333333;
46 52 }
47 53  
  54 +
  55 +/* Fix menu */
48 56 @media (max-width: 767px) {
49 57 .nav-collapse.user-menu {
50 58 float: left;
51 59 }
  60 +}
  61 +
  62 +@media (max-width: 980px) {
  63 + .dropdown ul.dropdown-menu {
  64 + display: block;
  65 + }
52 66 }
53 67 \ No newline at end of file
... ...
app/assets/stylesheets/components/video_player.css.scss
... ... @@ -7,6 +7,10 @@
7 7 box-shadow: 0 0 5px 5px #888;
8 8 }
9 9  
  10 +.video-wizard {
  11 + max-width: 50%;
  12 +}
  13 +
10 14 .video-vlibras {
11 15 max-width: 50%;
12 16 }
... ...
app/assets/stylesheets/v_libras/requests.css.scss 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +#vlibras-wizard {
  2 + .actions {
  3 + display: none;
  4 + }
  5 +}
0 6 \ No newline at end of file
... ...
app/controllers/v_libras/requests_controller.rb
... ... @@ -8,26 +8,23 @@ class VLibras::RequestsController &lt; ApplicationController
8 8 @request = VLibras::Request.new
9 9 end
10 10  
11   - def create
12   - @request = VLibras::Request.build_from_params(params, current_user)
13   -
14   - video = FileUploader.new
15   - video.cache!(params[:video])
16   -
17   - subtitle = FileUploader.new
18   - subtitle.cache!(params[:subtitle])
  11 + def new
19 12  
20   - files = { :video => video, :subtitle => subtitle }
  13 + end
21 14  
  15 + def create
  16 + @request = VLibras::Request.build_from_params(params, current_user)
22 17  
23 18 if @request.save
24   - @request.perform_request(files)
  19 + @request.perform_request(@request.files)
25 20  
26 21 flash[:success] = 'Sua requisição foi submetida com sucesso!'
27 22 redirect_to v_libras_videos_path
28 23 else
29   - flash[:error] = 'Algo deu errado com a sua requisição.'
30   - render :action => :rapid
  24 + flash[:error] = 'Algo deu errado com a sua requisição. Por favor verifique opções escolhidas.'
  25 + flash[:warning] = @request.errors.full_messages.to_sentence.humanize
  26 +
  27 + redirect_to :back
31 28 end
32 29 end
33 30  
... ...
app/models/v_libras/request.rb
... ... @@ -15,19 +15,21 @@
15 15  
16 16 class VLibras::Request < ActiveRecord::Base
17 17 serialize :params
18   - attr_accessor :video
  18 + attr_accessor :files
19 19  
20 20 belongs_to :owner, :class => User
21 21  
22 22 has_one :video, :class => VLibras::Video, :dependent => :destroy
23 23  
24 24 validates :service_type,
25   - presence: true,
26   - inclusion: { in: %w(video-legenda video), message: "%{value} is not a valid service type" }
  25 + presence: true,
  26 + inclusion: { in: %w(video-legenda video) }
27 27  
28 28 validates :status,
29 29 presence: true,
30   - inclusion: { in: %w(created processing error success), message: "%{value} is not a valid status" }
  30 + inclusion: { in: %w(created processing error success) }
  31 +
  32 + validate :match_files_with_service_type
31 33  
32 34 before_validation :default_values
33 35  
... ... @@ -37,9 +39,23 @@ class VLibras::Request &lt; ActiveRecord::Base
37 39 request = self.new
38 40  
39 41 request.service_type = params[:service]
40   - request.video_filename = params[:video].original_filename
41 42 request.owner = user
42 43  
  44 + request.files = {}
  45 +
  46 + if params[:video]
  47 + request.video_filename = params[:video].original_filename
  48 + video = FileUploader.new
  49 + video.cache!(params[:video])
  50 + request.files.merge!(:video => video)
  51 + end
  52 +
  53 + if params[:subtitle]
  54 + subtitle = FileUploader.new
  55 + subtitle.cache!(params[:subtitle])
  56 + request.files.merge!(:subtitle => subtitle)
  57 + end
  58 +
43 59 request.params = params[:params]
44 60  
45 61 request
... ... @@ -55,7 +71,18 @@ class VLibras::Request &lt; ActiveRecord::Base
55 71 end
56 72 handle_asynchronously :perform_request
57 73  
  74 +
58 75 private
  76 + def match_files_with_service_type
  77 + if files[:video].nil?
  78 + errors.add(:base, 'Você precisa enviar um vídeo.')
  79 + end
  80 +
  81 + if (service_type == 'video-legenda') && files[:subtitle].nil?
  82 + errors.add(:base, 'Você precisa enviar uma legenda.')
  83 + end
  84 + end
  85 +
59 86 def default_values
60 87 self.status ||= 'created'
61 88 end
... ...
app/views/layouts/_menu.haml
... ... @@ -9,9 +9,9 @@
9 9  
10 10 = link_to "GTAaaS", home_path, :class => "brand"
11 11  
12   - - if current_user.present?
13   - .nav-collapse
14   - %ul.nav
  12 + .nav-collapse
  13 + %ul.nav
  14 + - if current_user.present?
15 15 %li= link_to t('shared.main'), home_path
16 16  
17 17 %li.dropdown
... ... @@ -26,7 +26,7 @@
26 26 = t('wikivideos.my_videos')
27 27 - if current_user.videos.not_seen.any?
28 28 %span.label.label-success= current_user.videos.not_seen.size
29   - %li= link_to t('videos.new'), '#'
  29 + %li= link_to t('videos.new'), new_v_libras_request_path
30 30 %li.divider
31 31 %li= link_to t('shared.form_alternative'), rapid_v_libras_requests_path
32 32  
... ... @@ -42,7 +42,7 @@
42 42  
43 43 %li.hidden= link_to t('shared.slibras')
44 44  
45   - %li= link_to t('shared.about'), "http://gtaaas.lavid.ufpb.br/projeto", :target => "blank"
  45 + %li= link_to t('shared.about'), "http://gtaaas.lavid.ufpb.br/projeto", :target => "blank"
46 46  
47 47 - if current_user.present?
48 48 .nav-collapse.pull-right.user-menu
... ...
app/views/layouts/application.html.erb
... ... @@ -4,7 +4,10 @@
4 4 <title>GTAaaS</title>
5 5 <%= stylesheet_link_tag "application", :media => "all" %>
6 6 <%= javascript_include_tag "application" %>
  7 +
7 8 <%= yield :js %>
  9 + <%= yield :css %>
  10 +
8 11 <%= csrf_meta_tags %>
9 12 <meta name="viewport" content="width=device-width, initial-scale=1.0">
10 13  
... ...
app/views/static/home.haml
1   -%h1 Home
2 1 \ No newline at end of file
  2 +.breadcrumb
  3 + %h1 Menu
3 4 \ No newline at end of file
... ...
app/views/v_libras/requests/new.haml 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +- content_for :js do
  2 + = javascript_include_tag "jquery.steps.js"
  3 + = javascript_include_tag "v_libras/requests/shared"
  4 + = javascript_include_tag "v_libras/requests/new"
  5 +
  6 +- content_for :css do
  7 + = stylesheet_link_tag "jquery.steps.css"
  8 + = stylesheet_link_tag "v_libras/requests"
  9 +
  10 +.breadcrumb
  11 + %h3 Novo vídeo
  12 +
  13 += form_tag v_libras_requests_path, method: :post, :multipart => true, :id => 'vlibras-form' do |f|
  14 + .content.row-fluid
  15 + #vlibras-wizard
  16 + %h2 Vídeo
  17 + %section
  18 + %p
  19 + instruções + upload do arquivo de vído
  20 + só libera o botão quando o video for escolhido
  21 +
  22 + = html5_video_tag("/video.mp4", 'id', 'video-wizard', :autoplay => 'autoplay')
  23 +
  24 + .span4
  25 + = file_field_tag 'video', :id => 'video-upload'
  26 +
  27 + %h2 Áudio ou Legenda
  28 + %section
  29 + %p
  30 + escolha se é audio ou legenda
  31 + se for legenda, mostra o input file e quando arquivo for selecionado, libera o botão de avançar
  32 +
  33 + .span4
  34 + = file_field_tag 'subtitle', :id => 'subtitle-upload'
  35 + %h2 Posição
  36 + %section
  37 + %p
  38 + asd
  39 +
  40 + %h2 Tamanho
  41 + %section
  42 + %p
  43 + botão de
  44 +
  45 + #menu.center
  46 + = button_tag 'Próximo', :class => "btn btn-large btn-success", :id => 'btn-next', :disabled => true
  47 +
  48 +.row-fluid
  49 + #step-service
  50 +
  51 + #step-video-upload
  52 +
  53 + #step-subtitle-upload
  54 +
  55 + #step-size
  56 +
  57 + #step-position
... ...
app/views/v_libras/requests/rapid.haml
1 1 - content_for :js do
  2 + = javascript_include_tag "v_libras/requests/shared"
2 3 = javascript_include_tag "v_libras/requests/rapid"
3 4  
4 5 .row-fluid
... ... @@ -27,15 +28,18 @@
27 28 = radio_button_tag :service, 'video-legenda', false, :id => 'service-video-subtitle'
28 29 Legenda (.SRT)
29 30  
30   - #url.hide
  31 + #url.field.hide
31 32 = label_tag :video, t('videos.url'), :class => "bold"
32   - = file_field_tag :video, :onchange => "return check_video(this)"
33   - #legend.hide
  33 + = file_field_tag :video, :prompt => "Arquivo de vídeo", :id => 'video-upload'
  34 +
  35 + #legend.field.hide
34 36 = label_tag :subtitle, t('videos.subtitle'), :class => "bold"
35   - = file_field_tag :subtitle, :prompt => "LEGENDA", :onchange => "return check_subtitle(this)"
  37 + = file_field_tag :subtitle, :prompt => "Legenda", :id => 'subtitle-upload'
  38 +
36 39 .field
37 40 = label_tag 'params[tamanho]', t('videos.window_size'), :class => "bold"
38 41 = select_tag 'params[tamanho]', options_for_select([['Pequena', 'pequeno'], ['Média', 'medio'], ['Grande', 'grande']])
  42 +
39 43 .field
40 44 %p
41 45 %b
... ... @@ -43,6 +47,7 @@
43 47 = select_tag 'params[posicao]', options_for_select([[t('videos.top_left'),
44 48 'superior-esquerdo'], [t('videos.top_right'), 'superior-direito'],
45 49 [t('videos.bottom_right'),'inferior-direito'], [t('videos.bottom_left'), 'inferior-esquerdo']])
  50 +
46 51 .field
47 52 = label_tag 'params[transparencia]', t('videos.transparency'), :class => "bold"
48 53 = select_tag 'params[transparencia]', options_for_select([['Opaco', 'opaco'], ['Transparente', 'transparente']])
... ...
config/initializers/assets.rb
  1 +Rails.application.config.assets.precompile += %w( v_libras/requests/shared.js )
1 2 Rails.application.config.assets.precompile += %w( v_libras/requests/rapid.js )
2   -Rails.application.config.assets.precompile += %w( v_libras/videos/index.js )
3 3 \ No newline at end of file
  4 +Rails.application.config.assets.precompile += %w( v_libras/requests/new.js )
  5 +Rails.application.config.assets.precompile += %w( v_libras/videos/index.js )
  6 +Rails.application.config.assets.precompile += %w( jquery.steps.js )
  7 +
  8 +Rails.application.config.assets.precompile += %w( v_libras/requests.css )
  9 +Rails.application.config.assets.precompile += %w( jquery.steps.css )
4 10 \ No newline at end of file
... ...
config/locales/pt-BR.yml
... ... @@ -299,6 +299,10 @@ pt-BR:
299 299 one: 'Usuário'
300 300 other: 'Usuários'
301 301  
  302 + attributes:
  303 + v_libras/request:
  304 + service_type: 'Tipo de serviço'
  305 +
302 306 errors:
303 307 template:
304 308 header:
... ...
lib/api_client/client.rb
... ... @@ -4,37 +4,49 @@ module ApiClient::Client
4 4 include HTTMultiParty
5 5 default_timeout 10 * 60
6 6  
7   - def self.submit(request, files)
8   - o = { query: request.params.clone }
9   - o[:query].merge!({ :servico => request.service_type })
10   - o[:query].merge!({ :callback => "http://150.165.205.166:3000/v_libras/requests/callback?request_id=#{request.id}" })
11   -
12   - o[:query].merge!({ :video => files[:video].file.to_file })
13   -
14   - unless files[:subtitle].file.nil?
15   - o[:query].merge!({ :legenda => files[:subtitle].file.to_file })
16   - o[:query].merge!({ :linguagem => 'portugues' })
17   - end
18 7  
19   - Delayed::Worker.logger.debug "[VLibras::Request] Options: #{o}"
20   -
21   - response = self.post(ApiClient::API_URL, o)
  8 + def self.submit(request, files)
  9 + options = process_params(request, files)
22 10  
  11 + Delayed::Worker.logger.debug "[VLibras::Request] Options: #{options}"
  12 + response = self.post(ApiClient::API_URL, options)
23 13 Delayed::Worker.logger.debug "[VLibras::Request] Status #{response.response.code}"
24 14  
25 15 if response.response.code == '200'
26   -
  16 + # Response is processed by callback
27 17 else
28   - request.update!(:response => response.body, :status => 'error')
  18 + request.update!(:status => 'error', :response => response.body)
29 19 end
  20 +
30 21 rescue => e
31   - request.update!(:status => 'error', :response => e)
  22 + request.update!(:status => 'error', :response => e.to_s)
32 23 ensure
33 24 # FIXME: Running on another thread. Websocket not working :(
34 25 Delayed::Worker.logger.debug "[VLibras::Request] Sending message to websocket channel"
35 26 WebsocketRails[:requests_update].trigger(:update, {a: :b, c: :d})
36 27 end
37 28  
  29 +
  30 + #
  31 + # Process the params from the request AR object
  32 + # and return options to make the post request
  33 + #
  34 + def self.process_params(request, files)
  35 + options = { query: request.params.clone }
  36 + options[:query].merge!(:servico => request.service_type)
  37 + options[:query].merge!(:callback => "http://150.165.205.197:3000/v_libras/requests/callback?request_id=#{request.id}")
  38 +
  39 + options[:query].merge!(:video => files[:video].file.to_file)
  40 +
  41 + unless files[:subtitle].nil?
  42 + options[:query].merge!(:legenda => files[:subtitle].file.to_file)
  43 + options[:query].merge!(:linguagem => 'portugues')
  44 + end
  45 +
  46 + return options
  47 + end
  48 +
  49 +
38 50 private
39 51 def self.url_with_service(service)
40 52 URI.encode("#{ApiClient::API_URL}?servico=#{service}")
... ...
vendor/assets/javascripts/jquery.steps.js 0 → 100644
... ... @@ -0,0 +1,2015 @@
  1 +/*!
  2 + * jQuery Steps v1.0.7 - 05/07/2014
  3 + * Copyright (c) 2014 Rafael Staib (http://www.jquery-steps.com)
  4 + * Licensed under MIT http://www.opensource.org/licenses/MIT
  5 + */
  6 +;(function ($, undefined)
  7 +{
  8 +$.fn.extend({
  9 + _aria: function (name, value)
  10 + {
  11 + return this.attr("aria-" + name, value);
  12 + },
  13 +
  14 + _removeAria: function (name)
  15 + {
  16 + return this.removeAttr("aria-" + name);
  17 + },
  18 +
  19 + _enableAria: function (enable)
  20 + {
  21 + return (enable == null || enable) ?
  22 + this.removeClass("disabled")._aria("disabled", "false") :
  23 + this.addClass("disabled")._aria("disabled", "true");
  24 + },
  25 +
  26 + _showAria: function (show)
  27 + {
  28 + return (show == null || show) ?
  29 + this.show()._aria("hidden", "false") :
  30 + this.hide()._aria("hidden", "true");
  31 + },
  32 +
  33 + _selectAria: function (select)
  34 + {
  35 + return (select == null || select) ?
  36 + this.addClass("current")._aria("selected", "true") :
  37 + this.removeClass("current")._aria("selected", "false");
  38 + },
  39 +
  40 + _id: function (id)
  41 + {
  42 + return (id) ? this.attr("id", id) : this.attr("id");
  43 + }
  44 +});
  45 +
  46 +if (!String.prototype.format)
  47 +{
  48 + String.prototype.format = function()
  49 + {
  50 + var args = (arguments.length === 1 && $.isArray(arguments[0])) ? arguments[0] : arguments;
  51 + var formattedString = this;
  52 + for (var i = 0; i < args.length; i++)
  53 + {
  54 + var pattern = new RegExp("\\{" + i + "\\}", "gm");
  55 + formattedString = formattedString.replace(pattern, args[i]);
  56 + }
  57 + return formattedString;
  58 + };
  59 +}
  60 +
  61 +/**
  62 + * A global unique id count.
  63 + *
  64 + * @static
  65 + * @private
  66 + * @property _uniqueId
  67 + * @type Integer
  68 + **/
  69 +var _uniqueId = 0;
  70 +
  71 +/**
  72 + * The plugin prefix for cookies.
  73 + *
  74 + * @final
  75 + * @private
  76 + * @property _cookiePrefix
  77 + * @type String
  78 + **/
  79 +var _cookiePrefix = "jQu3ry_5teps_St@te_";
  80 +
  81 +/**
  82 + * Suffix for the unique tab id.
  83 + *
  84 + * @final
  85 + * @private
  86 + * @property _tabSuffix
  87 + * @type String
  88 + * @since 0.9.7
  89 + **/
  90 +var _tabSuffix = "-t-";
  91 +
  92 +/**
  93 + * Suffix for the unique tabpanel id.
  94 + *
  95 + * @final
  96 + * @private
  97 + * @property _tabpanelSuffix
  98 + * @type String
  99 + * @since 0.9.7
  100 + **/
  101 +var _tabpanelSuffix = "-p-";
  102 +
  103 +/**
  104 + * Suffix for the unique title id.
  105 + *
  106 + * @final
  107 + * @private
  108 + * @property _titleSuffix
  109 + * @type String
  110 + * @since 0.9.7
  111 + **/
  112 +var _titleSuffix = "-h-";
  113 +
  114 +/**
  115 + * An error message for an "index out of range" error.
  116 + *
  117 + * @final
  118 + * @private
  119 + * @property _indexOutOfRangeErrorMessage
  120 + * @type String
  121 + **/
  122 +var _indexOutOfRangeErrorMessage = "Index out of range.";
  123 +
  124 +/**
  125 + * An error message for an "missing corresponding element" error.
  126 + *
  127 + * @final
  128 + * @private
  129 + * @property _missingCorrespondingElementErrorMessage
  130 + * @type String
  131 + **/
  132 +var _missingCorrespondingElementErrorMessage = "One or more corresponding step {0} are missing.";
  133 +
  134 +/**
  135 + * Adds a step to the cache.
  136 + *
  137 + * @static
  138 + * @private
  139 + * @method addStepToCache
  140 + * @param wizard {Object} A jQuery wizard object
  141 + * @param step {Object} The step object to add
  142 + **/
  143 +function addStepToCache(wizard, step)
  144 +{
  145 + getSteps(wizard).push(step);
  146 +}
  147 +
  148 +function analyzeData(wizard, options, state)
  149 +{
  150 + var stepTitles = wizard.children(options.headerTag),
  151 + stepContents = wizard.children(options.bodyTag);
  152 +
  153 + // Validate content
  154 + if (stepTitles.length > stepContents.length)
  155 + {
  156 + throwError(_missingCorrespondingElementErrorMessage, "contents");
  157 + }
  158 + else if (stepTitles.length < stepContents.length)
  159 + {
  160 + throwError(_missingCorrespondingElementErrorMessage, "titles");
  161 + }
  162 +
  163 + var startIndex = options.startIndex;
  164 +
  165 + state.stepCount = stepTitles.length;
  166 +
  167 + // Tries to load the saved state (step position)
  168 + if (options.saveState && $.cookie)
  169 + {
  170 + var savedState = $.cookie(_cookiePrefix + getUniqueId(wizard));
  171 + // Sets the saved position to the start index if not undefined or out of range
  172 + var savedIndex = parseInt(savedState, 0);
  173 + if (!isNaN(savedIndex) && savedIndex < state.stepCount)
  174 + {
  175 + startIndex = savedIndex;
  176 + }
  177 + }
  178 +
  179 + state.currentIndex = startIndex;
  180 +
  181 + stepTitles.each(function (index)
  182 + {
  183 + var item = $(this), // item == header
  184 + content = stepContents.eq(index),
  185 + modeData = content.data("mode"),
  186 + mode = (modeData == null) ? contentMode.html : getValidEnumValue(contentMode,
  187 + (/^\s*$/.test(modeData) || isNaN(modeData)) ? modeData : parseInt(modeData, 0)),
  188 + contentUrl = (mode === contentMode.html || content.data("url") === undefined) ?
  189 + "" : content.data("url"),
  190 + contentLoaded = (mode !== contentMode.html && content.data("loaded") === "1"),
  191 + step = $.extend({}, stepModel, {
  192 + title: item.html(),
  193 + content: (mode === contentMode.html) ? content.html() : "",
  194 + contentUrl: contentUrl,
  195 + contentMode: mode,
  196 + contentLoaded: contentLoaded
  197 + });
  198 +
  199 + addStepToCache(wizard, step);
  200 + });
  201 +}
  202 +
  203 +/**
  204 + * Triggers the onCanceled event.
  205 + *
  206 + * @static
  207 + * @private
  208 + * @method cancel
  209 + * @param wizard {Object} The jQuery wizard object
  210 + **/
  211 +function cancel(wizard)
  212 +{
  213 + wizard.triggerHandler("canceled");
  214 +}
  215 +
  216 +function decreaseCurrentIndexBy(state, decreaseBy)
  217 +{
  218 + return state.currentIndex - decreaseBy;
  219 +}
  220 +
  221 +/**
  222 + * Removes the control functionality completely and transforms the current state to the initial HTML structure.
  223 + *
  224 + * @static
  225 + * @private
  226 + * @method destroy
  227 + * @param wizard {Object} A jQuery wizard object
  228 + **/
  229 +function destroy(wizard, options)
  230 +{
  231 + var eventNamespace = getEventNamespace(wizard);
  232 +
  233 + // Remove virtual data objects from the wizard
  234 + wizard.unbind(eventNamespace).removeData("uid").removeData("options")
  235 + .removeData("state").removeData("steps").removeData("eventNamespace")
  236 + .find(".actions a").unbind(eventNamespace);
  237 +
  238 + // Remove attributes and CSS classes from the wizard
  239 + wizard.removeClass(options.clearFixCssClass + " vertical");
  240 +
  241 + var contents = wizard.find(".content > *");
  242 +
  243 + // Remove virtual data objects from panels and their titles
  244 + contents.removeData("loaded").removeData("mode").removeData("url");
  245 +
  246 + // Remove attributes, CSS classes and reset inline styles on all panels and their titles
  247 + contents.removeAttr("id").removeAttr("role").removeAttr("tabindex")
  248 + .removeAttr("class").removeAttr("style")._removeAria("labelledby")
  249 + ._removeAria("hidden");
  250 +
  251 + // Empty panels if the mode is set to 'async' or 'iframe'
  252 + wizard.find(".content > [data-mode='async'],.content > [data-mode='iframe']").empty();
  253 +
  254 + var wizardSubstitute = $("<{0} class=\"{1}\"></{0}>".format(wizard.get(0).tagName, wizard.attr("class")));
  255 +
  256 + var wizardId = wizard._id();
  257 + if (wizardId != null && wizardId !== "")
  258 + {
  259 + wizardSubstitute._id(wizardId);
  260 + }
  261 +
  262 + wizardSubstitute.html(wizard.find(".content").html());
  263 + wizard.after(wizardSubstitute);
  264 + wizard.remove();
  265 +
  266 + return wizardSubstitute;
  267 +}
  268 +
  269 +/**
  270 + * Triggers the onFinishing and onFinished event.
  271 + *
  272 + * @static
  273 + * @private
  274 + * @method finishStep
  275 + * @param wizard {Object} The jQuery wizard object
  276 + * @param state {Object} The state container of the current wizard
  277 + **/
  278 +function finishStep(wizard, state)
  279 +{
  280 + var currentStep = wizard.find(".steps li").eq(state.currentIndex);
  281 +
  282 + if (wizard.triggerHandler("finishing", [state.currentIndex]))
  283 + {
  284 + currentStep.addClass("done").removeClass("error");
  285 + wizard.triggerHandler("finished", [state.currentIndex]);
  286 + }
  287 + else
  288 + {
  289 + currentStep.addClass("error");
  290 + }
  291 +}
  292 +
  293 +/**
  294 + * Gets or creates if not exist an unique event namespace for the given wizard instance.
  295 + *
  296 + * @static
  297 + * @private
  298 + * @method getEventNamespace
  299 + * @param wizard {Object} A jQuery wizard object
  300 + * @return {String} Returns the unique event namespace for the given wizard
  301 + */
  302 +function getEventNamespace(wizard)
  303 +{
  304 + var eventNamespace = wizard.data("eventNamespace");
  305 +
  306 + if (eventNamespace == null)
  307 + {
  308 + eventNamespace = "." + getUniqueId(wizard);
  309 + wizard.data("eventNamespace", eventNamespace);
  310 + }
  311 +
  312 + return eventNamespace;
  313 +}
  314 +
  315 +function getStepAnchor(wizard, index)
  316 +{
  317 + var uniqueId = getUniqueId(wizard);
  318 +
  319 + return wizard.find("#" + uniqueId + _tabSuffix + index);
  320 +}
  321 +
  322 +function getStepPanel(wizard, index)
  323 +{
  324 + var uniqueId = getUniqueId(wizard);
  325 +
  326 + return wizard.find("#" + uniqueId + _tabpanelSuffix + index);
  327 +}
  328 +
  329 +function getStepTitle(wizard, index)
  330 +{
  331 + var uniqueId = getUniqueId(wizard);
  332 +
  333 + return wizard.find("#" + uniqueId + _titleSuffix + index);
  334 +}
  335 +
  336 +function getOptions(wizard)
  337 +{
  338 + return wizard.data("options");
  339 +}
  340 +
  341 +function getState(wizard)
  342 +{
  343 + return wizard.data("state");
  344 +}
  345 +
  346 +function getSteps(wizard)
  347 +{
  348 + return wizard.data("steps");
  349 +}
  350 +
  351 +/**
  352 + * Gets a specific step object by index.
  353 + *
  354 + * @static
  355 + * @private
  356 + * @method getStep
  357 + * @param index {Integer} An integer that belongs to the position of a step
  358 + * @return {Object} A specific step object
  359 + **/
  360 +function getStep(wizard, index)
  361 +{
  362 + var steps = getSteps(wizard);
  363 +
  364 + if (index < 0 || index >= steps.length)
  365 + {
  366 + throwError(_indexOutOfRangeErrorMessage);
  367 + }
  368 +
  369 + return steps[index];
  370 +}
  371 +
  372 +/**
  373 + * Gets or creates if not exist an unique id from the given wizard instance.
  374 + *
  375 + * @static
  376 + * @private
  377 + * @method getUniqueId
  378 + * @param wizard {Object} A jQuery wizard object
  379 + * @return {String} Returns the unique id for the given wizard
  380 + */
  381 +function getUniqueId(wizard)
  382 +{
  383 + var uniqueId = wizard.data("uid");
  384 +
  385 + if (uniqueId == null)
  386 + {
  387 + uniqueId = wizard._id();
  388 + if (uniqueId == null)
  389 + {
  390 + uniqueId = "steps-uid-".concat(_uniqueId);
  391 + wizard._id(uniqueId);
  392 + }
  393 +
  394 + _uniqueId++;
  395 + wizard.data("uid", uniqueId);
  396 + }
  397 +
  398 + return uniqueId;
  399 +}
  400 +
  401 +/**
  402 + * Gets a valid enum value by checking a specific enum key or value.
  403 + *
  404 + * @static
  405 + * @private
  406 + * @method getValidEnumValue
  407 + * @param enumType {Object} Type of enum
  408 + * @param keyOrValue {Object} Key as `String` or value as `Integer` to check for
  409 + */
  410 +function getValidEnumValue(enumType, keyOrValue)
  411 +{
  412 + validateArgument("enumType", enumType);
  413 + validateArgument("keyOrValue", keyOrValue);
  414 +
  415 + // Is key
  416 + if (typeof keyOrValue === "string")
  417 + {
  418 + var value = enumType[keyOrValue];
  419 + if (value === undefined)
  420 + {
  421 + throwError("The enum key '{0}' does not exist.", keyOrValue);
  422 + }
  423 +
  424 + return value;
  425 + }
  426 + // Is value
  427 + else if (typeof keyOrValue === "number")
  428 + {
  429 + for (var key in enumType)
  430 + {
  431 + if (enumType[key] === keyOrValue)
  432 + {
  433 + return keyOrValue;
  434 + }
  435 + }
  436 +
  437 + throwError("Invalid enum value '{0}'.", keyOrValue);
  438 + }
  439 + // Type is not supported
  440 + else
  441 + {
  442 + throwError("Invalid key or value type.");
  443 + }
  444 +}
  445 +
  446 +/**
  447 + * Routes to the next step.
  448 + *
  449 + * @static
  450 + * @private
  451 + * @method goToNextStep
  452 + * @param wizard {Object} The jQuery wizard object
  453 + * @param options {Object} Settings of the current wizard
  454 + * @param state {Object} The state container of the current wizard
  455 + * @return {Boolean} Indicates whether the action executed
  456 + **/
  457 +function goToNextStep(wizard, options, state)
  458 +{
  459 + return paginationClick(wizard, options, state, increaseCurrentIndexBy(state, 1));
  460 +}
  461 +
  462 +/**
  463 + * Routes to the previous step.
  464 + *
  465 + * @static
  466 + * @private
  467 + * @method goToPreviousStep
  468 + * @param wizard {Object} The jQuery wizard object
  469 + * @param options {Object} Settings of the current wizard
  470 + * @param state {Object} The state container of the current wizard
  471 + * @return {Boolean} Indicates whether the action executed
  472 + **/
  473 +function goToPreviousStep(wizard, options, state)
  474 +{
  475 + return paginationClick(wizard, options, state, decreaseCurrentIndexBy(state, 1));
  476 +}
  477 +
  478 +/**
  479 + * Routes to a specific step by a given index.
  480 + *
  481 + * @static
  482 + * @private
  483 + * @method goToStep
  484 + * @param wizard {Object} The jQuery wizard object
  485 + * @param options {Object} Settings of the current wizard
  486 + * @param state {Object} The state container of the current wizard
  487 + * @param index {Integer} The position (zero-based) to route to
  488 + * @return {Boolean} Indicates whether the action succeeded or failed
  489 + **/
  490 +function goToStep(wizard, options, state, index)
  491 +{
  492 + if (index < 0 || index >= state.stepCount)
  493 + {
  494 + throwError(_indexOutOfRangeErrorMessage);
  495 + }
  496 +
  497 + if (options.forceMoveForward && index < state.currentIndex)
  498 + {
  499 + return;
  500 + }
  501 +
  502 + var oldIndex = state.currentIndex;
  503 + if (wizard.triggerHandler("stepChanging", [state.currentIndex, index]))
  504 + {
  505 + // Save new state
  506 + state.currentIndex = index;
  507 + saveCurrentStateToCookie(wizard, options, state);
  508 +
  509 + // Change visualisation
  510 + refreshStepNavigation(wizard, options, state, oldIndex);
  511 + refreshPagination(wizard, options, state);
  512 + loadAsyncContent(wizard, options, state);
  513 + startTransitionEffect(wizard, options, state, index, oldIndex);
  514 +
  515 + wizard.triggerHandler("stepChanged", [index, oldIndex]);
  516 + }
  517 + else
  518 + {
  519 + wizard.find(".steps li").eq(oldIndex).addClass("error");
  520 + }
  521 +
  522 + return true;
  523 +}
  524 +
  525 +function increaseCurrentIndexBy(state, increaseBy)
  526 +{
  527 + return state.currentIndex + increaseBy;
  528 +}
  529 +
  530 +/**
  531 + * Initializes the component.
  532 + *
  533 + * @static
  534 + * @private
  535 + * @method initialize
  536 + * @param options {Object} The component settings
  537 + **/
  538 +function initialize(options)
  539 +{
  540 + /*jshint -W040 */
  541 + var opts = $.extend(true, {}, defaults, options);
  542 +
  543 + return this.each(function ()
  544 + {
  545 + var wizard = $(this);
  546 + var state = {
  547 + currentIndex: opts.startIndex,
  548 + currentStep: null,
  549 + stepCount: 0,
  550 + transitionElement: null
  551 + };
  552 +
  553 + // Create data container
  554 + wizard.data("options", opts);
  555 + wizard.data("state", state);
  556 + wizard.data("steps", []);
  557 +
  558 + analyzeData(wizard, opts, state);
  559 + render(wizard, opts, state);
  560 + registerEvents(wizard, opts);
  561 +
  562 + // Trigger focus
  563 + if (opts.autoFocus && _uniqueId === 0)
  564 + {
  565 + getStepAnchor(wizard, opts.startIndex).focus();
  566 + }
  567 + });
  568 +}
  569 +
  570 +/**
  571 + * Inserts a new step to a specific position.
  572 + *
  573 + * @static
  574 + * @private
  575 + * @method insertStep
  576 + * @param wizard {Object} The jQuery wizard object
  577 + * @param options {Object} Settings of the current wizard
  578 + * @param state {Object} The state container of the current wizard
  579 + * @param index {Integer} The position (zero-based) to add
  580 + * @param step {Object} The step object to add
  581 + * @example
  582 + * $("#wizard").steps().insert(0, {
  583 + * title: "Title",
  584 + * content: "", // optional
  585 + * contentMode: "async", // optional
  586 + * contentUrl: "/Content/Step/1" // optional
  587 + * });
  588 + * @chainable
  589 + **/
  590 +function insertStep(wizard, options, state, index, step)
  591 +{
  592 + if (index < 0 || index > state.stepCount)
  593 + {
  594 + throwError(_indexOutOfRangeErrorMessage);
  595 + }
  596 +
  597 + // TODO: Validate step object
  598 +
  599 + // Change data
  600 + step = $.extend({}, stepModel, step);
  601 + insertStepToCache(wizard, index, step);
  602 + if (state.currentIndex !== state.stepCount && state.currentIndex >= index)
  603 + {
  604 + state.currentIndex++;
  605 + saveCurrentStateToCookie(wizard, options, state);
  606 + }
  607 + state.stepCount++;
  608 +
  609 + var contentContainer = wizard.find(".content"),
  610 + header = $("<{0}>{1}</{0}>".format(options.headerTag, step.title)),
  611 + body = $("<{0}></{0}>".format(options.bodyTag));
  612 +
  613 + if (step.contentMode == null || step.contentMode === contentMode.html)
  614 + {
  615 + body.html(step.content);
  616 + }
  617 +
  618 + if (index === 0)
  619 + {
  620 + contentContainer.prepend(body).prepend(header);
  621 + }
  622 + else
  623 + {
  624 + getStepPanel(wizard, (index - 1)).after(body).after(header);
  625 + }
  626 +
  627 + renderBody(wizard, state, body, index);
  628 + renderTitle(wizard, options, state, header, index);
  629 + refreshSteps(wizard, options, state, index);
  630 + if (index === state.currentIndex)
  631 + {
  632 + refreshStepNavigation(wizard, options, state);
  633 + }
  634 + refreshPagination(wizard, options, state);
  635 +
  636 + return wizard;
  637 +}
  638 +
  639 +/**
  640 + * Inserts a step object to the cache at a specific position.
  641 + *
  642 + * @static
  643 + * @private
  644 + * @method insertStepToCache
  645 + * @param wizard {Object} A jQuery wizard object
  646 + * @param index {Integer} The position (zero-based) to add
  647 + * @param step {Object} The step object to add
  648 + **/
  649 +function insertStepToCache(wizard, index, step)
  650 +{
  651 + getSteps(wizard).splice(index, 0, step);
  652 +}
  653 +
  654 +/**
  655 + * Handles the keyup DOM event for pagination.
  656 + *
  657 + * @static
  658 + * @private
  659 + * @event keyup
  660 + * @param event {Object} An event object
  661 + */
  662 +function keyUpHandler(event)
  663 +{
  664 + var wizard = $(this),
  665 + options = getOptions(wizard),
  666 + state = getState(wizard);
  667 +
  668 + if (options.suppressPaginationOnFocus && wizard.find(":focus").is(":input"))
  669 + {
  670 + event.preventDefault();
  671 + return false;
  672 + }
  673 +
  674 + var keyCodes = { left: 37, right: 39 };
  675 + if (event.keyCode === keyCodes.left)
  676 + {
  677 + event.preventDefault();
  678 + goToPreviousStep(wizard, options, state);
  679 + }
  680 + else if (event.keyCode === keyCodes.right)
  681 + {
  682 + event.preventDefault();
  683 + goToNextStep(wizard, options, state);
  684 + }
  685 +}
  686 +
  687 +/**
  688 + * Loads and includes async content.
  689 + *
  690 + * @static
  691 + * @private
  692 + * @method loadAsyncContent
  693 + * @param wizard {Object} A jQuery wizard object
  694 + * @param options {Object} Settings of the current wizard
  695 + * @param state {Object} The state container of the current wizard
  696 + */
  697 +function loadAsyncContent(wizard, options, state)
  698 +{
  699 + if (state.stepCount > 0)
  700 + {
  701 + var currentStep = getStep(wizard, state.currentIndex);
  702 +
  703 + if (!options.enableContentCache || !currentStep.contentLoaded)
  704 + {
  705 + switch (getValidEnumValue(contentMode, currentStep.contentMode))
  706 + {
  707 + case contentMode.iframe:
  708 + wizard.find(".content > .body").eq(state.currentIndex).empty()
  709 + .html("<iframe src=\"" + currentStep.contentUrl + "\" frameborder=\"0\" scrolling=\"no\" />")
  710 + .data("loaded", "1");
  711 + break;
  712 +
  713 + case contentMode.async:
  714 + var currentStepContent = getStepPanel(wizard, state.currentIndex)._aria("busy", "true")
  715 + .empty().append(renderTemplate(options.loadingTemplate, { text: options.labels.loading }));
  716 +
  717 + $.ajax({ url: currentStep.contentUrl, cache: false }).done(function (data)
  718 + {
  719 + currentStepContent.empty().html(data)._aria("busy", "false").data("loaded", "1");
  720 + });
  721 + break;
  722 + }
  723 + }
  724 + }
  725 +}
  726 +
  727 +/**
  728 + * Fires the action next or previous click event.
  729 + *
  730 + * @static
  731 + * @private
  732 + * @method paginationClick
  733 + * @param wizard {Object} The jQuery wizard object
  734 + * @param options {Object} Settings of the current wizard
  735 + * @param state {Object} The state container of the current wizard
  736 + * @param index {Integer} The position (zero-based) to route to
  737 + * @return {Boolean} Indicates whether the event fired successfully or not
  738 + **/
  739 +function paginationClick(wizard, options, state, index)
  740 +{
  741 + var oldIndex = state.currentIndex;
  742 +
  743 + if (index >= 0 && index < state.stepCount && !(options.forceMoveForward && index < state.currentIndex))
  744 + {
  745 + var anchor = getStepAnchor(wizard, index),
  746 + parent = anchor.parent(),
  747 + isDisabled = parent.hasClass("disabled");
  748 +
  749 + // Enable the step to make the anchor clickable!
  750 + parent._enableAria();
  751 + anchor.click();
  752 +
  753 + // An error occured
  754 + if (oldIndex === state.currentIndex && isDisabled)
  755 + {
  756 + // Disable the step again if current index has not changed; prevents click action.
  757 + parent._enableAria(false);
  758 + return false;
  759 + }
  760 +
  761 + return true;
  762 + }
  763 +
  764 + return false;
  765 +}
  766 +
  767 +/**
  768 + * Fires when a pagination click happens.
  769 + *
  770 + * @static
  771 + * @private
  772 + * @event click
  773 + * @param event {Object} An event object
  774 + */
  775 +function paginationClickHandler(event)
  776 +{
  777 + event.preventDefault();
  778 +
  779 + var anchor = $(this),
  780 + wizard = anchor.parent().parent().parent().parent(),
  781 + options = getOptions(wizard),
  782 + state = getState(wizard),
  783 + href = anchor.attr("href");
  784 +
  785 + switch (href.substring(href.lastIndexOf("#") + 1))
  786 + {
  787 + case "cancel":
  788 + cancel(wizard);
  789 + break;
  790 +
  791 + case "finish":
  792 + finishStep(wizard, state);
  793 + break;
  794 +
  795 + case "next":
  796 + goToNextStep(wizard, options, state);
  797 + break;
  798 +
  799 + case "previous":
  800 + goToPreviousStep(wizard, options, state);
  801 + break;
  802 + }
  803 +}
  804 +
  805 +/**
  806 + * Refreshs the visualization state for the entire pagination.
  807 + *
  808 + * @static
  809 + * @private
  810 + * @method refreshPagination
  811 + * @param wizard {Object} A jQuery wizard object
  812 + * @param options {Object} Settings of the current wizard
  813 + * @param state {Object} The state container of the current wizard
  814 + */
  815 +function refreshPagination(wizard, options, state)
  816 +{
  817 + if (options.enablePagination)
  818 + {
  819 + var finish = wizard.find(".actions a[href$='#finish']").parent(),
  820 + next = wizard.find(".actions a[href$='#next']").parent();
  821 +
  822 + if (!options.forceMoveForward)
  823 + {
  824 + var previous = wizard.find(".actions a[href$='#previous']").parent();
  825 + previous._enableAria(state.currentIndex > 0);
  826 + }
  827 +
  828 + if (options.enableFinishButton && options.showFinishButtonAlways)
  829 + {
  830 + finish._enableAria(state.stepCount > 0);
  831 + next._enableAria(state.stepCount > 1 && state.stepCount > (state.currentIndex + 1));
  832 + }
  833 + else
  834 + {
  835 + finish._showAria(options.enableFinishButton && state.stepCount === (state.currentIndex + 1));
  836 + next._showAria(state.stepCount === 0 || state.stepCount > (state.currentIndex + 1)).
  837 + _enableAria(state.stepCount > (state.currentIndex + 1) || !options.enableFinishButton);
  838 + }
  839 + }
  840 +}
  841 +
  842 +/**
  843 + * Refreshs the visualization state for the step navigation (tabs).
  844 + *
  845 + * @static
  846 + * @private
  847 + * @method refreshStepNavigation
  848 + * @param wizard {Object} A jQuery wizard object
  849 + * @param options {Object} Settings of the current wizard
  850 + * @param state {Object} The state container of the current wizard
  851 + * @param [oldIndex] {Integer} The index of the prior step
  852 + */
  853 +function refreshStepNavigation(wizard, options, state, oldIndex)
  854 +{
  855 + var currentOrNewStepAnchor = getStepAnchor(wizard, state.currentIndex),
  856 + currentInfo = $("<span class=\"current-info audible\">" + options.labels.current + " </span>"),
  857 + stepTitles = wizard.find(".content > .title");
  858 +
  859 + if (oldIndex != null)
  860 + {
  861 + var oldStepAnchor = getStepAnchor(wizard, oldIndex);
  862 + oldStepAnchor.parent().addClass("done").removeClass("error")._selectAria(false);
  863 + stepTitles.eq(oldIndex).removeClass("current").next(".body").removeClass("current");
  864 + currentInfo = oldStepAnchor.find(".current-info");
  865 + currentOrNewStepAnchor.focus();
  866 + }
  867 +
  868 + currentOrNewStepAnchor.prepend(currentInfo).parent()._selectAria().removeClass("done")._enableAria();
  869 + stepTitles.eq(state.currentIndex).addClass("current").next(".body").addClass("current");
  870 +}
  871 +
  872 +/**
  873 + * Refreshes step buttons and their related titles beyond a certain position.
  874 + *
  875 + * @static
  876 + * @private
  877 + * @method refreshSteps
  878 + * @param wizard {Object} A jQuery wizard object
  879 + * @param options {Object} Settings of the current wizard
  880 + * @param state {Object} The state container of the current wizard
  881 + * @param index {Integer} The start point for refreshing ids
  882 + */
  883 +function refreshSteps(wizard, options, state, index)
  884 +{
  885 + var uniqueId = getUniqueId(wizard);
  886 +
  887 + for (var i = index; i < state.stepCount; i++)
  888 + {
  889 + var uniqueStepId = uniqueId + _tabSuffix + i,
  890 + uniqueBodyId = uniqueId + _tabpanelSuffix + i,
  891 + uniqueHeaderId = uniqueId + _titleSuffix + i,
  892 + title = wizard.find(".title").eq(i)._id(uniqueHeaderId);
  893 +
  894 + wizard.find(".steps a").eq(i)._id(uniqueStepId)
  895 + ._aria("controls", uniqueBodyId).attr("href", "#" + uniqueHeaderId)
  896 + .html(renderTemplate(options.titleTemplate, { index: i + 1, title: title.html() }));
  897 + wizard.find(".body").eq(i)._id(uniqueBodyId)
  898 + ._aria("labelledby", uniqueHeaderId);
  899 + }
  900 +}
  901 +
  902 +function registerEvents(wizard, options)
  903 +{
  904 + var eventNamespace = getEventNamespace(wizard);
  905 +
  906 + wizard.bind("canceled" + eventNamespace, options.onCanceled);
  907 + wizard.bind("finishing" + eventNamespace, options.onFinishing);
  908 + wizard.bind("finished" + eventNamespace, options.onFinished);
  909 + wizard.bind("stepChanging" + eventNamespace, options.onStepChanging);
  910 + wizard.bind("stepChanged" + eventNamespace, options.onStepChanged);
  911 +
  912 + if (options.enableKeyNavigation)
  913 + {
  914 + wizard.bind("keyup" + eventNamespace, keyUpHandler);
  915 + }
  916 +
  917 + wizard.find(".actions a").bind("click" + eventNamespace, paginationClickHandler);
  918 +}
  919 +
  920 +/**
  921 + * Removes a specific step by an given index.
  922 + *
  923 + * @static
  924 + * @private
  925 + * @method removeStep
  926 + * @param wizard {Object} A jQuery wizard object
  927 + * @param options {Object} Settings of the current wizard
  928 + * @param state {Object} The state container of the current wizard
  929 + * @param index {Integer} The position (zero-based) of the step to remove
  930 + * @return Indecates whether the item is removed.
  931 + **/
  932 +function removeStep(wizard, options, state, index)
  933 +{
  934 + // Index out of range and try deleting current item will return false.
  935 + if (index < 0 || index >= state.stepCount || state.currentIndex === index)
  936 + {
  937 + return false;
  938 + }
  939 +
  940 + // Change data
  941 + removeStepFromCache(wizard, index);
  942 + if (state.currentIndex > index)
  943 + {
  944 + state.currentIndex--;
  945 + saveCurrentStateToCookie(wizard, options, state);
  946 + }
  947 + state.stepCount--;
  948 +
  949 + getStepTitle(wizard, index).remove();
  950 + getStepPanel(wizard, index).remove();
  951 + getStepAnchor(wizard, index).parent().remove();
  952 +
  953 + // Set the "first" class to the new first step button
  954 + if (index === 0)
  955 + {
  956 + wizard.find(".steps li").first().addClass("first");
  957 + }
  958 +
  959 + // Set the "last" class to the new last step button
  960 + if (index === state.stepCount)
  961 + {
  962 + wizard.find(".steps li").eq(index).addClass("last");
  963 + }
  964 +
  965 + refreshSteps(wizard, options, state, index);
  966 + refreshPagination(wizard, options, state);
  967 +
  968 + return true;
  969 +}
  970 +
  971 +function removeStepFromCache(wizard, index)
  972 +{
  973 + getSteps(wizard).splice(index, 1);
  974 +}
  975 +
  976 +/**
  977 + * Transforms the base html structure to a more sensible html structure.
  978 + *
  979 + * @static
  980 + * @private
  981 + * @method render
  982 + * @param wizard {Object} A jQuery wizard object
  983 + * @param options {Object} Settings of the current wizard
  984 + * @param state {Object} The state container of the current wizard
  985 + **/
  986 +function render(wizard, options, state)
  987 +{
  988 + // Create a content wrapper and copy HTML from the intial wizard structure
  989 + var wrapperTemplate = "<{0} class=\"{1}\">{2}</{0}>",
  990 + orientation = getValidEnumValue(stepsOrientation, options.stepsOrientation),
  991 + verticalCssClass = (orientation === stepsOrientation.vertical) ? " vertical" : "",
  992 + contentWrapper = $(wrapperTemplate.format(options.contentContainerTag, "content " + options.clearFixCssClass, wizard.html())),
  993 + stepsWrapper = $(wrapperTemplate.format(options.stepsContainerTag, "steps " + options.clearFixCssClass, "<ul role=\"tablist\"></ul>")),
  994 + stepTitles = contentWrapper.children(options.headerTag),
  995 + stepContents = contentWrapper.children(options.bodyTag);
  996 +
  997 + // Transform the wizard wrapper and remove the inner HTML
  998 + wizard.attr("role", "application").empty().append(stepsWrapper).append(contentWrapper)
  999 + .addClass(options.cssClass + " " + options.clearFixCssClass + verticalCssClass);
  1000 +
  1001 + // Add WIA-ARIA support
  1002 + stepContents.each(function (index)
  1003 + {
  1004 + renderBody(wizard, state, $(this), index);
  1005 + });
  1006 +
  1007 + stepTitles.each(function (index)
  1008 + {
  1009 + renderTitle(wizard, options, state, $(this), index);
  1010 + });
  1011 +
  1012 + refreshStepNavigation(wizard, options, state);
  1013 + renderPagination(wizard, options, state);
  1014 +}
  1015 +
  1016 +/**
  1017 + * Transforms the body to a proper tabpanel.
  1018 + *
  1019 + * @static
  1020 + * @private
  1021 + * @method renderBody
  1022 + * @param wizard {Object} A jQuery wizard object
  1023 + * @param body {Object} A jQuery body object
  1024 + * @param index {Integer} The position of the body
  1025 + */
  1026 +function renderBody(wizard, state, body, index)
  1027 +{
  1028 + var uniqueId = getUniqueId(wizard),
  1029 + uniqueBodyId = uniqueId + _tabpanelSuffix + index,
  1030 + uniqueHeaderId = uniqueId + _titleSuffix + index;
  1031 +
  1032 + body._id(uniqueBodyId).attr("role", "tabpanel")._aria("labelledby", uniqueHeaderId)
  1033 + .addClass("body")._showAria(state.currentIndex === index);
  1034 +}
  1035 +
  1036 +/**
  1037 + * Renders a pagination if enabled.
  1038 + *
  1039 + * @static
  1040 + * @private
  1041 + * @method renderPagination
  1042 + * @param wizard {Object} A jQuery wizard object
  1043 + * @param options {Object} Settings of the current wizard
  1044 + * @param state {Object} The state container of the current wizard
  1045 + */
  1046 +function renderPagination(wizard, options, state)
  1047 +{
  1048 + if (options.enablePagination)
  1049 + {
  1050 + var pagination = "<{0} class=\"actions {1}\"><ul role=\"menu\" aria-label=\"{2}\">{3}</ul></{0}>",
  1051 + buttonTemplate = "<li><a href=\"#{0}\" role=\"menuitem\">{1}</a></li>",
  1052 + buttons = "";
  1053 +
  1054 + if (!options.forceMoveForward)
  1055 + {
  1056 + buttons += buttonTemplate.format("previous", options.labels.previous);
  1057 + }
  1058 +
  1059 + buttons += buttonTemplate.format("next", options.labels.next);
  1060 +
  1061 + if (options.enableFinishButton)
  1062 + {
  1063 + buttons += buttonTemplate.format("finish", options.labels.finish);
  1064 + }
  1065 +
  1066 + if (options.enableCancelButton)
  1067 + {
  1068 + buttons += buttonTemplate.format("cancel", options.labels.cancel);
  1069 + }
  1070 +
  1071 + wizard.append(pagination.format(options.actionContainerTag, options.clearFixCssClass,
  1072 + options.labels.pagination, buttons));
  1073 +
  1074 + refreshPagination(wizard, options, state);
  1075 + loadAsyncContent(wizard, options, state);
  1076 + }
  1077 +}
  1078 +
  1079 +/**
  1080 + * Renders a template and replaces all placeholder.
  1081 + *
  1082 + * @static
  1083 + * @private
  1084 + * @method renderTemplate
  1085 + * @param template {String} A template
  1086 + * @param substitutes {Object} A list of substitute
  1087 + * @return {String} The rendered template
  1088 + */
  1089 +function renderTemplate(template, substitutes)
  1090 +{
  1091 + var matches = template.match(/#([a-z]*)#/gi);
  1092 +
  1093 + for (var i = 0; i < matches.length; i++)
  1094 + {
  1095 + var match = matches[i],
  1096 + key = match.substring(1, match.length - 1);
  1097 +
  1098 + if (substitutes[key] === undefined)
  1099 + {
  1100 + throwError("The key '{0}' does not exist in the substitute collection!", key);
  1101 + }
  1102 +
  1103 + template = template.replace(match, substitutes[key]);
  1104 + }
  1105 +
  1106 + return template;
  1107 +}
  1108 +
  1109 +/**
  1110 + * Transforms the title to a step item button.
  1111 + *
  1112 + * @static
  1113 + * @private
  1114 + * @method renderTitle
  1115 + * @param wizard {Object} A jQuery wizard object
  1116 + * @param options {Object} Settings of the current wizard
  1117 + * @param state {Object} The state container of the current wizard
  1118 + * @param header {Object} A jQuery header object
  1119 + * @param index {Integer} The position of the header
  1120 + */
  1121 +function renderTitle(wizard, options, state, header, index)
  1122 +{
  1123 + var uniqueId = getUniqueId(wizard),
  1124 + uniqueStepId = uniqueId + _tabSuffix + index,
  1125 + uniqueBodyId = uniqueId + _tabpanelSuffix + index,
  1126 + uniqueHeaderId = uniqueId + _titleSuffix + index,
  1127 + stepCollection = wizard.find(".steps > ul"),
  1128 + title = renderTemplate(options.titleTemplate, {
  1129 + index: index + 1,
  1130 + title: header.html()
  1131 + }),
  1132 + stepItem = $("<li role=\"tab\"><a id=\"" + uniqueStepId + "\" href=\"#" + uniqueHeaderId +
  1133 + "\" aria-controls=\"" + uniqueBodyId + "\">" + title + "</a></li>");
  1134 +
  1135 + stepItem._enableAria(options.enableAllSteps || state.currentIndex > index);
  1136 +
  1137 + if (state.currentIndex > index)
  1138 + {
  1139 + stepItem.addClass("done");
  1140 + }
  1141 +
  1142 + header._id(uniqueHeaderId).attr("tabindex", "-1").addClass("title");
  1143 +
  1144 + if (index === 0)
  1145 + {
  1146 + stepCollection.prepend(stepItem);
  1147 + }
  1148 + else
  1149 + {
  1150 + stepCollection.find("li").eq(index - 1).after(stepItem);
  1151 + }
  1152 +
  1153 + // Set the "first" class to the new first step button
  1154 + if (index === 0)
  1155 + {
  1156 + stepCollection.find("li").removeClass("first").eq(index).addClass("first");
  1157 + }
  1158 +
  1159 + // Set the "last" class to the new last step button
  1160 + if (index === (state.stepCount - 1))
  1161 + {
  1162 + stepCollection.find("li").removeClass("last").eq(index).addClass("last");
  1163 + }
  1164 +
  1165 + // Register click event
  1166 + stepItem.children("a").bind("click" + getEventNamespace(wizard), stepClickHandler);
  1167 +}
  1168 +
  1169 +/**
  1170 + * Saves the current state to a cookie.
  1171 + *
  1172 + * @static
  1173 + * @private
  1174 + * @method saveCurrentStateToCookie
  1175 + * @param wizard {Object} A jQuery wizard object
  1176 + * @param options {Object} Settings of the current wizard
  1177 + * @param state {Object} The state container of the current wizard
  1178 + */
  1179 +function saveCurrentStateToCookie(wizard, options, state)
  1180 +{
  1181 + if (options.saveState && $.cookie)
  1182 + {
  1183 + $.cookie(_cookiePrefix + getUniqueId(wizard), state.currentIndex);
  1184 + }
  1185 +}
  1186 +
  1187 +function startTransitionEffect(wizard, options, state, index, oldIndex)
  1188 +{
  1189 + var stepContents = wizard.find(".content > .body"),
  1190 + effect = getValidEnumValue(transitionEffect, options.transitionEffect),
  1191 + effectSpeed = options.transitionEffectSpeed,
  1192 + newStep = stepContents.eq(index),
  1193 + currentStep = stepContents.eq(oldIndex);
  1194 +
  1195 + switch (effect)
  1196 + {
  1197 + case transitionEffect.fade:
  1198 + case transitionEffect.slide:
  1199 + var hide = (effect === transitionEffect.fade) ? "fadeOut" : "slideUp",
  1200 + show = (effect === transitionEffect.fade) ? "fadeIn" : "slideDown";
  1201 +
  1202 + state.transitionElement = newStep;
  1203 + currentStep[hide](effectSpeed, function ()
  1204 + {
  1205 + var wizard = $(this)._showAria(false).parent().parent(),
  1206 + state = getState(wizard);
  1207 +
  1208 + if (state.transitionElement)
  1209 + {
  1210 + state.transitionElement[show](effectSpeed, function ()
  1211 + {
  1212 + $(this)._showAria();
  1213 + });
  1214 + state.transitionElement = null;
  1215 + }
  1216 + }).promise();
  1217 + break;
  1218 +
  1219 + case transitionEffect.slideLeft:
  1220 + var outerWidth = currentStep.outerWidth(true),
  1221 + posFadeOut = (index > oldIndex) ? -(outerWidth) : outerWidth,
  1222 + posFadeIn = (index > oldIndex) ? outerWidth : -(outerWidth);
  1223 +
  1224 + currentStep.animate({ left: posFadeOut }, effectSpeed,
  1225 + function () { $(this)._showAria(false); }).promise();
  1226 + newStep.css("left", posFadeIn + "px")._showAria()
  1227 + .animate({ left: 0 }, effectSpeed).promise();
  1228 + break;
  1229 +
  1230 + default:
  1231 + currentStep._showAria(false);
  1232 + newStep._showAria();
  1233 + break;
  1234 + }
  1235 +}
  1236 +
  1237 +/**
  1238 + * Fires when a step click happens.
  1239 + *
  1240 + * @static
  1241 + * @private
  1242 + * @event click
  1243 + * @param event {Object} An event object
  1244 + */
  1245 +function stepClickHandler(event)
  1246 +{
  1247 + event.preventDefault();
  1248 +
  1249 + var anchor = $(this),
  1250 + wizard = anchor.parent().parent().parent().parent(),
  1251 + options = getOptions(wizard),
  1252 + state = getState(wizard),
  1253 + oldIndex = state.currentIndex;
  1254 +
  1255 + if (anchor.parent().is(":not(.disabled):not(.current)"))
  1256 + {
  1257 + var href = anchor.attr("href"),
  1258 + position = parseInt(href.substring(href.lastIndexOf("-") + 1), 0);
  1259 +
  1260 + goToStep(wizard, options, state, position);
  1261 + }
  1262 +
  1263 + // If nothing has changed
  1264 + if (oldIndex === state.currentIndex)
  1265 + {
  1266 + getStepAnchor(wizard, oldIndex).focus();
  1267 + return false;
  1268 + }
  1269 +}
  1270 +
  1271 +function throwError(message)
  1272 +{
  1273 + if (arguments.length > 1)
  1274 + {
  1275 + message = message.format(Array.prototype.slice.call(arguments, 1));
  1276 + }
  1277 +
  1278 + throw new Error(message);
  1279 +}
  1280 +
  1281 +/**
  1282 + * Checks an argument for null or undefined and throws an error if one check applies.
  1283 + *
  1284 + * @static
  1285 + * @private
  1286 + * @method validateArgument
  1287 + * @param argumentName {String} The name of the given argument
  1288 + * @param argumentValue {Object} The argument itself
  1289 + */
  1290 +function validateArgument(argumentName, argumentValue)
  1291 +{
  1292 + if (argumentValue == null)
  1293 + {
  1294 + throwError("The argument '{0}' is null or undefined.", argumentName);
  1295 + }
  1296 +}
  1297 +
  1298 +/**
  1299 + * Represents a jQuery wizard plugin.
  1300 + *
  1301 + * @class steps
  1302 + * @constructor
  1303 + * @param [method={}] The name of the method as `String` or an JSON object for initialization
  1304 + * @param [params=]* {Array} Additional arguments for a method call
  1305 + * @chainable
  1306 + **/
  1307 +$.fn.steps = function (method)
  1308 +{
  1309 + if ($.fn.steps[method])
  1310 + {
  1311 + return $.fn.steps[method].apply(this, Array.prototype.slice.call(arguments, 1));
  1312 + }
  1313 + else if (typeof method === "object" || !method)
  1314 + {
  1315 + return initialize.apply(this, arguments);
  1316 + }
  1317 + else
  1318 + {
  1319 + $.error("Method " + method + " does not exist on jQuery.steps");
  1320 + }
  1321 +};
  1322 +
  1323 +/**
  1324 + * Adds a new step.
  1325 + *
  1326 + * @method add
  1327 + * @param step {Object} The step object to add
  1328 + * @chainable
  1329 + **/
  1330 +$.fn.steps.add = function (step)
  1331 +{
  1332 + var state = getState(this);
  1333 + return insertStep(this, getOptions(this), state, state.stepCount, step);
  1334 +};
  1335 +
  1336 +/**
  1337 + * Removes the control functionality completely and transforms the current state to the initial HTML structure.
  1338 + *
  1339 + * @method destroy
  1340 + * @chainable
  1341 + **/
  1342 +$.fn.steps.destroy = function ()
  1343 +{
  1344 + return destroy(this, getOptions(this));
  1345 +};
  1346 +
  1347 +/**
  1348 + * Triggers the onFinishing and onFinished event.
  1349 + *
  1350 + * @method finish
  1351 + **/
  1352 +$.fn.steps.finish = function ()
  1353 +{
  1354 + finishStep(this, getState(this));
  1355 +};
  1356 +
  1357 +/**
  1358 + * Gets the current step index.
  1359 + *
  1360 + * @method getCurrentIndex
  1361 + * @return {Integer} The actual step index (zero-based)
  1362 + * @for steps
  1363 + **/
  1364 +$.fn.steps.getCurrentIndex = function ()
  1365 +{
  1366 + return getState(this).currentIndex;
  1367 +};
  1368 +
  1369 +/**
  1370 + * Gets the current step object.
  1371 + *
  1372 + * @method getCurrentStep
  1373 + * @return {Object} The actual step object
  1374 + **/
  1375 +$.fn.steps.getCurrentStep = function ()
  1376 +{
  1377 + return getStep(this, getState(this).currentIndex);
  1378 +};
  1379 +
  1380 +/**
  1381 + * Gets a specific step object by index.
  1382 + *
  1383 + * @method getStep
  1384 + * @param index {Integer} An integer that belongs to the position of a step
  1385 + * @return {Object} A specific step object
  1386 + **/
  1387 +$.fn.steps.getStep = function (index)
  1388 +{
  1389 + return getStep(this, index);
  1390 +};
  1391 +
  1392 +/**
  1393 + * Inserts a new step to a specific position.
  1394 + *
  1395 + * @method insert
  1396 + * @param index {Integer} The position (zero-based) to add
  1397 + * @param step {Object} The step object to add
  1398 + * @example
  1399 + * $("#wizard").steps().insert(0, {
  1400 + * title: "Title",
  1401 + * content: "", // optional
  1402 + * contentMode: "async", // optional
  1403 + * contentUrl: "/Content/Step/1" // optional
  1404 + * });
  1405 + * @chainable
  1406 + **/
  1407 +$.fn.steps.insert = function (index, step)
  1408 +{
  1409 + return insertStep(this, getOptions(this), getState(this), index, step);
  1410 +};
  1411 +
  1412 +/**
  1413 + * Routes to the next step.
  1414 + *
  1415 + * @method next
  1416 + * @return {Boolean} Indicates whether the action executed
  1417 + **/
  1418 +$.fn.steps.next = function ()
  1419 +{
  1420 + return goToNextStep(this, getOptions(this), getState(this));
  1421 +};
  1422 +
  1423 +/**
  1424 + * Routes to the previous step.
  1425 + *
  1426 + * @method previous
  1427 + * @return {Boolean} Indicates whether the action executed
  1428 + **/
  1429 +$.fn.steps.previous = function ()
  1430 +{
  1431 + return goToPreviousStep(this, getOptions(this), getState(this));
  1432 +};
  1433 +
  1434 +/**
  1435 + * Removes a specific step by an given index.
  1436 + *
  1437 + * @method remove
  1438 + * @param index {Integer} The position (zero-based) of the step to remove
  1439 + * @return Indecates whether the item is removed.
  1440 + **/
  1441 +$.fn.steps.remove = function (index)
  1442 +{
  1443 + return removeStep(this, getOptions(this), getState(this), index);
  1444 +};
  1445 +
  1446 +/**
  1447 + * Sets a specific step object by index.
  1448 + *
  1449 + * @method setStep
  1450 + * @param index {Integer} An integer that belongs to the position of a step
  1451 + * @param step {Object} The step object to change
  1452 + **/
  1453 +$.fn.steps.setStep = function (index, step)
  1454 +{
  1455 + throw new Error("Not yet implemented!");
  1456 +};
  1457 +
  1458 +/**
  1459 + * Skips an certain amount of steps.
  1460 + *
  1461 + * @method skip
  1462 + * @param count {Integer} The amount of steps that should be skipped
  1463 + * @return {Boolean} Indicates whether the action executed
  1464 + **/
  1465 +$.fn.steps.skip = function (count)
  1466 +{
  1467 + throw new Error("Not yet implemented!");
  1468 +};
  1469 +
  1470 +/**
  1471 + * An enum represents the different content types of a step and their loading mechanisms.
  1472 + *
  1473 + * @class contentMode
  1474 + * @for steps
  1475 + **/
  1476 +var contentMode = $.fn.steps.contentMode = {
  1477 + /**
  1478 + * HTML embedded content
  1479 + *
  1480 + * @readOnly
  1481 + * @property html
  1482 + * @type Integer
  1483 + * @for contentMode
  1484 + **/
  1485 + html: 0,
  1486 +
  1487 + /**
  1488 + * IFrame embedded content
  1489 + *
  1490 + * @readOnly
  1491 + * @property iframe
  1492 + * @type Integer
  1493 + * @for contentMode
  1494 + **/
  1495 + iframe: 1,
  1496 +
  1497 + /**
  1498 + * Async embedded content
  1499 + *
  1500 + * @readOnly
  1501 + * @property async
  1502 + * @type Integer
  1503 + * @for contentMode
  1504 + **/
  1505 + async: 2
  1506 +};
  1507 +
  1508 +/**
  1509 + * An enum represents the orientation of the steps navigation.
  1510 + *
  1511 + * @class stepsOrientation
  1512 + * @for steps
  1513 + **/
  1514 +var stepsOrientation = $.fn.steps.stepsOrientation = {
  1515 + /**
  1516 + * Horizontal orientation
  1517 + *
  1518 + * @readOnly
  1519 + * @property horizontal
  1520 + * @type Integer
  1521 + * @for stepsOrientation
  1522 + **/
  1523 + horizontal: 0,
  1524 +
  1525 + /**
  1526 + * Vertical orientation
  1527 + *
  1528 + * @readOnly
  1529 + * @property vertical
  1530 + * @type Integer
  1531 + * @for stepsOrientation
  1532 + **/
  1533 + vertical: 1
  1534 +};
  1535 +
  1536 +/**
  1537 + * An enum that represents the various transition animations.
  1538 + *
  1539 + * @class transitionEffect
  1540 + * @for steps
  1541 + **/
  1542 +var transitionEffect = $.fn.steps.transitionEffect = {
  1543 + /**
  1544 + * No transition animation
  1545 + *
  1546 + * @readOnly
  1547 + * @property none
  1548 + * @type Integer
  1549 + * @for transitionEffect
  1550 + **/
  1551 + none: 0,
  1552 +
  1553 + /**
  1554 + * Fade in transition
  1555 + *
  1556 + * @readOnly
  1557 + * @property fade
  1558 + * @type Integer
  1559 + * @for transitionEffect
  1560 + **/
  1561 + fade: 1,
  1562 +
  1563 + /**
  1564 + * Slide up transition
  1565 + *
  1566 + * @readOnly
  1567 + * @property slide
  1568 + * @type Integer
  1569 + * @for transitionEffect
  1570 + **/
  1571 + slide: 2,
  1572 +
  1573 + /**
  1574 + * Slide left transition
  1575 + *
  1576 + * @readOnly
  1577 + * @property slideLeft
  1578 + * @type Integer
  1579 + * @for transitionEffect
  1580 + **/
  1581 + slideLeft: 3
  1582 +};
  1583 +
  1584 +var stepModel = $.fn.steps.stepModel = {
  1585 + title: "",
  1586 + content: "",
  1587 + contentUrl: "",
  1588 + contentMode: contentMode.html,
  1589 + contentLoaded: false
  1590 +};
  1591 +
  1592 +/**
  1593 + * An object that represents the default settings.
  1594 + * There are two possibities to override the sub-properties.
  1595 + * Either by doing it generally (global) or on initialization.
  1596 + *
  1597 + * @static
  1598 + * @class defaults
  1599 + * @for steps
  1600 + * @example
  1601 + * // Global approach
  1602 + * $.steps.defaults.headerTag = "h3";
  1603 + * @example
  1604 + * // Initialization approach
  1605 + * $("#wizard").steps({ headerTag: "h3" });
  1606 + **/
  1607 +var defaults = $.fn.steps.defaults = {
  1608 + /**
  1609 + * The header tag is used to find the step button text within the declared wizard area.
  1610 + *
  1611 + * @property headerTag
  1612 + * @type String
  1613 + * @default "h1"
  1614 + * @for defaults
  1615 + **/
  1616 + headerTag: "h1",
  1617 +
  1618 + /**
  1619 + * The body tag is used to find the step content within the declared wizard area.
  1620 + *
  1621 + * @property bodyTag
  1622 + * @type String
  1623 + * @default "div"
  1624 + * @for defaults
  1625 + **/
  1626 + bodyTag: "div",
  1627 +
  1628 + /**
  1629 + * The content container tag which will be used to wrap all step contents.
  1630 + *
  1631 + * @property contentContainerTag
  1632 + * @type String
  1633 + * @default "div"
  1634 + * @for defaults
  1635 + **/
  1636 + contentContainerTag: "div",
  1637 +
  1638 + /**
  1639 + * The action container tag which will be used to wrap the pagination navigation.
  1640 + *
  1641 + * @property actionContainerTag
  1642 + * @type String
  1643 + * @default "div"
  1644 + * @for defaults
  1645 + **/
  1646 + actionContainerTag: "div",
  1647 +
  1648 + /**
  1649 + * The steps container tag which will be used to wrap the steps navigation.
  1650 + *
  1651 + * @property stepsContainerTag
  1652 + * @type String
  1653 + * @default "div"
  1654 + * @for defaults
  1655 + **/
  1656 + stepsContainerTag: "div",
  1657 +
  1658 + /**
  1659 + * The css class which will be added to the outer component wrapper.
  1660 + *
  1661 + * @property cssClass
  1662 + * @type String
  1663 + * @default "wizard"
  1664 + * @for defaults
  1665 + * @example
  1666 + * <div class="wizard">
  1667 + * ...
  1668 + * </div>
  1669 + **/
  1670 + cssClass: "wizard",
  1671 +
  1672 + /**
  1673 + * The css class which will be used for floating scenarios.
  1674 + *
  1675 + * @property clearFixCssClass
  1676 + * @type String
  1677 + * @default "clearfix"
  1678 + * @for defaults
  1679 + **/
  1680 + clearFixCssClass: "clearfix",
  1681 +
  1682 + /**
  1683 + * Determines whether the steps are vertically or horizontally oriented.
  1684 + *
  1685 + * @property stepsOrientation
  1686 + * @type stepsOrientation
  1687 + * @default horizontal
  1688 + * @for defaults
  1689 + * @since 1.0.0
  1690 + **/
  1691 + stepsOrientation: stepsOrientation.horizontal,
  1692 +
  1693 + /*
  1694 + * Tempplates
  1695 + */
  1696 +
  1697 + /**
  1698 + * The title template which will be used to create a step button.
  1699 + *
  1700 + * @property titleTemplate
  1701 + * @type String
  1702 + * @default "<span class=\"number\">#index#.</span> #title#"
  1703 + * @for defaults
  1704 + **/
  1705 + titleTemplate: "<span class=\"number\">#index#.</span> #title#",
  1706 +
  1707 + /**
  1708 + * The loading template which will be used to create the loading animation.
  1709 + *
  1710 + * @property loadingTemplate
  1711 + * @type String
  1712 + * @default "<span class=\"spinner\"></span> #text#"
  1713 + * @for defaults
  1714 + **/
  1715 + loadingTemplate: "<span class=\"spinner\"></span> #text#",
  1716 +
  1717 + /*
  1718 + * Behaviour
  1719 + */
  1720 +
  1721 + /**
  1722 + * Sets the focus to the first wizard instance in order to enable the key navigation from the begining if `true`.
  1723 + *
  1724 + * @property autoFocus
  1725 + * @type Boolean
  1726 + * @default false
  1727 + * @for defaults
  1728 + * @since 0.9.4
  1729 + **/
  1730 + autoFocus: false,
  1731 +
  1732 + /**
  1733 + * Enables all steps from the begining if `true` (all steps are clickable).
  1734 + *
  1735 + * @property enableAllSteps
  1736 + * @type Boolean
  1737 + * @default false
  1738 + * @for defaults
  1739 + **/
  1740 + enableAllSteps: false,
  1741 +
  1742 + /**
  1743 + * Enables keyboard navigation if `true` (arrow left and arrow right).
  1744 + *
  1745 + * @property enableKeyNavigation
  1746 + * @type Boolean
  1747 + * @default true
  1748 + * @for defaults
  1749 + **/
  1750 + enableKeyNavigation: true,
  1751 +
  1752 + /**
  1753 + * Enables pagination if `true`.
  1754 + *
  1755 + * @property enablePagination
  1756 + * @type Boolean
  1757 + * @default true
  1758 + * @for defaults
  1759 + **/
  1760 + enablePagination: true,
  1761 +
  1762 + /**
  1763 + * Suppresses pagination if a form field is focused.
  1764 + *
  1765 + * @property suppressPaginationOnFocus
  1766 + * @type Boolean
  1767 + * @default true
  1768 + * @for defaults
  1769 + **/
  1770 + suppressPaginationOnFocus: true,
  1771 +
  1772 + /**
  1773 + * Enables cache for async loaded or iframe embedded content.
  1774 + *
  1775 + * @property enableContentCache
  1776 + * @type Boolean
  1777 + * @default true
  1778 + * @for defaults
  1779 + **/
  1780 + enableContentCache: true,
  1781 +
  1782 + /**
  1783 + * Shows the cancel button if enabled.
  1784 + *
  1785 + * @property enableCancelButton
  1786 + * @type Boolean
  1787 + * @default false
  1788 + * @for defaults
  1789 + **/
  1790 + enableCancelButton: false,
  1791 +
  1792 + /**
  1793 + * Shows the finish button if enabled.
  1794 + *
  1795 + * @property enableFinishButton
  1796 + * @type Boolean
  1797 + * @default true
  1798 + * @for defaults
  1799 + **/
  1800 + enableFinishButton: true,
  1801 +
  1802 + /**
  1803 + * Not yet implemented.
  1804 + *
  1805 + * @property preloadContent
  1806 + * @type Boolean
  1807 + * @default false
  1808 + * @for defaults
  1809 + **/
  1810 + preloadContent: false,
  1811 +
  1812 + /**
  1813 + * Shows the finish button always (on each step; right beside the next button) if `true`.
  1814 + * Otherwise the next button will be replaced by the finish button if the last step becomes active.
  1815 + *
  1816 + * @property showFinishButtonAlways
  1817 + * @type Boolean
  1818 + * @default false
  1819 + * @for defaults
  1820 + **/
  1821 + showFinishButtonAlways: false,
  1822 +
  1823 + /**
  1824 + * Prevents jumping to a previous step.
  1825 + *
  1826 + * @property forceMoveForward
  1827 + * @type Boolean
  1828 + * @default false
  1829 + * @for defaults
  1830 + **/
  1831 + forceMoveForward: false,
  1832 +
  1833 + /**
  1834 + * Saves the current state (step position) to a cookie.
  1835 + * By coming next time the last active step becomes activated.
  1836 + *
  1837 + * @property saveState
  1838 + * @type Boolean
  1839 + * @default false
  1840 + * @for defaults
  1841 + **/
  1842 + saveState: false,
  1843 +
  1844 + /**
  1845 + * The position to start on (zero-based).
  1846 + *
  1847 + * @property startIndex
  1848 + * @type Integer
  1849 + * @default 0
  1850 + * @for defaults
  1851 + **/
  1852 + startIndex: 0,
  1853 +
  1854 + /*
  1855 + * Animation Effect Configuration
  1856 + */
  1857 +
  1858 + /**
  1859 + * The animation effect which will be used for step transitions.
  1860 + *
  1861 + * @property transitionEffect
  1862 + * @type transitionEffect
  1863 + * @default none
  1864 + * @for defaults
  1865 + **/
  1866 + transitionEffect: transitionEffect.none,
  1867 +
  1868 + /**
  1869 + * Animation speed for step transitions (in milliseconds).
  1870 + *
  1871 + * @property transitionEffectSpeed
  1872 + * @type Integer
  1873 + * @default 200
  1874 + * @for defaults
  1875 + **/
  1876 + transitionEffectSpeed: 200,
  1877 +
  1878 + /*
  1879 + * Events
  1880 + */
  1881 +
  1882 + /**
  1883 + * Fires before the step changes and can be used to prevent step changing by returning `false`.
  1884 + * Very useful for form validation.
  1885 + *
  1886 + * @property onStepChanging
  1887 + * @type Event
  1888 + * @default function (event, currentIndex, newIndex) { return true; }
  1889 + * @for defaults
  1890 + **/
  1891 + onStepChanging: function (event, currentIndex, newIndex) { return true; },
  1892 +
  1893 + /**
  1894 + * Fires after the step has change.
  1895 + *
  1896 + * @property onStepChanged
  1897 + * @type Event
  1898 + * @default function (event, currentIndex, priorIndex) { }
  1899 + * @for defaults
  1900 + **/
  1901 + onStepChanged: function (event, currentIndex, priorIndex) { },
  1902 +
  1903 + /**
  1904 + * Fires after cancelation.
  1905 + *
  1906 + * @property onCanceled
  1907 + * @type Event
  1908 + * @default function (event) { }
  1909 + * @for defaults
  1910 + **/
  1911 + onCanceled: function (event) { },
  1912 +
  1913 + /**
  1914 + * Fires before finishing and can be used to prevent completion by returning `false`.
  1915 + * Very useful for form validation.
  1916 + *
  1917 + * @property onFinishing
  1918 + * @type Event
  1919 + * @default function (event, currentIndex) { return true; }
  1920 + * @for defaults
  1921 + **/
  1922 + onFinishing: function (event, currentIndex) { return true; },
  1923 +
  1924 + /**
  1925 + * Fires after completion.
  1926 + *
  1927 + * @property onFinished
  1928 + * @type Event
  1929 + * @default function (event, currentIndex) { }
  1930 + * @for defaults
  1931 + **/
  1932 + onFinished: function (event, currentIndex) { },
  1933 +
  1934 + /**
  1935 + * Contains all labels.
  1936 + *
  1937 + * @property labels
  1938 + * @type Object
  1939 + * @for defaults
  1940 + **/
  1941 + labels: {
  1942 + /**
  1943 + * Label for the cancel button.
  1944 + *
  1945 + * @property cancel
  1946 + * @type String
  1947 + * @default "Cancel"
  1948 + * @for defaults
  1949 + **/
  1950 + cancel: "Cancel",
  1951 +
  1952 + /**
  1953 + * This label is important for accessability reasons.
  1954 + * Indicates which step is activated.
  1955 + *
  1956 + * @property current
  1957 + * @type String
  1958 + * @default "current step:"
  1959 + * @for defaults
  1960 + **/
  1961 + current: "current step:",
  1962 +
  1963 + /**
  1964 + * This label is important for accessability reasons and describes the kind of navigation.
  1965 + *
  1966 + * @property pagination
  1967 + * @type String
  1968 + * @default "Pagination"
  1969 + * @for defaults
  1970 + * @since 0.9.7
  1971 + **/
  1972 + pagination: "Pagination",
  1973 +
  1974 + /**
  1975 + * Label for the finish button.
  1976 + *
  1977 + * @property finish
  1978 + * @type String
  1979 + * @default "Finish"
  1980 + * @for defaults
  1981 + **/
  1982 + finish: "Finish",
  1983 +
  1984 + /**
  1985 + * Label for the next button.
  1986 + *
  1987 + * @property next
  1988 + * @type String
  1989 + * @default "Next"
  1990 + * @for defaults
  1991 + **/
  1992 + next: "Next",
  1993 +
  1994 + /**
  1995 + * Label for the previous button.
  1996 + *
  1997 + * @property previous
  1998 + * @type String
  1999 + * @default "Previous"
  2000 + * @for defaults
  2001 + **/
  2002 + previous: "Previous",
  2003 +
  2004 + /**
  2005 + * Label for the loading animation.
  2006 + *
  2007 + * @property loading
  2008 + * @type String
  2009 + * @default "Loading ..."
  2010 + * @for defaults
  2011 + **/
  2012 + loading: "Loading ..."
  2013 + }
  2014 +};
  2015 +})(jQuery);
0 2016 \ No newline at end of file
... ...
vendor/assets/stylesheets/jquery.steps.css 0 → 100644
... ... @@ -0,0 +1,382 @@
  1 +/*
  2 + Common
  3 +*/
  4 +
  5 +.wizard,
  6 +.tabcontrol
  7 +{
  8 + display: block;
  9 + width: 100%;
  10 + overflow: hidden;
  11 +}
  12 +
  13 +.wizard a,
  14 +.tabcontrol a
  15 +{
  16 + outline: 0;
  17 +}
  18 +
  19 +.wizard ul,
  20 +.tabcontrol ul
  21 +{
  22 + list-style: none !important;
  23 + padding: 0;
  24 + margin: 0;
  25 +}
  26 +
  27 +.wizard ul > li,
  28 +.tabcontrol ul > li
  29 +{
  30 + display: block;
  31 + padding: 0;
  32 +}
  33 +
  34 +/* Accessibility */
  35 +.wizard > .steps .current-info,
  36 +.tabcontrol > .steps .current-info
  37 +{
  38 + position: absolute;
  39 + left: -999em;
  40 +}
  41 +
  42 +.wizard > .content > .title,
  43 +.tabcontrol > .content > .title
  44 +{
  45 + position: absolute;
  46 + left: -999em;
  47 +}
  48 +
  49 +
  50 +
  51 +/*
  52 + Wizard
  53 +*/
  54 +
  55 +.wizard > .steps
  56 +{
  57 + position: relative;
  58 + display: block;
  59 + width: 100%;
  60 +}
  61 +
  62 +.wizard.vertical > .steps
  63 +{
  64 + display: inline;
  65 + float: left;
  66 + width: 30%;
  67 +}
  68 +
  69 +.wizard > .steps .number
  70 +{
  71 + font-size: 1.429em;
  72 +}
  73 +
  74 +.wizard > .steps > ul > li
  75 +{
  76 + width: 25%;
  77 +}
  78 +
  79 +.wizard > .steps > ul > li,
  80 +.wizard > .actions > ul > li
  81 +{
  82 + float: left;
  83 +}
  84 +
  85 +.wizard.vertical > .steps > ul > li
  86 +{
  87 + float: none;
  88 + width: 100%;
  89 +}
  90 +
  91 +.wizard > .steps a,
  92 +.wizard > .steps a:hover,
  93 +.wizard > .steps a:active
  94 +{
  95 + display: block;
  96 + width: auto;
  97 + margin: 0 0.5em 0.5em;
  98 + padding: 1em 1em;
  99 + text-decoration: none;
  100 +
  101 + -webkit-border-radius: 5px;
  102 + -moz-border-radius: 5px;
  103 + border-radius: 5px;
  104 +}
  105 +
  106 +.wizard > .steps .disabled a,
  107 +.wizard > .steps .disabled a:hover,
  108 +.wizard > .steps .disabled a:active
  109 +{
  110 + background: #eee;
  111 + color: #aaa;
  112 + cursor: default;
  113 +}
  114 +
  115 +.wizard > .steps .current a,
  116 +.wizard > .steps .current a:hover,
  117 +.wizard > .steps .current a:active
  118 +{
  119 + background: #2184be;
  120 + color: #fff;
  121 + cursor: default;
  122 +}
  123 +
  124 +.wizard > .steps .done a,
  125 +.wizard > .steps .done a:hover,
  126 +.wizard > .steps .done a:active
  127 +{
  128 + background: #9dc8e2;
  129 + color: #fff;
  130 +}
  131 +
  132 +.wizard > .steps .error a,
  133 +.wizard > .steps .error a:hover,
  134 +.wizard > .steps .error a:active
  135 +{
  136 + background: #ff3111;
  137 + color: #fff;
  138 +}
  139 +
  140 +.wizard > .content
  141 +{
  142 + background: #eee;
  143 + display: block;
  144 + margin: 0.5em;
  145 + min-height: 35em;
  146 + overflow: hidden;
  147 + position: relative;
  148 + width: auto;
  149 +
  150 + -webkit-border-radius: 5px;
  151 + -moz-border-radius: 5px;
  152 + border-radius: 5px;
  153 +}
  154 +
  155 +.wizard.vertical > .content
  156 +{
  157 + display: inline;
  158 + float: left;
  159 + margin: 0 2.5% 0.5em 2.5%;
  160 + width: 65%;
  161 +}
  162 +
  163 +.wizard > .content > .body
  164 +{
  165 + float: left;
  166 + position: absolute;
  167 + width: 95%;
  168 + height: 95%;
  169 + padding: 2.5%;
  170 +}
  171 +
  172 +.wizard > .content > .body ul
  173 +{
  174 + list-style: disc !important;
  175 +}
  176 +
  177 +.wizard > .content > .body ul > li
  178 +{
  179 + display: list-item;
  180 +}
  181 +
  182 +.wizard > .content > .body > iframe
  183 +{
  184 + border: 0 none;
  185 + width: 100%;
  186 + height: 100%;
  187 +}
  188 +
  189 +.wizard > .content > .body input
  190 +{
  191 + display: block;
  192 + border: 1px solid #ccc;
  193 +}
  194 +
  195 +.wizard > .content > .body input[type="checkbox"]
  196 +{
  197 + display: inline-block;
  198 +}
  199 +
  200 +.wizard > .content > .body input.error
  201 +{
  202 + background: rgb(251, 227, 228);
  203 + border: 1px solid #fbc2c4;
  204 + color: #8a1f11;
  205 +}
  206 +
  207 +.wizard > .content > .body label
  208 +{
  209 + display: inline-block;
  210 + margin-bottom: 0.5em;
  211 +}
  212 +
  213 +.wizard > .content > .body label.error
  214 +{
  215 + color: #8a1f11;
  216 + display: inline-block;
  217 + margin-left: 1.5em;
  218 +}
  219 +
  220 +.wizard > .actions
  221 +{
  222 + position: relative;
  223 + display: block;
  224 + text-align: right;
  225 + width: 100%;
  226 +}
  227 +
  228 +.wizard.vertical > .actions
  229 +{
  230 + display: inline;
  231 + float: right;
  232 + margin: 0 2.5%;
  233 + width: 95%;
  234 +}
  235 +
  236 +.wizard > .actions > ul
  237 +{
  238 + display: inline-block;
  239 + text-align: right;
  240 +}
  241 +
  242 +.wizard > .actions > ul > li
  243 +{
  244 + margin: 0 0.5em;
  245 +}
  246 +
  247 +.wizard.vertical > .actions > ul > li
  248 +{
  249 + margin: 0 0 0 1em;
  250 +}
  251 +
  252 +.wizard > .actions a,
  253 +.wizard > .actions a:hover,
  254 +.wizard > .actions a:active
  255 +{
  256 + background: #2184be;
  257 + color: #fff;
  258 + display: block;
  259 + padding: 0.5em 1em;
  260 + text-decoration: none;
  261 +
  262 + -webkit-border-radius: 5px;
  263 + -moz-border-radius: 5px;
  264 + border-radius: 5px;
  265 +}
  266 +
  267 +.wizard > .actions .disabled a,
  268 +.wizard > .actions .disabled a:hover,
  269 +.wizard > .actions .disabled a:active
  270 +{
  271 + background: #eee;
  272 + color: #aaa;
  273 +}
  274 +
  275 +.wizard > .loading
  276 +{
  277 +}
  278 +
  279 +.wizard > .loading .spinner
  280 +{
  281 +}
  282 +
  283 +
  284 +
  285 +/*
  286 + Tabcontrol
  287 +*/
  288 +
  289 +.tabcontrol > .steps
  290 +{
  291 + position: relative;
  292 + display: block;
  293 + width: 100%;
  294 +}
  295 +
  296 +.tabcontrol > .steps > ul
  297 +{
  298 + position: relative;
  299 + margin: 6px 0 0 0;
  300 + top: 1px;
  301 + z-index: 1;
  302 +}
  303 +
  304 +.tabcontrol > .steps > ul > li
  305 +{
  306 + float: left;
  307 + margin: 5px 2px 0 0;
  308 + padding: 1px;
  309 +
  310 + -webkit-border-top-left-radius: 5px;
  311 + -webkit-border-top-right-radius: 5px;
  312 + -moz-border-radius-topleft: 5px;
  313 + -moz-border-radius-topright: 5px;
  314 + border-top-left-radius: 5px;
  315 + border-top-right-radius: 5px;
  316 +}
  317 +
  318 +.tabcontrol > .steps > ul > li:hover
  319 +{
  320 + background: #edecec;
  321 + border: 1px solid #bbb;
  322 + padding: 0;
  323 +}
  324 +
  325 +.tabcontrol > .steps > ul > li.current
  326 +{
  327 + background: #fff;
  328 + border: 1px solid #bbb;
  329 + border-bottom: 0 none;
  330 + padding: 0 0 1px 0;
  331 + margin-top: 0;
  332 +}
  333 +
  334 +.tabcontrol > .steps > ul > li > a
  335 +{
  336 + color: #5f5f5f;
  337 + display: inline-block;
  338 + border: 0 none;
  339 + margin: 0;
  340 + padding: 10px 30px;
  341 + text-decoration: none;
  342 +}
  343 +
  344 +.tabcontrol > .steps > ul > li > a:hover
  345 +{
  346 + text-decoration: none;
  347 +}
  348 +
  349 +.tabcontrol > .steps > ul > li.current > a
  350 +{
  351 + padding: 15px 30px 10px 30px;
  352 +}
  353 +
  354 +.tabcontrol > .content
  355 +{
  356 + position: relative;
  357 + display: inline-block;
  358 + width: 100%;
  359 + height: 35em;
  360 + overflow: hidden;
  361 + border-top: 1px solid #bbb;
  362 + padding-top: 20px;
  363 +}
  364 +
  365 +.tabcontrol > .content > .body
  366 +{
  367 + float: left;
  368 + position: absolute;
  369 + width: 95%;
  370 + height: 95%;
  371 + padding: 2.5%;
  372 +}
  373 +
  374 +.tabcontrol > .content > .body ul
  375 +{
  376 + list-style: disc !important;
  377 +}
  378 +
  379 +.tabcontrol > .content > .body ul > li
  380 +{
  381 + display: list-item;
  382 +}
0 383 \ No newline at end of file
... ...