2011-11-25 97 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