UNPKG

14.2 kBJavaScriptView Raw
1import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer, ViewContainerRef } from '@angular/core';
2import { NgControl } from '@angular/forms';
3import { TypeaheadContainerComponent } from './typeahead-container.component';
4import { TypeaheadUtils } from './typeahead-utils';
5import { Observable } from 'rxjs/Observable';
6import 'rxjs/add/observable/from';
7import 'rxjs/add/operator/debounceTime';
8import 'rxjs/add/operator/filter';
9import 'rxjs/add/operator/map';
10import 'rxjs/add/operator/mergeMap';
11import 'rxjs/add/operator/toArray';
12import { TypeaheadMatch } from './typeahead-match.class';
13import { ComponentLoaderFactory } from '../component-loader';
14export var TypeaheadDirective = (function () {
15 function TypeaheadDirective(control, viewContainerRef, element, renderer, cis) {
16 /** 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) */
17 this.typeaheadMinLength = void 0;
18 /** 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. */
19 this.typeaheadAsync = void 0;
20 /** match latin symbols. If true the word súper would match super and vice versa. */
21 this.typeaheadLatinize = true;
22 /** 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"). */
23 this.typeaheadSingleWords = true;
24 /** should be used only in case typeaheadSingleWords attribute is true. Sets the word delimiter to break words. Defaults to space. */
25 this.typeaheadWordDelimiters = ' ';
26 /** should be used only in case typeaheadSingleWords attribute is true. Sets the word delimiter to match exact phrase. Defaults to simple and double quotes. */
27 this.typeaheadPhraseDelimiters = '\'"';
28 /** fired when 'busy' state of this component was changed, fired on async mode only, returns boolean */
29 this.typeaheadLoading = new EventEmitter();
30 /** fired on every key event and returns true in case of matches are not detected */
31 this.typeaheadNoResults = new EventEmitter();
32 /** fired when option was selected, return object with data of this option */
33 this.typeaheadOnSelect = new EventEmitter();
34 /** fired when blur event occurres. returns the active item */
35 this.typeaheadOnBlur = new EventEmitter();
36 this.isTypeaheadOptionsListActive = false;
37 this.keyUpEventEmitter = new EventEmitter();
38 this.placement = 'bottom-left';
39 this.element = element;
40 this.ngControl = control;
41 this.viewContainerRef = viewContainerRef;
42 this.renderer = renderer;
43 this._typeahead = cis
44 .createLoader(element, viewContainerRef, renderer);
45 }
46 TypeaheadDirective.prototype.onChange = function (e) {
47 if (this._container) {
48 // esc
49 if (e.keyCode === 27) {
50 this.hide();
51 return;
52 }
53 // up
54 if (e.keyCode === 38) {
55 this._container.prevActiveMatch();
56 return;
57 }
58 // down
59 if (e.keyCode === 40) {
60 this._container.nextActiveMatch();
61 return;
62 }
63 // enter
64 if (e.keyCode === 13) {
65 this._container.selectActiveMatch();
66 return;
67 }
68 }
69 // For `<input>`s, use the `value` property. For others that don't have a
70 // `value` (such as `<span contenteditable="true">`, use `innerText`.
71 var value = e.target.value !== undefined
72 ? e.target.value
73 : e.target.innerText;
74 if (value.trim().length >= this.typeaheadMinLength) {
75 this.typeaheadLoading.emit(true);
76 this.keyUpEventEmitter.emit(e.target.value);
77 }
78 else {
79 this.typeaheadLoading.emit(false);
80 this.typeaheadNoResults.emit(false);
81 this.hide();
82 }
83 };
84 TypeaheadDirective.prototype.onFocus = function () {
85 if (this.typeaheadMinLength === 0) {
86 this.typeaheadLoading.emit(true);
87 this.keyUpEventEmitter.emit('');
88 }
89 };
90 TypeaheadDirective.prototype.onBlur = function () {
91 if (this._container && !this._container.isFocused) {
92 this.typeaheadOnBlur.emit(this._container.active);
93 this.hide();
94 }
95 };
96 TypeaheadDirective.prototype.onKeydown = function (e) {
97 // no container - no problems
98 if (!this._container) {
99 return;
100 }
101 // if items is visible - prevent form submition
102 if (e.keyCode === 13) {
103 e.preventDefault();
104 return;
105 }
106 };
107 TypeaheadDirective.prototype.ngOnInit = function () {
108 this.typeaheadOptionsLimit = this.typeaheadOptionsLimit || 20;
109 this.typeaheadMinLength = this.typeaheadMinLength === void 0
110 ? 1
111 : this.typeaheadMinLength;
112 this.typeaheadWaitMs = this.typeaheadWaitMs || 0;
113 // async should be false in case of array
114 if (this.typeaheadAsync === undefined && !(this.typeahead instanceof Observable)) {
115 this.typeaheadAsync = false;
116 }
117 if (this.typeahead instanceof Observable) {
118 this.typeaheadAsync = true;
119 }
120 if (this.typeaheadAsync) {
121 this.asyncActions();
122 }
123 else {
124 this.syncActions();
125 }
126 };
127 TypeaheadDirective.prototype.changeModel = function (match) {
128 var valueStr = match.value;
129 this.ngControl.viewToModelUpdate(valueStr);
130 this.ngControl.control.setValue(valueStr);
131 this.hide();
132 };
133 Object.defineProperty(TypeaheadDirective.prototype, "matches", {
134 get: function () {
135 return this._matches;
136 },
137 enumerable: true,
138 configurable: true
139 });
140 TypeaheadDirective.prototype.show = function () {
141 this._typeahead
142 .attach(TypeaheadContainerComponent)
143 .to(this.container)
144 .position({ attachment: 'bottom left' })
145 .show({
146 typeaheadRef: this,
147 placement: this.placement,
148 animation: false
149 });
150 this._container = this._typeahead.instance;
151 this._container.parent = this;
152 // This improves the speed as it won't have to be done for each list item
153 var normalizedQuery = (this.typeaheadLatinize
154 ? TypeaheadUtils.latinize(this.ngControl.control.value)
155 : this.ngControl.control.value).toString()
156 .toLowerCase();
157 this._container.query = this.typeaheadSingleWords
158 ? TypeaheadUtils.tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
159 : normalizedQuery;
160 this._container.matches = this._matches;
161 this.element.nativeElement.focus();
162 };
163 TypeaheadDirective.prototype.hide = function () {
164 if (this._typeahead.isShown) {
165 this._typeahead.hide();
166 this._container = null;
167 }
168 };
169 TypeaheadDirective.prototype.ngOnDestroy = function () {
170 this._typeahead.dispose();
171 };
172 TypeaheadDirective.prototype.asyncActions = function () {
173 var _this = this;
174 this.keyUpEventEmitter
175 .debounceTime(this.typeaheadWaitMs)
176 .mergeMap(function () { return _this.typeahead; })
177 .subscribe(function (matches) {
178 _this.finalizeAsyncCall(matches);
179 }, function (err) {
180 console.error(err);
181 });
182 };
183 TypeaheadDirective.prototype.syncActions = function () {
184 var _this = this;
185 this.keyUpEventEmitter
186 .debounceTime(this.typeaheadWaitMs)
187 .mergeMap(function (value) {
188 var normalizedQuery = _this.normalizeQuery(value);
189 return Observable.from(_this.typeahead)
190 .filter(function (option) {
191 return option && _this.testMatch(_this.normalizeOption(option), normalizedQuery);
192 })
193 .toArray();
194 })
195 .subscribe(function (matches) {
196 _this.finalizeAsyncCall(matches);
197 }, function (err) {
198 console.error(err);
199 });
200 };
201 TypeaheadDirective.prototype.normalizeOption = function (option) {
202 var optionValue = TypeaheadUtils.getValueFromObject(option, this.typeaheadOptionField);
203 var normalizedOption = this.typeaheadLatinize
204 ? TypeaheadUtils.latinize(optionValue)
205 : optionValue;
206 return normalizedOption.toLowerCase();
207 };
208 TypeaheadDirective.prototype.normalizeQuery = function (value) {
209 // If singleWords, break model here to not be doing extra work on each
210 // iteration
211 var normalizedQuery = (this.typeaheadLatinize ? TypeaheadUtils.latinize(value) : value)
212 .toString()
213 .toLowerCase();
214 normalizedQuery = this.typeaheadSingleWords
215 ?
216 TypeaheadUtils.tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
217 :
218 normalizedQuery;
219 return normalizedQuery;
220 };
221 TypeaheadDirective.prototype.testMatch = function (match, test) {
222 var spaceLength;
223 if (typeof test === 'object') {
224 spaceLength = test.length;
225 for (var i = 0; i < spaceLength; i += 1) {
226 if (test[i].length > 0 && match.indexOf(test[i]) < 0) {
227 return false;
228 }
229 }
230 return true;
231 }
232 else {
233 return match.indexOf(test) >= 0;
234 }
235 };
236 TypeaheadDirective.prototype.finalizeAsyncCall = function (matches) {
237 this.prepareMatches(matches);
238 this.typeaheadLoading.emit(false);
239 this.typeaheadNoResults.emit(!this.hasMatches());
240 if (!this.hasMatches()) {
241 this.hide();
242 return;
243 }
244 if (this._container) {
245 // This improves the speed as it won't have to be done for each list item
246 var normalizedQuery = (this.typeaheadLatinize
247 ? TypeaheadUtils.latinize(this.ngControl.control.value)
248 : this.ngControl.control.value).toString()
249 .toLowerCase();
250 this._container.query = this.typeaheadSingleWords
251 ? TypeaheadUtils.tokenize(normalizedQuery, this.typeaheadWordDelimiters, this.typeaheadPhraseDelimiters)
252 : normalizedQuery;
253 this._container.matches = this._matches;
254 }
255 else {
256 this.show();
257 }
258 };
259 TypeaheadDirective.prototype.prepareMatches = function (options) {
260 var _this = this;
261 var limited = options.slice(0, this.typeaheadOptionsLimit);
262 if (this.typeaheadGroupField) {
263 var matches_1 = [];
264 // extract all group names
265 var groups = limited
266 .map(function (option) { return TypeaheadUtils.getValueFromObject(option, _this.typeaheadGroupField); })
267 .filter(function (v, i, a) { return a.indexOf(v) === i; });
268 groups.forEach(function (group) {
269 // add group header to array of matches
270 matches_1.push(new TypeaheadMatch(group, group, true));
271 // add each item of group to array of matches
272 matches_1 = matches_1.concat(limited
273 .filter(function (option) { return TypeaheadUtils.getValueFromObject(option, _this.typeaheadGroupField) === group; })
274 .map(function (option) { return new TypeaheadMatch(option, TypeaheadUtils.getValueFromObject(option, _this.typeaheadOptionField)); }));
275 });
276 this._matches = matches_1;
277 }
278 else {
279 this._matches = limited.map(function (option) { return new TypeaheadMatch(option, TypeaheadUtils.getValueFromObject(option, _this.typeaheadOptionField)); });
280 }
281 };
282 TypeaheadDirective.prototype.hasMatches = function () {
283 return this._matches.length > 0;
284 };
285 TypeaheadDirective.decorators = [
286 { type: Directive, args: [{ selector: '[typeahead]', exportAs: 'bs-typeahead' },] },
287 ];
288 /** @nocollapse */
289 TypeaheadDirective.ctorParameters = function () { return [
290 { type: NgControl, },
291 { type: ViewContainerRef, },
292 { type: ElementRef, },
293 { type: Renderer, },
294 { type: ComponentLoaderFactory, },
295 ]; };
296 TypeaheadDirective.propDecorators = {
297 'typeahead': [{ type: Input },],
298 'typeaheadMinLength': [{ type: Input },],
299 'typeaheadWaitMs': [{ type: Input },],
300 'typeaheadOptionsLimit': [{ type: Input },],
301 'typeaheadOptionField': [{ type: Input },],
302 'typeaheadGroupField': [{ type: Input },],
303 'typeaheadAsync': [{ type: Input },],
304 'typeaheadLatinize': [{ type: Input },],
305 'typeaheadSingleWords': [{ type: Input },],
306 'typeaheadWordDelimiters': [{ type: Input },],
307 'typeaheadPhraseDelimiters': [{ type: Input },],
308 'typeaheadItemTemplate': [{ type: Input },],
309 'optionsListTemplate': [{ type: Input },],
310 'typeaheadLoading': [{ type: Output },],
311 'typeaheadNoResults': [{ type: Output },],
312 'typeaheadOnSelect': [{ type: Output },],
313 'typeaheadOnBlur': [{ type: Output },],
314 'container': [{ type: Input },],
315 'onChange': [{ type: HostListener, args: ['keyup', ['$event'],] },],
316 'onFocus': [{ type: HostListener, args: ['focus',] },],
317 'onBlur': [{ type: HostListener, args: ['blur',] },],
318 'onKeydown': [{ type: HostListener, args: ['keydown', ['$event'],] },],
319 };
320 return TypeaheadDirective;
321}());
322//# sourceMappingURL=typeahead.directive.js.map
\No newline at end of file