0

I am trying to make a multiselectlist in which you do not have to click ctrl or shift to add additional selections. I have prevented the default behavior in favor of the new functionality. It is mostly working except for two issues.

  1. If I scroll down and click an option it will usually, not always, go to the top of the scroll list. I can't figure out a way to stop this from happening. I have tried using scrollTop() and had to put it inside a setTimeout() to work(I have commented around this code). But it does not seem to work all the time and I sometimes end up with a flashing behavior where I have to click a few times to select an item because it is jumping to the top and back down. Is there a better way to set the ScrollTop value to remove the flashing behavior I get sometimes?

  2. The way I currently have the new select list working I cannot click and drag outside the select element to select all the options. I would like to have something similar to the default behavior when drag selecting but I can't seem to implement this type of behavior either. I have tried using mousemove capturing the y value and selecting options above or below a certain value but I could not get it working right. How would I implement this or is there a better way to do this?

I am fairly new to jquery and have been stumbling through the docs for a while now. Any help for the two problems above or just achieving the desired functionality with jquery if this is not a good way to achieve it is appreciated. Here is a link to a JSfiddle I have been working with.

(note for fiddle: Shift + click will only add items and shift + ctrl + click will only deselect items. Just clicking or click dragging will simply change option to opposite of current state.)

JQuery:

$(document).ready(function() {
  $("select[multiple = 'multiple']").each(function() {
    var idLink = this.id;
    $(this).children().each(function() {
      $(this).attr('listgroup', idLink)
    })
  })
})

var shiftKeyPressed = false;
var ctrlKeyPressed = false;

$("option").mousedown(function(e) {
  e.preventDefault();
  var listGroup = $(this).attr('listgroup');
  var parent = $(this).parent();
  parent.focus();
  parent.keydown(function(evt) {
    if (evt.shiftKey) {
      shiftKeyPressed = true;
    }
    if (evt.ctrlKey) {
      ctrlKeyPressed = true;
    }
  }).keyup(function(evt) {
    shiftKeyPressed = false;
    ctrlKeyPressed = false;
  })
  //1 of 3 scrollTop code
  var top = parent.scrollTop();
  setTimeout(function() {
    parent.scrollTop(top);
  }, 1)
    // end scrollTop section 1
  if ($(this).prop("selected")) {
    $(this).prop("selected", false);
  } else {
    $(this).prop("selected", "selected");
  }

  $("option[listgroup='" + listGroup + "']").mouseenter(function(e) {
     e.preventDefault();
     //2 of 3 scrollTop code
     setTimeout(function() {
      parent.scrollTop(top);
      //console.log('mouseEnter:' + top)
    }, 1)
    //end scrollTop section 2
    if (shiftKeyPressed && ctrlKeyPressed) {
      if ($(this).prop("selected")) {
        $(this).prop("selected", false);
        $(this).removeClass("checkSelected");
      }
    } else if (shiftKeyPressed) {
      $(this).prop("selected", "selected");
      $(this).addClass("checkSelected")
    } else {
      if ($(this).prop("selected")) {
        $(this).prop("selected", false);
        $(this).removeClass("checkSelected");
      } else {
        $(this).prop("selected", "selected");
        $(this).addClass("checkSelected")
      }
    }
  })
  //3 0f 3 scrollTopCode
   parent.scroll(function(e) {
    e.preventDefault();
    setTimeout(function() {
      top = parent.scrollTop();
      parent.scrollTop(top);
    }, 1)
    console.log(top);
  })
  //end scrollTop section 3
}).mouseup(function() {
  $("*").off("mouseenter");
  $("*").off("scroll");
})

Html pseudo:

<select> *some options </select>
<select> *some options </select>
<select> *some options </select>
  • I am confused. How can I multi select from a select list without an additional keypress? How does the user tell the select box that he has finished selecting elements? – Thallius Jul 06 '21 at 15:15
  • I am looking for the mouseup event to finish the selection. – Spencer Braswell Jul 06 '21 at 15:44
  • But what if i want to select item 1,3 and 5? – Thallius Jul 06 '21 at 16:32
  • Then just click 1, 3, and 5. Click and drag to select groups of congruent options. Let mouse up to skip items and click again to continue selecting. All previous selections will stay unless you scroll over them again in which case they will be deselected unless you hold the shift key – Spencer Braswell Jul 06 '21 at 16:37
  • Then you will have 3 mouseup Events. So I will select 3x 1 element but not 1x 3 elements – Thallius Jul 06 '21 at 16:38
  • I guess that is true but is that a problem as my mouse up event basically is just cancelling listeners applied by mouse down. – Spencer Braswell Jul 06 '21 at 16:43

1 Answers1

0

Okay, found an answer here answered by Joeytje50. I took the code from his gitHub not sure what credit I need to give him but I did modify his code a little bit as the functionality was a little buggy with a scrollable list. Here is what I ended up with. Still occasionally get a flicker when you click an option but does not seem to effect functionality.

// Copyright (c) 2018 Joeytje50. All rights reserved.
// This code is licenced under GNU GPL3+; see <http://www.gnu.org/licenses/>

// Source code and most recent version:
// https://gist.github.com/Joeytje50/2b73f9ac47010e7fdc5589788b80af77
// select all <options> within `sel` in the range [from,to]
// change their state to the state sel.options[from] is in,
// ie. change it to the current state of the first selected element
function selectRange(sel, from, to) {
  var toState = sel.options[from].selected;
  // make sure from < to:
  if (from > to) {
    var temp = from;
    from = to;
    to = temp;
  }
  if (!(sel instanceof HTMLSelectElement)) {
    throw new TypeError('selectRange requires a single non-jQuery select-element as first parameter');
  }
  // (de)select every element
  for (var i = from; i <= to; i++) {
    sel.options[i].selected = toState;
  }
}

$(function() {
  $('.addSelect').data('select-start', 0); // default selection start
  $('.addSelect').on('mousedown', function(e) {
    //1 0f 4 my additions save scroll position
    var clicked = $(this);
    var top = clicked.scrollTop();
    setTimeout(function() {
      clicked.scrollTop(top);
    }, 1)
    //end addition 1 
    $(this).focus();
    // clicking on the edge of the <select> shouldn't do anything special
    if (!$(e.target).is('option')) return;
    // if Ctrl is pressed, just let the built-in functionality take over
    //if (e.ctrlKey) return;
    // keep everything selected that's not affected by the range within a shift-click
    if (e.shiftKey) {
      var fromIdx = $(this).data('select-start')
      selectRange(this, $(this).data('select-start'), e.target.index);
      e.preventDefault();
      return false;
    }
    // save the starting <option> and the state to change to
    $(this).data('select-start', e.target.index);
    e.target.selected = !e.target.selected;
    e.preventDefault();
    // save a list of selected elements, to make sure only the selected <options>
    // are added or removed when dragging
    var selected = [];
    for (var i = 0; i < this.selectedOptions.length; i++) {
      selected.push(this.selectedOptions[i].index);
    }

    $(this).data('selected', selected);
    $(this).children('option').on('mouseenter', function(e) {
      //2 0f 4 my additions save scroll position
      setTimeout(function() {
        clicked.scrollTop(top);
      }, 1)
      //end addition 2 
      var sel = this.parentElement;
      // first reset all options to the original state
      for (var i = 0; i < sel.options.length; i++) {
        if ($(sel).data('selected').indexOf(i) == -1) {
          sel.options[i].selected = false;
        } else {
          sel.options[i].selected = true;
        }
      }
      // then apply the new range to the elements
      selectRange(sel, $(sel).data('select-start'), e.target.index);
    });
    //3 0f 4 of my additions save scroll position
    clicked.scroll(function(e) {
      setTimeout(function() {
        top = clicked.scrollTop();
        clicked.scrollTop(top);
      }, 1)
    });
    //end addition 3
  });
  // clean up events after click event has ended.
  $(window).on('mouseup', function() {
    $('.addSelect').children('option').off('mouseenter'); // remove mouseenter-events
    $('.addSelect').off('scroll'); //4 0f 4 my additions removed scroll
  });
});
select {
  min-height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select class='addSelect' id=1 multiple="multiple">
  <option value=1>Test 1</option>
  <option value=2>Test 2</option>
  <option value=3>Test 3</option>
  <option value=4>Test 4</option>
  <option value=5>Test 5</option>
  <option value=6>Test 6</option>
  <option value=7>Test 7</option>
  <option value=8>Test 8</option>
  <option value=9>Test 9</option>
  <option value=1 0>Test 10</option>
  <option value=1 1>Test 11</option>
  <option value=1 2>Test 12</option>
  <option value=1 3>Test 13</option>
  <option value=1 4>Test 14</option>
  <option value=1 5>Test 15</option>
  <option value=1 6>Test 16</option>
  <option value=1 7>Test 17</option>
  <option value=1 8>Test 18</option>
  <option value=1 9>Test 19</option>
  <option value=2 0>Test 20</option>
  <option value=2 1>Test 21</option>
  <option value=2 2>Test 22</option>
  <option value=2 3>Test 23</option>
  <option value=2 4>Test 24</option>
  <option value=2 5>Test 25</option>
  <option value=2 6>Test 26</option>
  <option value=2 7>Test 27</option>
  <option value=2 8>Test 28</option>
  <option value=2 9>Test 29</option>
  <option value=3 0>Test 30</option>
  <option value=3 1>Test 31</option>
  <option value=3 2>Test 32</option>
  <option value=3 3>Test 33</option>
  <option value=3 4>Test 34</option>
  <option value=3 5>Test 35</option>
  <option value=3 6>Test 36</option>
  <option value=3 7>Test 37</option>
  <option value=3 8>Test 38</option>
  <option value=3 9>Test 39</option>
  <option value=4 0>Test 40</option>
</select>

<select class='addSelect' id=2 multiple="multiple">
  <option value=1>Test 1</option>
  <option value=2>Test 2</option>
  <option value=3>Test 3</option>
  <option value=4>Test 4</option>
  <option value=5>Test 5</option>
  <option value=6>Test 6</option>
  <option value=7>Test 7</option>
  <option value=8>Test 8</option>
  <option value=9>Test 9</option>
  <option value=1 0>Test 10</option>
  <option value=1 1>Test 11</option>
  <option value=1 2>Test 12</option>
  <option value=1 3>Test 13</option>
  <option value=1 4>Test 14</option>
  <option value=1 5>Test 15</option>
  <option value=1 6>Test 16</option>
  <option value=1 7>Test 17</option>
  <option value=1 8>Test 18</option>
  <option value=1 9>Test 19</option>
  <option value=2 0>Test 20</option>
  <option value=2 1>Test 21</option>
  <option value=2 2>Test 22</option>
  <option value=2 3>Test 23</option>
  <option value=2 4>Test 24</option>
  <option value=2 5>Test 25</option>
  <option value=2 6>Test 26</option>
  <option value=2 7>Test 27</option>
  <option value=2 8>Test 28</option>
  <option value=2 9>Test 29</option>
  <option value=3 0>Test 30</option>
  <option value=3 1>Test 31</option>
  <option value=3 2>Test 32</option>
  <option value=3 3>Test 33</option>
  <option value=3 4>Test 34</option>
  <option value=3 5>Test 35</option>
  <option value=3 6>Test 36</option>
  <option value=3 7>Test 37</option>
  <option value=3 8>Test 38</option>
  <option value=3 9>Test 39</option>
  <option value=4 0>Test 40</option>
</select>

<select class='addSelect' id=3 multiple="multiple">
  <option value=1>Test 1</option>
  <option value=2>Test 2</option>
  <option value=3>Test 3</option>
  <option value=4>Test 4</option>
  <option value=5>Test 5</option>
  <option value=6>Test 6</option>
  <option value=7>Test 7</option>
  <option value=8>Test 8</option>
  <option value=9>Test 9</option>
  <option value=1 0>Test 10</option>
  <option value=1 1>Test 11</option>
  <option value=1 2>Test 12</option>
  <option value=1 3>Test 13</option>
  <option value=1 4>Test 14</option>
  <option value=1 5>Test 15</option>
  <option value=1 6>Test 16</option>
  <option value=1 7>Test 17</option>
  <option value=1 8>Test 18</option>
  <option value=1 9>Test 19</option>
  <option value=2 0>Test 20</option>
  <option value=2 1>Test 21</option>
  <option value=2 2>Test 22</option>
  <option value=2 3>Test 23</option>
  <option value=2 4>Test 24</option>
  <option value=2 5>Test 25</option>
  <option value=2 6>Test 26</option>
  <option value=2 7>Test 27</option>
  <option value=2 8>Test 28</option>
  <option value=2 9>Test 29</option>
  <option value=3 0>Test 30</option>
  <option value=3 1>Test 31</option>
  <option value=3 2>Test 32</option>
  <option value=3 3>Test 33</option>
  <option value=3 4>Test 34</option>
  <option value=3 5>Test 35</option>
  <option value=3 6>Test 36</option>
  <option value=3 7>Test 37</option>
  <option value=3 8>Test 38</option>
  <option value=3 9>Test 39</option>
  <option value=4 0>Test 40</option>
</select>