Skip to content

Commit

Permalink
feat(search): input event (#1038)
Browse files Browse the repository at this point in the history
  • Loading branch information
WLun001 authored and yggg committed Apr 11, 2019
1 parent 47aff73 commit 58fa556
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 3 deletions.
6 changes: 6 additions & 0 deletions src/app/playground-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,12 @@ export const PLAYGROUND_COMPONENTS: ComponentLink[] = [
component: 'SearchTestComponent',
name: 'Search Test',
},
{
path: 'search-with-input-event.component',
link: '/search/search-with-input-event.component',
component: 'SearchWithInputEventComponent',
name: 'Search With Input Event',
},
],
},
{
Expand Down
112 changes: 112 additions & 0 deletions src/framework/theme/components/search/search.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import createSpy = jasmine.createSpy;
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import {
NbLayoutModule,
NbSearchComponent,
NbSearchFieldComponent,
NbSearchModule,
NbSearchService,
NbThemeModule,
NbLayoutComponent,
} from '@nebular/theme';

describe('NbSearchFieldComponent', () => {
let fixture: ComponentFixture<NbSearchFieldComponent>;
let searchFieldComponent: NbSearchFieldComponent;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([]),
NbThemeModule.forRoot(),
NbSearchModule,
],
});

fixture = TestBed.createComponent(NbSearchFieldComponent);
searchFieldComponent = fixture.componentInstance;
fixture.detectChanges();
});

it('should emit input event when typing in input', fakeAsync(() => {
const inputSpy = createSpy('inputSpy');
searchFieldComponent.searchInput.subscribe(inputSpy);

const input: HTMLInputElement = fixture.componentInstance.inputElement.nativeElement;
input.dispatchEvent(new Event('input'));
tick();

expect(inputSpy).toHaveBeenCalledTimes(1);
}));

it('should emit input event with input value', fakeAsync(() => {
const inputValue = 'input text';
const inputSpy = createSpy('inputSpy');
searchFieldComponent.searchInput.subscribe(inputSpy);

const input: HTMLInputElement = fixture.componentInstance.inputElement.nativeElement;
input.value = inputValue;
input.dispatchEvent(new Event('input'));
tick();

expect(inputSpy).toHaveBeenCalledWith(inputValue);
}));
});

describe('NbSearchComponent', () => {
let fixture: ComponentFixture<NbSearchComponent>;
let searchComponent: NbSearchComponent;

function getSearchFieldComponent(): NbSearchFieldComponent {
return fixture.debugElement.query(By.directive(NbSearchFieldComponent)).componentInstance;
}

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([]),
NbThemeModule.forRoot(),
NbLayoutModule,
NbSearchModule,
],
});

TestBed.createComponent(NbLayoutComponent);

fixture = TestBed.createComponent(NbSearchComponent);
searchComponent = fixture.componentInstance;
fixture.detectChanges();
});

it('should listen to search field input event', fakeAsync(() => {
searchComponent.openSearch();
fixture.detectChanges();
tick();

const inputSpy = spyOn(searchComponent, 'emitInput');
const searchFieldComponent: NbSearchFieldComponent = getSearchFieldComponent();

searchFieldComponent.searchInput.emit();
tick();
expect(inputSpy).toHaveBeenCalledTimes(1);
}));

it(`should propagate search event to search service`, fakeAsync(() => {
searchComponent.openSearch();
fixture.detectChanges();
tick();
const searchFieldComponent: NbSearchFieldComponent = getSearchFieldComponent();

const searchService = TestBed.get(NbSearchService);
const searchInput = spyOn(searchService, 'searchInput').and.callThrough();
const searchTerm = 'search term';

searchFieldComponent.searchInput.emit(searchTerm);
tick();

expect(searchInput).toHaveBeenCalledTimes(1);
expect(searchInput).toHaveBeenCalledWith(searchTerm, undefined);
}));
});
17 changes: 14 additions & 3 deletions src/framework/theme/components/search/search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
ViewChild,
ChangeDetectorRef,
OnChanges,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';

Expand Down Expand Up @@ -55,6 +55,7 @@ import { NbOverlayService, NbOverlayRef, NbPortalDirective } from '../cdk';
<div class="form-content">
<input class="search-input"
#searchInput
(input)="emitSearchInput(searchInput.value)"
autocomplete="off"
[attr.placeholder]="placeholder"
tabindex="-1"
Expand Down Expand Up @@ -83,6 +84,7 @@ export class NbSearchFieldComponent implements OnChanges, AfterViewInit {

@Output() close = new EventEmitter();
@Output() search = new EventEmitter();
@Output() searchInput = new EventEmitter();

@ViewChild('searchInput') inputElement: ElementRef<HTMLInputElement>;

Expand Down Expand Up @@ -149,6 +151,10 @@ export class NbSearchFieldComponent implements OnChanges, AfterViewInit {
}
}

emitSearchInput(term: string) {
this.searchInput.emit(term);
}

focusInput() {
if (this.show && this.inputElement) {
this.inputElement.nativeElement.focus();
Expand Down Expand Up @@ -213,6 +219,7 @@ export class NbSearchFieldComponent implements OnChanges, AfterViewInit {
[placeholder]="placeholder"
[hint]="hint"
(search)="search($event)"
(searchInput)="emitInput($event)"
(close)="emitDeactivate()">
</nb-search-field>
`,
Expand Down Expand Up @@ -320,6 +327,10 @@ export class NbSearchComponent implements OnInit, OnDestroy {
this.hideSearch();
}

emitInput(term: string) {
this.searchService.searchInput(term, this.tag);
}

emitActivate() {
this.searchService.activateSearch(this.type, this.tag);
}
Expand Down
18 changes: 18 additions & 0 deletions src/framework/theme/components/search/search.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import createSpy = jasmine.createSpy;
import objectContaining = jasmine.objectContaining;
import { NbSearchService } from '@nebular/theme';

describe('NbSearchService', () => {
it('should emit onSearchInput when searchInput called', () => {
const term = 'term';
const tag = 'tag';
const searchService = new NbSearchService();
const onSearchInputSpy = createSpy('onSearchInput');
searchService.onSearchInput().subscribe(onSearchInputSpy);

searchService.searchInput(term, tag);

expect(onSearchInputSpy).toHaveBeenCalledTimes(1);
expect(onSearchInputSpy.calls.first().args).toEqual(objectContaining([{ term, tag }]));
});
});
18 changes: 18 additions & 0 deletions src/framework/theme/components/search/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class NbSearchService {
private searchSubmittings$ = new Subject<{ term: string, tag?: string }>();
private searchActivations$ = new Subject<{ searchType: string, tag?: string }>();
private searchDeactivations$ = new Subject<{ searchType: string, tag?: string }>();
private searchInput$ = new Subject<{ term: string, tag?: string }>();

/***
* Activate (open) search component
Expand Down Expand Up @@ -44,6 +45,15 @@ export class NbSearchService {
this.searchSubmittings$.next({ term, tag });
}

/**
* Trigger search submit by input event
* @param {string} term
* @param {string} tag
*/
searchInput(term: string, tag?: string) {
this.searchInput$.next({term, tag});
}

/**
* Subscribe to 'activate' event
* @returns Observable<{searchType: string; tag?: string}>
Expand All @@ -67,4 +77,12 @@ export class NbSearchService {
onSearchSubmit(): Observable<{ term: string, tag?: string }> {
return this.searchSubmittings$.pipe(share());
}

/**
* Subscribe to input event
* @returns Observable<{term: string; tag?: string}>
*/
onSearchInput(): Observable<{ term: string, tag?: string }> {
return this.searchInput$.pipe(share());
}
}
2 changes: 2 additions & 0 deletions src/framework/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export * from './components/calendar/calendar-range.module';
export * from './components/calendar/calendar-range.component';
export * from './components/calendar-kit';
export * from './components/layout/layout.module';
export * from './components/layout/layout.component';
export * from './components/layout/restore-scroll-top.service';
export * from './components/menu/menu.module';
export { NbMenuService, NbMenuItem } from './components/menu/menu.service';
Expand All @@ -32,6 +33,7 @@ export * from './components/user/user.module';
export * from './components/actions/actions.module';
export * from './components/search/search.module';
export * from './components/search/search.service';
export * from './components/search/search.component';
export * from './components/checkbox/checkbox.component';
export * from './components/checkbox/checkbox.module';
export * from './components/badge/badge.component';
Expand Down
5 changes: 5 additions & 0 deletions src/playground/without-layout/search/search-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SearchCustomizedTestComponent } from './search-customized-test.componen
import { SearchEventComponent } from './search-event.component';
import { SearchShowcaseComponent } from './search-showcase.component';
import { SearchTestComponent } from './search-test.component';
import { SearchWithInputEventComponent } from './search-with-input-event.component';

const routes: Route[] = [
{
Expand All @@ -28,6 +29,10 @@ const routes: Route[] = [
path: 'search-test.component',
component: SearchTestComponent,
},
{
path: 'search-with-input-event.component',
component: SearchWithInputEventComponent,
},
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import {NbSearchService} from '@nebular/theme';
import {debounceTime, distinctUntilChanged} from 'rxjs/operators';

@Component({
selector: 'nb-search-with-input-event',
template: `
<nb-layout>
<nb-layout-header fixed>
<nb-search type="rotate-layout" tag="search-input-event"></nb-search>
</nb-layout-header>
<nb-sidebar>
</nb-sidebar>
<nb-layout-column>
<nb-card accent="info">
<nb-card-header>You searched for:</nb-card-header>
<nb-card-body>
<h3>{{ value || '-' }}</h3>
</nb-card-body>
</nb-card>
</nb-layout-column>
</nb-layout>
`,
})
export class SearchWithInputEventComponent implements OnInit {
value: string;
constructor(private searchService: NbSearchService) {
}

ngOnInit() {
this.searchService.onSearchInput().pipe(
debounceTime(300),
distinctUntilChanged(),
).subscribe((data: { term: string, tag: string }) => {
this.value = data.term;
});
}

}
2 changes: 2 additions & 0 deletions src/playground/without-layout/search/search.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { SearchCustomizedTestComponent } from './search-customized-test.componen
import { SearchEventComponent } from './search-event.component';
import { SearchShowcaseComponent } from './search-showcase.component';
import { SearchTestComponent } from './search-test.component';
import { SearchWithInputEventComponent } from './search-with-input-event.component';

@NgModule({
declarations: [
SearchCustomizedTestComponent,
SearchEventComponent,
SearchShowcaseComponent,
SearchTestComponent,
SearchWithInputEventComponent,
],
imports: [
NbSearchModule,
Expand Down

0 comments on commit 58fa556

Please sign in to comment.