Reputation: 1427
the problem is in the chosen function I'm removing the checked status and selected class of an item in the list
so it works until I select the top one then it gets confused I need to change the validation to just return true or false
DEMO http://jsfiddle.net/XeELs/346/
JS
(function () {
var mobileForms = (function () {
return {
model: {
selection: {
validate: {
test: function (e) {
var that = this,
modelSelector = $('.divModelOptions'),
chosen = $('input:checked', modelSelector),
messages = $('.message', modelSelector),
minMessage = $('.min', modelSelector),
maxMessage = $('.max', modelSelector);
if (typeof (that.messageDisplayTime) !== 'undefined') clearTimeout(that.messageDisplayTime);
if (chosen.length >= that.min && chosen.length <= that.max) {
messages.slideUp(200);
return true;
} else if (chosen.length > that.max) {
if (that.max > 1) {
maxMessage.slideDown(400, function () {
that.messageDisplayTime = setTimeout(function () {
maxMessage.slideUp(200);
}, 3000);
});
if (typeof (e) !== 'undefined') e.preventDefault();
return false;
} else {
chosen[0].checked = false;
chosen.parents('li').removeClass('selected');
return true;
}
} else {
minMessage.slideDown(400);
return 'fail';
}
},
init: function () {
var modelSelector = $('.divModelOptions'),
html = '';
this.min = parseInt(modelSelector.attr('data-min'), 10);
this.max = parseInt(modelSelector.attr('data-max'), 10);
html = '\
<ul class="validation">\
<li class="message min">' + modelSelector.attr('data-min-msg') + '</li>\
<li class="message max">' + modelSelector.attr('data-max-msg') + '</li>\
</ul>';
modelSelector.prepend(html);
}
},
bindHandlers: function () {
var that = this,
modelSelector = $('.divModelOptions'),
chosen = $('input:checked', modelSelector);
var max = parseInt(modelSelector.attr('data-max'), 2);
$('input', modelSelector).click(function (e) {
var parent = $(this).parents('li');
if (that.validate.test(e)) {
if (parent.hasClass('selected')) {
parent.removeClass('selected');
} else {
parent.addClass('selected');
}
}
});
if (chosen.length) {
chosen.parents('li').addClass('selected');
that.validate.test();
}
$('form').submit(function (e) {
if (!that.validate.test() || that.validate.test() === 'fail') e.preventDefault();
});
},
init: function () {
this.validate.init();
this.bindHandlers();
}
},
nextButton: {
bindHandlers: function (button) {
button.one('click', function () {
mobileForms.showNextStep();
});
},
create: function () {
var nextCommandText = $('.divModelOptions').data('nextcommand');
// TODO: Next button should start off grey and become blue once a model is selected.
var button = $('<input type="button" value="' + nextCommandText + '" class="next-button">');
$('.divModelOptions').append(button);
this.bindHandlers(button);
},
init: function () {
this.create();
}
},
init: function () {
this.selection.init();
this.nextButton.init();
}
},
validation: {
focusOnError: function (messages) {
var message = $(messages[0]);
$('html, body').animate({
scrollTop: message.offset().top - 100
}, 500);
},
init: function () {
var visibleMessages;
$('form').submit(function () {
visibleMessages = $('.validation-message:visible, .validation .message:visible');
if (visibleMessages.length) {
mobileForms.validation.focusOnError(visibleMessages);
}
});
}
},
init: function () {
this.model.init();
this.validation.init();
}
};
})();
$(function () {
mobileForms.init();
});
})();
Upvotes: 0
Views: 786
Reputation: 43823
The code in question was rather difficult to follow as there were a lot of functions that are not really part of the question. It was also confusing having the error message element automatically hide, especially since multiple setTimeout()
functions can be attached by clicking many times.
It helps to see what is happening by setting your CSS as:
.divModelOptions input {
display: block;
}
The problem only occurs when data-min="1"
and data-max="1"
because of the else
alternative:
if (that.max > 1) {
} else {
chosen[0].checked = false;
chosen.parents('li').removeClass('selected');
return true;
}
which is entered since that.max === 1
so the 2nd clicked item always results in the first <input>
's checked attribute being set to false, regardless of which item was clicked and then all <li>
parents having the selected
class removed. However, since you return true;
then the calling code logic if
condition is satisfied.
if (that.validate.test(e)) {
if (parent.hasClass('selected')) {
parent.removeClass('selected');
//remove check status to input
} else {
parent.addClass('selected');
//add check status to input
}
}
which adds the selected
class to the newly selected <li>
.
Note, you get different inconsistent behaviour depending on if the first <li>
selected is the first one selected. If you select the 2nd, 3rd or 4th first.
I have tried to reduce the code in my answer to just address the problem of of showing a message if the number of selected items is under or over the defined range. So it might not be exactly what you are after but it should be a (hopefully) more simplified place to start.
JavaScript
(function () {
var mobileForms = (function () {
return {
model: {
selection: {
validate: {
inRange: function (e) {
var self = this,
chosen = $('input:checked', self.modelSelector),
minMessage = $('.min', self.modelSelector),
maxMessage = $('.max', self.modelSelector);
if (chosen.length === 0 || (chosen.length >= self.min && chosen.length <= self.max)) {
self.validation.slideUp(200);
} else {
maxMessage.toggle(chosen.length > self.max);
minMessage.toggle(chosen.length < self.min);
self.validation.slideDown(400);
}
return chosen.length <= self.max;
},
init: function () {
this.modelSelector = $('.divModelOptions');
this.validation = $('.validation', this.modelSelector);
this.min = parseInt(this.modelSelector.data('min'), 10);
this.max = parseInt(this.modelSelector.data('max'), 10);
this.validation.slideUp();
$('.min span').text(this.min);
$('.max span').text(this.max);
}
},
bindHandlers: function () {
var self = this;
$('input', this.modelSelector).click(function (e) {
var parent = $(this).closest('li');
parent.toggleClass('selected');
if (!self.validate.inRange()) {
parent.removeClass('selected');
return false; // stop checkbox receiving event
}
});
},
init: function () {
this.validate.init();
this.bindHandlers();
}
},
init: function () {
this.selection.init();
}
},
init: function () {
this.model.init();
}
};
})();
$(function () {
mobileForms.init();
});
})();
and for completeness…
CSS (identical to question)
.divModelOptions input {
cursor: pointer;
display: none;
float: left;
}
.divModelOptions label {
cursor: pointer;
}
.divModelOptions li.selected {
border:1px solid red
}
.divModelOptions .validation li {
display: none;
}
HTML (play with data-max and data-min to see different examples)
<div data-max="2" data-min="2" class="divModelOptions">
<ul class="validation">
<li class="message min">At least <span></span> should be selected</li>
<li class="message max">No more than <span></span> should be selected</li>
</ul>
<ul>
<li>
<label>
<img alt="" src="http://placehold.it/30x30" />
<input type="checkbox" id="Model" value="Model" name="Model" /><span rel="Model">Model</span>
</label>
</li>
<li>
<label>
<img alt="" src="http://placehold.it/30x30" />
<input type="checkbox" id="Model2" value="Model2" name="Model2" /><span rel="Model2">Model2</span>
</label>
</li>
<li>
<label>
<img alt="" src="http://placehold.it/30x30" />
<input type="checkbox" id="Model3" value="Model3" name="Model3" /><span rel="Model3">Model3</span>
</label>
</li>
<li>
<label>
<img alt="" src="http://placehold.it/30x30" />
<input type="checkbox" id="Model4" value="Model4" name="Model4" /><span rel="Model3">Model4</span>
</label>
</li>
</ul>
</div>
Upvotes: 0