Как исправить валидацию кнопок в плагине jQuery? - PullRequest
4 голосов
/ 25 ноября 2011

Я использую плагин jQuery под названием Stepy , который основан на плагине FormToWizard , чтобы позволить пользователям заполнить 10-ступенчатую форму.Stepy интегрируется с плагином jQuery Validation.

Я сталкиваюсь с проблемой, при которой, если в форме есть несколько переключателей, она выдает ошибку и не дает пользователю продолжить.Это происходит только после первой радиокнопки (первая радиокнопка подтверждается нормально) и только когда после радиокнопки есть шаги (если радиокнопка находится на последнем шаге, она работает нормально).

FireBug отображает «а не определено».Кроме того, кажется, что это происходит только при активированном плагине Validation («validate: true»).

Просматривая код проверки 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);

Ответы [ 2 ]

4 голосов
/ 26 ноября 2011

Привет, Михал и графика,

В jQuery Validation 1.9 есть проблема, которая по умолчанию игнорирует : скрытые поля как новую функцию, но возвращает неопределенное значение. Тогда undefined используется вдоль кода и прерывается при использовании. У нас много проблем с неопределенными возвратами, и на этот раз мы попытались избежать взлома jQuery Stepy и исправить его [1] в jQuery Validation 1.9.

Независимо от этого исправления, теперь мы должны убрать: hidden поля опции ignore (jQuery Validation), потому что опция titleClick (jQuery Stepy) также проверяет скрытые шаги, поскольку вы можете пропустить шаги, не представляя их.

Вы можете использовать предыдущую версию [2] без этой ошибки или использовать фиксированную версию [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

1 голос
/ 25 ноября 2011

Не знаю, но при переключении порядка вопросов ошибка остается на втором наборе радиостанций.Так что может показаться, что ваш код в порядке, и виноваты либо Степи, либо Валидатор (или соединение между ними). ​​

http://jsfiddle.net/yBEsM/

РЕДАКТИРОВАТЬ

Кажется, что ошибка вызвана введением 4-го набора полей, а не выходом из 3-го.

EDIT

... и снятие всех "обязательных" значений все еще генерирует ошибку, поэтомуЯ предполагаю, что проблема в Степи.

http://jsfiddle.net/4KEsg/

РЕДАКТИРОВАТЬ

... хотя, если включить опцию VALIDATE FALSE stepy, это тоже решит проблему.

РЕДАКТИРОВАТЬ

При включенной проверке достаточно одного переключателя, чтобы сломать его: http://jsfiddle.net/5n5BA/1/

...