import { IComponentController } from 'angular' import _ from 'lodash' import { Component, Inject, Input, Output } from '../decorators' export interface DisplayableObject { [key: string]: unknown displayValue: string } export type TagModel = string | DisplayableObject @Component({ selector: 'mflyTagInput', require: { ngModelCtrl: 'ngModel', }, template: require('./tag-input.html'), }) export default class TagInputController implements IComponentController { @Input() externalInput!: boolean @Input() isDisabled!: boolean @Input() inlineTags!: boolean @Input('@') inputId!: string @Input() required!: boolean @Output() onChange!: (v: { value: TagModel[] | undefined | null }) => void @Output() onRemove!: (v: { value: TagModel }) => void protected addText: string protected inputValue: string protected requiredText: string protected tags: TagModel[] | undefined | null = [] protected validationForm: ng.IFormController private ngModelCtrl: ng.INgModelController constructor( @Inject('$element') private $element: ng.IAugmentedJQuery, @Inject('$timeout') private $timeout: ng.ITimeoutService, @Inject('translateFactory') private translateFactory, ) {} $onInit() { this.ngModelCtrl.$render = () => { this.tags = this.ngModelCtrl.$viewValue this.setFormState({ skipDirty: true }) } this.addText = this.translateFactory.instant('JSUI.ADD') this.requiredText = this.translateFactory.instant('JSUI.REQUIRED') } add() { if (!this.tags) { this.tags = [] } if (this.inputValue === '') { return } const originalLength = this.tags.length // Dedupe, trim and remove empty entries const tagsToAdd = _(this.inputValue.split(',')) .map(_.trim) .compact() .uniq() .value() const newTags = _.union(this.tags, _.difference(tagsToAdd, this.tags)) if (newTags.length > originalLength) { this.tags = newTags this.ngModelCtrl.$setViewValue(this.tags) this.onChange({ value: this.tags }) this.setFormState() } this.inputValue = '' this.$element.find('input')[0].focus() } keyWasPressed($event: KeyboardEvent) { if (this.externalInput) { return } if ($event.keyCode === 13) { // Enter $event.preventDefault() this.add() } } remove(tag: TagModel) { if (this.tags) { _.pull(this.tags, tag) } // When removing the last tag, we want to set the model to `null` because // that's used in bulk edit to indicate we are clearing the field value. if (this.tags && this.tags.length === 0) { this.tags = null } this.ngModelCtrl.$setViewValue(this.tags) this.onChange({ value: this.tags }) this.onRemove({ value: tag }) this.setFormState() } setFormState(options: { skipDirty?: boolean } = {}) { this.$timeout(() => { // The controls may not be present in the DOM anymore; for example, if the // component is in a modal which has been closed. if (!this.validationForm || !this.validationForm.mflyTagInput) { return } // When the component loads for the first time, we want to be able to avoid // dirtying the form. Every other time tags are modified, we do dirty it. // This is mostly useful to make sure removing tags dirties the form, since // typing in the input box also dirties the control at the moment. if (!options.skipDirty) { this.validationForm.mflyTagInput.$setDirty() } const validity = !this.required || _.get(this.tags, 'length', 0) > 0 this.ngModelCtrl.$setValidity('required', validity) this.validationForm.mflyTagInput.$setValidity('required', validity) }) } }