New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple row selection using shift #369

Closed
adamclark64 opened this Issue Aug 11, 2015 · 42 comments

Comments

Projects
None yet
@adamclark64

adamclark64 commented Aug 11, 2015

I am was wondering about multiple row selection using shift to select all rows between the first and second row click, instead of the command click for each individual row. Fairly new otherwise I would fork and attempt to add this in

@cp2587

This comment has been minimized.

Show comment
Hide comment
@cp2587

cp2587 commented Aug 13, 2015

+1

@scotthovestadt

This comment has been minimized.

Show comment
Hide comment
@scotthovestadt

scotthovestadt Aug 15, 2015

Contributor

+1

Contributor

scotthovestadt commented Aug 15, 2015

+1

@scotthovestadt

This comment has been minimized.

Show comment
Hide comment
@scotthovestadt

scotthovestadt Aug 17, 2015

Contributor

I'm using the following code to accomplish this, but plan to submit a pull request to hopefully add support in the core module. The code works as you'd expect - if you use just SHIFT, all other rows outside of the range will be deselected. If you use CMD+SHIFT (or CTRL on Windows), other rows outside of the range will not be deselected.

function rowClicked(params) {
  var self = this;

  // We have to wait otherwise it overrides our selection
  setTimeout(function waitForAngularGridToFinish() {
    // Select multiple rows when the shift key was pressed
    if(params.event.shiftKey && self.previousSelectedRowIndex !== undefined) {
      var smallerNumber = params.rowIndex < self.previousSelectedRowIndex ? params.rowIndex : self.previousSelectedRowIndex;
      var biggerNumber = params.rowIndex > self.previousSelectedRowIndex ? params.rowIndex : self.previousSelectedRowIndex;

      for(var rowIndexToSelect = smallerNumber; rowIndexToSelect <= biggerNumber; rowIndexToSelect++) {
        self.api.selectIndex(rowIndexToSelect, true, false);
      }
    }

    self.previousSelectedRowIndex = params.rowIndex;
  }, 0);
}
Contributor

scotthovestadt commented Aug 17, 2015

I'm using the following code to accomplish this, but plan to submit a pull request to hopefully add support in the core module. The code works as you'd expect - if you use just SHIFT, all other rows outside of the range will be deselected. If you use CMD+SHIFT (or CTRL on Windows), other rows outside of the range will not be deselected.

function rowClicked(params) {
  var self = this;

  // We have to wait otherwise it overrides our selection
  setTimeout(function waitForAngularGridToFinish() {
    // Select multiple rows when the shift key was pressed
    if(params.event.shiftKey && self.previousSelectedRowIndex !== undefined) {
      var smallerNumber = params.rowIndex < self.previousSelectedRowIndex ? params.rowIndex : self.previousSelectedRowIndex;
      var biggerNumber = params.rowIndex > self.previousSelectedRowIndex ? params.rowIndex : self.previousSelectedRowIndex;

      for(var rowIndexToSelect = smallerNumber; rowIndexToSelect <= biggerNumber; rowIndexToSelect++) {
        self.api.selectIndex(rowIndexToSelect, true, false);
      }
    }

    self.previousSelectedRowIndex = params.rowIndex;
  }, 0);
}
@scotthovestadt

This comment has been minimized.

Show comment
Hide comment
@scotthovestadt

scotthovestadt Aug 17, 2015

Contributor

@ceolter I think that if multiple row selection is enabled, the SHIFT key multiple selection should be enabled by default and it shouldn't be a parameter. Do you agree?

Contributor

scotthovestadt commented Aug 17, 2015

@ceolter I think that if multiple row selection is enabled, the SHIFT key multiple selection should be enabled by default and it shouldn't be a parameter. Do you agree?

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Aug 17, 2015

Contributor

@scotthovestadt yes, shift parameter not neccessary.

Contributor

ceolter commented Aug 17, 2015

@scotthovestadt yes, shift parameter not neccessary.

@micabot

This comment has been minimized.

Show comment
Hide comment
@micabot

micabot Sep 25, 2015

+1
Any news on this?

micabot commented Sep 25, 2015

+1
Any news on this?

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Sep 25, 2015

Contributor

nope, on the backlog :(

Contributor

ceolter commented Sep 25, 2015

nope, on the backlog :(

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Oct 9, 2015

Contributor

Awmagawd I'd really love to have that feature on my grid. Definitely +1! (Any words on this @scotthovestadt ?)

Contributor

TFrascaroli commented Oct 9, 2015

Awmagawd I'd really love to have that feature on my grid. Definitely +1! (Any words on this @scotthovestadt ?)

@asfordmatt

This comment has been minimized.

Show comment
Hide comment
@asfordmatt

asfordmatt Nov 11, 2015

+1
This would be really nice, it is what most users expect with multi-select

asfordmatt commented Nov 11, 2015

+1
This would be really nice, it is what most users expect with multi-select

@vladimir-ivanov

This comment has been minimized.

Show comment
Hide comment
@vladimir-ivanov

vladimir-ivanov commented Nov 12, 2015

+1

@NisStrom

This comment has been minimized.

Show comment
Hide comment
@NisStrom

NisStrom commented Jan 20, 2016

+1

@jackyjieliu

This comment has been minimized.

Show comment
Hide comment
@jackyjieliu

jackyjieliu commented Jan 20, 2016

+1

@russell-miller

This comment has been minimized.

Show comment
Hide comment
@russell-miller

russell-miller Jan 29, 2016

Contributor

+1

Contributor

russell-miller commented Jan 29, 2016

+1

@killyosaur

This comment has been minimized.

Show comment
Hide comment
@killyosaur

killyosaur Feb 8, 2016

+1. Any ETA on when you'll be looking into adding this?

killyosaur commented Feb 8, 2016

+1. Any ETA on when you'll be looking into adding this?

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Feb 8, 2016

Contributor

no. doesn't meant not soon, just not ETA right now.

Contributor

ceolter commented Feb 8, 2016

no. doesn't meant not soon, just not ETA right now.

@anticommander

This comment has been minimized.

Show comment
Hide comment
@anticommander

anticommander Mar 8, 2016

I put together a pretty simple workaround given what's available in the existing API:

    $scope.gridOptions = {
      // Store app-specific parameters / functions here so they're accessible
      // within this object
      appSpecific:{},
      columnDefs:columnDefs,
      rowData:null,
      enableSorting:true,
      rowSelection:'multiple',
      suppressRowClickSelection:true,
      onRowClicked:function(row) {
        var lastSelectedRow = this.appSpecific.lastSelectedRow;
        var shiftKey = row.event.shiftKey,
          ctrlKey = row.event.ctrlKey;

        // If modifier keys aren't pressed then only select the row that was clicked
        if(!shiftKey && !ctrlKey) {
          this.api.deselectAll();
          this.api.selectNode(row.node, true);
        }
        // If modifier keys are used and there was a previously selected row
        else if(lastSelectedRow !== undefined) {
          // Select a block of rows
          if(shiftKey) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if(row.rowIndex < lastSelectedRow.rowIndex) {
              startIndex = row.rowIndex;
              endIndex = lastSelectedRow.rowIndex;
            }
            else {
              startIndex = lastSelectedRow.rowIndex;
              endIndex = row.rowIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for(var i = startIndex; i <= endIndex; i++) {
              this.api.selectIndex(i, true, true);
            }
          }
          // Select one more row
          if(ctrlKey) {
            this.api.selectIndex(row.rowIndex, true);
          }
        }
        // Store the recently clicked row for future use
        this.appSpecific.lastSelectedRow = row;
      },
      onBeforeSortChanged:function() {
        // Be sure to clear out the row selections on sort change since
        // row indexes will change
        this.api.deselectAll();
        this.appSpecific.lastSelectedRow = undefined;
      }
    };

anticommander commented Mar 8, 2016

I put together a pretty simple workaround given what's available in the existing API:

    $scope.gridOptions = {
      // Store app-specific parameters / functions here so they're accessible
      // within this object
      appSpecific:{},
      columnDefs:columnDefs,
      rowData:null,
      enableSorting:true,
      rowSelection:'multiple',
      suppressRowClickSelection:true,
      onRowClicked:function(row) {
        var lastSelectedRow = this.appSpecific.lastSelectedRow;
        var shiftKey = row.event.shiftKey,
          ctrlKey = row.event.ctrlKey;

        // If modifier keys aren't pressed then only select the row that was clicked
        if(!shiftKey && !ctrlKey) {
          this.api.deselectAll();
          this.api.selectNode(row.node, true);
        }
        // If modifier keys are used and there was a previously selected row
        else if(lastSelectedRow !== undefined) {
          // Select a block of rows
          if(shiftKey) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if(row.rowIndex < lastSelectedRow.rowIndex) {
              startIndex = row.rowIndex;
              endIndex = lastSelectedRow.rowIndex;
            }
            else {
              startIndex = lastSelectedRow.rowIndex;
              endIndex = row.rowIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for(var i = startIndex; i <= endIndex; i++) {
              this.api.selectIndex(i, true, true);
            }
          }
          // Select one more row
          if(ctrlKey) {
            this.api.selectIndex(row.rowIndex, true);
          }
        }
        // Store the recently clicked row for future use
        this.appSpecific.lastSelectedRow = row;
      },
      onBeforeSortChanged:function() {
        // Be sure to clear out the row selections on sort change since
        // row indexes will change
        this.api.deselectAll();
        this.appSpecific.lastSelectedRow = undefined;
      }
    };
@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 9, 2016

Contributor

That looks really nice! I'll try that out, because I'm desperate to have the functionality, and report back my findings. In the meantime, why don't you put it all together as a PR?

Contributor

TFrascaroli commented Mar 9, 2016

That looks really nice! I'll try that out, because I'm desperate to have the functionality, and report back my findings. In the meantime, why don't you put it all together as a PR?

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 9, 2016

Contributor

After a few trial and error I've got it working. It pains me to say that this has already been implemented by the author, but it's now under a commercial (paid) license. But, for those of you that want to continue using the free stuff, it's going to come in handy.

I've done some changes, the reasoning behind them is written in comments above the corresponding lines, starting with //CHANGED:

onRowClicked:function(row) {
                var lastSelectedRow = this.appSpecific.lastSelectedRow;
                var shiftKey = row.event.shiftKey,
                  ctrlKey = row.event.ctrlKey;

                // If modifier keys aren't pressed then only select the row that was clicked
                if(!shiftKey && !ctrlKey) {
                    //Removed the this.api.deselectAll(); since we can just tell the new selection to
                    //clear all previously selected rows (by passing a true in the second parameter)
                    //CHANGED: this.api.selectNode(row.node, true) to the following as per recomendation
                    //of the author (deprecation notice)
                    row.node.setSelected(true, true);
                }
                    // If modifier keys are used and there was a previously selected row
                else if(lastSelectedRow !== undefined) {
                    // Select a block of rows
                    if(shiftKey) {
                        var startIndex, endIndex;
                        // Get our start and end indexes correct
                        if(row.rowIndex < lastSelectedRow.rowIndex) {
                            startIndex = row.rowIndex;
                            endIndex = lastSelectedRow.rowIndex;
                        }
                        else {
                            startIndex = lastSelectedRow.rowIndex;
                            endIndex = row.rowIndex;
                        }
                        // Select all the rows between the previously selected row and the 
                        // newly clicked row
                        //CHANGED: `i <= endIndex` to the following so we skip the events callbacks (with the
                        //third argument `true`) in all but the last iteration
                        for(var i = startIndex; i < endIndex; i++) {
                            this.api.selectIndex(i, true, true);
                        }
                        //And we make the last one with a `false` so we get the event popped up.
                        this.api.selectIndex(i, true, false);
                    }
                    // Select one more row
                    if (ctrlKey) {
                        //CHANGED: this.api.selectNode(row.node, true) to the following as per recomendation
                        //of the author (deprecation notice)
                        row.node.setSelected(true);
                    }
                }
                // Store the recently clicked row for future use
                this.appSpecific.lastSelectedRow = row;
            },
Contributor

TFrascaroli commented Mar 9, 2016

After a few trial and error I've got it working. It pains me to say that this has already been implemented by the author, but it's now under a commercial (paid) license. But, for those of you that want to continue using the free stuff, it's going to come in handy.

I've done some changes, the reasoning behind them is written in comments above the corresponding lines, starting with //CHANGED:

onRowClicked:function(row) {
                var lastSelectedRow = this.appSpecific.lastSelectedRow;
                var shiftKey = row.event.shiftKey,
                  ctrlKey = row.event.ctrlKey;

                // If modifier keys aren't pressed then only select the row that was clicked
                if(!shiftKey && !ctrlKey) {
                    //Removed the this.api.deselectAll(); since we can just tell the new selection to
                    //clear all previously selected rows (by passing a true in the second parameter)
                    //CHANGED: this.api.selectNode(row.node, true) to the following as per recomendation
                    //of the author (deprecation notice)
                    row.node.setSelected(true, true);
                }
                    // If modifier keys are used and there was a previously selected row
                else if(lastSelectedRow !== undefined) {
                    // Select a block of rows
                    if(shiftKey) {
                        var startIndex, endIndex;
                        // Get our start and end indexes correct
                        if(row.rowIndex < lastSelectedRow.rowIndex) {
                            startIndex = row.rowIndex;
                            endIndex = lastSelectedRow.rowIndex;
                        }
                        else {
                            startIndex = lastSelectedRow.rowIndex;
                            endIndex = row.rowIndex;
                        }
                        // Select all the rows between the previously selected row and the 
                        // newly clicked row
                        //CHANGED: `i <= endIndex` to the following so we skip the events callbacks (with the
                        //third argument `true`) in all but the last iteration
                        for(var i = startIndex; i < endIndex; i++) {
                            this.api.selectIndex(i, true, true);
                        }
                        //And we make the last one with a `false` so we get the event popped up.
                        this.api.selectIndex(i, true, false);
                    }
                    // Select one more row
                    if (ctrlKey) {
                        //CHANGED: this.api.selectNode(row.node, true) to the following as per recomendation
                        //of the author (deprecation notice)
                        row.node.setSelected(true);
                    }
                }
                // Store the recently clicked row for future use
                this.appSpecific.lastSelectedRow = row;
            },
@anticommander

This comment has been minimized.

Show comment
Hide comment
@anticommander

anticommander Mar 9, 2016

@TFrascaroli thanks for adding some fixes to my initial implementation. If you'd like to make a PR on my behalf I'd be more than happy to let you do so since you've improved the code.

anticommander commented Mar 9, 2016

@TFrascaroli thanks for adding some fixes to my initial implementation. If you'd like to make a PR on my behalf I'd be more than happy to let you do so since you've improved the code.

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 9, 2016

Contributor

Nah, I really don't care about "I did it, I should get the credit". Furthermore, I don't think it would pass Niall's approval since he clearly states in his project .md that

PR's on new features are not generally accepted.

And third I'm kinda bussy right now 😄

Glad to help 👍

Contributor

TFrascaroli commented Mar 9, 2016

Nah, I really don't care about "I did it, I should get the credit". Furthermore, I don't think it would pass Niall's approval since he clearly states in his project .md that

PR's on new features are not generally accepted.

And third I'm kinda bussy right now 😄

Glad to help 👍

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 9, 2016

Contributor

Oh, and I found something else, change

if (ctrlKey) {
    row.node.setSelected(true);
}

to

if (ctrlKey) {
    if (this.api.gridOptionsWrapper.gridOptions.rowDeselection) {
        row.node.setSelected(!row.node.selected);
    } else {
        row.node.setSelected(true);
    }
}

So it emulates the deselection mecanism intended by the author (https://www.ag-grid.com/angular-grid-selection/index.php)

Contributor

TFrascaroli commented Mar 9, 2016

Oh, and I found something else, change

if (ctrlKey) {
    row.node.setSelected(true);
}

to

if (ctrlKey) {
    if (this.api.gridOptionsWrapper.gridOptions.rowDeselection) {
        row.node.setSelected(!row.node.selected);
    } else {
        row.node.setSelected(true);
    }
}

So it emulates the deselection mecanism intended by the author (https://www.ag-grid.com/angular-grid-selection/index.php)

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Mar 10, 2016

Contributor

I don't think it would pass Niall's approval you make me sound like a difficult person to work with!!!

re free vs enterprise, all the selection stuff is in the free version.

Contributor

ceolter commented Mar 10, 2016

I don't think it would pass Niall's approval you make me sound like a difficult person to work with!!!

re free vs enterprise, all the selection stuff is in the free version.

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 10, 2016

Contributor

I'm sorry if I made you feel that way, not what I intended. I just stated that because I thought you just would discard the hipothetical PR as "you're doing too much" (since you don't want the project to ultimatelly be a community project).

free vs enterprise, all the selection stuff is in the free version

Well, my bad.... but I don't know why I can't get it to work, neither in free nor in enterprise (I'm actually using this code here to make it work). I'll look into it.

Contributor

TFrascaroli commented Mar 10, 2016

I'm sorry if I made you feel that way, not what I intended. I just stated that because I thought you just would discard the hipothetical PR as "you're doing too much" (since you don't want the project to ultimatelly be a community project).

free vs enterprise, all the selection stuff is in the free version

Well, my bad.... but I don't know why I can't get it to work, neither in free nor in enterprise (I'm actually using this code here to make it work). I'll look into it.

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Mar 11, 2016

Contributor

@TFrascaroli lol - no offence taken at all my friend, you brought a smile to my face!! :)

Contributor

ceolter commented Mar 11, 2016

@TFrascaroli lol - no offence taken at all my friend, you brought a smile to my face!! :)

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 16, 2016

Contributor

@ceolter I'm back to spice this a little bit more.
In your docs you talk about selection, multiple selection and all that stuff (https://www.ag-grid.com/angular-grid-selection/index.php), but nowhere in that page it either implies or states that multiple range selection (the usual "CLICK + SHIFT + CLICK") works. And in fact, it doesn't (neither in your web nor in the many test cases I've put up). Therefore I', using my modified version of @anticommander 's code to make it work. No problems here.

The problem is that I now want to use enableRangeSelection (https://www.ag-grid.com/angular-grid-range-selection/index.php) and it doesn't work with my current setup. I think that, since I'm replacing the onRowClicked callback, it is absolutely foreseeable that it wouldn't work.

That brings up my question: will you implement click + shift + click selection in the future? The answer has been given, I know, just making sure. If the answer is still yes, will it be mutually exclusive with enableRangeSelection?
In the meantime I'll treat them as such (mutually exclusive) and disable/enable one or the other.

Contributor

TFrascaroli commented Mar 16, 2016

@ceolter I'm back to spice this a little bit more.
In your docs you talk about selection, multiple selection and all that stuff (https://www.ag-grid.com/angular-grid-selection/index.php), but nowhere in that page it either implies or states that multiple range selection (the usual "CLICK + SHIFT + CLICK") works. And in fact, it doesn't (neither in your web nor in the many test cases I've put up). Therefore I', using my modified version of @anticommander 's code to make it work. No problems here.

The problem is that I now want to use enableRangeSelection (https://www.ag-grid.com/angular-grid-range-selection/index.php) and it doesn't work with my current setup. I think that, since I'm replacing the onRowClicked callback, it is absolutely foreseeable that it wouldn't work.

That brings up my question: will you implement click + shift + click selection in the future? The answer has been given, I know, just making sure. If the answer is still yes, will it be mutually exclusive with enableRangeSelection?
In the meantime I'll treat them as such (mutually exclusive) and disable/enable one or the other.

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Mar 23, 2016

Contributor

yes i still plan to implement multi row selection. i haven't thought about range selection - but your right, they don't work well together from a usability. i'll need to think it through when i implement as to how they will work together.

personally i would only use one of click selection and range selection.

Contributor

ceolter commented Mar 23, 2016

yes i still plan to implement multi row selection. i haven't thought about range selection - but your right, they don't work well together from a usability. i'll need to think it through when i implement as to how they will work together.

personally i would only use one of click selection and range selection.

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 24, 2016

Contributor

Ok, I think I have a pretty good idea now of what should happen when you add all of this to the mix. I'll try to make a PR with some ideas and let you all know so you can all try it out. Obviously, you (@ceolter) don't have to merge the PR, but it can serve as a basis for your final implementation.

Contributor

TFrascaroli commented Mar 24, 2016

Ok, I think I have a pretty good idea now of what should happen when you add all of this to the mix. I'll try to make a PR with some ideas and let you all know so you can all try it out. Obviously, you (@ceolter) don't have to merge the PR, but it can serve as a basis for your final implementation.

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Mar 24, 2016

Contributor

@TFrascaroli :)

this is deffo high on my list. if you raise a PR, it will be a kick in the bum for me to get it done!

Contributor

ceolter commented Mar 24, 2016

@TFrascaroli :)

this is deffo high on my list. if you raise a PR, it will be a kick in the bum for me to get it done!

@killyosaur

This comment has been minimized.

Show comment
Hide comment
@killyosaur

killyosaur Mar 30, 2016

While you are working on this, keep in mind that this should work when a user is only using check box selection as well as on row click. The above solution doesn't work with a setup that has row click selection disabled. (or it overrides the suppression of the row click selection, and considering the onRowClick event does not fire when a row is selected through a check box, this can be a bit of a pain.)

killyosaur commented Mar 30, 2016

While you are working on this, keep in mind that this should work when a user is only using check box selection as well as on row click. The above solution doesn't work with a setup that has row click selection disabled. (or it overrides the suppression of the row click selection, and considering the onRowClick event does not fire when a row is selected through a check box, this can be a bit of a pain.)

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Mar 30, 2016

Contributor

You're absolutely right, sir. I missed this completely. I won't be able to
work on this until the weekend, but I can tell you I'll be doing thing much
differently than this approach. I won't try to build a path (like in this
case) but rather build the functionality in the grid itself, and maybe
@ceolter can review and merge the PR. We'll see.... looks like we're all
bussy 😄
El 30/3/2016 22:43, "killyosaur" notifications@github.com escribió:

While you are working on this, keep in mind that this should work when a
user is only using check box selection as well as on row click. The above
solution doesn't work with a setup that has row click selection disabled.
(or it overrides the suppression of the row click selection, and
considering the onRowClick event does not fire when a row is selected
through a check box, this can be a bit of a pain.)


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#369 (comment)

Contributor

TFrascaroli commented Mar 30, 2016

You're absolutely right, sir. I missed this completely. I won't be able to
work on this until the weekend, but I can tell you I'll be doing thing much
differently than this approach. I won't try to build a path (like in this
case) but rather build the functionality in the grid itself, and maybe
@ceolter can review and merge the PR. We'll see.... looks like we're all
bussy 😄
El 30/3/2016 22:43, "killyosaur" notifications@github.com escribió:

While you are working on this, keep in mind that this should work when a
user is only using check box selection as well as on row click. The above
solution doesn't work with a setup that has row click selection disabled.
(or it overrides the suppression of the row click selection, and
considering the onRowClick event does not fire when a row is selected
through a check box, this can be a bit of a pain.)


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#369 (comment)

@clintb

This comment has been minimized.

Show comment
Hide comment
@clintb

clintb Mar 31, 2016

@ceolter, @TFrascaroli; I hope this helps.

// Use this if you want enable users to have 
// "SELECT, (shift, control, or both), SELECT" 
// for range selection.

// This configuration assumes you only care about 
// selections via the checkboxes.

// This made it so I don't have to fool with onRowClicked at all.




// [BEGIN] capture key events
var _window = window;

if (!_window.keyvents) {

    _window.keyvents = {
        alt: false,
        shift: false,
        ctrl:false
    };

    // yes, I took the easy way out (jQuery) for key events. So what. :)
    $(window).keydown(function(ev) {
        _window.keyvents.alt = ev.altKey;
        _window.keyvents.shift = ev.shiftKey;
        _window.keyvents.ctrl = ev.ctrlKey;
    });

    $(window).keyup(function() {
        _window.keyvents.alt = false;
        _window.keyvents.shift = false;
        _window.keyvents.ctrl = false;
    });


}
// [END] capture key events


var f_handleRangeSelection = function(grid, event, individualSelectTask) {


    if (individualSelectTask === undefined) individualSelectTask = function() {};

    if (grid.selectingRange === true) {
        individualSelectTask();
        return;
    }

    var node = event.node;
    var select = node.selected;

    var lastSelectedRow = grid.appSpecific.lastSelectedRow;

    var selectRange = _window.keyvents.shift || _window.keyvents.ctrl;

    grid.selectingRange = selectRange;

    // If modifier keys aren't pressed then only select the row that was clicked
    if (selectRange === false) {
        individualSelectTask();
    }
    // If modifier keys are used and there was a previously selected row
    else if (lastSelectedRow !== undefined) {
        // Select a block of rows
        if (selectRange === true) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if (node.childIndex < lastSelectedRow.childIndex) {
                startIndex = node.childIndex;
                endIndex = lastSelectedRow.childIndex;
            } else {
                startIndex = lastSelectedRow.childIndex;
                endIndex = node.childIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for (var i = startIndex; i < endIndex; i++) {
                grid.api.selectIndex(i, select, true);
            }

            individualSelectTask();
        }
    }
    // Store the recently clicked row for future use
    grid.appSpecific.lastSelectedRow = node;
    grid.selectingRange = false;
};

var gridOptions = {
    selectingRange: false, // custom property for this purpose
    appSpecific: {}, // part of what anticommander originally contributed
    rowData: null,
    rowBuffer: 5,
    rowSelection: 'multiple',
    suppressRowClickSelection: true, // I do this because I only want selection (as well as range selection) to happen via the checkboxes
    onRowSelected: function (event) {

        // basically, wrap whatever you were going to do for 
        //'onRowSelected' with your handling of range selection
        f_handleRangeSelection(this, event, function() {

            // [BEGIN] whatever you were going to do...


            //event.node.data.selected = event.node.selected;
            //if (event.node.gridOptionsWrapper.gridOptions.slaveGrids[0]) {
            //  scope.selectRow(this.slaveGrids[0], event.node.id, event.node.selected);
            //  scope.setFilter(this.slaveGrids[0]);
            //}
            //scope.setFilter(event.node.gridOptionsWrapper.gridOptions);
            //updateRollingNumbers();


            // [END] whatever you were going to do...


        });


    },
    onBeforeSortChanged: function () {
        // Be sure to clear out the last selected row on sort change since
        // row indexes will change
        this.appSpecific.lastSelectedRow = undefined;
    },
    columnDefs: [
        { headerName: '', suppressMenu: true, checkboxSelection: true, maxWidth: 30, suppressSorting: true },
        { headerName: 'Make', field: 'make' },
        { headerName: 'Model', field: 'model' },
        { headerName: 'Year', field: 'year' }
    ]
};              

clintb commented Mar 31, 2016

@ceolter, @TFrascaroli; I hope this helps.

// Use this if you want enable users to have 
// "SELECT, (shift, control, or both), SELECT" 
// for range selection.

// This configuration assumes you only care about 
// selections via the checkboxes.

// This made it so I don't have to fool with onRowClicked at all.




// [BEGIN] capture key events
var _window = window;

if (!_window.keyvents) {

    _window.keyvents = {
        alt: false,
        shift: false,
        ctrl:false
    };

    // yes, I took the easy way out (jQuery) for key events. So what. :)
    $(window).keydown(function(ev) {
        _window.keyvents.alt = ev.altKey;
        _window.keyvents.shift = ev.shiftKey;
        _window.keyvents.ctrl = ev.ctrlKey;
    });

    $(window).keyup(function() {
        _window.keyvents.alt = false;
        _window.keyvents.shift = false;
        _window.keyvents.ctrl = false;
    });


}
// [END] capture key events


var f_handleRangeSelection = function(grid, event, individualSelectTask) {


    if (individualSelectTask === undefined) individualSelectTask = function() {};

    if (grid.selectingRange === true) {
        individualSelectTask();
        return;
    }

    var node = event.node;
    var select = node.selected;

    var lastSelectedRow = grid.appSpecific.lastSelectedRow;

    var selectRange = _window.keyvents.shift || _window.keyvents.ctrl;

    grid.selectingRange = selectRange;

    // If modifier keys aren't pressed then only select the row that was clicked
    if (selectRange === false) {
        individualSelectTask();
    }
    // If modifier keys are used and there was a previously selected row
    else if (lastSelectedRow !== undefined) {
        // Select a block of rows
        if (selectRange === true) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if (node.childIndex < lastSelectedRow.childIndex) {
                startIndex = node.childIndex;
                endIndex = lastSelectedRow.childIndex;
            } else {
                startIndex = lastSelectedRow.childIndex;
                endIndex = node.childIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for (var i = startIndex; i < endIndex; i++) {
                grid.api.selectIndex(i, select, true);
            }

            individualSelectTask();
        }
    }
    // Store the recently clicked row for future use
    grid.appSpecific.lastSelectedRow = node;
    grid.selectingRange = false;
};

var gridOptions = {
    selectingRange: false, // custom property for this purpose
    appSpecific: {}, // part of what anticommander originally contributed
    rowData: null,
    rowBuffer: 5,
    rowSelection: 'multiple',
    suppressRowClickSelection: true, // I do this because I only want selection (as well as range selection) to happen via the checkboxes
    onRowSelected: function (event) {

        // basically, wrap whatever you were going to do for 
        //'onRowSelected' with your handling of range selection
        f_handleRangeSelection(this, event, function() {

            // [BEGIN] whatever you were going to do...


            //event.node.data.selected = event.node.selected;
            //if (event.node.gridOptionsWrapper.gridOptions.slaveGrids[0]) {
            //  scope.selectRow(this.slaveGrids[0], event.node.id, event.node.selected);
            //  scope.setFilter(this.slaveGrids[0]);
            //}
            //scope.setFilter(event.node.gridOptionsWrapper.gridOptions);
            //updateRollingNumbers();


            // [END] whatever you were going to do...


        });


    },
    onBeforeSortChanged: function () {
        // Be sure to clear out the last selected row on sort change since
        // row indexes will change
        this.appSpecific.lastSelectedRow = undefined;
    },
    columnDefs: [
        { headerName: '', suppressMenu: true, checkboxSelection: true, maxWidth: 30, suppressSorting: true },
        { headerName: 'Make', field: 'make' },
        { headerName: 'Model', field: 'model' },
        { headerName: 'Year', field: 'year' }
    ]
};              
@clintb

This comment has been minimized.

Show comment
Hide comment
@clintb

clintb Mar 31, 2016

Doh! It doesn't like using 'selectIndex'.

So, I modified the for loop in the range selection handler. Also, made it work in either direction (select or unselect).

// Use this if you want enable users to have 
// "SELECT, (shift, control, or both), SELECT" 
// for range selection.

// This configuration assumes you only care about 
// selections via the checkboxes.

// This made it so I don't have to fool with onRowClicked at all.




// [BEGIN] capture key events
var _window = window;

if (!_window.keyvents) {

    _window.keyvents = {
        alt: false,
        shift: false,
        ctrl:false
    };

    // yes, I took the easy way out (jQuery) for key events. So what.
    $(window).keydown(function(ev) {
        _window.keyvents.alt = ev.altKey;
        _window.keyvents.shift = ev.shiftKey;
        _window.keyvents.ctrl = ev.ctrlKey;
    });

    $(window).keyup(function() {
        _window.keyvents.alt = false;
        _window.keyvents.shift = false;
        _window.keyvents.ctrl = false;
    });


}
// [END] capture key events


var f_handleRangeSelection = function(grid, event, individualSelectTask) {


    if (individualSelectTask === undefined) individualSelectTask = function() {};

    if (grid.selectingRange === true) {
        individualSelectTask();
        return;
    }

    var node = event.node;
    var select = node.selected;

    var lastSelectedRow = grid.appSpecific.lastSelectedRow;

    var selectRange = _window.keyvents.shift || _window.keyvents.ctrl;

    grid.selectingRange = selectRange;

    // If modifier keys aren't pressed then only select the row that was clicked
    if (selectRange === false) {
        individualSelectTask();
    }
    // If modifier keys are used and there was a previously selected row
    else if (lastSelectedRow !== undefined) {
        // Select a block of rows
        if (selectRange === true) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if (node.childIndex < lastSelectedRow.childIndex) {
                startIndex = node.childIndex;
                endIndex = lastSelectedRow.childIndex;
            } else {
                startIndex = lastSelectedRow.childIndex;
                endIndex = node.childIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for (var i = startIndex; i < endIndex; i++) {
                grid.api.rowModel.getRow(i).setSelected(select, false, true); // [CHANGED]
            }

            individualSelectTask();
        }
    }
    // Store the recently clicked row for future use
    grid.appSpecific.lastSelectedRow = node;
    grid.selectingRange = false;
};

var gridOptions = {
    selectingRange: false, // custom property for this purpose
    appSpecific: {}, // part of what anticommander originally contributed
    rowData: null,
    rowBuffer: 5,
    rowSelection: 'multiple',
    suppressRowClickSelection: true, // I do this because I only want selection (as well as range selection) to happen via the checkboxes
    onRowSelected: function (event) {

        // basically, wrap whatever you were going to do for 
        //'onRowSelected' with your handling of range selection
        f_handleRangeSelection(this, event, function() {

            // [BEGIN] whatever you were going to do...


            //event.node.data.selected = event.node.selected;
            //if (event.node.gridOptionsWrapper.gridOptions.slaveGrids[0]) {
            //  scope.selectRow(this.slaveGrids[0], event.node.id, event.node.selected);
            //  scope.setFilter(this.slaveGrids[0]);
            //}
            //scope.setFilter(event.node.gridOptionsWrapper.gridOptions);
            //updateRollingNumbers();


            // [END] whatever you were going to do...


        });


    },
    onBeforeSortChanged: function () {
        // Be sure to clear out the last selected row on sort change since
        // row indexes will change
        this.appSpecific.lastSelectedRow = undefined;
    },
    columnDefs: [
        { headerName: '', suppressMenu: true, checkboxSelection: true, maxWidth: 30, suppressSorting: true },
        { headerName: 'Make', field: 'make' },
        { headerName: 'Model', field: 'model' },
        { headerName: 'Year', field: 'year' }
    ]
};              

clintb commented Mar 31, 2016

Doh! It doesn't like using 'selectIndex'.

So, I modified the for loop in the range selection handler. Also, made it work in either direction (select or unselect).

// Use this if you want enable users to have 
// "SELECT, (shift, control, or both), SELECT" 
// for range selection.

// This configuration assumes you only care about 
// selections via the checkboxes.

// This made it so I don't have to fool with onRowClicked at all.




// [BEGIN] capture key events
var _window = window;

if (!_window.keyvents) {

    _window.keyvents = {
        alt: false,
        shift: false,
        ctrl:false
    };

    // yes, I took the easy way out (jQuery) for key events. So what.
    $(window).keydown(function(ev) {
        _window.keyvents.alt = ev.altKey;
        _window.keyvents.shift = ev.shiftKey;
        _window.keyvents.ctrl = ev.ctrlKey;
    });

    $(window).keyup(function() {
        _window.keyvents.alt = false;
        _window.keyvents.shift = false;
        _window.keyvents.ctrl = false;
    });


}
// [END] capture key events


var f_handleRangeSelection = function(grid, event, individualSelectTask) {


    if (individualSelectTask === undefined) individualSelectTask = function() {};

    if (grid.selectingRange === true) {
        individualSelectTask();
        return;
    }

    var node = event.node;
    var select = node.selected;

    var lastSelectedRow = grid.appSpecific.lastSelectedRow;

    var selectRange = _window.keyvents.shift || _window.keyvents.ctrl;

    grid.selectingRange = selectRange;

    // If modifier keys aren't pressed then only select the row that was clicked
    if (selectRange === false) {
        individualSelectTask();
    }
    // If modifier keys are used and there was a previously selected row
    else if (lastSelectedRow !== undefined) {
        // Select a block of rows
        if (selectRange === true) {
            var startIndex, endIndex;
            // Get our start and end indexes correct
            if (node.childIndex < lastSelectedRow.childIndex) {
                startIndex = node.childIndex;
                endIndex = lastSelectedRow.childIndex;
            } else {
                startIndex = lastSelectedRow.childIndex;
                endIndex = node.childIndex;
            }
            // Select all the rows between the previously selected row and the 
            // newly clicked row
            for (var i = startIndex; i < endIndex; i++) {
                grid.api.rowModel.getRow(i).setSelected(select, false, true); // [CHANGED]
            }

            individualSelectTask();
        }
    }
    // Store the recently clicked row for future use
    grid.appSpecific.lastSelectedRow = node;
    grid.selectingRange = false;
};

var gridOptions = {
    selectingRange: false, // custom property for this purpose
    appSpecific: {}, // part of what anticommander originally contributed
    rowData: null,
    rowBuffer: 5,
    rowSelection: 'multiple',
    suppressRowClickSelection: true, // I do this because I only want selection (as well as range selection) to happen via the checkboxes
    onRowSelected: function (event) {

        // basically, wrap whatever you were going to do for 
        //'onRowSelected' with your handling of range selection
        f_handleRangeSelection(this, event, function() {

            // [BEGIN] whatever you were going to do...


            //event.node.data.selected = event.node.selected;
            //if (event.node.gridOptionsWrapper.gridOptions.slaveGrids[0]) {
            //  scope.selectRow(this.slaveGrids[0], event.node.id, event.node.selected);
            //  scope.setFilter(this.slaveGrids[0]);
            //}
            //scope.setFilter(event.node.gridOptionsWrapper.gridOptions);
            //updateRollingNumbers();


            // [END] whatever you were going to do...


        });


    },
    onBeforeSortChanged: function () {
        // Be sure to clear out the last selected row on sort change since
        // row indexes will change
        this.appSpecific.lastSelectedRow = undefined;
    },
    columnDefs: [
        { headerName: '', suppressMenu: true, checkboxSelection: true, maxWidth: 30, suppressSorting: true },
        { headerName: 'Make', field: 'make' },
        { headerName: 'Model', field: 'model' },
        { headerName: 'Year', field: 'year' }
    ]
};              
@apala5

This comment has been minimized.

Show comment
Hide comment
@apala5

apala5 Apr 13, 2016

To overcome the issue mentioned by @TFrascaroli. I just modified his code and add a one line change the ag-grid.js file.

I tested with multiple selection, single row selection, check box selection, check box multi selection and everything works good.

This solution will work whether suppressRowClickSelection is enabled or not.

Basically the row selection event will work as per the grid native functionality, Just registered the shift key as one of the multi selected key in ag-grid which will prevent deselection of a selected row.

//In ag-grid.js file modify the the below function to add the event.shiftKey in or condition.
RenderedRow.prototype.onRowClicked = function (event) {
      var multiSelectKeyPressed = event.ctrlKey || event.metaKey || event.shiftKey;
}

//In your js file add the onRowClicked evetn for the grid
onRowClicked: function (row) {               
                var lastSelectedRow = this.appSpecific.lastSelectedRow;
                var shiftKey = row.event.shiftKey,
                  ctrlKey = row.event.ctrlKey;

                // If modifier keys aren't pressed then only select the row that was clicked
                //if (!shiftKey && !ctrlKey) {
                //    this.api.deselectAll();
                //    this.api.selectNode(row.node, true);
                //}
                    // If modifier keys are used and there was a previously selected row
                 if (lastSelectedRow !== undefined) {
                    // Select a block of rows
                    if (shiftKey) {
                        var startIndex, endIndex;
                        // Get our start and end indexes correct
                        if (row.rowIndex < lastSelectedRow.rowIndex) {
                            startIndex = row.rowIndex;
                            endIndex = lastSelectedRow.rowIndex;
                        }
                        else {
                            startIndex = lastSelectedRow.rowIndex;
                            endIndex = row.rowIndex;
                        }
                        // Select all the rows between the previously selected row and the 
                        // newly clicked row
                        for (var i = startIndex; i < endIndex; i++) {
                            this.api.selectIndex(i, true, true);
                        }

                        //And we make the last one with a `false` so we get the event popped up.
                        this.api.selectIndex(i, true, false);
                    }
                    // Select one more row
                    //if (ctrlKey) {
                    //    this.api.selectIndex(row.rowIndex, true);
                    //}
                }
                // Store the recently clicked row for future use
                 this.appSpecific.lastSelectedRow = row;

            }

apala5 commented Apr 13, 2016

To overcome the issue mentioned by @TFrascaroli. I just modified his code and add a one line change the ag-grid.js file.

I tested with multiple selection, single row selection, check box selection, check box multi selection and everything works good.

This solution will work whether suppressRowClickSelection is enabled or not.

Basically the row selection event will work as per the grid native functionality, Just registered the shift key as one of the multi selected key in ag-grid which will prevent deselection of a selected row.

//In ag-grid.js file modify the the below function to add the event.shiftKey in or condition.
RenderedRow.prototype.onRowClicked = function (event) {
      var multiSelectKeyPressed = event.ctrlKey || event.metaKey || event.shiftKey;
}

//In your js file add the onRowClicked evetn for the grid
onRowClicked: function (row) {               
                var lastSelectedRow = this.appSpecific.lastSelectedRow;
                var shiftKey = row.event.shiftKey,
                  ctrlKey = row.event.ctrlKey;

                // If modifier keys aren't pressed then only select the row that was clicked
                //if (!shiftKey && !ctrlKey) {
                //    this.api.deselectAll();
                //    this.api.selectNode(row.node, true);
                //}
                    // If modifier keys are used and there was a previously selected row
                 if (lastSelectedRow !== undefined) {
                    // Select a block of rows
                    if (shiftKey) {
                        var startIndex, endIndex;
                        // Get our start and end indexes correct
                        if (row.rowIndex < lastSelectedRow.rowIndex) {
                            startIndex = row.rowIndex;
                            endIndex = lastSelectedRow.rowIndex;
                        }
                        else {
                            startIndex = lastSelectedRow.rowIndex;
                            endIndex = row.rowIndex;
                        }
                        // Select all the rows between the previously selected row and the 
                        // newly clicked row
                        for (var i = startIndex; i < endIndex; i++) {
                            this.api.selectIndex(i, true, true);
                        }

                        //And we make the last one with a `false` so we get the event popped up.
                        this.api.selectIndex(i, true, false);
                    }
                    // Select one more row
                    //if (ctrlKey) {
                    //    this.api.selectIndex(row.rowIndex, true);
                    //}
                }
                // Store the recently clicked row for future use
                 this.appSpecific.lastSelectedRow = row;

            }

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter May 9, 2016

Contributor

i have implemented this, will be in the next release.

Contributor

ceolter commented May 9, 2016

i have implemented this, will be in the next release.

@ceolter ceolter closed this May 9, 2016

@mikeerickson

This comment has been minimized.

Show comment
Hide comment
@mikeerickson

mikeerickson May 9, 2016

@ceolter Not trying to put you on the spot, just curious you estimated ETA. This is one of the open items on my POC list. I was going to implement it myself, but if you are going to added it to agGrid core, then I would be better off waiting for you :-)

mikeerickson commented May 9, 2016

@ceolter Not trying to put you on the spot, just curious you estimated ETA. This is one of the open items on my POC list. I was going to implement it myself, but if you are going to added it to agGrid core, then I would be better off waiting for you :-)

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter May 11, 2016

Contributor

if not this week, then next week. that's my estimate.

Contributor

ceolter commented May 11, 2016

if not this week, then next week. that's my estimate.

@pccjamie

This comment has been minimized.

Show comment
Hide comment
@pccjamie

pccjamie Jun 20, 2016

Hi @ceolter - thanks for all the work. Just to clarify. Which release is this fix part of? Is it 427 which you just released a few days ago? https://github.com/ceolter/ag-grid/releases/tag/4.2.7

pccjamie commented Jun 20, 2016

Hi @ceolter - thanks for all the work. Just to clarify. Which release is this fix part of? Is it 427 which you just released a few days ago? https://github.com/ceolter/ag-grid/releases/tag/4.2.7

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Jun 28, 2016

Contributor

no this went in weeks ago. last weeks beta5 release also has it.

Contributor

ceolter commented Jun 28, 2016

no this went in weeks ago. last weeks beta5 release also has it.

@Anruin

This comment has been minimized.

Show comment
Hide comment
@Anruin

Anruin Dec 12, 2016

Hi, @ceolter , should it work with virtual paging? It had worked for us when we've used in-memory row model, but then we switched to virtual paging mode with data retrieved from server. Now only Ctrl+Click works for multiple selection, Shift+Click doesn't. We're using 6.0.x at the moment. Can it be related to outdated version? Thanks.

Anruin commented Dec 12, 2016

Hi, @ceolter , should it work with virtual paging? It had worked for us when we've used in-memory row model, but then we switched to virtual paging mode with data retrieved from server. Now only Ctrl+Click works for multiple selection, Shift+Click doesn't. We're using 6.0.x at the moment. Can it be related to outdated version? Thanks.

@ceolter

This comment has been minimized.

Show comment
Hide comment
@ceolter

ceolter Jan 5, 2017

Contributor

only works with normal row model. this is because the viewport row model doesn't necessarily have all the rows loaded, so shift selection would only partially work if it was implemented.

Contributor

ceolter commented Jan 5, 2017

only works with normal row model. this is because the viewport row model doesn't necessarily have all the rows loaded, so shift selection would only partially work if it was implemented.

@grovesNL

This comment has been minimized.

Show comment
Hide comment
@grovesNL

grovesNL Jan 11, 2017

Has anyone attempted to use this with dragging as well? Ideally Click then Drag could select ranges (the same as Shift-Click, then Click). When using with Ctrl it would combine the selections just like the behavior described in this issue.

Basically a similar idea to the enterprise feature for Range Selection but acting on full rows.

grovesNL commented Jan 11, 2017

Has anyone attempted to use this with dragging as well? Ideally Click then Drag could select ranges (the same as Shift-Click, then Click). When using with Ctrl it would combine the selections just like the behavior described in this issue.

Basically a similar idea to the enterprise feature for Range Selection but acting on full rows.

@TFrascaroli

This comment has been minimized.

Show comment
Hide comment
@TFrascaroli

TFrascaroli Jan 12, 2017

Contributor
Contributor

TFrascaroli commented Jan 12, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment