2

尝试为基于 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));
  }

}
4

0 回答 0