Skip to content

Commit

Permalink
feat(lib): option to activate two-way databinding of selectedItems
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentjdc committed Mar 6, 2020
1 parent fbc0bda commit f814a12
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 31 deletions.
1 change: 1 addition & 0 deletions projects/ngx-drag-to-select/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface Shortcuts {
export interface DragToSelectConfig {
selectedClass: string;
shortcuts: Partial<Shortcuts>;
selectedItemsTwoWayDataBinding?: boolean;
}

export interface MousePosition {
Expand Down
52 changes: 22 additions & 30 deletions projects/ngx-drag-to-select/src/lib/select-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ import {
UpdateAction,
UpdateActions,
PredicateFn,
BoundingBox
BoundingBox,
DragToSelectConfig
} from './models';

import { AUDIT_TIME, NO_SELECT_CLASS } from './constants';
Expand All @@ -67,6 +68,7 @@ import {
hasMinimumSize
} from './utils';
import { KeyboardEventsService } from './keyboard-events.service';
import { CONFIG } from './tokens';

@Component({
selector: 'dts-select-container',
Expand Down Expand Up @@ -96,7 +98,12 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After
@ContentChildren(SelectItemDirective, { descendants: true })
private $selectableItems: QueryList<SelectItemDirective>;

@Input() selectedItems: any;
@Input('selectedItems') set selectedItems(selectedItems) {
if (this.config.selectedItemsTwoWayDataBinding) {
this.selectItems(item => selectedItems.indexOf(item) >= 0);
this.deselectItems(item => selectedItems.indexOf(item) < 0);
}
}
@Input() selectOnDrag = true;
@Input() disabled = false;
@Input() disableDrag = false;
Expand Down Expand Up @@ -128,6 +135,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After
private _lastRangeSelection: Map<SelectItemDirective, boolean> = new Map();

constructor(
@Inject(CONFIG) private config: DragToSelectConfig,
@Inject(PLATFORM_ID) private platformId: Object,
private shortcuts: ShortcutService,
private keyboardEvents: KeyboardEventsService,
Expand Down Expand Up @@ -304,29 +312,21 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After
}

private _initSelectedItemsChange() {
this._selectedItems$
.pipe(
auditTime(AUDIT_TIME),
takeUntil(this.destroy$)
)
.subscribe({
next: selectedItems => {
this.selectedItemsChange.emit(selectedItems);
this.select.emit(selectedItems);
},
complete: () => {
this.selectedItemsChange.emit([]);
}
});
this._selectedItems$.pipe(auditTime(AUDIT_TIME), takeUntil(this.destroy$)).subscribe({
next: selectedItems => {
this.selectedItemsChange.emit(selectedItems);
this.select.emit(selectedItems);
},
complete: () => {
this.selectedItemsChange.emit([]);
}
});
}

private _observeSelectableItems() {
// Listen for updates and either select or deselect an item
this.updateItems$
.pipe(
withLatestFrom(this._selectedItems$),
takeUntil(this.destroy$)
)
.pipe(withLatestFrom(this._selectedItems$), takeUntil(this.destroy$))
.subscribe(([update, selectedItems]: [UpdateAction, any[]]) => {
const item = update.item;

Expand All @@ -346,11 +346,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After

// Update the container as well as all selectable items if the list has changed
this.$selectableItems.changes
.pipe(
withLatestFrom(this._selectedItems$),
observeOn(asyncScheduler),
takeUntil(this.destroy$)
)
.pipe(withLatestFrom(this._selectedItems$), observeOn(asyncScheduler), takeUntil(this.destroy$))
.subscribe(([items, selectedItems]: [QueryList<SelectItemDirective>, any[]]) => {
const newList = items.toArray();
this._selectableItems = newList;
Expand All @@ -371,11 +367,7 @@ export class SelectContainerComponent implements AfterViewInit, OnDestroy, After
const containerScroll$ = fromEvent(this.host, 'scroll');

merge(resize$, windowScroll$, containerScroll$)
.pipe(
startWith('INITIAL_UPDATE'),
auditTime(AUDIT_TIME),
takeUntil(this.destroy$)
)
.pipe(startWith('INITIAL_UPDATE'), auditTime(AUDIT_TIME), takeUntil(this.destroy$))
.subscribe(() => {
this.update();
});
Expand Down
83 changes: 82 additions & 1 deletion projects/ngx-drag-to-select/src/lib/select-container.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Component, ViewChild, ViewChildren, QueryList } from '@angular/core';
import { DragToSelectModule } from './drag-to-select.module';
import { SelectContainerComponent } from './select-container.component';
import { By } from '@angular/platform-browser';
import { SelectItemDirective } from './select-item.directive';
import { skip } from 'rxjs/operators';

function triggerDomEvent(eventType: string, target: HTMLElement | Element, eventData: object = {}): void {
const event: Event = document.createEvent('Event');
Expand Down Expand Up @@ -43,6 +44,33 @@ class TestComponent {
itemDeselected(value: any) {}
}

@Component({
template: `
<dts-select-container
[(selectedItems)]="selectedItems"
(itemSelected)="itemSelected($event)"
(itemDeselected)="itemDeselected($event)"
#selectContainer
>
<span [dtsSelectItem]="item" #selectItem="dtsSelectItem" *ngFor="let item of items">Item #{{ item.id }}</span>
</dts-select-container>
`
})
class TestDynamicItemsComponent {
@ViewChild('selectContainer', { static: true })
selectContainer: SelectContainerComponent;

items = [{ id: 1 }, { id: 2 }, { id: 3 }];

@ViewChildren('selectItem')
selectItems: QueryList<SelectItemDirective>;

selectedItems = [];

itemSelected(value: any) {}
itemDeselected(value: any) {}
}

describe('SelectContainerComponent', () => {
let fixture: ComponentFixture<TestComponent>;
let testComponent: TestComponent;
Expand Down Expand Up @@ -208,3 +236,56 @@ describe('SelectContainerComponent', () => {
});
});
});

describe('TwoWayDataBinding', () => {
let fixture: ComponentFixture<TestDynamicItemsComponent>;
let testComponent: TestDynamicItemsComponent;
let selectContainerInstance: SelectContainerComponent;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TestDynamicItemsComponent],
imports: [
DragToSelectModule.forRoot({
selectedItemsTwoWayDataBinding: true
})
]
}).compileComponents();
}));

beforeEach(() => {
window.getSelection = jest.fn().mockReturnValue({});
fixture = TestBed.createComponent(TestDynamicItemsComponent);
testComponent = fixture.componentInstance;
selectContainerInstance = fixture.componentInstance.selectContainer;
fixture.detectChanges();
});

it('should select items', done => {
const result = testComponent.items.slice(1, 1);

selectContainerInstance.select.subscribe(items => {
expect(testComponent.selectedItems.length).toBe(result.length);
expect(items).toEqual(result);
expect(testComponent.selectedItems).toEqual(result);
done();
});

selectContainerInstance.selectedItems = result;
});

it('should select items if selectedItems changes', fakeAsync(done => {
const result = testComponent.items.slice(1, 2);

selectContainerInstance.select.pipe(skip(1)).subscribe(items => {
expect(testComponent.selectedItems.length).toBe(result.length);
expect(items).toEqual(result);
expect(testComponent.selectedItems).toEqual(result);
done();
});

selectContainerInstance.selectedItems = testComponent.items.slice(1, 1);
tick();
selectContainerInstance.selectedItems = result;
}));
});

0 comments on commit f814a12

Please sign in to comment.