Skip to content

wip: cdk selection #11770

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

Closed
wants to merge 14 commits into from
Closed

wip: cdk selection #11770

wants to merge 14 commits into from

Conversation

amcdnl
Copy link
Contributor

@amcdnl amcdnl commented Jun 12, 2018

WIP of CDK Selection Directive - Addresses #11309

Note: This PR includes 2 changes to Selection Model.

  • Change onChange event to change to honor standard
  • Add optional trackBy

@amcdnl amcdnl self-assigned this Jun 12, 2018
@amcdnl amcdnl requested a review from jelbourn June 12, 2018 21:34
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Jun 12, 2018
@d3lm
Copy link

d3lm commented Jun 13, 2018

First things first, really great work on the CDK selection feature @amcdnl 👏

I was thinking whether it made sense to add also a drag-to-select feature to the CDK as part of this WIP. I have recently (a few weeks back) implemented a library called ngx-drag-to-select for this and people seem to like it and they also start using it in their apps. It also covers mobile use-cases and should provide you with everything you need to implement it in many different scenarios. Here's a demo page. I tried to focus heavily on performance and also ran some performance audits. The results were pretty good (~60 FPS).

@jelbourn What do you think about integrating this into the Angular CDK as part of the selection feature? I would be totally open to completely overhaul the source code and re-write it if necessary so that the source code satisfies the Angular coding guideline / style. There also still some room for improvement, especially when it comes to the API and how to use it.

Any thoughts?

@amcdnl
Copy link
Contributor Author

amcdnl commented Jun 13, 2018

@jelbourn - Updated API per the feedback.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass

export class CdkSelection<T> implements OnInit {

/** List of all active selections. */
@Input('cdkSelection') selections: T[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectedItems ?

Should this be a Set instead of an array?

  • A large set of items might get expensive if you're doing a lot of array manipulation
  • I'm guessing the items aren't ordered

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: set - I suppose it could be but from a public API perspective that feels odd. I totally see the value of doing that internally though. Also, the API of the selection model class is a array so since this is more just directive bindings for that I was trying to honor that api. Let me know your thoughts.

* - Multiple: Multiple items can be selected.
* - Modifier Multiple: Multiple items can be selected using modifier keys such as ctrl or shift.
*/
@Input('cdkSelectionStrategy') strategy: 'single' | 'multiple' | 'modifierMultiple' = 'single';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could potentially bake this into the selector, e.g. cdkMultiSelection

Might also want to just do single and multiple with the modifier thing as a separate option, something like requireModifierKey

Copy link
Contributor Author

@amcdnl amcdnl Jun 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Input('cdkSelectionTrackBy') trackBy: (model: T) => any;

/** Whether the selections can be cleared after selection. */
@Input('cdkSelectionClearable')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Clear" makes me thing it will do the whole thing, maybe "deselect" or "toggle"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private _clearable: boolean = true;

/** The max number of selections that can be selected */
@Input('cdkSelectionMaxSelections')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cdkSelectionMaxSelected ?

}

/** Returns whether the control is multi select or not. */
getIsMultiple() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just isMultiple()


/** Deregister a control from the set. */
deregister(ctrl) {
const idx = this._set.indexOf(ctrl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idx -> toggleIndex

}

/** Deregister a control from the set. */
deregister(ctrl) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctrl -> toggle (here and elsewhere)

}

/** Set the user selection for the element. */
setTextSelection(type: string) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite follow why this is here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I do SHIFT + CLICK I want to set the text selection on the entire parent directive area to a specified state.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a comment with the explanation

}

/** Update the selections given the arguments. */
private _setSelections(ctrl: any) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function looks like it could use some comments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about Set the selection state given a toggle directive and its activated options.?

}

/** Get the index of the given element in the dom tree relative to the other toggles. */
private _getIndex(sortedSet: any[], sourceCtrl: any) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_getToggleIndex ?

@amcdnl
Copy link
Contributor Author

amcdnl commented Jun 15, 2018

@jelbourn - Updated per feedback.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass

/** The modifier that was invoked. */
modifier: 'shift' | 'meta' | null;

private _destroy = new Subject();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_destroy -> _destroyed

private _destroy = new Subject();

constructor(
public elementRef: ElementRef,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack; it needs a user-facing JsDoc, then

}

/** Invoke toggle on the parent selection directive. */
_onToggle() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_onToggle -> _toggle() ?

@@ -0,0 +1,63 @@
The `selection` package handles managing selection state on components
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I'll do an editing pass on this after it's submitted since it's easier than doing back and forth review of longer copy.


constructor(public elementRef: ElementRef) {}

clickITem(id: string, modifier?: 'meta' | 'shift') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clickItem


/** Set the selection state given a toggle directive and its activated options. */
private _setSelections(toggle) {
const ctrls = this._getSortedSet();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctrls -> either toggles or toggleControls (here and in all other places). I personally like toggleControls as being the most explicit.

/** Set the selection state given a toggle directive and its activated options. */
private _setSelections(toggle) {
const ctrls = this._getSortedSet();
const index = this._getToggleIndex(ctrls, toggle);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could index be more specific?

const { modifier, model } = toggle;
const selections = this._selectionModel.selected;

if (this.mode === 'multiple' && modifier === 'shift') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the body of this method be broken up into two other methods: _setMultipleSelections and _setsingleSelections? Right now it feels like it has a lot going on.


if (Array.isArray(toggle.model)) {
if (this.mode === 'multiple' &&
(this.maxSelections === undefined ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this condition be broken into a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda feels like overkill IMO.

}

/** Get the set of controls sorted by DOM order. */
private _getSortedSet(): any[] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_getSortedSet doesn't seem accurate since it's not returning a Set

@amcdnl
Copy link
Contributor Author

amcdnl commented Jun 24, 2018

@jelbourn -

What do you think about making CTRL + A do select all?

Also the selection-list component has a compareWith input rather than a trackBy. Do you think we should update this to match that?

re: implementation on select list - All of the mixins don't have any arguments in the constructor. For this I need to inject a variety of things into the injector so I don't think we can do it cleanly. I think it would be a better candidate for inheritance or encapsulated composition. Thoughts?

@jelbourn
Copy link
Member

  • We use compareWith in autocomplete, too, I think. We can have a separate discussion about whether we want to unify them.
  • I'm not sure about the select-all. The amount of code for a downstream user to add it is pretty low, no?
  • What we do for mixins that require some dependency is that the "seed" class is required to have those as properties, e.g. the color mixin depends on starting from a class that implements HasElementRef

'[class.cdk-selection-selected]': 'selected',
'(mousedown)': '_setModifiers($event)',
'(click)': 'toggle()',
/** Right click should select item. */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be a // style comment ( /** */ is only for JsDoc)

@ngbot
Copy link

ngbot bot commented Jun 29, 2018

Hi @amcdnl! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@amcdnl
Copy link
Contributor Author

amcdnl commented Jul 15, 2018

@jelbourn

I'm not sure about the select-all. The amount of code for a downstream user to add it is pretty low, no?

Thats fine. So we would be adding a selectAll and deselectAll?

What we do for mixins that require some dependency is that the "seed" class is required to have those as properties, e.g. the color mixin depends on starting from a class that implements HasElementRef

I was playing with making this a mixin but the complexity exploded. I think it would be better as a higher order component.

@@ -34,12 +34,13 @@ export class SelectionModel<T> {
}

/** Event emitted when the value has changed. */
onChange: Subject<SelectionChange<T>> | null = this._emitChanges ? new Subject() : null;
change: Subject<SelectionChange<T>> | null = this._emitChanges ? new Subject() : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I've already made this change in this PR and deprecated it - #8286 - I'm going to revert this back to the original name and we can consolidate on that merge :)

@@ -39,7 +39,8 @@ export class SelectionModel<T> {
constructor(
private _multiple = false,
initiallySelectedValues?: T[],
private _emitChanges = true) {
private _emitChanges = true,
private _trackBy?: (model) => any) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there aren't any unit tests that cover this new trackBy behavior; this would be necessary since it's part of the main library rather than cdk-experimental

@ngbot
Copy link

ngbot bot commented Aug 21, 2018

Hi @amcdnl! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

1 similar comment
@ngbot
Copy link

ngbot bot commented Aug 21, 2018

Hi @amcdnl! This PR has merge conflicts due to recent upstream merges.
Please help to unblock it by resolving these conflicts. Thanks!

@mmalerba mmalerba added aaa and removed aaa labels Apr 25, 2019
@Adam-Michalski
Copy link

@amcdnl this is great feature which is not yet merged. Can You make updates with changes from review?

@jelbourn jelbourn closed this Oct 1, 2020
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Nov 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes PR author has agreed to Google's Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants