• File: tagin.module.js
  • File size: 6.23 KB
  • MIME-type: text/x-c++
  • Charset: utf-8
/*!
* Tagin v2.0.2 (https://tagin.netlify.app/)
* Copyright 2020-2021 Erwin Heldy G
* Licensed under MIT (https://github.com/erwinheldy/tagin/blob/master/LICENSE)
*/
class Tagin {
    classElement = 'tagin';
    classWrapper = 'tagin-wrapper';
    classTag = 'tagin-tag';
    classRemove = 'tagin-tag-remove';
    classInput = 'tagin-input';
    classInputHidden = 'tagin-input-hidden';
    target;
    wrapper;
    input;
    separator;
    placeholder;
    duplicate;
    transform;
    enter;
    constructor(inputElement, options) {
        this.target = inputElement;
        this.separator = options?.separator || inputElement.dataset.taginSeparator || ',';
        this.placeholder = options?.placeholder || inputElement.dataset.taginPlaceholder || '';
        this.duplicate = options?.duplicate || inputElement.dataset.taginDuplicate !== undefined;
        this.transform = options?.transform || inputElement.dataset.taginTransform || 'input => input';
        this.enter = options?.enter || inputElement.dataset.taginEnter !== undefined;
        this.createWrapper();
        this.autowidth();
        this.addEventListener();
    }
    createWrapper() {
        const tags = this.getValue() === '' ? '' : this.getValues().map(val => this.createTag(val)).join('');
        const input = document.createElement('input');
        input.type = 'text';
        input.className = this.classInput;
        input.placeholder = this.placeholder;
        const wrapper = document.createElement('div');
        wrapper.className = `${this.classWrapper} ${this.target.className}`;
        wrapper.classList.remove(this.classElement);
        wrapper.insertAdjacentHTML('afterbegin', tags);
        wrapper.insertAdjacentElement('beforeend', input);
        this.target.insertAdjacentElement('afterend', wrapper); // insert wrapper after input
        this.wrapper = wrapper;
        this.input = input;
    }
    createTag(value) {
        const onclick = `this.closest('div').dispatchEvent(new CustomEvent('tagin:remove', { detail: this }))`;
        return `<span class="${this.classTag}">${value}<span onclick="${onclick}" class="${this.classRemove}"></span></span>`;
    }
    getValue() {
        return this.target.value.trim();
    }
    getValues() {
        return this.getValue().split(this.separator);
    }
    getTags() {
        return Array.from(this.wrapper.getElementsByClassName(this.classTag)).map(tag => tag.textContent);
    }
    getTag() {
        return this.getTags().join(this.separator);
    }
    updateValue() {
        this.target.value = this.getTag();
        this.target.dispatchEvent(new Event('change'));
    }
    autowidth() {
        const fakeEl = document.createElement('div');
        fakeEl.classList.add(this.classInput, this.classInputHidden);
        const string = this.input.value || this.input.placeholder || '';
        fakeEl.innerHTML = string.replace(/ /g, '&nbsp;');
        document.body.appendChild(fakeEl);
        this.input.style.setProperty('width', Math.ceil(parseInt(window.getComputedStyle(fakeEl).width.replace('px', ''))) + 1 + 'px');
        fakeEl.remove();
    }
    addEventListener() {
        const wrapper = this.wrapper;
        const input = this.input;
        // Focus to input
        wrapper.addEventListener('click', () => input.focus());
        // Toggle focus class
        input.addEventListener('focus', () => wrapper.classList.add('focus'));
        input.addEventListener('blur', () => wrapper.classList.remove('focus'));
        // Add tag when input
        input.addEventListener('input', () => {
            this.appendTag();
            this.autowidth();
        });
        // Add tag when blur
        input.addEventListener('blur', () => {
            this.appendTag(true);
            this.autowidth();
        });
        input.addEventListener('keydown', (e) => {
            // Remove with backspace
            if (input.value === '' && e.key === 'Backspace' && wrapper.getElementsByClassName(this.classTag).length) {
                wrapper.querySelector(`.${this.classTag}:last-of-type`).remove();
                this.updateValue();
            }
            // Add with Enter
            if (input.value !== '' && e.key === 'Enter' && this.enter) {
                this.appendTag(true);
                this.autowidth();
                e.preventDefault();
            }
        });
        wrapper.addEventListener('tagin:remove', (e) => {
            if (e['detail'] instanceof HTMLSpanElement) {
                e['detail'].parentElement.remove();
                this.updateValue();
            }
        });
        this.target.addEventListener('change', () => this.updateTag());
    }
    appendTag(force = false) {
        const input = this.input;
        const value = eval(this.transform)(input.value.trim());
        if (value === '')
            input.value = '';
        if (input.value.includes(this.separator) || (force && input.value !== '')) {
            value
                .split(this.separator)
                .filter((i) => i !== '')
                .forEach((val) => {
                if (this.getTags().includes(val) && this.duplicate === false) {
                    this.alertExist(val);
                }
                else {
                    input.insertAdjacentHTML('beforebegin', this.createTag(val));
                    this.updateValue();
                }
            });
            input.value = '';
            input.removeAttribute('style');
        }
    }
    alertExist(value) {
        for (const el of this.wrapper.getElementsByClassName(this.classTag)) {
            if (el.textContent === value && el instanceof HTMLSpanElement) {
                el.style.transform = 'scale(1.09)';
                setTimeout(() => { el.removeAttribute('style'); }, 150);
            }
        }
    }
    updateTag() {
        if (this.getValue() !== this.getTag()) {
            [...this.wrapper.getElementsByClassName(this.classTag)].map(tag => tag.remove());
            this.getValue().trim() !== '' && this.input.insertAdjacentHTML('beforebegin', this.getValues().map(val => this.createTag(val)).join(''));
        }
    }
    addTag(tag) {
        this.input.value = (Array.isArray(tag) ? tag.join(this.separator) : tag) + this.separator;
        this.input.dispatchEvent(new Event('input'));
    }
}

export { Tagin as default };