ngx-bootstrap
Version:
Native Angular Bootstrap Components
320 lines • 14 kB
JavaScript
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