Skip to content

Commit

Permalink
feat: ng-option support for simple dropdowns (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
anjmao committed Oct 31, 2017
1 parent a81880e commit 6d841ef
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 14 deletions.
2 changes: 1 addition & 1 deletion demo/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const appRoutes: Routes = [
redirectTo: '/forms',
pathMatch: 'full'
},
{ path: 'forms', component: ReactiveFormsComponent, data: { title: 'Real life example' } },
{ path: 'forms', component: ReactiveFormsComponent, data: { title: 'Reactive forms' } },
{ path: 'bindings', component: SelectBindingsComponent, data: { title: 'Data bindings' } },
{ path: 'filter', component: SelectSearchComponent, data: { title: 'Filter and autocomplete'} },
{ path: 'tags', component: SelectTagsComponent, data: { title: 'Tags'} },
Expand Down
29 changes: 29 additions & 0 deletions demo/app/examples/reactive-forms.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
selector: 'reactive-forms',
template: `
<form [formGroup]="heroForm" novalidate>
<div class="form-row">
<div class="form-group col-md-6">
<label for="heroId">Basic select</label>
<ng-select formControlName="heroId">
<ng-option value="hero1">
<img src="{{basePath}}/assets/batman.png" width="20px" height="20px" /> Batman
</ng-option>
<ng-option value="hero2">
<img src="{{basePath}}/assets/spidey.png" width="20px" height="20px" /> Spider-Man
</ng-option>
<ng-option value="hero3">
<img src="{{basePath}}/assets/thor.png" width="20px" height="20px" /> Thor
</ng-option>
</ng-select>
</div>
<div class="form-group col-md-6">
<label for="yesno">Yes/No</label>
<ng-select formControlName="agree">
<ng-option [value]="true">Yes</ng-option>
<ng-option [value]="false">No</ng-option>
</ng-select>
</div>
</div>
<hr>
<div class="form-group">
<label for="state">Multi select</label>
<ng-select *ngIf="isCitiesControlVisible"
Expand Down Expand Up @@ -116,6 +142,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
})
export class ReactiveFormsComponent {

basePath = window['basePath'] === '/' ? '' : window['basePath'];
heroForm: FormGroup;

isCitiesControlVisible = true;
Expand Down Expand Up @@ -145,6 +172,8 @@ export class ReactiveFormsComponent {
this.loadPhotos();

this.heroForm = this.fb.group({
heroId: 'hero1',
agree: '',
selectedCitiesIds: [],
age: '',
album: '',
Expand Down
2 changes: 1 addition & 1 deletion demo/app/layout/sidenav-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Component } from '@angular/core';
template: `
<ul class="nav flex-column">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/forms">Real life example</a>
<a class="nav-link" routerLink="/forms">Reactive forms</a>
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="/filter">Filter and autocomplete</a>
Expand Down
Binary file added demo/assets/batman.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/assets/spidey.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/assets/thor.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions demo/index.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script>
var ngSelectVersion = '<%= htmlWebpackPlugin.options.ngSelectVersion %>';
var basePath = '<%= htmlWebpackPlugin.options.basePath %>';
</script>
</head>

Expand Down
5 changes: 1 addition & 4 deletions scripts/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,6 @@ module.exports = function makeWebpackConfig() {
emitErrors: false,
failOnHint: false
},
sassLoader: {
//includePaths: [path.resolve(__dirname, "node_modules/foundation-sites/scss")]
},
postcss: [
autoprefixer({
browsers: ['last 2 version']
Expand All @@ -134,7 +131,7 @@ module.exports = function makeWebpackConfig() {

new CopyWebpackPlugin([
{
from: root('./demo/assets')
from: root('./demo/assets'), to: 'assets'
}
])
],
Expand Down
13 changes: 13 additions & 0 deletions src/ng-select/ng-option.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, Input, Host, ElementRef } from '@angular/core';
import { NgOption } from './ng-select.types';

@Component({
selector: 'ng-option',
template: `<ng-content></ng-content>`
})
export class NgOptionComponent {
@Input() value: any;

constructor(public elementRef: ElementRef) {
}
}
5 changes: 3 additions & 2 deletions src/ng-select/ng-select.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<ng-template #defaultLabelTemplate let-item="item">
<div class="ng-value-wrapper default" [class.disabled]="item.disabled">
<span class="ng-value-icon left" (click)="unselect(item); $event.stopPropagation()" aria-hidden="true">×</span>
<span class="ng-value-label">{{item[bindLabel]}}</span>
<span class="ng-value-label" [innerHTML]="item[bindLabel]"></span>
</div>
</ng-template>

Expand Down Expand Up @@ -47,14 +47,15 @@
[class.marked]="item === itemsList.markedItem">

<ng-template #defaultOptionTemplate>
<span class="ng-option-label">{{item[bindLabel]}}</span>
<span class="ng-option-label" [innerHTML]="item[bindLabel]"></span>
</ng-template>

<ng-template
[ngTemplateOutlet]="optionTemplate || defaultOptionTemplate"
[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>
Expand Down
5 changes: 5 additions & 0 deletions src/ng-select/ng-select.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ ng-select {
@include box-sizing;
}

ng-option{
display: block;
@include box-sizing;
}

virtual-scroll {
display: block;
height: auto;
Expand Down
21 changes: 18 additions & 3 deletions src/ng-select/ng-select.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ describe('NgSelectComponent', function () {
});

describe('Custom templates', () => {
it('display custom header template', async(() => {
it('should display custom header template', async(() => {
const fixture = createTestingModule(
NgSelectBasicTestCmp,
`<ng-select [items]="cities" [(ngModel)]="selectedCity">
Expand All @@ -519,8 +519,7 @@ describe('NgSelectComponent', function () {
});
}));

it('display custom dropdown option template', async(() => {

it('should display custom dropdown option template', async(() => {
const fixture = createTestingModule(
NgSelectBasicTestCmp,
`<ng-select [items]="cities" [(ngModel)]="selectedCity">
Expand All @@ -536,6 +535,22 @@ describe('NgSelectComponent', function () {
expect(el).not.toBeNull();
});
}));

it('should create items from ng-option', fakeAsync(() => {
const fixture = createTestingModule(
NgSelectBasicTestCmp,
`<ng-select [(ngModel)]="selectedCity">
<ng-option [value]="true">Yes</ng-option>
<ng-option [value]="false">No</ng-option>
</ng-select>`);

tickAndDetectChanges(fixture);

const items = fixture.componentInstance.select.itemsList.items;
expect(items.length).toBe(2);
expect(items[0]).toEqual(jasmine.objectContaining({label: 'Yes', value: true, index: 0}));
expect(items[1]).toEqual(jasmine.objectContaining({label: 'No', value: false, index: 1}));
}));
});

describe('Multiple', () => {
Expand Down
39 changes: 36 additions & 3 deletions src/ng-select/ng-select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
OnInit,
OnDestroy,
OnChanges,
AfterViewInit,
forwardRef,
ChangeDetectorRef,
Input,
Expand All @@ -17,7 +18,7 @@ import {
ElementRef,
ChangeDetectionStrategy,
Optional,
Renderer2
Renderer2, ContentChildren, QueryList
} from '@angular/core';

import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
Expand All @@ -26,6 +27,7 @@ import { VirtualScrollComponent } from './virtual-scroll.component';
import { NgOption, KeyCode, NgSelectConfig } from './ng-select.types';
import { ItemsList } from './items-list';
import { Subject } from 'rxjs/Subject';
import { NgOptionComponent } from './ng-option.component';

const NG_SELECT_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
Expand All @@ -44,12 +46,13 @@ const NG_SELECT_VALUE_ACCESSOR = {
'role': 'dropdown'
}
})
export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, ControlValueAccessor {
export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit, ControlValueAccessor {

@ContentChild(NgOptionTemplateDirective, { read: TemplateRef }) optionTemplate: TemplateRef<any>;
@ContentChild(NgLabelTemplateDirective, { read: TemplateRef }) labelTemplate: TemplateRef<any>;

@ViewChild(VirtualScrollComponent) dropdownList: VirtualScrollComponent;
@ContentChildren(NgOptionComponent, { descendants: true }) ngOptions: QueryList<NgOptionComponent>;
@ViewChild('filterInput') filterInput;

// inputs
Expand Down Expand Up @@ -117,6 +120,12 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, ControlV
this.handleDocumentClick();
}

ngAfterViewInit() {
if (this.ngOptions.length > 0 && this.items.length === 0) {
this.setItemsFromNgOptions();
}
}

ngOnChanges(changes) {
if (changes.bindLabel || changes.bindValue) {
this.itemsList.setBindOptions(this.bindLabel, this.bindValue);
Expand Down Expand Up @@ -342,6 +351,30 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, ControlV
}
}

private setItemsFromNgOptions() {
if (!this.bindValue) {
this.bindValue = 'value';
this.itemsList.setBindOptions(this.bindLabel, this.bindValue);
}

const handleNgOptions = (options) => {
this.items = options.map(option => ({
value: option.value,
label: option.elementRef.nativeElement.innerHTML
}));
this.itemsList.setItems(this.items);

if (this._ngModel) {
this.itemsList.clearSelected();
this.selectWriteValue(this._ngModel);
}
this.detectChanges();
};

this.ngOptions.changes.subscribe(options => handleNgOptions(options));
handleNgOptions(this.ngOptions);
}

private handleDocumentClick() {
const handler = ($event) => {
// prevent close if clicked on select
Expand Down Expand Up @@ -399,7 +432,7 @@ export class NgSelectComponent implements OnInit, OnDestroy, OnChanges, ControlV
if (item) {
this.itemsList.select(item);
} else {
if (!this.bindValue) {
if (val instanceof Object) {
this.itemsList.addItem(val);
this.itemsList.select(val);
}
Expand Down
3 changes: 3 additions & 0 deletions src/ng-select/ng-select.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { NgOptionTemplateDirective, NgLabelTemplateDirective } from './ng-templa
import { VirtualScrollModule } from './virtual-scroll.component';
import { SpinnerComponent } from './spinner.component';
import { NgSelectConfig } from './ng-select.types';
import { NgOptionComponent } from './ng-option.component';

@NgModule({
declarations: [
NgSelectComponent,
NgOptionComponent,
NgOptionTemplateDirective,
NgLabelTemplateDirective,
SpinnerComponent
Expand All @@ -20,6 +22,7 @@ import { NgSelectConfig } from './ng-select.types';
],
exports: [
NgSelectComponent,
NgOptionComponent,
NgOptionTemplateDirective,
NgLabelTemplateDirective
]
Expand Down

0 comments on commit 6d841ef

Please sign in to comment.