tagInput: A simple jQuery plugin for tag entry using Bootstrap 4

johna by | October 15, 2019 | Jquery/Javascript Web Development

img1096_taginput.jpg

For a recent website project I needed a way to enter multiple tags.

I tried a few open-source scripts but didn't find something that suited my needs. Some had browser compatibility, some had other issues, and some were huge!

I just wanted something simple that I could easily modify to suit my own needs, so I wrote my own.

My requirements:

• Progressive enhancement (if JavaScript is not available or not working, revert to a simple input text for data entry instead)
• Not allow duplicate tags
• Use Bootstrap 4.x classes
• Validate entered tags
• Autocomplete functionality using HTML 5 datalist
• Autocomplete from an AJAX data source

Demo

Update history

v1.0 - Initial release
v1.1 - Restricted minimum and maximum length of tags, handled tab key (can add tag, or if input text empty then moves to next field), added public method to add tags
v1.2 - Handled enter key or click on datalist option

//tagInput by John Avis Oct 16, 2019
//v1.2

(function ($, win) {
$.fn.tagInput = function () {
return this.each(function () {

//Single function used to add tags
function addTag(tagName) {
var cleanTagName = tagName.trim();

if (cleanTagName != '') {
//Input validation (this example allows minimum 2 and maximum 50 upper and lower case letters and numbers - change if required)
if (/^([a-zA-Z0-9]){2,50}$/.test(cleanTagName) === false) {
return '2-50 letters or numbers only';
}

//Check for duplicates (remove if not required)
var tags = el.val().split(",");
var rawTagName = rawTag(cleanTagName)
for (var i = 0; i < tags.length; i++) {
if (rawTag(tags[i]) === rawTagName) {
return 'Duplicate tag';
}
}

//check for maximum number of tags reached
if (tags.length >= 10) {
return 'Only 10 tags allowed';
}

//Add a tag to the list
el.val(el.val() + (el.val() !== '' ? ',' : '') + cleanTagName);
$('#' + id + '_form').before('<div class="float-left mr-1 mb-1"><span class="btn btn-secondary btn-sm">' + cleanTagName + ' <button type="button" class="btn badge badge-light" value="' + cleanTagName + '">×</a></span></div>');
return true;
}
}

function rawTag(tagName) {
//Compare tags by their raw values only by converting to lower case//
//You may also use this to remove any non alpha-numeric characters (if allowed) if some characters are ignored for comparisons
return tagName.toLowerCase();
}

//Global variables - the element (jquery object) itself, and the ID which is used as a prefix for dynamic elements
var el = $(this);
var id = el.attr('id');

//Add a reference to the object as data
el.data('tagInput', this);

//Hide the original input text
el.hide();

//Create the HTML elements for displaying the list of tags and for entering a new tag
el.after('<div id="' + id + '_list" class="clearfix border rounded pl-1 pt-1">' +
'<div id="' + id + '_form" class="float-left mb-1">' +
'<input id="' + id + '_input" type="text" autocomplete="off" class="form-control form-control-sm" maxlength="50" list="' + id + '_datalist" placeholder="Enter a tag" />' +
'<div id="' + id + '_error" class="text-danger small" style="display:none"></div>' +
'<button id="' + id + '_add" class="btn btn-outline-secondary sr-only" type="button">Add tag</button>' +
'</div>' +
'</div>' +
'<datalist id="' + id + '_datalist"></datalist>');

//Attempt to add any existing comma separated values from the input text (can change separator if required)
var tags = el.val().split(",");
el.val('');
for (var i = 0; i < tags.length; i++) {
addTag(tags[i]);
}

var lastVal = '';

//Watch for blur and keyup
$('#' + id + '_input').bind('blur keyup', function (e) {

//Empty and hide any existing validation error
$('#' + id + '_error').html('').hide();

//Execute if blur event or enter key pressed (could also trap other keystrokes if required)
if (e.type === 'blur' || (e.type === 'keyup' && (typeof e.keyCode === 'undefined' || e.keyCode === 13 || (e.shiftKey === false && e.keyCode === 9)))) {

//Empty and hide any existing validation error
$('#' + id + '_error').html('').hide();

//Attempt to add the tag and store the result
var result = addTag($(this).val());
if (result === true) {

//If was successful then clear the input text and autocomplete datalist
$(this).val('');
$('#' + id + '_datalist').html('');

//If enter or tab key was pressed then set focus back to input text
if (e.type === 'keyup' && (typeof e.keyCode === 'undefined' || e.keyCode === 13 || (e.shiftKey === false && e.keyCode === 9))) {
$(this).focus();
}
}
else {
$('#' + id + '_error').html(result).show();
}
}
//Autocomplete datalist is only populated if at least one character has been entered (change if required)
else if ($(this).val().length < 1) {
//Less than 1 charcters so make sure the datalist is empty
$('#' + id + '_datalist').html('');
}
//Execute if more than one character has been entered, will populate the datalist with matches
else if (lastVal != $(this).val().trim()) {
//Empty the datalist
$('#' + id + '_datalist').html('');

//Populate the datalist with dummy data (the first letter entered plus numbers 0 to 50)
//Replace with an AJAX request or remove if not required
for (i = 0; i < 50; i++) {
$('#' + id + '_datalist').append('<option>' + $(this).val().substring(0, 1) + i + '</option>');
}
}

lastVal = $(this).val().trim();
});

//Prevent the enter key from submitting the form, and prevent tab key default action if input text has data
$('#' + id + '_input').bind('keydown', function (e) {
if (e.keyCode === 13 || (e.shiftKey === false && e.keyCode === 9 && $(this).val().trim() !== '')) {
e.preventDefault();
return false;
}
});

//Watch for delete tag button pressed
$('#' + id + '_list').on('click', '.badge', function () {

//Get current tags
var tags = el.val().split(',');

//Clear current values, ready to add all but the deleted tag
el.val('');

//Iterate through current tags and add all but the deleted tag
for (var i = 0; i < tags.length; i++) {
if (tags[i] != $(this).val()) {
el.val(el.val() + (el.val() !== '' ? ',' : '') + tags[i]);
}
}

//Remove the deleted tag element
$(this).parent().parent().remove();

//Set focus back to the input text
el.focus();
});

//A public method for adding extra tags
this.addTags = function (tagNames) {
var tags = tagNames.split(',');
for (var i = 0; i < tags.length; i++) {
addTag(tags[i]);
}
}
});
};
}(jQuery, window));

Related Posts

Web Development

How to set up a debugging using the Turnkey Linux LAMP stack and VS Code

by johna | December 19, 2023
The second part in my guide to setting up a website and database using the Turnkey Linux LAMP stack.

Website Hosting Web Development

How to set up a website and database using the Turnkey Linux LAMP stack

by johna | November 18, 2023
If you need to host your own website for the purposes of web development, Turnkey Linux LAMP Stack is an easy to install all-in-one solution that you can set up on a spare computer or a VM (Virtual Machine).

Web Development

Intermittent "Unable to read data from the transport connection: net_io_connectionclosed" errors

by johna | May 6, 2020
If you are having intermittent problems sending email in .NET using System.Net.Mail consider switching libraries.

Comments

There are no comments yet. Be the first to leave a comment!

Leave a Comment

About

...random postings about web development and programming, Internet, computers and electronics topics.

I recommend ASPnix for web hosting and Crazy Domains for domain registration.

Subscribe

Get the latest posts delivered to your inbox.