File

packages/ui/src/lib/select/select.component.ts

Description

The SelectComponent will render a dropdown of a given list.

https://components.entrecode.de/ui/select?e=1

Implements

ControlValueAccessor OnInit OnChanges

Metadata

changeDetection ChangeDetectionStrategy.OnPush
encapsulation ViewEncapsulation.None
providers { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SelectComponent), multi: true, }
selector ec-select
template
selectTemplate

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(elementRef: ElementRef, cdr: ChangeDetectorRef)
Parameters :
Name Type Optional
elementRef ElementRef No
cdr ChangeDetectorRef No

Inputs

config
Type : ListConfig<T>

Configuration Object for List

disabled
Type : ListConfig<T>

If true, the input will be disabled

focusEvent
Type : EventEmitter<boolean>
Default value : new EventEmitter()

Event emitter to focus input

formControl
Type : FormControl

The formControl that is used.

list
Type : List<T>

The Instance of the List

placeholder
Type : string

Input placeholder

selection
Type : Selection<T>

The used selection

solo
Type : boolean

Wether or not the selection should be solo

value
Type : Array<T> | T

The visible items

values
Type : Array<T>

Available Items

Outputs

add
Type : EventEmitter<Item<T>>

Emits when an item is being added

changed
Type : EventEmitter<Selection<T>>

Event emitter on item selection

enter
Type : EventEmitter<SelectComponent<T>>

Emits the query when enter is pressed

enterPressed
Type : Subject<string>

Subject that is nexted when enter is pressed

itemClick
Type : EventEmitter<Item<T>>

Event emitter on selected item click

remove
Type : EventEmitter<Item<T>>

Emits when an item is being removed

Methods

activate
activate(e)
Parameters :
Name Optional
e No
Returns : void
addItem
addItem(item: Item)

Adds the given ite, emits add output if observed

Parameters :
Name Type Optional
item Item<any> No
Returns : void
cancelDrag
cancelDrag(item, e, target)

is called when the drag stops in any kind of way.

Parameters :
Name Optional Default value
item No
e No
target No e.target
Returns : void
canRemove
canRemove()
Returns : boolean
Public clickItem
clickItem(item, e?)

Is called when a selected item is clicked

Parameters :
Name Optional
item No
e Yes
Returns : void
filterDropdownList
filterDropdownList(listComponent: ListComponent, query)
Parameters :
Name Type Optional
listComponent ListComponent<any> No
query No
Returns : void
focus
focus(e)
Parameters :
Name Optional
e No
Returns : void
focusSearchbar
focusSearchbar()
Returns : void
getArray
getArray(value)
Parameters :
Name Optional
value No
Returns : any
getParentTree
getParentTree(el, tree: [])
Parameters :
Name Type Optional Default value
el No
tree [] No []
Returns : any
handleKey
handleKey(e, list)
Parameters :
Name Optional
e No
list No
Returns : void
hasSoloSelection
hasSoloSelection()
Returns : boolean
initSelection
initSelection()

creates the collection from the config

Returns : void
Public listItemClicked
listItemClicked(item, list?)

Select handler. Toggles selection.

Parameters :
Name Optional
item No
list Yes
Returns : void
ngOnChanges
ngOnChanges()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onChange
onChange()

Fires on selection change. Hides dropdown if solo

Returns : void
onDragStart
onDragStart(item, e, target)

is called when an element is dragged by the user. hides element in selection

Parameters :
Name Optional Default value
item No
e No
target No e.target
Returns : void
onDrop
onDrop(e)

called when the element is dropped. moves item in selection.

Parameters :
Name Optional
e No
Returns : void
preventDefault
preventDefault(e)
Parameters :
Name Optional
e No
Returns : void
registerOnChange
registerOnChange(fn)

registers change method. (handled by angular)

Parameters :
Name Optional
fn No
Returns : void
registerOnTouched
registerOnTouched()
Returns : void
removeItem
removeItem(item: Item, e?)

Removes the given item from selection

Parameters :
Name Type Optional
item Item<any> No
e Yes
Returns : void
setDisabledState
setDisabledState(isDisabled)
Parameters :
Name Optional
isDisabled No
Returns : void
use
use(value, event)

Uses the given value as selection items

Parameters :
Name Optional Default value
value No
event No true
Returns : void
useConfig
useConfig(config: ListConfig)

Initializes either with values, collection or list. Creates Selection with config.

Parameters :
Name Type Optional Default value
config ListConfig<T> No {}
Returns : void
writeValue
writeValue(value: any)

Called when the model changes

Parameters :
Name Type Optional
value any No
Returns : void

Properties

Public cdr
Type : ChangeDetectorRef
dragged
Type : Item<T>

the current dragged element

dropdown
Type : PopComponent
Decorators :
@ViewChild('dropdown', {static: true})

The selection dropdown

dropdownList
Type : ListComponent<any>
Decorators :
@ViewChild(ListComponent, {static: true})

The list in the dropdown

dropdownLoader
Type : LoaderComponent
Decorators :
@ViewChild('dropdownLoader', {static: true})

The loader inside the dropdown

Public elementRef
Type : ElementRef
propagateChange
Default value : () => {...}

Propagates formControl/ngModel changes

searchbar
Type : SearchbarComponent
Decorators :
@ViewChild(SearchbarComponent)

The nested searchbar

toggleItem
Type : Subject<Item<T>>
Default value : new Subject()

Subject that is nexted when an item is being selected (clicked or entered on)

Select

You can pass an array of items that will be used for a selection instance.

<ec-select [list]="products" [(ngModel)]="values"></ec-select>

{{values | json}}

TODO: update

import {
  Component,
  EventEmitter,
  forwardRef,
  Input,
  OnChanges,
  OnInit,
  Output,
  ViewChild,
  ViewEncapsulation,
  ElementRef,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl } from '@angular/forms';
import { List, ListConfig, Selection } from '@ec.components/core';
import { Item } from '@ec.components/core';
import { PopComponent } from '../pop/pop.component';
import { SearchbarComponent } from '../list/searchbar/searchbar.component';
import { ListComponent } from '../list/list.component';
import { Subject } from 'rxjs';
import { LoaderComponent } from '../loader/loader.component';
import { selectTemplate } from './select.component.html';

/**
 * The SelectComponent will render a dropdown of a given list.
 *
 * <example-url>https://components.entrecode.de/ui/select?e=1</example-url>
 * */
@Component({
  selector: 'ec-select',
  /*   templateUrl: './select.component.html', */
  template: selectTemplate,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SelectComponent),
      multi: true,
    },
  ],
})
export class SelectComponent<T> implements ControlValueAccessor, OnInit, OnChanges {
  /** the current dragged element */
  dragged: Item<T>;
  /** Configuration Object for List */
  @Input() config: ListConfig<T>;
  /** If true, the input will be disabled */
  @Input() disabled: ListConfig<T>;
  /** The visible items */
  @Input() value: Array<T> | T;
  /** The used selection */
  @Input() selection: Selection<T>;
  /** Input placeholder */
  @Input() placeholder: string;
  /** Event emitter on item selection */
  @Output() changed: EventEmitter<Selection<T>> = new EventEmitter();
  /** Event emitter on selected item click */
  @Output() itemClick: EventEmitter<Item<T>> = new EventEmitter();
  /** Emits when an item is being removed */
  @Output() remove: EventEmitter<Item<T>> = new EventEmitter();
  /** Emits when an item is being added */
  @Output() add: EventEmitter<Item<T>> = new EventEmitter();
  /** Emits the query when enter is pressed */
  @Output() enter: EventEmitter<SelectComponent<T>> = new EventEmitter();
  /** Subject that is nexted when enter is pressed */
  @Output() enterPressed: Subject<string> = new Subject();
  /** The Instance of the List */
  @Input() list: List<T>;
  /** Available Items */
  @Input() values: Array<T>;
  /** Wether or not the selection should be solo */
  @Input() solo: boolean;
  /** Event emitter to focus input */
  @Input() focusEvent: EventEmitter<boolean> = new EventEmitter();
  /** The selection dropdown */
  @ViewChild('dropdown', { static: true }) dropdown: PopComponent;
  /** The loader inside the dropdown */
  @ViewChild('dropdownLoader', { static: true }) dropdownLoader: LoaderComponent;
  /** The list in the dropdown */
  @ViewChild(ListComponent, { static: true }) dropdownList: ListComponent<any>;
  /** The nested searchbar */
  @ViewChild(SearchbarComponent) searchbar: SearchbarComponent;
  /** Subject that is nexted when an item is being selected (clicked or entered on) */
  toggleItem: Subject<Item<T>> = new Subject();
  /** The formControl that is used. */
  @Input() formControl: FormControl;

  constructor(public elementRef: ElementRef, public cdr: ChangeDetectorRef) {
    this.toggleItem.asObservable().subscribe((item) => {
      if (this.selection.has(item)) {
        this.removeItem(item);
      } else {
        this.addItem(item);
      }
    });
    this.enterPressed.asObservable().subscribe((query) => {
      this.enter.emit(this);
    });
  }

  getParentTree(el, tree = []) {
    if (el.parentNode) {
      return tree.concat([el.parentNode]);
    }
    return tree;
  }

  ngOnInit() {
    this.initSelection();
  }

  ngOnChanges() {
    this.initSelection();
  }

  /** creates the collection from the config */
  initSelection() {
    if (this.values) {
      if (this.list) {
        console.warn('ec-select: list is overwritten by values', this.list);
      }
      this.list = new List(this.values, this.config);
      delete this.values;
    }
    if (this.list && !this.config) {
      this.config = this.list.config;
    }
    if (!this.config) {
      // || !this.config.selectMode
      return;
    }
    this.config = Object.assign({ solo: this.solo }, this.config);
    const value: Array<T> = Array.isArray(this.value) ? this.value : this.value ? [this.value] : [];

    if (!this.formControl) {
      this.formControl = new FormControl(value || []);
    }
    this.selection = new Selection(value, this.config);
    this.cdr.markForCheck();
    this.selection.update$.subscribe(() => {
      this.onChange();
    });
  }

  /** Called when the model changes */
  writeValue(value: any) {
    this.use(value, false);
  }

  /** Removes the given item from selection */
  removeItem(item: Item<any>, e?) {
    if (e) {
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
    }
    if (this.remove.observers.length) {
      this.remove.emit(item);
    } else {
      this.selection.remove(item);
    }
  }
  /** Adds the given ite, emits add output if observed */
  addItem(item: Item<any>) {
    if (this.add.observers.length) {
      this.add.emit(item);
    } else {
      this.selection.toggle(item);
    }
    this.cdr.markForCheck();
  }

  getArray(value) {
    return Array.isArray(value) ? value : value ? [value] : [];
  }

  /** Uses the given value as selection items */
  use(value, event = true) {
    Object.assign(this.config || {}, { solo: this.solo });
    this.value = this.getArray(value);
    if (!this.selection) {
      return;
    }
    if (!this.value.length) {
      this.selection.removeAll();
      return;
    }
    Object.assign(this.config, { selection: this.selection });
    const list = new List(this.value, this.config);
    this.selection.replaceWith(list.items, event);
  }

  /** Initializes either with values, collection or list. Creates Selection with config. */
  useConfig(config: ListConfig<T> = {}) {
    this.config = Object.assign(this.config || {}, config);
    this.use(this.value, false);
    this.initSelection();
  }

  /** Is called when a selected item is clicked*/
  public clickItem(item, e?) {
    if (this.itemClick.observers.length) {
      this.itemClick.emit(item);
    }
  }

  /** Select handler. Toggles selection. */
  public listItemClicked(item, list?) {
    this.toggleItem.next(item);
    // TODO: prevent default to prevent bluring searchbear.
    // refocusing is not possible because that will activate the pop again..
    if (list && list.list.isFiltered()) {
      list.list.clearFilter();
    }
  }

  focus(e) {
    if (this.dropdown && !this.dropdown.active) {
      this.dropdown.show();
    }
  }

  hasSoloSelection() {
    return this.config.solo && !this.selection.isEmpty();
  }

  focusSearchbar() {
    if (this.searchbar) {
      this.searchbar.focusEvent.emit(true);
    }
  }

  /** Fires on selection change. Hides dropdown if solo */
  onChange() {
    this.changed.emit(this.selection);
    if (this.dropdown && this.hasSoloSelection()) {
      this.dropdown.hide();
    } else {
      this.focusSearchbar();
    }
    this.value = this.selection.items.map((i) => i.resolve());
    return this.propagateChange(this.selection.getValue());
  }

  /** Propagates formControl/ngModel changes */
  propagateChange = (_: any) => { };
  /** registers change method. (handled by angular) */
  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() { }

  /** is called when an element is dragged by the user. hides element in selection */
  onDragStart(item, e, target = e.target) {
    this.dragged = item;
    window.requestAnimationFrame(function () {
      target.style.display = 'none';
    });
  }
  /** called when the element is dropped. moves item in selection. */
  onDrop(e) {
    if (e.isExternal) {
      return;
    }
    let index = e.index;
    if (this.selection.index(this.dragged) < e.index) {
      index -= 1;
    }
    this.selection.move(this.dragged, index);
  }

  /** is called when the drag stops in any kind of way. */
  cancelDrag(item, e, target = e.target) {
    delete this.dragged;
    window.requestAnimationFrame(function () {
      target.style.display = 'inherit';
    });
  }

  activate(e) {
    if (this.dropdown) {
      this.dropdown.show(e);
    }
    if (this.searchbar) {
      this.searchbar.focusEvent.emit(true);
    }
  }

  preventDefault(e) {
    e.preventDefault();
    e.stopPropagation();
    e.stopImmediatePropagation();
  }

  canRemove() {
    return !this.config || !this.config.disableRemove;
  }

  handleKey(e, list) {
    const { event, query } = e;
    if (!list) {
      console.warn('no dropdown given');
      return;
    }
    switch (event.key) {
      case 'ArrowUp':
        if (!this.dropdown.active) {
          this.dropdown.show(event);
        } else {
          list.focusPrev();
        }
        this.preventDefault(event);
        break;
      case 'ArrowDown':
        if (!this.dropdown.active) {
          this.dropdown.show(event);
        } else {
          list.focusNext();
        }
        this.preventDefault(event);
        break;
      case 'ArrowRight':
        list.list.pagination.next();
        this.preventDefault(event);
        break;
      case 'ArrowLeft':
        list.list.pagination.prev();
        this.preventDefault(event);
        break;
      case 'Enter':
        if (list.focusItem) {
          if (list.list.isFiltered()) {
            list.list.clearFilter();
          }
          this.toggleItem.next(list.focusItem);
        } else {
          this.enterPressed.next(query);
        }
        this.preventDefault(event);
        break;
      case 'Backspace':
        if (!this.selection.isEmpty() && query === '' && this.canRemove()) {
          this.removeItem(this.selection.items[this.selection.items.length - 1]);
          this.preventDefault(event);
        }
        break;
      case 'Tab':
        if (this.dropdown) {
          this.dropdown.hide();
        }
        break;
      default:
        return;
    }
  }

  filterDropdownList(listComponent: ListComponent<any>, query) {
    if (!listComponent) {
      /* console.warn('cannot filter yet: list not ready'); */
      return;
    }
    this.dropdown.show();
    Promise.resolve(listComponent.filter(this.config.label, query)).then(() => {
      listComponent.focusFirst();
    });
  }

  setDisabledState(isDisabled) {
    this.disabled = isDisabled;
  }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""