Max06270
Max06270

Reputation: 67

Auto check/uncheck the "All" checkbox

I am new to JS and I have a page with 2 filters, I am stuck and I am trying to:

  1. check automatically the (All) checkbox if every other checkbox of the filter are checked.

  2. when a user unchecked a checkbox, automatically uncheck the (All) checkbox.

I am trying the following code but it is not really working...

HTML:

<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>

<div id="filter2">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>

JS:

$(".checkboxlistitem").change(function() {
$(".select-all").prop("checked", $(".checkboxlistitem:checked").length ==    $(".checkboxlistitem").length);});

JSFIDDLE:

https://jsfiddle.net/Max06270/5scgbnww/

Let me know if you need clarifications, Thanks in advance, Max

Upvotes: 2

Views: 9829

Answers (3)

Yedidya Rashi
Yedidya Rashi

Reputation: 1094

for those that want to use vanilla js:

const checkboxes = document.getElementsByClassName("checkboxlistitem");
const selectAllBtn = document.getElementById("select-all");
for (let i = 0; i < checkboxes.length; i++) {
  checkboxes[i].addEventListener("change", () => {
    for (let i = 0; i < checkboxes.length; i++) {
      if (checkboxes[i].checked === false) {
        selectAllBtn.checked = false;
        break;
      } else {
        selectAllBtn.checked = true;
      }
    }
  });
}
// select all button

selectAllBtn.addEventListener("change", (event) => {
  for (let i = 0, n = checkboxes.length; i < n; i++) {
    checkboxes[i].checked = event.target.checked;
    
  }
});
<div id="filter1">
  <form action='#'>
    <input type="checkbox" value="all" id="select-all" checked>(All)<br>
    <input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1<br>
    <input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2<br>
    <input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3<br>
    <input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4<br>
    <input type="submit" value="Apply">
  </form> 
</div>

Upvotes: 0

zzzzBov
zzzzBov

Reputation: 179046

Fixing the form you have:

Your selectors of $('.checkboxlistitem') and $('.checkboxlistitem:checked') select all elements with those classes, and are therefor selecting the checkboxes in both forms. To fix this they need context.

Within jQuery's event callbacks, this refers to the element that the event is fired on. That means that we can use relative selectors to find the appropriate elements:

// find the nearest ancestor that's a form
$form = $(this).closest('form');

// find all the `.checkboxlistitem` elements within the form
$checkboxes = $form.find('.checkboxlistitem');

// find the all checkbox within the form
$all = $form.find('.select-all');

// if there are any unchecked checkboxes the all option should be unchecked
$all.prop('checked', !$checkboxes.not(':checked').length);

// leaving this as it was in the jsfiddle
// Select/Unselect all chexboxes of the form
$(".select-all").change(function () {
    $(this).siblings().prop('checked', $(this).prop("checked"));
});

$('.checkboxlistitem').change(function () {
  var $form,
      $checkboxes,
      $all;
  
  // find the nearest ancestor that's a form
  $form = $(this).closest('form');
  
  // find all the `.checkboxlistitem` elements within the form
  $checkboxes = $form.find('.checkboxlistitem');
  
  // find the all checkbox within the form
  $all = $form.find('.select-all');
  
  // if there are any unchecked checkboxes the all option should be unchecked
  $all.prop('checked', !$checkboxes.not(':checked').length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>

<div id="filter2">
<form action='#'>
<input type="checkbox" value="all" class="select-all" checked>(All)
<br>
<input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1
<br>
<input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2
<br>
<input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3
<br>
<input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4
<br>
<input type="submit" value="Apply">
</form>
</div>


This is great and all, but lets take it up a notch by improving the accessibility of your form and the behavior of partially complete checkbox lists.

Form accessibility

Labels

As-is your checkboxes have no labels.

You added text after them, certainly, but a screen reader has no way of knowing what each checkbox represents.

To fix this, use <label> elements.

<form action="#">
  <label>
    <input type="checkbox" value="all" class="select-all" checked>
    (All)
  </label>
  <br>
  <label>
    <input type="checkbox" value="v1" class="checkboxlistitem" checked>
    F1: Value1
  </label>
  <br>
  ...
</form>

They can either have the checkbox nested or have their [for] attributes point to the [id] attributes of the <input> elements, or both.

<form action="#">
  <input type="checkbox" id="f1-all" value="all" class="select-all" checked> <label for="f1-all">(All)</label>
  <br>
  <input type="checkbox" id="f1-v1" value="v1" class="checkboxlistitem" checked> <label for="f1-v1">F1: Value1</label>
  <br>
  ...
</form>

Hierarchy

There's a different hierarchical relationship between the "all" checkbox and the individual F#: Value# checkboxes.

To fix this, use <fieldset> and <legend> elements.

<form action="#">
  <fieldset>
    <legend>
      <label>
        <input type="checkbox" value="all" checked>
        <span>Select All in Filter 1</span>
      </label>
    </legend>
    <label>
      <input type="checkbox" value="v1" checked>
      <span>F1: Value1</span>
    </label>
    <br>
    ...
  </fieldset>
</form>

Line separation

You're currently using <br> elements to separate the fields onto different lines.

<p> elements are often used.

<form action="#">
  <fieldset>
    <legend>...</legend>
    <p>
      <label>
        <input type="checkbox" value="v1" checked>
        <span>F1: Value1</span>
      </label>
    </p>
    ...
  </fieldset>
</form>

Names

You're using the [value] attributes, which is good, but none of the <input> elements have [name] attributes, which means that the data won't be sent to the server, and calling jQuery's .serialize() on the forms won't get you the appropriate data.

Set the [name] on the checkboxes that have important values (the "all" checkbox won't be necessary):

<form action="#">
  <fieldset>
    <legend>...</legend>
    <p>
      <label>
        <input type="checkbox" name="v1" value="1" checked>
        <span>F1: Value1</span>
      </label>
    </p>
    ...
  </fieldset>
</form>

Alternatively, you can set all the [name] attributes to filter and rely on the various values to produce query strings such as filter=v1&filter=v2, but this is an advanced technique that's likely going to run into bugs with querystring parsing, so proceed with caution.

Partially Complete Checkbox Lists

While it's nice to be able to automatically uncheck the "all" checkbox, when some checkboxes are checked and other checkboxes are unchecked the unchecked state doesn't exactly make sense for the "all" checkbox. It's not at "none" and it's not at "all".

Enter the indeterminate state.

Checkboxes have a third state that's only accessible from JavaScript that was designed for exactly this situation.

You can set a given checkbox to the indeterminate state by toggling its indeterminate property:

document.querySelector('input[type="checkbox"]').indeterminate = true;

or with jQuery:

$('input[type="checkbox"]').first().prop('indeterminate', true);

Checking whether the list is in an indeterminate state can be done by differentiating between:

  1. No checkboxes checked
  2. All checkboxes checked
  3. Some checkboxes checked and unchecked

In practice this might look something like:

checkedCount = $checkboxes.filter(':checked').length;

if (!checkedCount) {
  // no checkboxes checked
  $all.prop({
    checked: false,
    indeterminate: false
  });
} else if (checkedCount === $checkboxes.length) {
  // all checkboxes checked
  $all.prop({
    checked: true,
    indeterminate: false
  });
} else {
  // some checkboxes checked and unchecked
  $all.prop({
    indeterminate: true
  });
}

All Together Now

A fully fleshed out implementation of all of the above looks like:

$('.filter').on('change', '[value="all"]', function () {
  var $form,
      $checkboxes,
      $all;
  
  $form = $(this).closest('form');
  $checkboxes = $form.find('[name="filter"]');
  $all = $form.find('[value="all"]');
  
  $checkboxes.prop('checked', $all.prop('checked'));
}).on('change', '[name="filter"]', function () {
  var $form,
      $checkboxes,
      $all,
      checkedCount;
  
  $form = $(this).closest('form');
  $checkboxes = $form.find('[name="filter"]');
  $all = $form.find('[value="all"]');
  
  checkedCount = $checkboxes.filter(':checked').length;
  
  if (!checkedCount) {
    // no checkboxes checked
    $all.prop({
      checked: false,
      indeterminate: false
    });
  } else if (checkedCount === $checkboxes.length) {
    // all checkboxes checked
    $all.prop({
      checked: true,
      indeterminate: false
    });
  } else {
    // some checkboxes checked and unchecked
    $all.prop({
      indeterminate: true
    });
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1" class="filter">
  <form action='#'>
    <fieldset>
      <legend>
        <label>
          <input type="checkbox" value="all" checked>
          <span>Select All in Filter 1</span>
        </label>
      </legend>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v1" checked>
          <span>F1: Value1</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v2" checked>
          <span>F1: Value2</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v3" checked>
          <span>F1: Value3</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v4" checked>
          <span>F1: Value4</span>
        </label>
      </p>
    </fieldset>
    <input type="submit" value="Apply">
  </form>
</div>

<div id="filter2" class="filter">
  <form action='#'>
    <fieldset>
      <legend>
        <label>
          <input type="checkbox" value="all" checked>
          <span>Select All in Filter 2</span>
        </label>
      </legend>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v1" checked>
          <span>F2: Value1</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v2" checked>
          <span>F2: Value2</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v3" checked>
          <span>F2: Value3</span>
        </label>
      </p>
      <p>
        <label>
          <input type="checkbox" name="filter" value="v4" checked>
          <span>F2: Value4</span>
        </label>
      </p>
    </fieldset>
    <input type="submit" value="Apply">
  </form>
</div>

Upvotes: 2

Pat
Pat

Reputation: 2750

You're jquery selectors were too generic, so they were affecting both forms. Below should be a working example of what you want:

$(".select-all").change(function () {
    $(this).siblings().prop('checked', $(this).prop("checked"));
});

$(".checkboxlistitem").change(function() {
	  var checkboxes = $(this).parent().find('.checkboxlistitem');
    var checkedboxes = checkboxes.filter(':checked');

    if(checkboxes.length === checkedboxes.length) {
     $(this).parent().find('.select-all').prop('checked', true);
    } else {
    $(this).parent().find('.select-all').prop('checked', false);
    }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="filter1">
  <form action='#'>
    <input type="checkbox" value="all" class="select-all" checked>(All)<br>
    <input type="checkbox" value="v1" class="checkboxlistitem" checked>F1: Value1<br>
    <input type="checkbox" value="v2" class="checkboxlistitem" checked>F1: Value2<br>
    <input type="checkbox" value="v3" class="checkboxlistitem" checked>F1: Value3<br>
    <input type="checkbox" value="v4" class="checkboxlistitem" checked>F1: Value4<br>
    <input type="submit" value="Apply">
  </form> 
</div>

<br>

<div id="filter2">
  <form action='#'>
    <input type="checkbox" value="all" class="select-all" checked>(All)<br>
    <input type="checkbox" value="v1" class="checkboxlistitem" checked>F2: Value1<br>
    <input type="checkbox" value="v2" class="checkboxlistitem" checked>F2: Value2<br>
    <input type="checkbox" value="v3" class="checkboxlistitem" checked>F2: Value3<br>
    <input type="checkbox" value="v4" class="checkboxlistitem" checked>F2: Value4<br>
    <input type="submit" value="Apply">
  </form> 
</div>

https://jsfiddle.net/0epu6Lnn/

Upvotes: 5

Related Questions