17

Demo

I want to select multiple rows using Windows Shift and Ctrl keys, like multiple folder selection in Windows.

From table of selected rows I have to get the first column (student id) and pass to server side C# and delete those records from database.

I have written a code in javascript but the classname is not being applied to <tr> on Shift or Ctrl+ left click.

HTML

<table id="tableStudent" border="1">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Class</th>
        </tr>
    </thead>
    <tbody>
        <tr onmousedown="RowClick(this,false);">
            <td>1</td>
            <td>John</td>
            <td>4th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>2</td>
            <td>Jack</td>
            <td>5th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>3</td>
            <td>Michel</td>
            <td>6th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>4</td>
            <td>Mike</td>
            <td>7th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>5</td>
            <td>Yke</td>
            <td>8th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>6</td>
            <td>4ke</td>
            <td>9th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>7</td>
            <td>7ke</td>
            <td>10th</td>
        </tr>
    </tbody>
</table>

JavaScript

var selectedrow;
function RowClick(currenttr, lock) {
var trs =tableStudent.tBodies[0].getElementsByTagName("tr");
var cnt;
    if(window.event.button==2)
    {
        if(currenttr.className=='selected')
        return false;
    }
alert(trs.length);
if (((window.event.shiftKey) && (window.event.ctrlKey) ) ||(window.event.shiftKey))
    {
        for(var j=0; j<trs.length; j++)
        {
            if (trs[j].className!='normallock')
            {
                trs[j].className='normal';
            }
        }
        var mark=false;

        if (typeof(selectedrow)=="undefined")
        {
            selectedrow=currenttr;
            selectedrow.className='selected'
            return false;
        }
        for(var j=0; j<trs.length; j++)
        {

            if ((trs[j].id ==selectedrow.id) || (trs[j].id ==currenttr.id) )
            {
                if (trs[j].className!='normallock')
                {
                trs[j].className='selected'
                mark = !(mark);
                }
            }
            else
            {
                if(mark==true)
                {
                    if (trs[j].className!='normallock')
                    trs[j].className='selected'
                }
            }
        }
    }
    else if(window.event.ctrlKey)
    {
        //if ctrl key is seelcted while selecting the patients
        // select the patient with currently clicked row plus
        // maintain the previous seelcted status
        cnt=0;
        for(var j=0; j<trs.length; j++)
        {
            if(trs[j].id == currenttr.id)
            {
                if(trs[j].className=='selected')
                {
                    trs[j].className='normal';
                }else
                {
                    trs[j].className='selected';
                }
            }
            if(trs[j].className=='selected')
            {
                cnt++;
            }
        }

        if(cnt==0)
        {
            selectedrow=undefined;
            return false;
        }
    }
    else
    {
        for(var j=0; j<trs.length; j++)
        {
            if(trs[j].id == currenttr.id)
            {
                trs[j].className='selected'
            }
            else
            {
                if (trs[j].className!='normallock')
                trs[j].className='normal';
            }

        }
    }
    selectedrow=currenttr;
}
andyb
  • 43,435
  • 12
  • 121
  • 150
ND's
  • 2,155
  • 6
  • 38
  • 59
  • The code in the fiddle is not jQuery but you have tagged the question as jQuery. So do you use jQuery library in your project? Also, please edit the question with more detail about the exact problem you have. – andyb Jul 31 '13 at 07:51
  • not use jquery in project...Remove the jquery tag – ND's Jul 31 '13 at 07:59
  • What i want i have to select multiple rows using Window shift and control key.Example like multiple folder selection in Windows PC..From table of selected rows i have to get(first coloumn) student id and pass to server side C# and delete those records from database. – ND's Jul 31 '13 at 08:01
  • And what is the error in the demo? I see a JavaScript `alert` but no error. – andyb Jul 31 '13 at 08:16
  • Classname not applied to tr on cntrl+key cntrl+shft key – ND's Jul 31 '13 at 08:28
  • Isn't `window.event` non-standard? – Neil Jul 31 '13 at 12:12

6 Answers6

41

It's probably not all of the functionality you want, since the question is a bit vague, but he's an attempt at adding Ctrl or Shift+ left mouse button to select or deselect multiple table rows - see demo and code below. Disclaimer: Only tested in Chrome and code can almost certainly be optimised.

JavaScript

var lastSelectedRow;
var trs = document.getElementById('tableStudent').tBodies[0].getElementsByTagName('tr');

// disable text selection
document.onselectstart = function() {
    return false;
}

function RowClick(currenttr, lock) {
    if (window.event.ctrlKey) {
        toggleRow(currenttr);
    }

    if (window.event.button === 0) {
        if (!window.event.ctrlKey && !window.event.shiftKey) {
            clearAll();
            toggleRow(currenttr);
        }

        if (window.event.shiftKey) {
            selectRowsBetweenIndexes([lastSelectedRow.rowIndex, currenttr.rowIndex])
        }
    }
}

function toggleRow(row) {
    row.className = row.className == 'selected' ? '' : 'selected';
    lastSelectedRow = row;
}

function selectRowsBetweenIndexes(indexes) {
    indexes.sort(function(a, b) {
        return a - b;
    });

    for (var i = indexes[0]; i <= indexes[1]; i++) {
        trs[i-1].className = 'selected';
    }
}

function clearAll() {
    for (var i = 0; i < trs.length; i++) {
        trs[i].className = '';
    }
}

HTML

<table id="tableStudent" border="1">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Class</th>
        </tr>
    </thead>
    <tbody>
        <tr onmousedown="RowClick(this,false);">
            <td>1</td>
            <td>John</td>
            <td>4th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>2</td>
            <td>Jack</td>
            <td>5th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>3</td>
            <td>Michel</td>
            <td>6th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>4</td>
            <td>Mike</td>
            <td>7th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>5</td>
            <td>Yke</td>
            <td>8th</td>
        </tr>
         <tr onmousedown="RowClick(this,false);">
            <td>6</td>
            <td>4ke</td>
            <td>9th</td>
        </tr>
        <tr onmousedown="RowClick(this,false);">
            <td>7</td>
            <td>7ke</td>
            <td>10th</td>
        </tr>
    </tbody>
</table>

CSS

.selected {
    background: lightBlue
}

I would also look at addEventListener vs onclick and move the event handler binding out of the HTML and into JavaScript. This is known as Unobtrusive Javascript.

Resources you might want to read:

Community
  • 1
  • 1
andyb
  • 43,435
  • 12
  • 121
  • 150
  • andyb thanks for your reply i am facing one issue when i click first row with ctrl+click and 7th row with ctrl+shift click then 1 to 7 all rows selected but in your fiddle select first and last only – ND's Jul 31 '13 at 09:37
  • 1
    I copied the way Windows works. To select a range you left click once and then shift+click the second one. You do not need ctrl in this instance. I suspect that my logic will need adapting if you want users to use ctrl+shift+click to select a range. If you debug the code you should see exactly what the problem is. _Hint: is has something to do with the `lastSelectedRow` variable_. – andyb Jul 31 '13 at 09:47
  • can u please do for me i am not getting the exact reason behind after debug – ND's Jul 31 '13 at 09:53
  • 2
    A simple debugging would have revealed that the `lastSelectedRow` was being overwritten on the second click so that the "range" indices were actually the same. Also, as I already said, using ctrl+shift+click to select a range *is not* duplicating Windows. If you really can't work it out just change the first `if` condition to `if (window.event.ctrlKey && !window.event.shiftKey)` and it should work. – andyb Jul 31 '13 at 10:52
9

I made it work with all the Windows 7 explorer behaviors and jquery mouse events.

http://jsfiddle.net/ubershmekel/nUV23/6/

Note that:

  • When you just click, you set a pivot for the next shift-click
  • Use Ctrl-Shift to expand your current selection and not pivot like Shift-alone does.
  • Use Ctrl-click to add a pivot, you can use Ctrl-Shift to then expand that selection around the new pivot.

The js:

var selectionPivot;
// 1 for left button, 2 for middle, and 3 for right.
var LEFT_MOUSE_BUTTON = 1;
var trs = document.getElementById('tableStudent').tBodies[0].getElementsByTagName('tr');
var idTds = $('td:first-child');
idTds.each(function(idx, val) {
    // onselectstart because IE doesn't respect the css `user-select: none;`
    val.onselectstart = function() { return false; };
    $(val).mousedown(function(event) {
        if(event.which != LEFT_MOUSE_BUTTON) {
            return;
        }
        var row = trs[idx];
        if (!event.ctrlKey && !event.shiftKey) {
            clearAll();
            toggleRow(row);
            selectionPivot = row;
            return;
        }
        if (event.ctrlKey && event.shiftKey) {
            selectRowsBetweenIndexes(selectionPivot.rowIndex, row.rowIndex);
            return;
        }
        if (event.ctrlKey) {
            toggleRow(row);
            selectionPivot = row;
        }
        if (event.shiftKey) {
            clearAll();
            selectRowsBetweenIndexes(selectionPivot.rowIndex, row.rowIndex);
        }
    });
});

function toggleRow(row) {
    row.className = row.className == 'selected' ? '' : 'selected';
}

function selectRowsBetweenIndexes(ia, ib) {
    var bot = Math.min(ia, ib);
    var top = Math.max(ia, ib);

    for (var i = bot; i <= top; i++) {
        trs[i-1].className = 'selected';
    }
}

function clearAll() {
    for (var i = 0; i < trs.length; i++) {
        trs[i].className = '';
    }
}

And the CSS:

.selected {
    background: #bdf;
}

td:first-child {
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -o-user-select: none;
    user-select: none;
}

td,th {
    padding: 3px;
    border: 2px solid #aaa;
}

table {
    border-collapse: collapse;
}
ubershmekel
  • 11,864
  • 10
  • 72
  • 89
  • I found this very usefull and made a
    • version for those who might encounter here! http://jsfiddle.net/BerkerYuceer/cry26oja/ Thanks a lot!
    – Berker Yüceer Jun 12 '20 at 18:06
8

Here's a jQuery plugin I wrote recently for a project. Thought sharing...

Works exactly like you're used to, + it's extremely fast cause it operates over an Array without the need to check for attributes, classes etc, and the add/removeClass triggers only on the selected elements:

// Use like:
// $("table").selekt();
//
// Available options:
$("table").selekt({
  children: "tr",           // Elements to target (default: "tbody tr")
  className: "selected",    // Desired CSS class  (default: "selected")
  onSelect: function(sel) { // Useful callback
    $("span").text(sel.length + ' in ' + this.id);
  }
});
.selected { background: #0bf; }
table {border: 1px solid #555;display: inline-block; vertical-align: top;}
<p>Seleceted: <span id="info">0</span></p>

<table id="table_1">
  <tr><td>1 SELECT ME</td></tr>
  <tr><td>2 SELECT ME</td></tr>
  <tr><td>3 SELECT ME</td></tr>
  <tr><td>4 SELECT ME</td></tr>
  <tr><td>5 SELECT ME</td></tr>
  <tr><td>6 SELECT ME</td></tr>
</table>

<table id="table_2">
  <tr><td>1 SELECT ME</td></tr>
  <tr><td>2 SELECT ME</td></tr>
  <tr><td>3 SELECT ME</td></tr>
  <tr><td>4 SELECT ME</td></tr>
</table>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
;(function($) {
  // selekt jQuery plugin // http://stackoverflow.com/a/35813513/383904
  $.fn.selekt = function() {

    var settings = $.extend({
      children: "tbody tr",
      className: "selected",
      onSelect: function() {}
    }, arguments[0] || {});

    return this.each(function(_, that) {
      var $ch = $(this).find(settings.children),
        sel = [],
        last;

      $ch.on("mousedown", function(ev) {
        var isCtrl = (ev.ctrlKey || ev.metaKey),
          isShift = ev.shiftKey,
          ti = $ch.index(this),
          li = $ch.index(last),
          ai = $.inArray(this, sel);

        if (isShift || isCtrl) ev.preventDefault();

        $(sel).removeClass(settings.className);

        if (isCtrl) {
          if (ai > -1) sel.splice(ai, 1);
          else sel.push(this);
        } else if (isShift && sel.length > 0) {
          if (ti > li) ti = [li, li = ti][0];
          sel = $ch.slice(ti, li + 1);
        } else {
          sel = ai < 0 || sel.length > 1 ? [this] : [];
        }

        last = this;
        $(sel).addClass(settings.className);
        settings.onSelect.call(that, sel);
      });
    });
  };
}(jQuery));
</script>
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • 1
    Works on Safari on mac, but you should check for meta || control so that the command key works. Ctrl +shift click to add a range to an existing selection would be nice too. – Alexandre Roger Sep 13 '16 at 19:44
  • I've inherited code that uses this plugin and found it works great as long as the grid is not sorted. Selecting multiple rows after sorting seems to use the original sort order. Where does that array come from and where can I find the sorted array? – flipdoubt Apr 11 '18 at 15:34
  • @flipdoubt if you're sorting the array of DOM elements you should expand the above script to be able to destroy it and reistantiate it with the newly ordered elements. Something like `$myList.selekt('destroy')` and than do again `$myList.selekt(mySelektoptionsObject)` – Roko C. Buljan Apr 11 '18 at 16:36
  • To destroy the above plugin simply place before `var settings....` this: `if(arguments[0] === 'destroy') { return this.find('*').off('.selekt'); }` Also make sure to replace the mousedown with the appropriate namespace: `$ch.on("mousedown.selekt", function(ev) {` – Roko C. Buljan Apr 11 '18 at 17:09
0

Check this example:

JSFiddle: Highlight list with shift and ctrl

Part of the code:

switch(e.type) {
    case "keydown" :
        console.log('k_down');
        keysPressed.push(e.keyCode);
        break;
    case "keyup" :
        console.log('k_up');
        var idx = keysPressed.indexOf(e.keyCode);
        if (idx >= 0)
            keysPressed.splice(idx, 1);
        break;
}

Sources could be found here: Source files github

Sergey NN
  • 754
  • 7
  • 9
0

I know this question is already answered and it's pretty old, but I found the answer by andyb to be super helpful. Perhaps it was because andyb's answer might be outdated now, but I ended up having to change his solution a bit to work with my project, so I figured I'd share my updated version. Here is what I ended up with, using a sprinkling of jQuery.

$(document).ready(function(){
    //put all the table rows in a variable after page load to pass in to RowClick
    var trs = $('#tableStudent tr')
    //bind the click handler to all the table rows
    $('tr').on('click', function(){
        //call the RowClick function on click event
        RowClick($(this),false,trs)
    })
})

//declare variable to store the most recently clicked row
var lastSelectedRow;

// disable text selection
document.onselectstart = function() {
    return false;
}

function RowClick(currentrow, lock, rows) {
    //if control is held down, toggle the row
    if (window.event.ctrlKey) {
        toggleRow(currentrow);
    }

    //if there are no buttons held down...
    if (window.event.button === 0) {

        //if neither control or shift are held down...
        if (!window.event.ctrlKey && !window.event.shiftKey) {
            //clear selection
            clearAll(rows);
            //toggle clicked row
            toggleRow(currentrow);
        }

        //if shift is held down...
        if (window.event.shiftKey) {
            //pass the indexes of the last selected row and currently selected row along with all rows
            selectRowsBetweenIndexes([lastSelectedRow.index(), currentrow.index()], rows)
        }
    }
}

function toggleRow(row) {
    //if the row is not the header row...
    if (!row.hasClass('header-row')){
        //if the row is selected...
        if (row.hasClass('selected')){
            //deselect it
            row.removeClass('selected')
        }
        else{
            //otherwise, select it
            row.addClass('selected')
        }
        //reassign the most recently selected row
        lastSelectedRow = row;
    }
}

function selectRowsBetweenIndexes(indexes,rows) {
    //sort the indexes in ascending order
    indexes.sort(function(a, b) {
        return a - b;
    });

    //for every row starting at the first index, until the second index...
    for (var i = indexes[0]; i <= indexes[1]; i++) {
        //select the row
        $(rows[i+1]).addClass('selected');
    }
}

function clearAll(rows) {
    //for all rows...
    for (var i = 0; i < rows.length; i++) {
        //deselect each row
        $(rows[i]).removeClass("selected");
    }
}
0

Following code is modification from Robo C Buljan, since i wanted to multiselect using checkboxes and shift key

<includeScript value="/jquery-3.2.0.min.js" />
<script>
 ;(function($) {
 // selekt jQuery plugin // http://stackoverflow.com/a/35813513/383904
 $.fn.selekt = function() {
   var settings = $.extend({
       children: "td input[type='checkbox'][name='ids']",
       onSelect: function(){
       }
   }, arguments[0] || {});
   return this.each(function(_, that){
     var $ch = $(this).find(settings.children),
     sel = [],
     last;
     $ch.on("mouseup", function(ev) {
      /* Note 1: Remember this code is run when a checkbox is clicked and is run before checbox's state changes because of click 
      i.e. to say if the checkbox was checked and we clicked it to uncheck, then this event handler (mouse up)code is called before the unchecing happens */
       if(ev.shiftKey || ev.ctrlKey){
         ev.preventDefault();
         ev.stopPropagation();
       }
       var self = this;
       var ti = $ch.index(this), // index of current element in the matching elements
           li = $ch.index(last), // index of last element in the matching elements
           ai = $.inArray(this, sel); // index of this in the sel array
       if(ev.ctrlKey) {
         if(ai > -1) sel.splice(ai, 1);
         else sel.push(this);
       }
       else if(ev.shiftKey && sel.length > 0) {
         if(ti > li) ti = [li, li=ti][0];
         sel = $ch.slice(ti, li+1);
       }
       else {
         sel = ai < 0 || sel.length > 1 ? [this] : [];
       }
       last = this;
       /* purpose 2 
       code to check checkboxes inside the array*/
       $(sel).each(function(index, checkbox){
        /* see/search Note 1 in comments, if the checkbox is already checked/unchecked then uncheck/check all the elements straight from the last element correspondingly */
        if(self.checked) { 
         if( checkbox != self){
           checkbox.checked = false;
         }
        } else { 
         if( checkbox != self){
           checkbox.checked = true;
         }
        }
       })
       /*end of purpose 2*/

       // settings.onSelect.call(that, sel); // this is defined just in case we want to call some function  after the select/deselect operation
     });
   });
 };
 }(jQuery));
 setTimeout(function(){
  $("table.list").selekt();  
 },500)
 
</script>
Akshay Vijay Jain
  • 13,461
  • 8
  • 60
  • 73