--- /dev/null
+import {Component, Input, Host, OnInit} from '@angular/core';
+import {ComboboxComponent} from './combobox.component';
+
+@Component({
+ selector: 'eg-combobox-entry',
+ template: '<ng-template></ng-template>'
+})
+export class ComboboxEntryComponent implements OnInit{
+
+ @Input() entryId: any;
+ @Input() entryLabel: string;
+ @Input() selected: boolean;
+
+ constructor(@Host() private combobox: ComboboxComponent) {}
+
+ ngOnInit() {
+ if (this.selected) {
+ this.combobox.startId = this.entryId;
+ }
+ this.combobox.addEntries(
+ [{id: this.entryId, label: this.entryLabel}]);
+ }
+}
+
+
--- /dev/null
+
+<!-- todo disabled -->
+<ng-template #displayTemplate let-r="result">
+{{r.label}}
+</ng-template>
+
+<div class="d-flex">
+ <input type="text"
+ class="form-control"
+ [placeholder]="placeholder"
+ [(ngModel)]="selected"
+ [ngbTypeahead]="filter"
+ [resultTemplate]="displayTemplate"
+ [inputFormatter]="formatDisplayString"
+ (click)="click$.next($event.target.value)"
+ (blur)="onBlur()"
+ (selectItem)="selectorChanged($event)"
+ #instance="ngbTypeahead"/>
+ <div class="d-flex flex-column icons" (click)="openMe($event)">
+ <span class="material-icons">keyboard_arrow_up</span>
+ <span class="material-icons">keyboard_arrow_down</span>
+ </div>
+</div>
--- /dev/null
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map} from 'rxjs/operators/map';
+import {mapTo} from 'rxjs/operators/mapTo';
+import {debounceTime} from 'rxjs/operators/debounceTime';
+import {distinctUntilChanged} from 'rxjs/operators/distinctUntilChanged';
+import {merge} from 'rxjs/operators/merge';
+import {filter} from 'rxjs/operators/filter';
+import {Subject} from 'rxjs/Subject';
+import {NgbTypeahead, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
+import {StoreService} from '@eg/core/store.service';
+
+export interface ComboboxEntry {
+ id: any;
+ label: string;
+ freetext?: boolean;
+}
+
+@Component({
+ selector: 'eg-combobox',
+ templateUrl: './combobox.component.html',
+ styles: [`
+ .icons {margin-left:-18px}
+ .material-icons {font-size: 16px;font-weight:bold}
+ `]
+})
+export class ComboboxComponent implements OnInit {
+
+ selected: ComboboxEntry;
+ click$: Subject<string>;
+ entrylist: ComboboxEntry[];
+ freeTextId: number;
+
+ @ViewChild('instance') instance: NgbTypeahead;
+
+ // Placeholder text for selector input
+ @Input() placeholder = '';
+
+ @Input() persistKey: string; // TODO
+
+ // Display all entries when the user clicks in the text filter
+ // box regardless of any text that already exists there.
+ @Input() clickShowsAll = true;
+
+ @Input() allowFreeText = false;
+
+ // If true select the first item in the list
+ @Input() selectFirst: boolean;
+
+ // Entry ID of the default entry to select (optional)
+ // onChange() is NOT fired when applying the default value
+ @Input() startId: any;
+
+ @Input() set entries(el: ComboboxEntry[]) {
+ this.addEntries(el);
+ }
+
+ // Emitted when the value is changed via UI.
+ @Output() onChange: EventEmitter<ComboboxEntry>;
+
+ // Useful for massaging the match string prior to comparison
+ // and display. Default version trims leading/trailing spaces.
+ formatDisplayString: (ComboboxEntry) => string;
+
+ constructor(
+ private elm: ElementRef,
+ private store: StoreService,
+ ) {
+ this.entrylist = [];
+ this.click$ = new Subject<string>();
+ this.onChange = new EventEmitter<ComboboxEntry>();
+ this.freeTextId = -1;
+
+ this.formatDisplayString = (result: ComboboxEntry) => {
+ return result.label.trim();
+ };
+ }
+
+ ngOnInit() {
+ }
+
+ openMe($event) {
+ // Give the input a chance to focus then fire the click
+ // handler to force open the typeahead
+ this.elm.nativeElement.getElementsByTagName('input')[0].focus();
+ setTimeout(() => this.click$.next(''));
+ }
+
+ // Called by combobox-entry.component
+ addEntries(entries: ComboboxEntry[]) {
+ entries.forEach(entry => {
+
+ if (this.entrylist.filter(e => e.id === entry.id).length) {
+ // avoid dupes
+ return;
+ }
+
+ this.entrylist.push(entry);
+
+ if (this.startId === entry.id) {
+ this.selected = entry;
+ } else if (this.selectFirst && this.entrylist.length === 1) {
+ this.selected = entry;
+ }
+ });
+ }
+
+ onBlur() {
+
+ if (typeof this.selected === 'string' && this.selected !== '') {
+ // Free text entered which does not match a known entry
+
+ if (this.allowFreeText) {
+ // translate it into a dummy ComboboxEntry
+ // and manually fire the onchange handler.
+ this.selected = {
+ id: this.freeTextId--,
+ label: this.selected,
+ freetext: true
+ }
+ this.selectorChanged(
+ {item: this.selected, preventDefault: () => true});
+ } else {
+ // If free text is now allowed, clear the value when
+ // the user navigates away to avoid confusion.
+ this.selected = null;
+ }
+ }
+ }
+
+ // Fired by the typeahead to inform us of a change.
+ // This only fires when an item in the list is selected, not when
+ // the value is cleared or free-text is used.
+ selectorChanged(selEvent: NgbTypeaheadSelectItemEvent) {
+ console.log('selector changed');
+ this.onChange.emit(selEvent.item.id);
+ }
+
+ filter = (text$: Observable<string>): Observable<ComboboxEntry[]> => {
+ return text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged(),
+
+ merge(
+ // Inject a specifier indicating the source of the
+ // action is a user click instead of a text entry.
+ this.click$
+ .pipe(filter(() => !this.instance.isPopupOpen()))
+ .pipe(map(nothing => {
+ if (this.clickShowsAll) {
+ return '_CLICK_';
+ } else {
+ return nothing;
+ }
+ }))
+ ),
+
+ map(term => {
+ if (term === '' || term === '_CLICK_') {
+ // Click events display all visible entrylist
+ return this.entrylist;
+ }
+
+ // Filter entrylist whose labels substring-match the
+ // text entered.
+ return this.entrylist.filter(entry =>
+ entry.label.toLowerCase().indexOf(term.toLowerCase()) > -1
+ );
+ })
+ );
+ }
+}
+
+
-/** TODO PORT ME TO <eg-typeahead> */
+/** TODO PORT ME TO <eg-combobox> */
import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {map} from 'rxjs/operators/map';
+++ /dev/null
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef} from '@angular/core';
-import {Observable} from 'rxjs/Observable';
-import {map} from 'rxjs/operators/map';
-import {mapTo} from 'rxjs/operators/mapTo';
-import {debounceTime} from 'rxjs/operators/debounceTime';
-import {distinctUntilChanged} from 'rxjs/operators/distinctUntilChanged';
-import {merge} from 'rxjs/operators/merge';
-import {filter} from 'rxjs/operators/filter';
-import {Subject} from 'rxjs/Subject';
-import {NgbTypeahead, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
-import {StoreService} from '@eg/core/store.service';
-
-export interface TypeaheadEntry {
- id: any;
- label: string;
- freetext?: boolean;
-}
-
-@Component({
- selector: 'eg-typeahead',
- templateUrl: './typeahead.component.html',
- styles: [`
- .icons {margin-left:-18px}
- .material-icons {font-size: 16px;font-weight:bold}
- `]
-})
-export class TypeaheadComponent implements OnInit {
-
- selected: TypeaheadEntry;
- click$: Subject<string>;
- entrylist: TypeaheadEntry[];
- freeTextId: number;
-
- @ViewChild('instance') instance: NgbTypeahead;
-
- // Placeholder text for selector input
- @Input() placeholder = '';
-
- @Input() persistKey: string; // TODO
-
- // Display all entries when the user clicks in the text filter
- // box regardless of any text that already exists there.
- @Input() clickShowsAll = true;
-
- @Input() allowFreeText = false;
-
- // Entry ID of the default entry to select (optional)
- // onChange() is NOT fired when applying the default value
- @Input() startId: any;
-
- @Input() set entries(el: TypeaheadEntry[]) {
- this.entrylist = el;
- }
-
- // Emitted when the value is changed via UI.
- @Output() onChange: EventEmitter<TypeaheadEntry>;
-
- // Useful for massaging the match string prior to comparison
- // and display. Default version trims leading/trailing spaces.
- formatDisplayString: (TypeaheadEntry) => string;
-
- constructor(
- private elm: ElementRef,
- private store: StoreService,
- ) {
- this.entrylist = [];
- this.click$ = new Subject<string>();
- this.onChange = new EventEmitter<TypeaheadEntry>();
- this.freeTextId = -1;
-
- this.formatDisplayString = (result: TypeaheadEntry) => {
- return result.label.trim();
- };
- }
-
- ngOnInit() {
- if (this.startId) {
- this.selected = this.entrylist.filter(
- e => e.id === this.startId)[0];
- }
- }
-
- openMe($event) {
- // Give the input a chance to focus then fire the click
- // handler to force open the typeahead
- this.elm.nativeElement.getElementsByTagName('input')[0].focus();
- setTimeout(() => this.click$.next(''));
- }
-
- onBlur() {
-
- if (typeof this.selected === 'string' && this.selected !== '') {
- // Free text entered which does not match a known entry
-
- if (this.allowFreeText) {
- // translate it into a dummy TypeaheadEntry
- // and manually fire the onchange handler.
- this.selected = {
- id: this.freeTextId--,
- label: this.selected,
- freetext: true
- }
- this.selectorChanged(
- {item: this.selected, preventDefault: () => true});
- } else {
- // If free text is now allowed, clear the value when
- // the user navigates away to avoid confusion.
- this.selected = null;
- }
- }
- }
-
- // Fired by the typeahead to inform us of a change.
- // This only fires when an item in the list is selected, not when
- // the value is cleared or free-text is used.
- selectorChanged(selEvent: NgbTypeaheadSelectItemEvent) {
- console.log('selector changed');
- this.onChange.emit(selEvent.item.id);
- }
-
- filter = (text$: Observable<string>): Observable<TypeaheadEntry[]> => {
- return text$.pipe(
- debounceTime(200),
- distinctUntilChanged(),
-
- merge(
- // Inject a specifier indicating the source of the
- // action is a user click instead of a text entry.
- this.click$
- .pipe(filter(() => !this.instance.isPopupOpen()))
- .pipe(map(nothing => {
- if (this.clickShowsAll) {
- return '_CLICK_';
- } else {
- return nothing;
- }
- }))
- ),
-
- map(term => {
- if (term === '' || term === '_CLICK_') {
- // Click events display all visible entrylist
- return this.entrylist;
- }
-
- // Filter entrylist whose labels substring-match the
- // text entered.
- return this.entrylist.filter(entry =>
- entry.label.toLowerCase().indexOf(term.toLowerCase()) > -1
- );
- })
- );
- }
-}
-
-
import {NgModule, ModuleWithProviders} from '@angular/core';
import {EgCommonModule} from '@eg/common.module';
import {StaffBannerComponent} from './share/staff-banner.component';
-import {TypeaheadComponent} from '@eg/share/typeahead/typeahead.component';
+import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
import {AccessKeyDirective} from '@eg/share/accesskey/accesskey.directive';
import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
@NgModule({
declarations: [
StaffBannerComponent,
- TypeaheadComponent,
+ ComboboxComponent,
+ ComboboxEntryComponent,
OrgSelectComponent,
AccessKeyDirective,
AccessKeyInfoComponent,
exports: [
EgCommonModule,
StaffBannerComponent,
- TypeaheadComponent,
+ ComboboxComponent,
+ ComboboxEntryComponent,
OrgSelectComponent,
AccessKeyDirective,
AccessKeyInfoComponent,
<button class="btn btn-info" (click)="testToast()">Test Toast Message</button>
</div>
<div class="col-lg-4">
- Typeahead: <eg-typeahead [entries]="taEntries" [clickShowsAll]="true"></eg-typeahead>
+ Typeahead: <eg-combobox [entries]="taEntries" [clickShowsAll]="true"></eg-combobox>
</div>
</div>
<!-- /Progress Dialog Experiments ----------------------------- -->