2011-11-25 95 views
4

我正在使用名为Stepy的jQuery插件,它基于FormToWizard插件,以允许用户完成10步表单。 Stepy与jQuery Validation插件集成。如何修复jQuery插件中的单选按钮验证?

我遇到了一个问题,如果表单上有多个单选按钮,它会抛出错误并且不会让用户继续。这只发生在第一个单选按钮(第一个单选按钮验证正确)之后,并且只有在单选按钮之后有多个步骤时(如果单选按钮处于最后一步,它才能正常工作)。

FireBug显示“a is undefined”。此外,这似乎只发生在验证插件被激活(“验证:真”)。

通过Stepy和jQuery验证代码,我似乎无法弄清楚为什么会发生这种情况。

我有一个工作示例贴:http://jsfiddle.net/5Rd7A/3/

任何想法?

的Javascript:

$(function() { 

    $('#custom').stepy({ 
     backLabel: 'Backward', 
     block: true, 
     errorImage: true, 
     nextLabel: 'Forward', 
     titleClick: true, 
     validate: true 
    }); 

}); 

HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html> 
    <body> 
    <form id="custom" name="custom"> 
     <fieldset title="Thread 1"> 
     <legend>description one</legend> 
     <label>Question A:</label> <input type="text" id="question_a" name="question_a" class="required"> 
     <label>Question B:</label> <input type="text" id="question_b" name="question_b"> 
     </fieldset> 
     <fieldset title="Thread 2"> 
     <legend>description two</legend> 
     <label>Question C:</label> <input type="text" id="question_c" name="question_c" class="required"> 
     <label>Question D:</label> 
     <input id="answer_d1" type="radio" name="question_d" class="required"> Answer D1 
     <input id="answer_d2" type="radio" name="question_d" class="required"> Answer D2 
     </fieldset> 
     <fieldset title="Thread 3"> 
     <legend>description three</legend> 
     <label>Question E:</label> <input type="text" id="question_e" name="question_e" class="required"> 
     <label>Question F:</label> 
     <input id="answer_f1" type="radio" name="question_f" class="required"> Answer F1 
     <input id="answer_f2" type="radio" name="question_f" class="required"> Answer F2 
     </fieldset> 
     <fieldset title="Thread 4"> 
     <legend>description four</legend> 
     <label>Question G:</label> <input type="text" id="question_g" name="question_g" class="required"> 
     <label>Question H:</label> <input type="text" id="question_h" name="question_h" class="required"> 
     </fieldset> 
     <input type="submit" class="finish" value="Finish!"> 
    </form><br> 
    </body> 
</html> 

stepy.js

;(function($) { 

    var methods = { 
     init: function(options) { 
      return this.each(function() { 

       var opt  = $.extend({}, $.fn.stepy.defaults, options), 
        $this = $(this).data('options', opt), 
        id  = $this.attr('id'); 

       if (id === undefined) { 
        id = 'stepy-' + $this.index(); 
        $this.attr('id', id); 
       } 

       var $titlesWrapper = $('<ul/>', { id: id + '-titles', 'class': 'stepy-titles' }); 

       if (opt.titleTarget) { 
        $(opt.titleTarget).html($titlesWrapper); 
       } else { 
        $titlesWrapper.insertBefore($this); 
       } 

       if (opt.validate) { 
        $this.append('<div class="stepy-error"/>'); 
       } 

       var $steps  = $this.children('fieldset'), 
        $step  = undefined, 
        $legend  = undefined, 
        description = '', 
        title  = ''; 

       $steps.each(function(index) { 
        $step = $(this); 

        $step 
        .addClass('step') 
        .attr('id', id + '-step-' + index) 
        .append('<p id="' + id + '-buttons-' + index + '" class="' + id + '-buttons"/>'); 

        $legend = $step.children('legend'); 

        if (!opt.legend) { 
         $legend.hide(); 
        } 

        description = ''; 

        if (opt.description) { 
         if ($legend.length) { 
          description = '<span>' + $legend.html() + '</span>'; 
         } else { 
          $.error(id + ': the legend element of the step ' + (index + 1) + ' is required to set the description!'); 
         } 
        } 

        title = $step.attr('title'); 
        title = (title != '') ? '<div>' + title + '</div>': '--'; 

        $titlesWrapper.append('<li id="' + id + '-title-' + index + '">' + title + description + '</li>'); 

        if (index == 0) { 
         if ($steps.length > 1) { 
          methods.createNextButton.call($this, index); 
         } 
        } else { 
         methods.createBackButton.call($this, index); 

         $step.hide(); 

         if (index < $steps.length - 1) { 
          methods.createNextButton.call($this, index); 
         } 
        } 
       }); 

       var $titles = $titlesWrapper.children(); 

       $titles.first().addClass('current-step'); 

       var $finish = $this.children('.finish'); 

       if (opt.finishButton) { 
        if ($finish.length) { 
         var isForm  = $this.is('form'), 
          onSubmit = undefined; 

         if (opt.finish && isForm) { 
          onSubmit = $this.attr('onsubmit'); 
          $this.attr('onsubmit', 'return false;'); 
         } 

         $finish.click(function(evt) { 
          if (opt.finish && !methods.execute.call($this, opt.finish, $steps.length - 1)) { 
           evt.preventDefault(); 
          } else { 
           if (isForm) { 
            if (onSubmit) { 
             $this.attr('onsubmit', onSubmit); 
            } else { 
             $this.removeAttr('onsubmit'); 
            } 

            var isSubmit = $finish.attr('type') == 'submit'; 

            if (!isSubmit && (!opt.validate || methods.validate.call($this, $steps.length - 1))) { 
             $this.submit(); 
            } 
           } 
          } 
         }); 

         $finish.appendTo($this.find('p:last')); 
        } else { 
         $.error(id + ': element with class name "finish" missing!'); 
        } 
       } 

       if (opt.titleClick) { 
        $titles.click(function() { 
         var array = $titles.filter('.current-step').attr('id').split('-'), // TODO: try keep the number in an attribute. 
          current = parseInt(array[array.length - 1], 10), 
          clicked = $(this).index(); 

         if (clicked > current) { 
          if (opt.next && !methods.execute.call($this, opt.next, clicked)) { 
           return false; 
          } 
         } else if (clicked < current) { 
          if (opt.back && !methods.execute.call($this, opt.back, clicked)) { 
           return false; 
          } 
         } 

         if (clicked != current) { 
          methods.step.call($this, (clicked) + 1); 
         } 
        }); 
       } else { 
        $titles.css('cursor', 'default'); 
       } 

       $steps.delegate('input[type="text"], input[type="password"]', 'keypress', function(evt) { 
        var key = (evt.keyCode ? evt.keyCode : evt.which); 

        if (key == 13) { 
         evt.preventDefault(); 

         var $buttons = $(this).parent().children('.' + id + '-buttons'); 

         if ($buttons.length) { 
          var $next = $buttons.children('.button right-aligned'); 

          if ($next.length) { 
           $next.click(); 
          } else { 
           var $finish = $buttons.children('.finish'); 

           if ($finish.length) { 
            $finish.click(); 
           } 
          } 
         } 
        } 
       }); 

       $steps.first().find(':input:visible:enabled').first().select().focus(); 
      }); 
     }, createBackButton: function(index) { 
      var $this = this, 
       id  = this.attr('id'), 
       opt  = this.data('options'); 

      $('<a/>', { id: id + '-back-' + index, href: 'javascript:void(0);', 'class': 'button left-aligned', html: opt.backLabel }).click(function() { 
       if (!opt.back || methods.execute.call($this, opt.back, index - 1)) { 
        methods.step.call($this, (index - 1) + 1); 
       } 
      }).appendTo($('#' + id + '-buttons-' + index)); 
     }, createNextButton: function(index) { 
      var $this = this, 
       id  = this.attr('id'), 
       opt  = this.data('options'); 

      $('<a/>', { id: id + '-next-' + index, href: 'javascript:void(0);', 'class': 'button right-aligned', html: opt.nextLabel }).click(function() { 
       if (!opt.next || methods.execute.call($this, opt.next, index + 1)) { 
        methods.step.call($this, (index + 1) + 1); 
       } 
      }).appendTo($('#' + id + '-buttons-' + index)); 
     }, execute: function(callback, index) { 
      var isValid = callback.call(this, index + 1); 

      return isValid || isValid === undefined; 
     }, step: function(index) { 
      index--; 

      var $steps = this.children('fieldset'); 

      if (index > $steps.length - 1) { 
       index = $steps.length - 1; 
      } 

      var opt = this.data('options'); 
       max = index; 

      if (opt.validate) { 
       var isValid = true; 

       for (var i = 0; i < index; i++) { 
        isValid &= methods.validate.call(this, i); 

        if (opt.block && !isValid) { 
         max = i; 
         break; 
        } 
       } 
      } 

      $steps.hide().eq(max).show(); 

      var $titles = $('#' + this.attr('id') + '-titles').children(); 

      $titles.removeClass('current-step').eq(max).addClass('current-step'); 

      if (this.is('form')) { 
       var $fields = undefined; 

       if (max == index) { 
        $fields = $steps.eq(max).find(':input:enabled:visible'); 
       } else { 
        $fields = $steps.eq(max).find('.error').select().focus(); 
       } 

       $fields.first().select().focus(); 
      } 

      if (opt.select) { 
       opt.select.call(this, max + 1); 
      } 

      return this; 
     }, validate: function(index) { 
      if (!this.is('form')) { 
       return true; 
      } 

      var $step = this.children('fieldset').eq(index), 
       isValid = true, 
       $title = $('#' + this.attr('id') + '-titles').children().eq(index), 
       opt  = this.data('options'), 
       $this = this; 

      $($step.find(':input:enabled').get().reverse()).each(function() { 

       var fieldIsValid = $this.validate().element($(this)); 

       if (fieldIsValid === undefined) { 
        fieldIsValid = true; 
       } 

       isValid &= fieldIsValid; 

       if (isValid) { 
        if (opt.errorImage) { 
         $title.removeClass('error-image'); 
        } 
       } else { 
        if (opt.errorImage) { 
         $title.addClass('error-image'); 
        } 

        $this.validate().focusInvalid(); 
       } 
      }); 

      return isValid; 
     } 
    }; 

    $.fn.stepy = function(method) { 
     if (methods[method]) { 
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 
     } else if (typeof method === 'object' || !method) { 
      return methods.init.apply(this, arguments); 
     } else { 
      $.error('Method ' + method + ' does not exist!'); 
     } 
    }; 

    $.fn.stepy.defaults = { 
     back:   undefined, 
     backLabel:  '&lt; Back', 
     block:   false, 
     description: true, 
     errorImage:  false, 
     finish:   undefined, 
     finishButton: true, 
     legend:   true, 
     next:   undefined, 
     nextLabel:  'Next &gt;', 
     titleClick:  false, 
     titleTarget: undefined, 
     validate:  false, 
     select:   undefined 
    }; 

})(jQuery); 

回答

4

喜Michale和graphicdivine,

这是jQuery验证1.9中的一个问题,对于默认忽略:隐藏字段作为新功能,但返回未定义。然后在代码中使用未定义,并在使用时中断。 我们有很多关于未定义的返回的问题,这次尝试避免jQuery Stepy的黑客入侵,并在jQuery Validation 1.9上修复它[1]。

无论现在这个错误修复,我们必须取消忽略选项(jQuery验证)的隐藏字段,因为选项titleClick(jQuery Stepy)也验证隐藏步骤,因为您可以跳过步骤而不显示它。

你可以使用以前的版本[2]没有这个bug,或者使用固定版本[3],而不是官方。

[1] https://github.com/jzaefferer/jquery-validation/pull/263
[2] http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.js
[3] github.com/wbotelhos/jquery-validation

+0

非常感谢你......这让我疯狂! :) – Michael

+0

我不明白为什么你描述的bug会影响单选按钮,但我很高兴你找到了一个修复程序。 – graphicdivine

+0

有同样的问题,这解决了它 - 谢谢! – cantaffordretail

1

我不知道,但通过切换的问题,错误遗体顺序在第二组无线电。所以看起来你的代码是好的,它可能是Stepy或Validator(或它们之间的连接),这些都是错误的。

http://jsfiddle.net/yBEsM/

编辑

看来,错误是由进入第四字段集引发的,而不是离开第三位。

编辑

...并把所有的“需要” S关仍然会产生错误,所以我猜Stepy的问题。

http://jsfiddle.net/4KEsg/

编辑

...虽然,把假stepy的validate选项也解决了这个问题。

编辑

随着验证开启,一个单选按钮,就足以打破它:http://jsfiddle.net/5n5BA/1/

+0

感谢寻找到这一点。设置“validate:false”可以消除错误(http://jsfiddle.net/4KEsg/2/)。我认为它一定是集成,因为Stepy在没有验证的情况下可以很好地处理多个单选按钮。 – Michael

+0

它只有在使用单选按钮之后有一个步/字段集时才会将其分开。如果单选按钮处于最后一步,则它工作正常。 http://jsfiddle.net/5n5BA/2/ – Michael