Skip to content

Commit

Permalink
feat(tagging): allow creating new options
Browse files Browse the repository at this point in the history
closes #34
  • Loading branch information
varnastadeus committed Oct 20, 2017
1 parent 1e6e1c3 commit 4149375
Show file tree
Hide file tree
Showing 13 changed files with 211 additions and 87 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ map: {
- [x] Good base functionality test coverage
- [x] Multiselect support
- [x] Autocomplete
- [ ] Custom tags
- [x] Custom tags
- [ ] Accessibility

## API
Expand All @@ -95,6 +95,8 @@ map: {
| bindLabel | string | `label` | no | Object property to use for label. Default `label` |
| bindValue | string | `-` | no | Object property to use for selected model. By default binds to whole object. |
| [clearable] | boolean | `true` | no | Allow to clear selected value. Default `true`|
| multiple | boolean | `false` | no | Allows to select multiple items. |
| [addTag] | Function or boolean | `false` | no | Using boolean simply adds tag with value as bindLabel. If you want custom properties add function which returns object. |
| placeholder | string | `-` | no | Placeholder text. |
| notFoundText | string | `No items found` | no | Set custom text when filter returns empty result |
| typeToSearchText | string | `Type to search` | no | Set custom text when using Typeahead |
Expand All @@ -108,7 +110,7 @@ map: {
| (open) | Fired on select dropdown open |
| (close) | Fired on select dropdown close |

# Change Detection
## Change Detection
Ng-select component implements `OnPush` change detection which means the dirty checking checks for immutable
data types. That means if you do object mutations like:

Expand Down
4 changes: 2 additions & 2 deletions src/demo/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ <h3 class="title">
<a class="nav-link" routerLink="/forms" routerLinkActive="active">Reactive forms</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/filter-client" routerLinkActive="active">Filter <small>(client side)</small></a>
<a class="nav-link" routerLink="/filter" routerLinkActive="active">Filter and autocomplete</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/filter-server" routerLinkActive="active">Filter <small>(server side)</small></a>
<a class="nav-link" routerLink="/tags" routerLinkActive="active">Tags</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/multiselect" routerLinkActive="active">Multiselect</a>
Expand Down
8 changes: 6 additions & 2 deletions src/demo/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import '../style/styles.scss';

import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/map';

@Component({
selector: 'demo-app',
Expand Down
11 changes: 5 additions & 6 deletions src/demo/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { SelectSearchComponent } from './examples/search.component';
import { ReactiveFormsComponent } from './examples/reactive-forms.component';
import { SelectEventsComponent } from './examples/events.component';
import { SelectMultiComponent } from './examples/multi.component';
import { SelectAutocompleteComponent } from './examples/autocomplete.component';
import { SelectTagsComponent } from './examples/tags.component';

import { LayoutHeaderComponent } from './layout/header.component';

Expand All @@ -26,8 +26,8 @@ const appRoutes: Routes = [
},
{ path: 'forms', component: ReactiveFormsComponent, data: { title: 'Reactive forms' } },
{ path: 'bindings', component: SelectBindingsComponent, data: { title: 'Custom bindings' } },
{ path: 'filter-client', component: SelectSearchComponent },
{ path: 'filter-server', component: SelectAutocompleteComponent },
{ path: 'filter', component: SelectSearchComponent },
{ path: 'tags', component: SelectTagsComponent },
{ path: 'templates', component: SelectWithTemplatesComponent },
{ path: 'multiselect', component: SelectMultiComponent },
{ path: 'events', component: SelectEventsComponent },
Expand All @@ -36,7 +36,7 @@ const appRoutes: Routes = [
@NgModule({
imports: [
BrowserModule,
NgSelectModule.forRoot({ notFoundText: 'No items found', typeToSearchText: 'Type to search' }),
NgSelectModule.forRoot({ notFoundText: 'No items found', typeToSearchText: 'Type to search', addTagText: 'Add item' }),
CommonModule,
FormsModule,
ReactiveFormsModule,
Expand All @@ -57,8 +57,7 @@ const appRoutes: Routes = [
ReactiveFormsComponent,
SelectEventsComponent,
SelectMultiComponent,
SelectAutocompleteComponent,

SelectTagsComponent,
LayoutHeaderComponent
],
bootstrap: [AppComponent]
Expand Down
69 changes: 0 additions & 69 deletions src/demo/app/examples/autocomplete.component.ts

This file was deleted.

65 changes: 63 additions & 2 deletions src/demo/app/examples/search.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Component, EventEmitter } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Component({
selector: 'select-search',
template: `
<h5>Client side</h5>
<hr>
<label>Search in label text (default)</label>
<ng-select [items]="companies"
bindLabel="name"
Expand All @@ -21,6 +25,32 @@ import { Component, EventEmitter } from '@angular/core';
<p>
Selected value: {{selectedCompany2 | json}}
</p>
<h5>Server side</h5>
<hr>
<label>Search with autocomplete in Github accounts</label>
<ng-select [items]="items"
notFoundText="No results found"
typeToSearchText="Search for github account"
bindLabel="login"
[placeholder]="placeholder"
[multiple]="multiple"
[typeahead]="typeahead"
[(ngModel)]="githubAccount">
<ng-template ng-option-tmp let-item="item">
<img [src]="item.avatar_url" width="20px" height="20px"> {{item.login}}
</ng-template>
</ng-select>
<br>
<button class="btn btn-secondary btn-sm" (click)="toggleMultiple()">Toggle multiple</button>
<p>
Selected github account:
<span *ngIf="githubAccount">
<img [src]="githubAccount.avatar_url" width="20px" height="20px"> {{githubAccount.login}}
</span>
</p>
`
})
export class SelectSearchComponent {
Expand All @@ -30,17 +60,35 @@ export class SelectSearchComponent {
filteredCompanies2 = [];
selectedCompany: any;
selectedCompany2?: any;
githubAccount: any;
items = [];
typeahead = new EventEmitter<string>();
placeholder = 'Type in me. I am single';
multiple = false;

customFilter = new EventEmitter<string>();

/* tslint:disable */
companiesNames = ['Miškas', 'Žalias', 'Flexigen', 'Rooforia', 'Rooblia', 'Tropoli', 'Eargo', 'Gadtron', 'Elentrix', 'Terragen', 'Medalert', 'Xelegyl', 'Bristo', 'Xylar', 'Imperium', 'Kangle', 'Earwax', 'Zanity', 'Portico', 'Tsunamia', 'Kage', 'Comstar', 'Radiantix', 'Bostonic', 'Geekko', 'Eventex', 'Stockpost', 'Silodyne', 'Enersave', 'Perkle', 'Pyramis', 'Accuprint', 'Papricut', 'Pathways', 'Circum', 'Gology', 'Buzzworks', 'Dancerity', 'Zounds', 'Diginetic', 'Snips', 'Chillium', 'Exotechno', 'Accufarm', 'Vidto', 'Signidyne', 'Escenta', 'Sureplex', 'Quarmony', 'Interfind', 'Exoswitch', 'Mondicil', 'Pyramia', 'Digitalus', 'Earthplex', 'Limozen', 'Twiist', 'Tubalum', 'Securia', 'Uni', 'Biospan', 'Zensus', 'Memora'];
/* tslint:enable */

constructor(private http: HttpClient) {
this.typeahead
.distinctUntilChanged()
.debounceTime(200)
.switchMap(term => this.loadGithubUsers(term))
.subscribe(items => {
this.items = items;
}, (err) => {
console.log(err);
this.items = [];
});
}

ngOnInit() {
this.companiesNames.forEach((c, i) => {
this.companies.push({id: i, name: c});
this.companies2.push({id: i, name: c});
this.companies.push({ id: i, name: c });
this.companies2.push({ id: i, name: c });
});

this.filteredCompanies2 = [...this.companies2];
Expand All @@ -49,6 +97,19 @@ export class SelectSearchComponent {
this.filteredCompanies2 = term ? this.companies2.filter(x => term === 'Rooforia' && x.name === term) : this.companies2;
});
}

loadGithubUsers(term: string): Observable<any[]> {
if (term) {
return this.http.get<any>(`https://api.github.com/search/users?q=${term}`).map(rsp => rsp.items);
} else {
return Observable.of([]);
}
}

toggleMultiple() {
this.multiple = !this.multiple;
this.placeholder = this.multiple ? 'Type in me. I am multiple.' : 'Type in me. I am single.';
}
}


49 changes: 49 additions & 0 deletions src/demo/app/examples/tags.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Component, EventEmitter } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';

@Component({
selector: 'select-tags',
template: `
<label>Default tags</label>
<ng-select [items]="companies"
[addTag]="true"
bindLabel="name"
[(ngModel)]="selectedCompany">
</ng-select>
<p>
Selected value: {{selectedCompany | json}}
</p>
<hr>
<label>Custom tags</label>
<ng-select [items]="companies"
[addTag]="addTag"
multiple="true"
bindLabel="name"
[(ngModel)]="selectedCompanyCustom">
</ng-select>
<p>
Selected value: {{selectedCompanyCustom | json}}
</p>
<hr>
`
})
export class SelectTagsComponent {

companies: any[] = [];

companiesNames = ['Miškas', 'Žalias', 'Flexigen'];

ngOnInit() {
this.companiesNames.forEach((c, i) => {
this.companies.push({ id: i, name: c });
});
}

addTag = (name) => {
return { name: name, tag: true };
}
}


6 changes: 6 additions & 0 deletions src/lib/src/items-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ export class ItemsList {
this._selected.splice(this._selected.length - 1, 1);
}

addTag(item: NgOption) {
item.index = this.items.length;
this.items.push(item);
this.filteredItems.push(item);
}

clearSelected() {
this._selected.forEach((item) => {
item.selected = false;
Expand Down
5 changes: 4 additions & 1 deletion src/lib/src/ng-select.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@
[ngTemplateOutletContext]="{ item: item, index: item.index }">
</ng-template>
</div>
<div class="ng-option marked" role="option" (click)="selectTag()" *ngIf="addTag && itemsList.filteredItems.length === 0">
<span><span class="ng-tag-label">{{addTagText}}</span>{{filterValue}}</span>
</div>
</virtual-scroll>

<div class="ng-menu" *ngIf="showNoItemsFound()">
<div class="ng-menu" *ngIf="showNoItemsFound() && !addTag">
<div class="ng-option disabled">
{{notFoundText}}
</div>
Expand Down
6 changes: 6 additions & 0 deletions src/lib/src/ng-select.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ ng-select {
color: #cccccc;
cursor: default;
}

.ng-tag-label {
padding-right: 5px;
font-size: 80%;
font-weight: 400;
}
}
.ng-clear-zone {
-webkit-animation: Select-animation-fadeIn 200ms;
Expand Down

0 comments on commit 4149375

Please sign in to comment.