Implementing CTRL and SHIFT Select of table rows (using ng-repeat)

2,880 views
Skip to first unread message

Skattabrain

unread,
Jul 30, 2014, 4:50:30 PM7/30/14
to ang...@googlegroups.com
Hello,

So my Angular JS dilemma of the day is with a shift/control select feature I am implementing on a table. Here is the scenario..

  • I have a large list - $scope.users
  • I can sort and filter this list
  • it's a huge list... 10K +
  • I use paging (and the performance is incredible... as if there were hundreds not thousands of records)
  • Clicking a row toggles true/false on a property "selected"
  • I have buttons that add/remove "selected" users to a new list and then I send it off to the server via ajax
I have CTRL click implemented and working.... SHIFT is giving me trouble.

I get the last selected record index, then the current (when SHIFT is depressed) and that's good... however changing the sort order changes the UI but I'm setting selected from my controller... which is looking at $scope.users and not the view currently in ngRepeat. If I could get the right order to the scope I think I could get this working in conjunction with pagination.

So shift select user #1 on page 1 and with user #50 on page #3 would get users 1 - 50.

Any ideas on this? I can't seem to find anything... and AngularUI does shift select... however if you try it in their paging it doesn't work.

Any input would be greatly appreciated!

Eric Miller

unread,
Jul 31, 2014, 1:28:44 AM7/31/14
to ang...@googlegroups.com
Heheh. This is a surprisingly hard problem, to emulate file system selection behavior. I enjoyed this one myself :)
Not really angular-specific, but it's fun. As far as I know nobody has written a lib for it. It's tough to isolate a selection model from UI and data dependencies, so you're probably on your own to build it.

Here's how I solved it (as I recall).

1: Click by itself deselects everything, then selects whatever you clicked, unless it was selected, then it deselects this. Subtle difference from...
2: CTRL click toggles. This is easy.
3: Either click or CTRL click sets a "lastSingleClick" variable. This is your anchor for the shift click.
4: Shift click selects from your "lastSingleClick" until the clicked index. Your selection model knows the full set of selections, but you need to separately store "currentSpanSelection"s, because...
5: Every time you shift click, the first thing you do is deselect everything in "currentSpanSelections", because the behavior file systems show is spans fipping around if you change what you shift+click on.

So the total set of selection data you need for this algo is:

* currentSpan = [] // array of the most recent shift selected span
* lastSingleClick = 3 // the index of the last single click that happened, naked or CTRL.
* selections = [] // all of the selections

Skattabrain

unread,
Jul 31, 2014, 9:38:20 AM7/31/14
to ang...@googlegroups.com
Thanks for your post Eric.

I wish I could say it's fun, I'm under deadline. It was my bright idea to bring Angular in as a solution for dealing with xx,xxx rows at a time... I literally started learning AngularJS 2 weeks ago... I'm feeling the heat to solve this problem. We were using DataTables & jQuery but had to limit our data set to a few thousand rows and even that was sluggish but I could manage about 4000. We want to manage the entire record set which is between 10K and 15K depending on the client. 

Angular has been handling it like a champ... but man it's a paradigm shift in how you do things... and yes, this problem is not trivial but makes all the tutorials I've seen look trivial! lol

So regarding 1, 2 & 3... that is what I am currently doing and that is working. Using shift I end up with 2 indexes... the start and the end.

I am using logic in the controller to mark an item in the scope to selected true or false. This is my problem with Shift select... I can only get the correct range if my ngRepeat is unsorted and unfiltered. As soon as I filter or change the sort order... $index is useless to me as the indexes don't line up with the scope. Could you elaborate on this specifically?

How do you select the range, regardless of the state of the view? And paging throws another problem into the mix... but at this point, I'd like to get it working just on the current page of results.

Sander Elias

unread,
Aug 6, 2014, 5:53:44 AM8/6/14
to ang...@googlegroups.com
Hi Skattabrain,

I wrote some directives for that quite a while ago.have a look at this plunk,
Is that what you where looking for?

Regards
Sander

Skattabrain

unread,
Aug 6, 2014, 3:28:07 PM8/6/14
to ang...@googlegroups.com
I don't think so. Solved my problem though...

in my ng-repeat i do...

ng-repeat="user in (filteredUsers = (users | filter | filter)"

and then you have $scope.filteredUsers

Eric Miller

unread,
Aug 7, 2014, 2:30:07 AM8/7/14
to ang...@googlegroups.com
Whoaaaah Buddy.

It reads like your problem is in your persistence structure.

How do you plan to store the selection state? You're right, you can't use `$index`. I said `index` but didn't emphasize the difference.

I persist my selections by index into the full data set, not the ng-repeat display area. I couldn't rely on ids (if you create something it doesn't have an id until the server responds to the POST). If you CAN rely on ids, I'd suggest that.

Mostly, think about your persistence strategy. If it's not going to work on a whiteboard, it's not going to work on code. Write it out. Make sure it works when you switch the shift+click. Make sure it works when you crtrl+click to deselect something in the middle of a span selection. The simplest algo I could think of had the three pieces of state I mentioned.

You can't really set them on the objects themselves. You make a separate thing, an angular factory, maybe you call it `selectionModel`. This thing is your components interface into selection. It has anything you need to interact with selections. Maybe selectionModel.selectedItems, and selectionModel.isSelected(myItem), and selectionModel.makeSelection(items). It's your interface for everything. If you isolate the problem, you can solve the algo separate from the UI. As long as selectionModel.isSelected(myItem) returns the correct true or false answer, you can drive the UI however you want.

This is just what I did though. Maybe it won't work for you. Probably I'm not as aligned with your exact problem as you'd like. What I can tell you is dropping flags onto the objects themselves just isn't enough state to get the behavior you want if you're emulating a file system's selection model.

Skattabrain

unread,
Aug 7, 2014, 8:23:00 AM8/7/14
to ang...@googlegroups.com
Hi Eric,

I'll make a plunk of my solution when I'm done. 

I'll be a bit embarrassed of my organization though, I'm just getting used to the angular way, I'll need to organize and reduce my code first. I see your points, not sure my head is around them yet but I will have a look today. I am toggling .selected on the user object in the controller. I know I need to move it out of the controller. I'm just use $scope.filteredUsers for shift clicking events. It is working, but your ideas sound like a nice improvement. 

Thanks!

Kasim Doctor

unread,
Aug 12, 2014, 9:11:49 PM8/12/14
to ang...@googlegroups.com
Hi,

Could you please post a plunk of your solution ? I am new to Angular and I am trying to implement Ctrl + Select , Shift + Select in my table using ng-repeat.

many thanks,
Kasim
Reply all
Reply to author
Forward
0 new messages