1
\$\begingroup\$

I'm coding a jQuery plugin that basically checks if a set of checkboxes groups have at least x options checked. A checkbox group is named as name=group[]. You'd call the plugin like:

$(':checkbox').checkpass({min: 1}) // Make sure at least one checkbox per group is checked

I've been working on this piece of code for a couple days trying to make it as short and fast as possible because I'm going to implement this on a very large form. Is it possible to make it shorter/faster, or have I reached the limit?

You can check a live example here.

(function($) {
    $.fn.checkpass = function(options) {

        var opt = $.extend({
            min: function() {
                return (min === 'undefined') ? 1 : min;
            },
            errorContainer: '<ul class="checkpass-errors">',
            errorWrap: '<li class="error">'
        }, options);

        var $ckboxes = this,
            names = [],
            num = 0,
            $errors = $(opt.errorContainer);

        $ckboxes.each(function() {
            var name = this.name;
            if (!~$.inArray(name, names)) {
                names.push(name);
            }
        });

        for (var i = 0, l = names.length; i < l; i++) {
            $checked = $ckboxes.filter('[name="' + names[i] + '"]:checked');
            if ($checked[opt.min - 1]) {
                num++;
            } else {
                if (opt.min > 0) {
                    var $err = $(opt.errorWrap),
                        _remaining = opt.min - $checked.length,
                        _name = names[i].match(/\w+/).toString()
                                .replace(/^\w/, function($0) {
                                    return $0.toUpperCase();
                                });
                    $err.html('<strong>' + _name +
                              ':</strong> Check ' +
                              _remaining + ' more.').appendTo($errors);
                }
            }
        }

        return {
            isValid: !! (num === names.length),
            errors: $errors
        };
    };
})(jQuery);
\$\endgroup\$
3
  • \$\begingroup\$ FYI: shorted !== faster \$\endgroup\$
    – tereško
    Feb 27 '12 at 0:29
  • \$\begingroup\$ Well, in a way, yeah. I'm looking to make it tighter lets say. By removing unnecessary stuff like toto2 suggested. Any other ideas? \$\endgroup\$
    – elclanrs
    Feb 27 '12 at 0:39
  • \$\begingroup\$ I think you can also do "min = min || 1" instead of "min = (min === 'undefined') ? 1 : min" \$\endgroup\$
    – Hans
    Feb 28 '12 at 15:54
1
\$\begingroup\$

Instead of using an array for names, you could use some implementation of a set, which automatically skips duplications; see here.

Instead of keeping the num variable, I would just check if errors is empty to set isValid.

Actually, you can combine both suggestions by replacing names by an object that stores the unique names associated with their respective error message (or null if it passes).

{"animals[]" : someErrorMessage,  "drinks[]" : null, "colors[]" : someOtherErrorMessage}

You would need to replace your for-loop by a for (name in names) and you would only check if there is not enough check marks and add an error if that's the case.

I played with your fiddle and made a fork:

(function($) {
    $.fn.isIncomplete = function(options) {

        var opt = $.extend({
            min: function() {
                return (min === 'undefined') ? 1 : min;
            }
        }, options);

        var $ckboxes = this;
        var names = {};

        $ckboxes.each(function() {
            names[this.name] = null;
        });

        var errors = [];
        for (name in names) {
            var $checked = $ckboxes.filter('[name="' + name + '"]:checked');
            if ($checked.length < opt.min) {
                isValid = false;
                var _remaining = opt.min - $checked.length;
                var _name = name.match(/\w+/).toString().replace(/^\w/, function($0) {
                    return $0.toUpperCase();
                });
                errors.push('<strong>' + _name + ':</strong> Check ' + _remaining + ' more.');
            }
        }

        if (errors.length == 0) return null;
        else return errors;
    };
})(jQuery);


// Testing...
$('button').click(function() {
    var incompleteErrors = $(':checkbox').isIncomplete({
        min: 1
    });
    if (incompleteErrors) {
        ul = $('<ul>');
        for (var i = 0; i < incompleteErrors.length; i++)
        ul.append($('<li>').html(incompleteErrors[i]));
        $("#errors").html(ul);
    } else {
        $("#errors").html("");
        alert('Passed!');
    }
});​
\$\endgroup\$
7
  • \$\begingroup\$ Yes, this is what I was looking for. Great ideas. I'm going to try to get rid of num which I don't think I need after all. Also, do you think that dedupe() will perform better than $.inArray()? \$\endgroup\$
    – elclanrs
    Feb 27 '12 at 0:38
  • \$\begingroup\$ No, don't use dedupe! That's the bad example they give before giving the 2 lines solutions with the Set (just a normal object -- aka associative array). It's not clear that it's faster since there probably is some hashing done in the background, but at least it's much shorter. \$\endgroup\$
    – toto2
    Feb 27 '12 at 0:44
  • \$\begingroup\$ I just tried getting rid of num and doing what you suggested and it works nicely. Check it out jsfiddle.net/elclanrs/GFCKA. I'm gonna give a try to your other suggestion. \$\endgroup\$
    – elclanrs
    Feb 27 '12 at 0:48
  • \$\begingroup\$ See my edit. I made some other changes, such as having an isValid variable which I change in the loop if there is an error and using an Object instead of an array, as I wrote above. \$\endgroup\$
    – toto2
    Feb 27 '12 at 1:16
  • \$\begingroup\$ I think it would be better to wrap the error message in html outside of checkPass. checkPass should just return the errors as strings and then the recipient of the result does whatever he wants: put the strings in html, print them to console.log, etc. That would simplify checkPass quite a bit, both in the option set up at the start and in the for-loop. You would still need to wrap the error messages in html elsewhere, but I think it does belong elsewhere. \$\endgroup\$
    – toto2
    Feb 27 '12 at 1:22
0
\$\begingroup\$

No need for all that code! (unless I'm missing something)
This simple code should do: (converted to a query plugin of course :)   )

function checkpass(a){
return (document.querySelectorAll('input[type="checkbox"]:checked').length>a)
}
\$\endgroup\$
1
  • \$\begingroup\$ The idea is to check if at least one checkbox is checked per group. Your example looks for a checked in all checkboxes and doesn't work per group. \$\endgroup\$
    – elclanrs
    Mar 8 '12 at 21:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.