DD77
DD77

Reputation: 1427

Jquery toggle li based on data-max and data-min with validation message

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

Answers (1)

andyb
andyb

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.

jsFiddle demo

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

Related Questions