尝试为基于 Angular2 的 Web 应用程序使用 TypeAhead 输入,特别是“标签”输入字段,您可以在其中选择多个标签。
通过键盘选择工作正常。但是我在通过鼠标选择时遇到了一些问题。当我单击先前输入的“标签”时,列表关闭,并且我的选择不会在输入字段中更新。
我已经通过浏览器进行了调试,并确定一个模糊事件正在发送到 Typeahead 组件,因为我单击了下拉列表中的一个选项,因此它在应用选择之前关闭。blur 事件似乎来自输入本身,它正在向下渗透到 typeahead 组件中,导致 hide() 运行。
我试图抑制模糊事件被传递(请参阅代码中的我的 stopEvent() 方法),但这似乎没有帮助,它仍然会运行。
任何有关如何解决此问题的想法将不胜感激。
标记:
<div class="tags-input" >
<div class="newtagarea">
<input
[(ngModel)]="candidateTag"
[typeahead]="allTagLabels"
(typeaheadOnSelect)="addTag($event)"
[typeaheadOptionField]="'label'"
(keyup)="inputHandler($event)"
(blur)="stopEvent($event)"
[typeaheadOptionsLimit]="5"
/>
</div>
</div>
打字稿:
import {Component, Input, Output, OnInit, EventEmitter} from 'angular2/core';
import {FORM_DIRECTIVES} from 'angular2/common';
import {TYPEAHEAD_DIRECTIVES} from 'ng2-bootstrap/ng2-bootstrap';
import {Tag} from '../../model/tag';
import {Errors} from '../../model/errors';
import {TagService} from '../../services/tag.service';
@Component({
selector: 'tags-input',
moduleId: module.id,
styleUrls: ['./tagsinput.css', './tags.css'],
templateUrl: './tagsinput.html',
directives: [FORM_DIRECTIVES, TYPEAHEAD_DIRECTIVES],
providers: [TagService]
})
export class TagsInput implements OnInit {
@Input() public placeHolderText : string;
@Output() tagUpdate = new EventEmitter();
@Output() typeaheadOnSelect = new EventEmitter();
private _tags: Tag[];
private allTags: Tag[];
private allTagLabels: string[];
private filteredList: string[];
private candidateTag: string = '';
private previousCandidateTag: string = '';
private tagsInputFocus: boolean = false;
private currentPlaceHolderText : string;
constructor(public tagService: TagService) { }
@Input()
set tags(t: Tag[]) {
if (t && t.length > 0) {
this._tags = t.sort((a: Tag,b: Tag) => {
if (a.label.toLowerCase() < b.label.toLowerCase()) {
return -1;
} else if (a.label.toLowerCase() > b.label.toLowerCase()) {
return 1;
} else {
return 0;
}
});
} else {
this._tags = [];
}
// always clean up input field
this.candidateTag = '';
this.previousCandidateTag = '';
}
get tags() {
return this._tags;
}
ngOnInit(): void {
if (!this.tags) {
this.tags = [];
}
this.loadTags();
}
loadTags(onComplete: (result: boolean) => void = null) {
var me = this;
this.tagService.getTags().subscribe(
vals => {
me.allTags = vals;
me.allTagLabels = [];
if (vals) {
vals.forEach(t => {
me.allTagLabels.push(t.label);
});
}
if (onComplete) {
onComplete(true);
}
},
err => {
// TODO: handle errors
console.log(err);
if (onComplete) {
onComplete(false);
}
});
this.currentPlaceHolderText = this.placeHolderText;
}
addTag(event: any): void {
this.addAndPossiblyCreateTag(event.item);
this.tagUpdate.emit(this.tags);
}
inputHandler(event: any): void {
event.preventDefault();
if ((event.code === 'Enter') || (event.code === 'NumpadEnter')) {
if (event.currentTarget.nextElementSibling === null) {
// User has hit enter in the text box, not picked from dropdown
this.addAndPossiblyCreateTag(event.currentTarget.value);
}
event.target.value = '';
} else if (event.code === 'Backspace') {
if ((this.previousCandidateTag === '') && (this.candidateTag === '')) {
this.removeLastEnteredTagAndNotify();
} else {
console.log(this.previousCandidateTag);
this.filterTagLabels();
}
} else {
this.filterTagLabels();
}
this.previousCandidateTag = this.candidateTag;
}
restoreFocus(el: any): void {
el.focus();
}
removeElement(i: number): void {
this.tags.splice(i, 1);
this.candidateTag = '';
this.tagUpdate.emit(this.tags);
}
showTagsInputFocus() {
this.tagsInputFocus = true;
}
stopEvent(event) {
if (! event) {
return;
}
if (event.preventDefault) {
event.preventDefault();
}
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
if (event.stopPropagation) {
event.stopPropagation();
}
}
hideTagsInputFocus() {
this.tagsInputFocus = false;
}
private addAndPossiblyCreateTag(value: string): void {
var tag = value.trim();
if (!tag || tag.length === 0) {
return;
}
let me = this;
if (me.getCurrentTagLabelsLowerCase().indexOf(tag.toLowerCase()) > -1) {
// given tag is already part of current tags
this.candidateTag = '';
return;
}
let existingTag: Tag = this.getExistingTag(tag);
if (!existingTag) {
// tag does not exist, we need to create it
let newTag: Tag = new Tag();
newTag.label = tag;
this.tagService.createTag(newTag).subscribe(
(val: Tag) => {
newTag = val;
this.allTags.push(newTag);
this.allTagLabels.push(newTag.label);
this.addTagAndNotify(newTag);
},
(err: Errors) => {
if (err.validationErrors) {
// if this is validation error - it means tag already exists - i.e. most likely
// someone else added it meantime. Need to refresh our tag list..
this.loadTags((res) => {
if (res) {
// try to reload tag and add it
let existingTag: Tag = this.getExistingTag(tag);
if (existingTag) {
this.addTagAndNotify(existingTag);
} else {
// TODO: not sure if it can happen
console.log('Could not create tag [' + tag + ']');
}
}
});
} else if (err.genericErrors) {
// TODO: handle generic errors
console.log(err.genericErrors);
}
});
} else {
// existing tag, just add it to the asset
this.addTagAndNotify(existingTag);
}
}
private addTagAndNotify(tag: Tag): void {
let existingTags: Tag[] = this.tags;
existingTags.push(tag);
this.candidateTag = '';
this.previousCandidateTag = this.candidateTag;
this.tagUpdate.emit(existingTags);
this.currentPlaceHolderText = this.placeHolderText;
}
private getCurrentTagLabelsLowerCase(): string[] {
let labels: string[] = [];
if (this.tags) {
for (var i = 0; i < this.tags.length; i++) {
labels.push(this.tags[i].label.toLowerCase());
}
}
return labels;
}
private getExistingTag(tag: string): Tag {
if (!this.allTags) { return null; }
for (var i = 0; i < this.allTags.length; i++) {
if (this.allTags[i].label.toLowerCase() === tag.toLowerCase()) {
return this.allTags[i];
}
}
return null;
}
private removeLastEnteredTagAndNotify(): void {
this.tags.pop();
this.candidateTag = '';
this.tagUpdate.emit(this.tags);
}
private filterTagLabels() : void {
this.filteredList = this.allTagLabels.filter(function(el) {
return this.getCurrentTagLabelsLowerCase().indexOf(el.toLowerCase()) < 0;
}.bind(this));
}
}