I am using a jQuery plugin called Stepy, which is based of the FormToWizard plugin, to allow users to plete a 10-step form. Stepy integrates with the jQuery Validation plugin.
I am running into an issue where if there are multiple radio buttons on the form, it throws and error and does not let the user continue. This only happens after the first radio button (the first radio button validates fine) and only when there are steps after the radio button (if the radio button is on the last step it works fine).
FireBug is displaying "a is undefined". Also, this only seems to happen when the Validation plugin is activated ("validate: true").
Looking through the Stepy and jQuery Validate code, I can't seem to figure out why this is happening.
I have a working example posted: /
Any ideas?
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: '< Back',
block: false,
description: true,
errorImage: false,
finish: undefined,
finishButton: true,
legend: true,
next: undefined,
nextLabel: 'Next >',
titleClick: false,
titleTarget: undefined,
validate: false,
select: undefined
};
})(jQuery);
I am using a jQuery plugin called Stepy, which is based of the FormToWizard plugin, to allow users to plete a 10-step form. Stepy integrates with the jQuery Validation plugin.
I am running into an issue where if there are multiple radio buttons on the form, it throws and error and does not let the user continue. This only happens after the first radio button (the first radio button validates fine) and only when there are steps after the radio button (if the radio button is on the last step it works fine).
FireBug is displaying "a is undefined". Also, this only seems to happen when the Validation plugin is activated ("validate: true").
Looking through the Stepy and jQuery Validate code, I can't seem to figure out why this is happening.
I have a working example posted: http://jsfiddle/5Rd7A/3/
Any ideas?
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: '< Back',
block: false,
description: true,
errorImage: false,
finish: undefined,
finishButton: true,
legend: true,
next: undefined,
nextLabel: 'Next >',
titleClick: false,
titleTarget: undefined,
validate: false,
select: undefined
};
})(jQuery);
Share
Improve this question
edited Nov 26, 2011 at 15:22
Michael
asked Nov 25, 2011 at 15:39
MichaelMichael
2,29415 gold badges49 silver badges80 bronze badges
2 Answers
Reset to default 4Hi Michale and graphicdivine,
It is a problem inside jQuery Validation 1.9 that for default ignore :hidden fields as new feature, but returns undefined. Then undefined is used along the code and break when used. We have a lot of issue about undefined returns, and this time a tried avoid hack on jQuery Stepy and fix it [1] on jQuery Validation 1.9.
Regardless of this bug fix now we have to take away the :hidden fields of the ignore option (jQuery Validation), because the option titleClick (jQuery Stepy) validates the hidden steps either, since you can skip steps without presenting it.
You can to use previous version [2] without this bug, or use the fixed version [3], not official yet.
[1] https://github./jzaefferer/jquery-validation/pull/263
[2] http://ajax.aspnetcdn./ajax/jquery.validate/1.8/jquery.validate.js
[3] github./wbotelhos/jquery-validation
I don't know, but by switching the order of the questions, the error remains on the second set of radios. So it would seem that your code is fine, and it's either Stepy or Validator (or the join between them) that are at fault.
http://jsfiddle/yBEsM/
EDIT
It seems that the error is triggered by entering the 4th fieldset, not by leaving the 3rd.
EDIT
...and taking all the "required"s off still generates the error, so I'm guessing that Stepy is the problem.
http://jsfiddle/4KEsg/
EDIT
...although, turning FALSE stepy's VALIDATE option also solves the problem.
EDIT
With validate turned on, a single radio button is enough to break it: http://jsfiddle/5n5BA/1/
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745219283a4617168.html
评论列表(0条)