Skip to content

Commit

Permalink
feat: destroy dropdown panel on close (#286)
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao authored Feb 26, 2018
1 parent 23b4e14 commit 14e1da5
Show file tree
Hide file tree
Showing 26 changed files with 756 additions and 578 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ map: {
| addTagText | `string` | `Add item` | no | Set custom text when using tagging |
| loadingText | `string` | `Loading...` | no | Set custom text when for loading items |
| [typeahead] | `Subject` | `-` | no | Custom autocomplete or filter. |
| [disableVirtualScroll] | `boolean` | false | no | Disable virtual scroll |
| dropdownPosition | `bottom`,`top`,`auto` | `bottom` | no | Set the dropdown position on open |
| appendTo | `string` | null | no | Append drodown to body or any other element using css selector |
| loading | `boolean` | `-` | no | you can set the loading state from the outside (e.g. async items loading) |
Expand All @@ -151,6 +150,7 @@ map: {
| (clear) | Fired on clear icon click |
| (add) | Fired when item is selected |
| (remove) | Fired when item is removed |
| (scrollToEnd) | Fired when scrolled to the end of items. Can be used for loading more items in chunks. |

## Change Detection
Ng-select component implements `OnPush` change detection which means the dirty checking checks for immutable
Expand Down
2 changes: 1 addition & 1 deletion demo/app/examples/dropdown-positions.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';

@Component({
selector: 'select-bindings',
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.Default,
template: `
<p>
By default the dropdown is displayed below the ng-select.
Expand Down
5 changes: 5 additions & 0 deletions demo/app/examples/events.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface AngSelectEvent {
(blur)="onBlur($event)"
(clear)="onClear()"
(add)="onAdd($event)"
(scrollToEnd)="onScrollToEnd($event)"
(remove)="onRemove($event)"
(change)="onChange($event)">
</ng-select>
Expand Down Expand Up @@ -86,6 +87,10 @@ export class SelectEventsComponent {
onClear() {
this.events.push({name: '(clear)', value: null});
}

onScrollToEnd($event) {
this.events.push({name: '(scrollToEnd)', value: $event});
}
}


5 changes: 3 additions & 2 deletions demo/app/examples/multi.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { DataService } from '../shared/data.service';
<ul>
<li *ngFor="let item of selectedPeople1">{{item.name}}</li>
</ul>
<button (click)="clearModel()" class="btn btn-secondary btn-sm">Clear model</button>
<button (click)="clearModel1()" class="btn btn-secondary btn-sm">Clear model</button>
</div>
<hr/>
Expand All @@ -39,7 +39,7 @@ import { DataService } from '../shared/data.service';
<ul>
<li *ngFor="let item of selectedPeople1">{{item.name}}</li>
</ul>
<button (click)="clearModel()" class="btn btn-secondary btn-sm">Clear model</button>
<button (click)="clearModel2()" class="btn btn-secondary btn-sm">Clear model</button>
</div>
<hr/>
Expand All @@ -61,6 +61,7 @@ import { DataService } from '../shared/data.service';
<ng-select
[items]="githubUsers$ | async"
[multiple]="true"
bindLabel="login"
[(ngModel)]="selectedUsers">
<ng-template ng-label-tmp let-item="item" let-clear="clear">
Expand Down
31 changes: 19 additions & 12 deletions demo/app/examples/virtual-scroll.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,48 @@ import { HttpClient } from '@angular/common/http';

@Component({
selector: 'select-tags',
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.Default,
template: `
<p>
By default ng-select enables virtual scroll for more 20 items. You can turn it off by setting disableVirtualScroll to true.
In this example we are loading 5000 items but only ~30 of them are rendered in the dom.
In this example we are loading many items but only ~30 of them are rendered in the dom.
This allows to load as big data as you want.
</p>
---html,true
<ng-select [items]="photos"
[disableVirtualScroll]="disableVirtualScroll"
<ng-select [items]="photosBuffer"
bindLabel="title"
bindValue="thumbnailUrl"
placeholder="Select photo"
[(ngModel)]="photo">
(scrollToEnd)="fetchMore($event)">
<ng-template ng-header-tmp>
<small class="form-text text-muted">Loaded {{photosBuffer.length}} of {{photos.length}}</small>
</ng-template>
<ng-template ng-option-tmp let-item="item" let-index="index">
<b>{{index}}</b> {{item.title}}
</ng-template>
</ng-select>
---
<small class="form-text text-muted">5000 items with virtual scroll</small>
<br>
<button class="btn btn-secondary btn-sm" (click)="toggleVirtualScroll()">Toggle virtual scroll</button>
`
})
export class VirtualScrollComponent {

photos = [];
disableVirtualScroll = false;
photosBuffer = [];
bufferSize = 50;

constructor(private http: HttpClient) {}

ngOnInit() {
this.http.get<any[]>('https://jsonplaceholder.typicode.com/photos').subscribe(photos => {
this.photos = photos;
this.photosBuffer = this.photos.slice(0, this.bufferSize);
});
}

toggleVirtualScroll() {
this.disableVirtualScroll = !this.disableVirtualScroll;
fetchMore($event: {start: number; end: number}) {
const len = this.photosBuffer.length;
if ($event.end === len) {
const more = this.photos.slice(len, this.bufferSize + len);
this.photosBuffer = this.photosBuffer.concat(more);
}
}
}
9 changes: 0 additions & 9 deletions scripts/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,6 @@ module.exports = function makeWebpackConfig() {
]
},
plugins: [
// Define env constiables to help with builds
// Reference: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
new webpack.DefinePlugin({
// Environment helpers
'process.env': {
ENV: JSON.stringify(ENV)
}
}),

// Workaround needed for angular 2 angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
Expand Down
10 changes: 8 additions & 2 deletions scripts/webpack.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ module.exports = function makeWebpackConfig() {
loader: 'raw-loader!sass-loader'
},

{test: /\.html$/, loader: 'raw-loader', exclude: root('src', 'public')}
{ test: /\.html$/, loader: 'raw-loader', exclude: root('src', 'public') }
]
};

Expand All @@ -46,7 +46,7 @@ module.exports = function makeWebpackConfig() {
enforce: 'post',
include: path.resolve('src'),
loader: 'istanbul-instrumenter-loader',
exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/, /search-helper\.ts$/]
exclude: [/\.spec\.ts$/, /\.e2e\.ts$/, /node_modules/, /search-helper\.ts$/, /testing/]
});
}

Expand All @@ -57,6 +57,12 @@ module.exports = function makeWebpackConfig() {
});

config.plugins = [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('TEST')
},
}),

// Workaround needed for angular 2 angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
Expand Down
47 changes: 47 additions & 0 deletions src/ng-select/ng-dropdown-panel.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.ng-dropdown-panel {
box-sizing: border-box;
position: absolute;
width: 100%;
z-index: 3;
-webkit-overflow-scrolling: touch;
.ng-dropdown-panel-items {
display: block;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
max-height: 240px;
overflow-y: auto;
.ng-option {
box-sizing: border-box;
cursor: pointer;
display: block;
.highlighted {
font-weight: bold;
text-decoration: underline;
}
&.disabled {
cursor: default;
}
}
}

.scroll-host {
overflow: hidden;
overflow-y: auto;
position: relative;
display: block;
-webkit-overflow-scrolling: touch;
}
.scrollable-content {
top: 0;
left: 0;
width: 100%;
height: 100%;
position: absolute;
}
.total-padding {
width: 1px;
opacity: 0;
}
}
76 changes: 76 additions & 0 deletions src/ng-select/ng-dropdown-panel.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Component, Injectable, ViewChild, ErrorHandler, NgZone } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NgDropdownPanelComponent } from './ng-dropdown-panel.component';
import { NgSelectComponent } from './ng-select.component';
import { WindowService } from './window.service';
import { VirtualScrollService } from './virtual-scroll.service';
import { ItemsList } from './items-list';
import { MockNgZone, MockNgWindow } from '../testing/mocks';
import { TestsErrorHandler } from '../testing/helpers';

@Component({
template: `
<ng-dropdown-panel *ngIf="isOpen"
class="ng-dropdown-panel"
[items]="itemsList.filteredItems"
(update)="viewPortItems = $event">
<div class="ng-option" *ngFor="let item of viewPortItems">{{item.label}}</div>
</ng-dropdown-panel>
`
})
@Injectable()
class MockNgSelect {
@ViewChild(NgDropdownPanelComponent) dropdownPanel: NgDropdownPanelComponent;
itemsList = new ItemsList(<any>this);
viewPortItems = [];
isOpen = true;
}

describe('NgDropdowPanel', function () {
let fixture: ComponentFixture<MockNgSelect>;

beforeEach(() => {
fixture = TestBed.configureTestingModule({
providers: [
VirtualScrollService,
{ provide: ErrorHandler, useClass: TestsErrorHandler },
{ provide: NgZone, useFactory: () => new MockNgZone() },
{ provide: WindowService, useFactory: () => new MockNgWindow() },
{ provide: NgSelectComponent, useClass: MockNgSelect },
],
declarations: [
NgDropdownPanelComponent,
MockNgSelect
]
}).createComponent(MockNgSelect);

fixture.detectChanges();
});

it('should render items', async(() => {
fixture.componentInstance.itemsList.setItems(['A', 'B', 'C'], true);
fixture.detectChanges();

fixture.whenStable().then(() => {
expect(fixture.componentInstance.dropdownPanel.items.length).toBe(3);
const options = fixture.debugElement.nativeElement.querySelectorAll('.ng-option');
expect(options.length).toBe(3);
expect(options[0].innerText).toBe('A');
expect(options[1].innerText).toBe('B');
expect(options[2].innerText).toBe('C');
});
}));

it('should not render items when items length is zero', async(() => {
fixture.componentInstance.itemsList.setItems([]);
fixture.detectChanges();

fixture.whenStable().then(() => {
expect(fixture.componentInstance.dropdownPanel.items.length).toBe(0);
const options = fixture.debugElement.nativeElement.querySelectorAll('.ng-option');
expect(options.length).toBe(0);
});
}));

});

Loading

0 comments on commit 14e1da5

Please sign in to comment.