UNPKG

ngx-bootstrap

Version:
320 lines 14 kB
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer, ViewContainerRef } from '@angular/core'; import { NgControl } from '@angular/forms'; import { TypeaheadContainerComponent } from './typeahead-container.component'; import { getValueFromObject, latinize, tokenize } from './typeahead-utils'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/observable/from'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/toArray'; import { TypeaheadMatch } from './typeahead-match.class'; import { ComponentLoaderFactory } from '../component-loader'; export var TypeaheadDirective = (function () { function TypeaheadDirective(control, viewContainerRef, element, renderer, cis) { /** minimal no of characters that needs to be entered before typeahead kicks-in. When set to 0, typeahead shows on focus with full list of options (limited as normal by typeaheadOptionsLimit) */ this.typeaheadMinLength = void 0; /** should be used only in case of typeahead attribute is array. If true - loading of options will be async, otherwise - sync. true make sense if options array is large. */ this.typeaheadAsync = void 0; /** match latin symbols. If true the word súper would match super and vice versa. */ this.typeaheadLatinize = true; /** break words with spaces. If true the text "exact phrase" here match would match with match exact phrase here but not with phrase here exact match (kind of "google style"). */ this.typeaheadSingleWords = true; /** should be used only in case typeaheadSingleWords attribute is true. Sets the word delimiter to break words. Defaults to space. */ this.typeaheadWordDelimiters = ' '; /** should be used only in case typeaheadSingleWords attribute is true. Sets the word delimiter to match exact phrase. Defaults to simple and double quotes. */ this.typeaheadPhraseDelimiters = '\'"'; /** fired when 'busy' state of this component was changed, fired on async mode only, returns boolean */ this.typeaheadLoading = new EventEmitter(); /** fired on every key event and returns true in case of matches are not detected */ this.typeaheadNoResults = new EventEmitter(); /** fired when option was selected, return object with data of this option */ this.typeaheadOnSelect = new EventEmitter(); /** fired when blur event occurres. returns the active item */ this.typeaheadOnBlur = new EventEmitter(); this.isTypeaheadOptionsListActive = false; this.keyUpEventEmitter = new EventEmitter(); this.placement = 'bottom-left'; this.element = element; this.ngControl = control; this.viewContainerRef = viewContainerRef; this.renderer = renderer; this._typeahead = cis .createLoader(element, viewContainerRef, renderer); } TypeaheadDirective.prototype.onChange = function (e) { if (this._container) { // esc if (e.keyCode === 27) { this.hide(); return; } // up if (e.keyCode === 38) { this._container.prevActiveMatch(); return; } // down if (e.keyCode === 40) { this._container.nextActiveMatch(); return; } // enter if (e.keyCode === 13) { this._container.selectActiveMatch(); return; } } // For `<input>`s, use the `value` property. For others that don't have a // `value` (such as `<span contenteditable="true">`, use `innerText`. var value = e.target.value !== undefined ? e.target.value : e.target.innerText; if (value.trim().length >= this.typeaheadMinLength) { this.typeaheadLoading.emit(true); this.keyUpEventEmitter.emit(e.target.value); } else { this.typeaheadLoading.emit(false); this.typeaheadNoResults.emit(false); this.hide(); } }; TypeaheadDirective.prototype.onFocus = function () { if (this.typeaheadMinLength === 0) { this.typeaheadLoading.emit(true); this.keyUpEventEmitter.emit(''); } }; TypeaheadDirective.prototype.onBlur = function () { if (this._container && !this._container.isFocused) { this.typeaheadOnBlur.emit(this._container.active); this.hide(); } }; TypeaheadDirective.prototype.onKeydown = function (e) { // no container - no problems if (!this._container) { return; } // if items is visible - prevent form submition if (e.keyCode === 13) { e.preventDefault(); return; } }; TypeaheadDirective.prototype.ngOnInit = function () { this.typeaheadOptionsLimit = this.typeaheadOptionsLimit || 20; this.typeaheadMinLength = this.typeaheadMinLength === void 0 ? 1 : this.typeaheadMinLength; this.typeaheadWaitMs = this.typeaheadWaitMs || 0; // async should be false in case of array if (this.typeaheadAsync === undefined && !(this.typeahead instanceof Observable)) { this.typeaheadAsync = false; } if (this.typeahead instanceof Observable) { this.typeaheadAsync = true; } if (this.typeaheadAsync) { this.asyncActions(); } else { this.syncActions(); } }; TypeaheadDirective.prototype.changeModel = function (match) { var valueStr = match.value; this.ngControl.viewToModelUpdate(valueStr); this.ngControl.control.setValue(valueStr); this.hide(); }; Object.defineProperty(TypeaheadDirective.prototype, "matches", { get: function () { return this._matches; }, enumerable: true, configurable: true }); TypeaheadDirective.prototype.show = function () { this._typeahead .attach(TypeaheadContainerComponent) .to(this.container) .position({ attachment: 'bottom left' }) .show({ typeaheadRef: this, placement: this.placement, animation: false }); this._container = this._typeahead.instance; this._container.parent = this; // This improves the speed as it won't have to be done for each list item var normalizedQuery = (this.typeaheadLatinize ? latinize(this.ngControl.control.value) : this.ngControl.control.value).toString() .toLowerCase(); this._container.query = this.typeaheadSingleWords ? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters) : normalizedQuery; this._container.matches = this._matches; this.element.nativeElement.focus(); }; TypeaheadDirective.prototype.hide = function () { if (this._typeahead.isShown) { this._typeahead.hide(); this._container = null; } }; TypeaheadDirective.prototype.ngOnDestroy = function () { this._typeahead.dispose(); }; TypeaheadDirective.prototype.asyncActions = function () { var _this = this; this.keyUpEventEmitter .debounceTime(this.typeaheadWaitMs) .mergeMap(function () { return _this.typeahead; }) .subscribe(function (matches) { _this.finalizeAsyncCall(matches); }, function (err) { console.error(err); }); }; TypeaheadDirective.prototype.syncActions = function () { var _this = this; this.keyUpEventEmitter .debounceTime(this.typeaheadWaitMs) .mergeMap(function (value) { var normalizedQuery = _this.normalizeQuery(value); return Observable.from(_this.typeahead) .filter(function (option) { return option && _this.testMatch(_this.normalizeOption(option), normalizedQuery); }) .toArray(); }) .subscribe(function (matches) { _this.finalizeAsyncCall(matches); }, function (err) { console.error(err); }); }; TypeaheadDirective.prototype.normalizeOption = function (option) { var optionValue = getValueFromObject(option, this.typeaheadOptionField); var normalizedOption = this.typeaheadLatinize ? latinize(optionValue) : optionValue; return normalizedOption.toLowerCase(); }; TypeaheadDirective.prototype.normalizeQuery = function (value) { // If singleWords, break model here to not be doing extra work on each // iteration var normalizedQuery = (this.typeaheadLatinize ? latinize(value) : value) .toString() .toLowerCase(); normalizedQuery = this.typeaheadSingleWords ? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters) : normalizedQuery; return normalizedQuery; }; TypeaheadDirective.prototype.testMatch = function (match, test) { var spaceLength; if (typeof test === 'object') { spaceLength = test.length; for (var i = 0; i < spaceLength; i += 1) { if (test[i].length > 0 && match.indexOf(test[i]) < 0) { return false; } } return true; } else { return match.indexOf(test) >= 0; } }; TypeaheadDirective.prototype.finalizeAsyncCall = function (matches) { this.prepareMatches(matches); this.typeaheadLoading.emit(false); this.typeaheadNoResults.emit(!this.hasMatches()); if (!this.hasMatches()) { this.hide(); return; } if (this._container) { // This improves the speed as it won't have to be done for each list item var normalizedQuery = (this.typeaheadLatinize ? latinize(this.ngControl.control.value) : this.ngControl.control.value).toString() .toLowerCase(); this._container.query = this.typeaheadSingleWords ? tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters) : normalizedQuery; this._container.matches = this._matches; } else { this.show(); } }; TypeaheadDirective.prototype.prepareMatches = function (options) { var _this = this; var limited = options.slice(0, this.typeaheadOptionsLimit); if (this.typeaheadGroupField) { var matches_1 = []; // extract all group names var groups = limited .map(function (option) { return getValueFromObject(option, _this.typeaheadGroupField); }) .filter(function (v, i, a) { return a.indexOf(v) === i; }); groups.forEach(function (group) { // add group header to array of matches matches_1.push(new TypeaheadMatch(group, group, true)); // add each item of group to array of matches matches_1 = matches_1.concat(limited .filter(function (option) { return getValueFromObject(option, _this.typeaheadGroupField) === group; }) .map(function (option) { return new TypeaheadMatch(option, getValueFromObject(option, _this.typeaheadOptionField)); })); }); this._matches = matches_1; } else { this._matches = limited.map(function (option) { return new TypeaheadMatch(option, getValueFromObject(option, _this.typeaheadOptionField)); }); } }; TypeaheadDirective.prototype.hasMatches = function () { return this._matches.length > 0; }; TypeaheadDirective.decorators = [ { type: Directive, args: [{ selector: '[typeahead]', exportAs: 'bs-typeahead' },] }, ]; /** @nocollapse */ TypeaheadDirective.ctorParameters = function () { return [ { type: NgControl, }, { type: ViewContainerRef, }, { type: ElementRef, }, { type: Renderer, }, { type: ComponentLoaderFactory, }, ]; }; TypeaheadDirective.propDecorators = { 'typeahead': [{ type: Input },], 'typeaheadMinLength': [{ type: Input },], 'typeaheadWaitMs': [{ type: Input },], 'typeaheadOptionsLimit': [{ type: Input },], 'typeaheadOptionField': [{ type: Input },], 'typeaheadGroupField': [{ type: Input },], 'typeaheadAsync': [{ type: Input },], 'typeaheadLatinize': [{ type: Input },], 'typeaheadSingleWords': [{ type: Input },], 'typeaheadWordDelimiters': [{ type: Input },], 'typeaheadPhraseDelimiters': [{ type: Input },], 'typeaheadItemTemplate': [{ type: Input },], 'optionsListTemplate': [{ type: Input },], 'typeaheadLoading': [{ type: Output },], 'typeaheadNoResults': [{ type: Output },], 'typeaheadOnSelect': [{ type: Output },], 'typeaheadOnBlur': [{ type: Output },], 'container': [{ type: Input },], 'onChange': [{ type: HostListener, args: ['keyup', ['$event'],] },], 'onFocus': [{ type: HostListener, args: ['focus',] },], 'onBlur': [{ type: HostListener, args: ['blur',] },], 'onKeydown': [{ type: HostListener, args: ['keydown', ['$event'],] },], }; return TypeaheadDirective; }()); //# sourceMappingURL=typeahead.directive.js.map