Do you like this project? Please support my Mecha CMS project too. Thank you!

Tag Picker 4.2.1

Tag Picker is a JavaScript application designed to simplify the management of comma-separated lists of words, offering a minimalist interface for an enhanced user experience. Users can easily add tags by typing a word and pressing the , key (by default), or remove tags with a quick click (or by using common keys such as the Backspace or Delete keys). Developers can easily integrate and customize this application styles to match the design of their web project.

It supports native keyboard interaction as if it were plain text. You should be able to copy, cut, and paste from/to the mask. Click a tag to select it, press the Control key while clicking the tag to select multiple tags. You can also select the tags using the combination of Shift and ArrowLeft or ArrowRight keys.

Usage

Note: CSS variables have been removed since version 4.0.0. From now on, I will only provide a minimal style just to make viable interaction. Expect it to look ugly by default. The main goal of this application is to be able to integrate it seamlessly into your web design. By providing a minimal style, it is easy to add other styles to it to adapt it to your own web design. Have a look at the source code on this page to see how I adapted the style to resemble the default style of the previous version.

Browser

<!DOCTYPE html>
<html dir="ltr">
  <head>
    <meta charset="utf-8">
    <link href="./index.min.css" rel="stylesheet">
    <style>
      .tag-picker {
        width: 100%;
      }
    </style>
  </head>
  <body>
    <p>
      <input type="text">
    </p>
    <script src="./index.min.js"></script>
    <script>
      const picker = new TagPicker(document.querySelector('input'));
    </script>
  </body>
</html>

Node.js

Functions and methods in this application are mostly native JavaScript and are intended for use by the browser. Node.js doesn’t know about the DOM, so this kind of practice will probably be used more often to build new browser packages than to be used directly in the Node.js server.

CommonJS

const TagPicker = require('@taufik-nurrohman/tag-picker').default;

const picker = new TagPicker(document.querySelector('input'));

ECMAScript

import TagPicker from '@taufik-nurrohman/tag-picker';

const picker = new TagPicker(document.querySelector('input'));

Tests

Tweaks

Constructors

TagPicker(self, state)

const picker = new TagPicker(self, join = ', ');
const picker = new TagPicker(self, state = {
    escape: [','],
    join: ', ',
    max: Infinity,
    min: 0,
    pattern: null,
    time: {
        error: 1000
    }
    with: []
});

TagPicker.Tags(picker, tags)

This constructor is used internally. It is the return value of the picker.tags property. It contains methods like get(), has(), let(), and set() to modify the current tags.

const tags = new TagPicker.Tags(picker, ['Tag 1', 'Tag 2', 'Tag 3']);

Parameters

self

The <input> element.

join

The character to join the tags (usually a comma followed by a space).

state

The configuration data.

state.escape

List of characters to trigger the tag insertion. It is [','] by default, which means that you cannot have a ',' character in the tag name. You can also add '\n' and '\t' to the list so that it will insert a tag when you press the Tab or Enter key.

const picker = new TagPicker(document.querySelector('input'), {
    escape: [',', ' ', '\n', '\t']
});

state.join

The character to join the tags (usually a comma followed by a space).

const picker = new TagPicker(document.querySelector('input'), '+');
const picker = new TagPicker(document.querySelector('input'), {
    join: '+'
});

state.max

The maximum number of tags that can be inserted.

const picker = new TagPicker(document.querySelector('input'), {
    max: 25
});

state.min

The minimum number of tags that must be inserted to be able to submit the form. The default value of this state is set to 0, but if the required attribute is present on the <input> element, then the default value of this state will be set to 1.

const picker = new TagPicker(document.querySelector('input'), {
    min: 5
});

state.pattern

If defined, the tag will be inserted only if the input matches the pattern. if value is defined as a function, this configuration will be used to convert the current input to the desired tag format. return value can be a string or an array containing the desired tag text and its attributes.

const picker = new TagPicker(document.querySelector('input'), {
    pattern: '^[a-z\\d]+(-[a-z\\d]+)*$'
});

picker.on('is.tag', name => {
    console.info('The input “' + name + '” is a valid tag name.');
});

picker.on('not.tag', name => {
    console.error('The input “' + name + '” is not a valid tag name.');
});
const picker = new TagPicker(document.querySelector('input'), {
    pattern: function (value) {
        return (value || "").toLowerCase().replace(/[^a-z\d-]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, "");
    }
});
const picker = new TagPicker(document.querySelector('input'), {
    pattern: function (value) {
        return [value, {
            value: (value || "").toLowerCase().replace(/[^a-z\d-]/gi, '-').replace(/-+/g, '-').replace(/^-|-$/g, "")
        }];
    }
});

state.time

Stores configuration data related to time in milliseconds.

state.time.error

Determines how long the invalid state is presented to the user. If the value is less than or equal to 0, the built-in invalid state will be disabled.

const picker = new TagPicker(document.querySelector('input'), {
    time: {
        error: 999999999 // As long as possible
    }
});
const picker = new TagPicker(document.querySelector('input'), {
    time: {
        error: 0 // Disable the built-in invalid state
    }
});

state.with

List of callable functions or objects containing an attach() method to be invoked each time the application is initialized. A very simple “plugin” system.

Methods

Instance Methods

Instance methods are methods available through the results of a TagPicker construct.

picker.attach(self, state)

Re-initializes the application and its extensions after it has been detached.

picker.attach();

picker.blur()

Blurs from the tag picker’s input.

picker.blur();

picker.detach()

Removes the application view and executes the detach() method of the extensions, if they are present.

picker.detach();

picker.fire(event, data, that)

Fires an event.

picker.fire('change', [picker.value]);

picker.focus(mode = true)

Focuses to the tag picker’s input. If it has a text, it will select the text as well.

picker.focus(); // Focus and select the text
picker.focus(-1); // Focus and put the caret to the start of the text
picker.focus(+1); // Focus and put the caret to the end of the text

picker.off(event, task)

Removes an event.

picker.off('change'); // Remove all events from the `change` event container
picker.off('change', onChange); // Remove `onChange` event from the `change` event container

picker.on(event, task)

Adds a new event.

picker.on('change', function () {
    console.log(this.value.split(this.state.join));
});
function onChange() {
    console.log(this.value.split(this.state.join));
}

picker.on('change', onChange);

Static Methods

Static methods are methods available directly on the TagPicker object.

TagPicker.from(self, state)

Creates a new TagPicker instance.

const picker = TagPicker.from(document.querySelector('input'));

TagPicker.of(self)

Gets TagPicker instance of an element.

document.querySelectorAll('input').forEach(self => {
    const picker = TagPicker.of(self);
});

Properties

Instance Properties

Instance properties are properties available through the results of a TagPicker construct.

picker.active

Gets or sets the active state of the tag picker. By setting the value to false or true, the disabled state of the source element will also be set automatically.

picker.active = false; // Make the tag picker “disabled”
picker.active = true; // Make the tag picker “enabled”

picker.fix

Gets or sets the read-only state of the tag picker. By setting the value to false or true, the read-only state of the source element will also be set automatically.

picker.fix = true; // Make the tag picker “read-only”

picker.hooks

Returns the events data.

console.log(picker.hooks);

picker.mask

Returns the tag picker’s mask.

picker.mask.classList.add(picker.state.n + '--dark');

picker.max

Proxy that passes to the picker.state.max property, with additional actions that are executed while the value is being set.

console.log(picker.max); // Returns the `picker.state.max` value
picker.max = 4; // Allow a maximum of 4 tags to be inserted.

picker.min

Proxy that passes to the picker.state.min property, with additional actions that are executed while the value is being set.

console.log(picker.min); // Returns the `picker.state.min` value
picker.min = 4; // Insert at least 4 tags to be able to submit the form.

picker.self

Returns the <input> element.

console.log(picker.self.getAttribute('name'));

picker.state

Returns the application states if any.

console.log(picker.state);

picker.tags

Gets the current tags list data, or sets (overwrites) the current tags list with a new data.

console.log(picker.tags);
picker.tags = [
    'Tag 1',
    'Tag 2',
    'Tag 3'
];

// Or…
picker.tags.set('Tag 1');
picker.tags.set('Tag 2');
picker.tags.set('Tag 3');
picker.tags = [
    ['Tag 1', {
        value: 'tag-1'
    }],
    ['Tag 2', {
        value: 'tag-2'
    }],
    ['Tag 3', {
        title: 'This is a tag description.',
        value: 'tag-3'
    }]
];

// Or…
picker.tags.set('tag-1', 'Tag 1');
picker.tags.set('tag-2', 'Tag 2');
picker.tags.set('tag-3', ['Tag 3', {
    title: 'This is a tag description.'
}]);
picker.tags = {
    'tag-1': 'Tag 1',
    'tag-2': 'Tag 2',
    'tag-3': 'Tag 3'
};

// Or…
picker.tags.set('tag-1', 'Tag 1');
picker.tags.set('tag-2', 'Tag 2');
picker.tags.set('tag-3', 'Tag 3');
const tags = new Map;

tags.set('tag-1', 'Tag 1');
tags.set('tag-2', 'Tag 2');
tags.set('tag-3', 'Tag 3');

picker.tags = tags;
picker.tags.at(key)

Returns the raw tag data from its key.

console.log(picker.tags.at('tag-1'));
picker.tags.count()

Returns the total number of tags.

console.log(picker.tags.count());
picker.tags.get(key)

Returns the tag position in the list, starting from 0.

if (-1 !== picker.tags.get('tag-1')) { … }
picker.tags.has(key)

Returns true if the tag is present.

if (picker.tags.has('tag-1')) { … }
picker.tags.let(key)

Removes a tag by its key.

picker.tags.let('tag-1'); // Remove “tag-1” tag
picker.tags.let(); // Remove all tags
picker.tags.set(key, value)

Adds a new tag with the given value or key and value.

picker.tags.set('tag-1');
picker.tags.set('tag-2', 'Tag 2');
picker.tags.set('tag-3', ['Tag 3', {
    title: 'This is a tag description.'
}]);

picker.text

Gets or sets the current text in the tag picker’s input.

console.log(picker.text);
picker.text = 'tag-1, tag-2, tag-3';

picker.value

Proxy that passes to the picker.self.value property, with additional actions that are executed while the value is being set.

console.log(picker.value);
picker.on('change', function () {
    console.log(this.value);
});

picker.value = 'tag-1, tag-2, tag-3';

picker.vital

Gets or sets the required state of the tag picker. By setting the value to false or true, the required state of the source element will also be set automatically. If the value is set to true and the current picker.min value is 0, then it will change the current picker.min value to 1.

picker.vital = true; // Make the tag picker “required”

Static Properties

Static properties are properties available directly on the TagPicker object.

TagPicker._

Alias for TagPicker.prototype.

TagPicker._.clear = function () {
    return (this.text = this.value = ""), this;
};

const picker = new TagPicker(document.querySelector('input'));

picker.clear(); // Clear value

TagPicker.state

Returns the default values of picker.state.

const picker = new TagPicker(document.querySelector('input'), {
    test: [1, 2, true]
});

console.log([TagPicker.state, picker.state]);

TagPicker.version

Returns the application version.

Extensions

Anatomy of an Extension

Extension as a function:

function Extension(self, state = {}) {
    this.test = 1;
    return this;
}

Object.defineProperty(Extension, 'name', {
    value: 'Extension'
});

Extension as an object:

const Extension = {
    attach: function (self, state = {}) {
        this.test = 1;
        return this;
    },
    detach: function (self, state = {}) {
        delete this.test;
        return this;
    },
    name: 'Extension'
};

Usage of an Extension

As a core extension:

TagPicker.state.with.push(Extension);

As an optional extension:

const picker = new TagPicker(document.querySelector('input'), {
    with: [Extension]
});

List of Extensions

License

This project is licensed under the terms of the MIT license. If this project has saved you time, please donate. Thank you! ❤️