Create an interactive tags widget with HTML, CSS, and JS

A few days ago I just had a need to create some plain tags widget with possibilities to add and remove tags in the form.
I only had HTML, CSS, and JS (In fact there were also jQuery and Bootstrap 4).

In the result of this article you will get something like that:

During the development I was using jQuery 3.2.1, Bootstrap 4, and Font Awesome (just do add one icon). And speaking of Java Script, I have written everything with the ES6 support.

So, let’s say that we have some entry adding form (such as an article or whatever) and you want to add support of tags. That’s definetely a good idea so you started to think how to implement that.

We can start from simple form with the following html markup:

<form action="someYourAction">
<div class="form-group row tags">
    <div class="col-lg-12">
        <h5>Adding tags</h5>
    </div>
    <div class="col-lg-2">
        <!--The following tag will be the input field for tags-->
        <input name="tagHelp" id="tags-input" type="text" class="form-control form-control-lg" placeholder="Enter the tag"/>
        <small id="tagHelp" class="form-text text-muted"><i class="fas fa-tags"></i> Finish each tag with comma to submit new tag</small>
    </div>
    <div class="col-lg-6">
        <!--In this field we will reflect all entered tags to send them to some server later-->
        <input name="tags" id="tags-holder" type="text" disabled class="form-control form-control-lg" placeholder="Alias for the url"/>

        <!--This empty div will be used as container for representation and manipulations with added tags. That's just a presentation layer
        In fact, tags will be sent through the previously described input with id #tags-holder-->
        <div class="tags-holder-block"></div>
    </div>
</div>
.
.
.
.
<input type="submit" class="btn btn-primary" value="Save"/>
</form>

The general idea about this code is that we keep tags into some hidden input and at the same time we show added tags inside a visible div block. I mean we are going to do that. It’s just our skeleton.

Using a representation in the div block we also will be able to remove tags. And in the field which is hidden we will collet tags in the comma separated view (tag1,tag2,tag3).

First, I would add some basic CSS styles for this code above. We need to add some style for future tags inside the div block and hide the field with tags.

/* Width of the field for tags input */
.tags-input { width: 200px; }

/* Hidding the input that we considered as hidden container for tags */
#tags-holder { display: none; }

/* style of the tags inside DIV block */
.tags-holder-block { padding-top: 12px;}
.tags-holder-block > span {
    padding: 10px;
    background: #606080;
    color: #e5e5e5;
}

That’s just a basic style for tags. Of course we could improve that by rewriting that with flexes which is more flexible (funny word – flexible – flexes). Because right in case of a few rows of tags we can see some collisions. But I will left that for you becuase I just want to show you the idea how we can do that.

So, let’s consider that we put tags into different spans like that:
<span>Some tag</span>. And we also need to add some remove sign there. <span>Some tag <i class="fa fa-times"></i> </span>.

The magic is always behind the scene. Let’s talk about Java Script part.
I would just create a few functions with some event listener (of course using jQuery).
Take a look at the following code. It’s pretty good commented so you should understand that clearly.

/**
 * TagsHolder is a constant object with defined funcitons
 * and a few statefull properties that help to handle the
 * tags input and prepare tags to send from the form.
 * @type {String}
 */
const TagsHolder = {
     /**
     * tagsHolder is a property to contain an element 
     * that we are using as hidden input field for 
     * tags that will be sent from the form.
     */
    tagsHolder: '',
    /**
     * tagsInput is a property that will be initialized 
     * with the proper jQuery element that we consider
     * as input field for tags.
     */
    tagsInput: '',
    /**
     * Just a method to initialize properties and the 
     * listener on the keyup event for tagsInput.
     * @return {[type]} [description]
     */
    init: () => {
        TagsHelper.tagsHolder = $('#tags-holder');
        TagsHelper.tagsInput = $('#tags-input');
        TagsHelper.inputTags();
    },

    /**
     * Sets up a listener that awaits for a keyup 
     * event on the tagsInput. Then checks the 
     * input and adds tag to the hidden field as
     * well as to DIV block to represent added
     * tags in spans.
     */
    inputTags: () => {TagsHelper.tagsInput.keyup((event) => {
            // getting an entered character from the event
            let entered = event.key;

            // check that character is a comma 
            if (entered === ',') {
                // getting an entered tag and flushing the field
                let tempText = TagsHelper.tagsInput.val();
                TagsHelper.tagsInput.val("");

                // find div block, get existed tags (if present)
                // append new tag with the following template 
                // put into the div new appended HTML
                let holderBlock = $('.tags-holder-block');
                let htmlBefore = holderBlock.html();
                holderBlock.html(
                    `${htmlBefore}&nbsp
                    <span>
                    ${tempText.replace(",", "")} 
                    <i class="fa fa-times" onclick="TagsHelper.removeTag(this);"></i>
                    </span>`
                );

                // append new tag to the hidden field
                let tempValue = TagsHelper.tagsHolder.val();
                tempValue += tempText + " ";
                TagsHelper.tagsHolder.val(tempValue);
            }
        });
    },
    /**
     * Removes passed element (tag) from the 
     * hidden field and from the visible div
     * block (tags container)
     */
    removeTag: (elem) => {
        // get the tag span
        let tagToDelete = $(elem.parentElement).text();
        // find this tag in the hidden field and delete substring with comma
        let tags = TagsHelper.tagsHolder.val();
        tags.replace(tagToDelete + ",", "");
        // remove span with the tag from the page
        elem.parentElement.remove();
    }
};

$(document).ready(() => {
    TagsHelper.init();
});

That’s a pretty straight-forward solution. Sure, it could be refactored (I didn’t have a chance to perform even one refactoring iteration).

The interesting thing that we have handled an pretty exciting situation here. As you could notice there is an onclick attribute in span. And there is a reason for that. The point is that once DOM is ready we can’t catch events on those spans because there are not a part of the DOM that was downloaded earlier. And you can’t just put them into this DOM.

So, we added this onclick attribute to handle this situation. otherwise we coudln’t get any information about those spans that we created in the page via JS code dynamically after DOM already has downloaded. As argument of the method removeTag the this was passed that allows us to obtain a lot of information about the particluar tag from which this funciton has been invoked.

So, the picture a one more time.

Hope that will be helpful and educational. Thanks.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Leave a Reply

Your email address will not be published. Required fields are marked *