2

I'm trying to achieve jquery multi select dropdown with checkboxes with customized apply and cancel button inside dropdown. When I select Select All, dropdown is closing unexpectedly, even I tried to use stopPropagation but still it is closing the dropdown.

Any help would be highly appreciated

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- MultiSelect CSS & JS -->
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.css"
/>
<script src="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.min.js"></script>

<h2>Select Options</h2>
<select id="my-select" multiple>
  <option value="apple">Apple</option>
  <option value="banana">Banana</option>
  <option value="cherry">Cherry</option>
  <option value="date">Date</option>
</select>

$(document).ready(function() {
  $('#my-select').multiselect({
    placeholder: 'Select fruits',
    onControlOpen: function() {
      const $optionsContainer = $('.ms-options')
      // Add Select All checkbox once
      if ($('.select-all-container').length === 0) {
        const $selectAllContainer = $(`
<div class="select-all-container">
<label><input type="checkbox" id="select-all-checkbox"> Select All</label>
</div>
`)
        $optionsContainer.prepend($selectAllContainer)
        // Real fix: Stop mousedown before plugin closes the dropdown
        $(document).on('mousedown', function(e) {
          if ($(e.target).closest('.select-all-container').length) {
            e.stopPropagation()
          }
        })

        $('#select-all-checkbox').on('change', function() {
          const isChecked = $(this).is(':checked')
          $('#my-select option').prop('selected', isChecked)
          $('#my-select').multiselect('reload')
        })
      }
      // Add Apply/Cancel buttons once
      if ($('.custom-button-wrapper').length === 0) {
        const $wrapper = $('<div class="custom-button-wrapper"></div>')

        const $apply = $('<button class="custom-button">Apply</button>').on(
          'click',
          function() {
            const selected = $('#my-select').val()
            alert('Selected: ' + (selected ? selected.join(', ') : 'None'))
          },
        )

        const $cancel = $(
          '<button class="custom-button cancel">Cancel</button>',
        ).on('click', function() {
          $('#my-select').val([]).multiselect('reload')
          $('.ms-parent').find('.ms-drop').hide()
        })
        $wrapper.append($apply).append($cancel)
        $optionsContainer.append($wrapper)
      }
    },
  })
})
.custom-button {
  display: inline-block;
  margin: 10px 5px 5px;
  padding: 5px 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.custom-button.cancel {
  background-color: #dc3545;
}

.custom-button-wrapper {
  text-align: center;
  padding-bottom: 10px;
}

.select-all-container {
  padding: 5px 10px;
  border-bottom: 1px solid #ccc;
  background: #f9f9f9;
  user-select: none;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- MultiSelect CSS & JS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.css" />
<script src="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.min.js"></script>
<h2>Select Options</h2>
<select id="my-select" multiple>
  <option value="apple">Apple</option>
  <option value="banana">Banana</option>
  <option value="cherry">Cherry</option>
  <option value="date">Date</option>
</select>

2
  • Seems like this question is not related with Angular. If yes, please remove the [angular] tag. Thanks. Commented May 12 at 6:00
  • the $('#my-select').multiselect('reload') call is done after the mousedown event which causes the UI to close even if you stop that event Commented May 12 at 6:14

2 Answers 2

4

We can enable selectAll property in the multiselect options.

$(document).ready(function () {
  $('#my-select').multiselect({
  selectAll: true,
  ...

Then use CSS to not destroy (display: none) but just hide (visibility:none) and make it look hidden using (position: absolute; left:-9999px) using the below CSS.

a.ms-selectall.global {
  visibility: hidden;
  position: absolute;
  left: -9999px;
}

Finally on button click, we find this select all element and click it.

    $('#select-all-checkbox').on('change', function(e) {
      e.preventDefault();
      const isChecked = $(this).is(':checked');
      // $('#my-select option').prop('selected', isChecked);
      $('#select-all-checkbox').parents('.select-all-container').siblings('.ms-selectall.global').click();
      $('#select-all-checkbox').prop('checked', checkAllSelected(element));
    });

When the options are changed, we need to manage the state of the select all checkbox, for this we use the event onOptionClick.

onOptionClick: function(element) {
  $('#select-all-checkbox').prop('checked', checkAllSelected(element));
},

function checkAllSelected(selectElement) {
  var selectedOptions = $(selectElement).find('option:selected');
  var allOptions = $(selectElement).find('option');
  return selectedOptions.length === allOptions.length;
}

$(document).ready(function() {
  $('#my-select').multiselect({
    selectAll: true,
    placeholder: 'Select fruits',
    onOptionClick: function(element) {
      $('#select-all-checkbox').prop('checked', checkAllSelected(element));
    },
    onControlOpen: function(element) {
      const $optionsContainer = $('.ms-options');

      // Add Select All checkbox once
      if ($('.select-all-container').length === 0) {
        const $selectAllContainer = $(`
          <div class="select-all-container">
            <label><input type="checkbox" id="select-all-checkbox"> Select All</label>
          </div>
        `);
        $optionsContainer.prepend($selectAllContainer);

        // Real fix: Stop mousedown before plugin closes the dropdown
        $(document).on('mousedown', function(e) {
          if ($(e.target).closest('.select-all-container').length) {
            e.stopPropagation();
          }
        });

        $('#select-all-checkbox').on('change', function(e) {
          e.preventDefault();
          const isChecked = $(this).is(':checked');
          // $('#my-select option').prop('selected', isChecked);
          $('#select-all-checkbox').parents('.select-all-container').siblings('.ms-selectall.global').click();
          $('#select-all-checkbox').prop('checked', checkAllSelected(element));
        });
      }

      // Add Apply/Cancel buttons once
      if ($('.custom-button-wrapper').length === 0) {
        const $wrapper = $('<div class="custom-button-wrapper"></div>');

        const $apply = $('<button class="custom-button">Apply</button>').on('click', function() {
          const selected = $('#my-select').val();
          $('#my-select').next('.ms-options-wrap').removeClass('ms-active');
          alert('Selected: ' + (selected ? selected.join(', ') : 'None'));
        });

        const $cancel = $('<button class="custom-button cancel">Cancel</button>').on('click', function() {
          $('#my-select').val([]).multiselect('reload');
          $('.ms-parent').find('.ms-drop').hide();
        });

        $wrapper.append($apply).append($cancel);
        $optionsContainer.append($wrapper);
      }
    }
  });
});
.custom-button {
  display: inline-block;
  margin: 10px 5px 5px;
  padding: 5px 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.custom-button.cancel {
  background-color: #dc3545;
}

.custom-button-wrapper {
  text-align: center;
  padding-bottom: 10px;
}

.select-all-container {
  padding: 5px 10px;
  border-bottom: 1px solid #ccc;
  background: #f9f9f9;
  user-select: none;
}

a.ms-selectall.global {
  visibility: hidden;
  position: absolute;
  left: -9999px;
}
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>MultiSelect with Fixed Select All</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

  <!-- MultiSelect CSS & JS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.css">
  <script src="https://cdn.jsdelivr.net/gh/nobleclem/jQuery-MultiSelect/jquery.multiselect.min.js"></script>

</head>

<body>

  <h2>Select Options</h2>
  <select id="my-select" multiple>
    <option value="apple">Apple</option>
    <option value="banana">Banana</option>
    <option value="cherry">Cherry</option>
    <option value="date">Date</option>
  </select>
</body>

</html>

Sign up to request clarification or add additional context in comments.

4 Comments

Might be a fault in the original code, but the given actions make the UI not be as expected: Select all -> unselect a couple options -> Toggle select all. Now you toggle everything but the Select All is not on. Clicking again leaves it on while all fruits are deselected.
@Narem Murali, Same issue, any fix ?
@UI_Dev please share the steps to replicate, when I checked, It was working fine
I checked before your edit, now it looks good. Marking as answer, Thanks
1

I know that you would like to use jQuery and all that, but I just thought that it would be interesting to see how this could be done in vanilla JavaScript. So, here it is if anyone else is interested.

const form01 = document.forms.form01;
const fruits_details = document.querySelector('details[name="fruits"]');

form01.addEventListener('change', e => {
  let form = e.target.form;
  switch (e.target.name) {
    case 'all':
      [...form.others.elements].forEach(input => {
        input.checked = e.target.checked;
      });
      break;
    default:
      let unchecked = [...form.others.elements]
        .filter(input => !input.checked);
      form.all.checked = (unchecked.length == 0) ?? true;
  }
});

form01.addEventListener('change', e => {
  updateSummary(e.target.form);
});



form01.addEventListener('click', e => {
  switch (e.target.name) {
    case 'apply':
      e.target.closest('details').open = false;
      break;
    case 'cancel':
      cancelDetails(e.target);
      break;
  }
});

fruits_details.addEventListener('toggle', e => {
  if (e.target.open) saveState(e.target);
});

function saveState(details) {
  let form = details.closest('form');
  details.checked = [...form.others.elements]
    .filter(input => input.checked);
}

function cancelDetails(button) {
  let details = button.closest('details');
  let form = button.form;
  details.open = false;
  [...form.others.elements].forEach(input => input.checked = false);
  form.all.checked = false;
  [...details.checked].forEach(input => input.checked = true);
  updateSummary(form);
}

function updateSummary(form) {
  let checked = [...form.others.elements]
    .filter(input => input.checked);
  let details = form.others.closest('details');
  details.querySelector('summary span').innerText = checked
    .map(input => input.name).join(', ');
}
details[name="fruits"] summary {
  border: solid black thin;
  direction: rtl;
  align-items: left;
  padding: .2em;
}

details[name="fruits"] summary span {
  float: left;
}

details[name="fruits"] summary::marker {
  content: "⯇";
}

details[name="fruits"][open] summary::marker {
  content: "⯆";
}

fieldset {
  border: none;
  margin: 0;
  padding: 0;
}

details[name="fruits"] {
  display: flex;
  flex-direction: column;
}

details[name="fruits"] fieldset {
  display: flex;
  flex-direction: column;
}

details[name="fruits"] label {
  background-color: #ccc;
}
<form name="form01">
  <h2>Select Options</h2>
  <details name="fruits">
    <summary><span></span></summary>
    <label><input type="checkbox" name="all">All</label>
    <fieldset name="others">
      <label><input type="checkbox" name="apple">Apple</label>
      <label><input type="checkbox" name="banana">Banana</label>
      <label><input type="checkbox" name="cherry">Cherry</label>
      <label><input type="checkbox" name="date">Date</label>
    </fieldset>
    <div>
      <button type="button" name="apply">Apply</button>
      <button type="button" name="cancel">Cancel</button>
    </div>
  </details>
</form>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.