{{r.fm.name()}} ({{getOrgShortname(r.fm.owning_lib())}})
</ng-template>
-<div class="d-flex">
- <input type="text"
- class="form-control"
- [ngClass]="{'text-success font-italic font-weight-bold': selected && selected.freetext}"
- [placeholder]="placeholder"
- [name]="name"
- [disabled]="isDisabled"
- [required]="isRequired"
- [(ngModel)]="selected"
- [ngbTypeahead]="filter"
- [resultTemplate]="getResultTemplate()"
- [inputFormatter]="formatDisplayString"
- (click)="onClick($event)"
- (blur)="onBlur()"
- container="body"
- (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>
+<ng-container *ngIf="readOnly">
+ <span *ngIf="!selected" i18n><Unset></span>
+ <ng-container *ngIf="selected">
+ <ng-container *ngTemplateOutlet="getResultTemplate();context:{result: selected}">
+ </ng-container>
+</ng-container>
+
+<ng-container *ngIf="!readOnly">
+ <div class="d-flex">
+ <input type="text"
+ class="form-control"
+ [id]="domId"
+ [ngClass]="{
+ 'text-success font-italic font-weight-bold': selected && selected.freetext,
+ 'form-control-sm': smallFormControl
+ }"
+ [placeholder]="placeholder"
+ [name]="name"
+ [disabled]="isDisabled"
+ [required]="isRequired"
+ [(ngModel)]="selected"
+ [ngbTypeahead]="filter"
+ [resultTemplate]="getResultTemplate()"
+ [inputFormatter]="formatDisplayString"
+ (click)="onClick($event)"
+ (blur)="onBlur()"
+ container="body"
+ (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>
-</div>
+</ng-container>
multi: true
}]
})
-export class ComboboxComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges {
+export class ComboboxComponent
+ implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges {
+
+ static domIdAuto = 0;
selected: ComboboxEntry;
click$: Subject<string>;
entrylist: ComboboxEntry[];
- @ViewChild('instance', { static: true }) instance: NgbTypeahead;
- @ViewChild('defaultDisplayTemplate', { static: true}) defaultDisplayTemplate: TemplateRef<any>;
+ @ViewChild('instance', {static: false}) instance: NgbTypeahead;
+ @ViewChild('defaultDisplayTemplate', {static: true}) defaultDisplayTemplate: TemplateRef<any>;
@ViewChildren(IdlClassTemplateDirective) idlClassTemplates: QueryList<IdlClassTemplateDirective>;
+ @Input() domId = 'eg-combobox-' + ComboboxComponent.domIdAuto++;
+
// Applies a name attribute to the input.
// Useful in forms.
@Input() name: string;
@Input() inputSize: number = null;
+ // If true, applies form-control-sm CSS
+ @Input() smallFormControl = false;
+
// Add a 'required' attribute to the input
isRequired: boolean;
@Input() set required(r: boolean) {
@Input() idlClass: string;
@Input() startIdFiresOnChange: boolean;
+ // This will be appended to the async data retrieval query
+ // when fetching objects by idlClass.
+ @Input() idlQueryAnd: {[field: string]: any};
+
+ // Display the selected value as text instead of within
+ // the typeahead
+ @Input() readOnly = false;
+
// Allow the selected entry ID to be passed via the template
// This does NOT not emit onChange events.
@Input() set selectedId(id: any) {
if (id === undefined) { return; }
// clear on explicit null
- if (id === null) { this.selected = null; }
+ if (id === null) {
+ this.selected = null;
+ return;
+ }
if (this.entrylist.length) {
this.selected = this.entrylist.filter(e => e.id === id)[0];
const args = {};
const extra_args = { order_by : {} };
args[field] = {'ilike': `%${term}%`}; // could -or search on label
+ if (this.idlQueryAnd) {
+ Object.assign(args, this.idlQueryAnd);
+ }
extra_args['order_by'][this.idlClass] = field;
extra_args['limit'] = 100;
if (this.idlIncludeLibraryInLabel) {
// Apply a default selection where needed
applySelection() {
- if (this.startId !== null &&
- this.entrylist && !this.defaultSelectionApplied) {
+ if (this.entrylist && !this.defaultSelectionApplied) {
const entry =
this.entrylist.filter(e => e.id === this.startId)[0];
}
addAsyncEntry(entry: ComboboxEntry) {
+ if (!entry) { return; }
// Avoid duplicate async entries
if (!this.asyncIds['' + entry.id]) {
this.asyncIds['' + entry.id] = true;
if (typeof this.selected === 'string') {
if (this.allowFreeText && this.selected !== '') {
- // Free text entered which does not match a known entry
- // translate it into a dummy ComboboxEntry
- this.selected = {
- id: null,
- label: this.selected,
- freetext: true
- };
+ const freeText = this.entrylist.filter(e => e.id === null)[0];
+
+ if (freeText) {
+
+ // If we already had a free text entry, just replace
+ // the label with the new value
+ freeText.label = this.selected;
+ this.selected = freeText;
+
+ } else {
+
+ // Free text entered which does not match a known entry
+ // translate it into a dummy ComboboxEntry
+ this.selected = {
+ id: null,
+ label: this.selected,
+ freetext: true
+ };
+ }
} else {
return of(term);
}
- let searchTerm: string;
- searchTerm = term;
- if (searchTerm === '_CLICK_') {
+ let searchTerm = term;
+ if (term === '_CLICK_') {
if (this.asyncSupportsEmptyTermClick) {
+ // Search for "all", but retain and propage the _CLICK_
+ // term so the filter knows to open the selector
searchTerm = '';
} else {
+ // Skip the final filter map and display nothing.
return of();
}
}
(entry: ComboboxEntry) => this.addAsyncEntry(entry),
err => {},
() => {
- observer.next(searchTerm);
+ observer.next(term);
observer.complete();
}
);
// click action occurred.
if (term === '') { return []; }
- // In sync-data mode, a click displays the full list.
- if (term === '_CLICK_' && !this.asyncDataSource) {
- return this.entrylist;
- }
+ // If we make it this far, _CLICK_ means show everything.
+ if (term === '_CLICK_') { term = ''; }
// Filter entrylist whose labels substring-match the
// text entered.