File

src/app/shared/components/select/select.component.ts

Implements

OnInit OnChanges

Metadata

selector cd-select
styleUrls ./select.component.scss
templateUrl ./select.component.html

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(i18n: I18n)
Parameters :
Name Type Optional
i18n I18n No

Inputs

customBadges
Default value : false
customBadgeValidators
Type : ValidatorFn[]
Default value : []
data
Type : Array<string>
Default value : []
elemClass
Type : string
messages
Type : SelectMessages
Default value : new SelectMessages({}, this.i18n)
options
Type : Array<SelectOption>
Default value : []
selectionLimit
Type : number

Outputs

selection
Type : EventEmitter

Methods

addCustomOption
addCustomOption()
Returns : void
Private addOption
addOption(name: string)
Parameters :
Name Type Optional
name string No
Returns : void
Private forceOptionsToReflectData
forceOptionsToReflectData()
Returns : void
Private initFilter
initFilter()
Returns : void
Private initMissingOptions
initMissingOptions()
Returns : void
isCreatable
isCreatable()
Returns : any
ngOnChanges
ngOnChanges()
Returns : void
ngOnInit
ngOnInit()
Returns : void
removeItem
removeItem(item: string)
Parameters :
Name Type Optional
item string No
Returns : void
Private resetFilter
resetFilter()
Returns : void
selectOption
selectOption()
Returns : void
triggerSelection
triggerSelection(option: SelectOption)
Parameters :
Name Type Optional
option SelectOption No
Returns : void
updateFilter
updateFilter()
Returns : void
Private updateOptions
updateOptions()
Returns : void

Properties

filter
Type : FormControl
filteredOptions
Type : Array<SelectOption>
Default value : []
form
Type : CdFormGroup
Object
Default value : Object
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { FormControl, ValidatorFn } from '@angular/forms';

import { I18n } from '@ngx-translate/i18n-polyfill';
import * as _ from 'lodash';

import { CdFormGroup } from '../../forms/cd-form-group';
import { SelectMessages } from './select-messages.model';
import { SelectOption } from './select-option.model';

@Component({
  selector: 'cd-select',
  templateUrl: './select.component.html',
  styleUrls: ['./select.component.scss']
})
export class SelectComponent implements OnInit, OnChanges {
  @Input()
  elemClass: string;
  @Input()
  data: Array<string> = [];
  @Input()
  options: Array<SelectOption> = [];
  @Input()
  messages = new SelectMessages({}, this.i18n);
  @Input()
  selectionLimit: number;
  @Input()
  customBadges = false;
  @Input()
  customBadgeValidators: ValidatorFn[] = [];

  @Output()
  selection = new EventEmitter();

  form: CdFormGroup;
  filter: FormControl;
  Object = Object;
  filteredOptions: Array<SelectOption> = [];

  constructor(private i18n: I18n) {}

  ngOnInit() {
    this.initFilter();
    if (this.data.length > 0) {
      this.initMissingOptions();
    }
    this.options = _.sortBy(this.options, ['name']);
    this.updateOptions();
  }

  private initFilter() {
    this.filter = new FormControl('', { validators: this.customBadgeValidators });
    this.form = new CdFormGroup({ filter: this.filter });
    this.filteredOptions = [...(this.options || [])];
  }

  private initMissingOptions() {
    const options = this.options.map((option) => option.name);
    const needToCreate = this.data.filter((option) => options.indexOf(option) === -1);
    needToCreate.forEach((option) => this.addOption(option));
    this.forceOptionsToReflectData();
  }

  private addOption(name: string) {
    this.options.push(new SelectOption(false, name, ''));
    this.options = _.sortBy(this.options, ['name']);
    this.triggerSelection(this.options.find((option) => option.name === name));
  }

  triggerSelection(option: SelectOption) {
    if (
      !option ||
      (this.selectionLimit && !option.selected && this.data.length >= this.selectionLimit)
    ) {
      return;
    }
    option.selected = !option.selected;
    this.updateOptions();
    this.selection.emit({ option: option });
  }

  private updateOptions() {
    this.data.splice(0, this.data.length);
    this.options.forEach((option: SelectOption) => {
      if (option.selected) {
        this.data.push(option.name);
      }
    });
    this.updateFilter();
  }

  updateFilter() {
    this.filteredOptions = this.options.filter((option) => option.name.includes(this.filter.value));
  }

  private forceOptionsToReflectData() {
    this.options.forEach((option) => {
      if (this.data.indexOf(option.name) !== -1) {
        option.selected = true;
      }
    });
  }

  ngOnChanges() {
    if (this.filter) {
      this.updateFilter();
    }
    if (!this.options || !this.data || this.data.length === 0) {
      return;
    }
    this.forceOptionsToReflectData();
  }

  selectOption() {
    if (this.filteredOptions.length === 0) {
      this.addCustomOption();
    } else {
      this.triggerSelection(this.filteredOptions[0]);
      this.resetFilter();
    }
  }

  addCustomOption() {
    if (!this.isCreatable()) {
      return;
    }
    this.addOption(this.filter.value);
    this.resetFilter();
  }

  isCreatable() {
    return (
      this.customBadges &&
      this.filter.valid &&
      this.filter.value.length > 0 &&
      this.filteredOptions.every((option) => option.name !== this.filter.value)
    );
  }

  private resetFilter() {
    this.filter.setValue('');
    this.updateFilter();
  }

  removeItem(item: string) {
    this.triggerSelection(
      this.options.find((option: SelectOption) => option.name === item && option.selected)
    );
  }
}
<ng-template #popTemplate>
  <form name="form"
        #formDir="ngForm"
        [formGroup]="form"
        novalidate>
    <div [ngClass]="{'has-error': form.showError('filter', formDir)}">
      <input type="text"
             formControlName="filter"
             i18n-placeholder
             [placeholder]="messages.filter"
             (keyup)="$event.keyCode == 13 ? selectOption() : updateFilter()"
             class="form-control text-center" />
      <ng-container *ngFor="let error of Object.keys(messages.customValidations)">
        <span class="help-block text-center"
              *ngIf="form.showError('filter', formDir) && filter.hasError(error)">
          {{ messages.customValidations[error] }}
        </span>
      </ng-container>
    </div>
  </form>
  <div *ngFor="let option of filteredOptions"
       class="select-menu-item"
       [class.disabled]="data.length === selectionLimit && !option.selected"
       (click)="triggerSelection(option)">
    <div class="select-menu-item-icon">
      <i class="fa fa-check"
         aria-hidden="true"
         *ngIf="option.selected"></i>
      &nbsp;
    </div>
    <div class="select-menu-item-content">
      {{ option.name }}
      <ng-container *ngIf="option.description">
        <br>
        <small class="text-muted">
          {{ option.description }}&nbsp;
        </small>
      </ng-container>
    </div>
  </div>
  <div *ngIf="isCreatable()"
       class="select-menu-item"
       (click)="addCustomOption(filter.value)">
    <div class="select-menu-item-icon">
      <i class="fa fa-tag"
         aria-hidden="true"></i>
      &nbsp;
    </div>
    <div class="select-menu-item-content">
      {{ messages.add }} '{{ filter.value }}'
    </div>
  </div>
  <div class="has-warning"
       *ngIf="data.length === selectionLimit">
    <span class="help-block text-center text-warning"
          [tooltip]="messages.selectionLimit.tooltip"
          *ngIf="data.length === selectionLimit">
      {{ messages.selectionLimit.text }}
    </span>
  </div>
</ng-template>

<a class="select-menu-edit"
   [ngClass]="elemClass"
   [popover]="popTemplate"
   placement="bottom"
   container="body"
   outsideClick="true"
   *ngIf="options.length > 0">
  <ng-content></ng-content>
</a>
<span class="text-muted"
      *ngIf="data.length === 0 && options.length > 0">
  {{ messages.empty }}
</span>
<span class="text-muted"
      *ngIf="options.length === 0">
  {{ messages.noOptions }}
</span>

./select.component.scss

@import '../../../../defaults';

.select-menu-item {
  display: block;
  cursor: pointer;
  border-bottom: 1px solid $color-transparent;
  font-size: 12px;
  &:hover {
    background-color: $color-whitesmoke-gray;
  }
}
.select-menu-item-icon {
  float: left;
  padding: 0.5em;
  width: 3em;
}
.select-menu-item-content {
  padding: 0.5em;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""