Search code examples
javascriptc#autocompletetagsblazor

Blazor autocomplete tagging


Similar to this question, I am looking for a component to add tags to an object like in stackoverflow, meaning autocomplete text field for tags. I am either looking for a native component or a way to wrap a JS solution so that it can be used in blazor.

Another idea is a multi-select component with checkboxes like discussed here, but I do not really like the idea.

Ideally I would provide a list of all tags and bind to a list on an item for which the tags are being set.


Solution

  • Usually, it's not right practice to share full code as SO is not a code sharing site and it's generally asked What you have tried so far?. However since Blazor is a new technology thus beginners struggle with finding a good solution or a plugin on web to meet their requirement thus I'm considering this as an exception.

    Now answer to your question Creating a new component for adding tags. You can use below solution that I created in one of the project I'm working on. This doesn't require any JS and can be handled with C# only to create a Tag. You can also check the Blazor Fiddle solution in action that I have prepared for you on fiddle. Hope this is what you are looking for.

    @using System;
    @using System.Collections.Generic;
    @using System.Linq;
    @using System.Text.RegularExpressions;
    
    <style>
    
    .suggestion-container {
        position: relative;
    }
    
    .tagsinput, .tagsinput * {
        box-sizing: border-box
    }
    
    .tagsinput {
        display: -webkit-box;
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-flex-wrap: wrap;
        -ms-flex-wrap: wrap;
        flex-wrap: wrap;
        background: #fff;
        font-size: 14px;
        line-height: 20px;
        color: #556270;
        padding: 5px 5px 0;
        border: 1px solid #e6e6e6;
        border-radius: 2px
    }
    
        .tagsinput.focus {
            border-color: #ccc
        }
    
        .tagsinput .tag {
            position: relative;
            background: #556270;
            display: block;
            max-width: 100%;
            word-wrap: break-word;
            color: #fff;
            padding: 5px 30px 5px 5px;
            border-radius: 2px;
            margin: 0 5px 5px 0
        }
    
            .tagsinput .tag .tag-remove {
                position: absolute;
                background: 0 0;
                display: block;
                width: 30px;
                height: 30px;
                top: 0;
                right: 0;
                cursor: pointer;
                text-decoration: none;
                text-align: center;
                color: #ff6b6b;
                line-height: 30px;
                padding: 0;
                border: 0
            }
    
                .tagsinput .tag .tag-remove:after, .tagsinput .tag .tag-remove:before {
                    background: #ff6b6b;
                    position: absolute;
                    display: block;
                    width: 10px;
                    height: 2px;
                    top: 14px;
                    left: 10px;
                    content: ''
                }
    
                .tagsinput .tag .tag-remove:before {
                    -webkit-transform: rotateZ(45deg);
                    transform: rotateZ(45deg)
                }
    
                .tagsinput .tag .tag-remove:after {
                    -webkit-transform: rotateZ(-45deg);
                    transform: rotateZ(-45deg)
                }
    
        .tagsinput div {
            -webkit-box-flex: 1;
            -webkit-flex-grow: 1;
            -ms-flex-positive: 1;
            flex-grow: 1
        }
    
            .tagsinput div input {
                background: 0 0;
                display: block;
                width: 100%;
                font-size: 14px;
                line-height: 20px;
                padding: 5px;
                border: 0;
                margin: 0 5px 5px 0
            }
    
                .tagsinput div input:focus {
                    color: #495057;
                    background-color: #fff;
                    border-color: #80bdff;
                    outline: 0;
                    box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
                }
    
                .tagsinput div input.error {
                    color: #ff6b6b
                }
    
                .tagsinput div input::-ms-clear {
                    display: none
                }
    
                .tagsinput div input::-webkit-input-placeholder {
                    color: #ccc;
                    opacity: 1
                }
    
                .tagsinput div input:-moz-placeholder {
                    color: #ccc;
                    opacity: 1
                }
    
                .tagsinput div input::-moz-placeholder {
                    color: #ccc;
                    opacity: 1
                }
    
                .tagsinput div input:-ms-input-placeholder {
                    color: #ccc;
                    opacity: 1
                }
    </style>
    
    <div class="suggestion-container w-75">
        <div id="@($"{Id}_tagsinput")" class="tagsinput">
    
            @if (Tags != null && Tags.Any())
            {
                @foreach (var tag in Tags)
                {
                    <span class="tag">
                        <span class="tag-text">@tag</span>
                        <span class="tag-remove" @onclick="() => DeleteTag(tag)" />
                    </span>
                }
            }
    
            <div id="@($"{Id}_addTag")">
                <div class="@(IsContainSpecialCharacter ? "tag-tooltip" : string.Empty)">
                    <input id="@($"{Id}_tag")"
                           class="tag-input"
                           placeholder="Add tags"
                           autocomplete="off"
                           @bind-value="Value"
                           @bind-value:event="oninput"
                           @onkeyup="AddTags" />
    
                    @if (IsContainSpecialCharacter)
                    {
                        <div class="error-right d-inline-flex p-2">
                            <i class="oi oi-warning text-warning p-1"></i>
                            <p class="text-left m-0 p-1">Special characters not allowed.</p>
                            <i></i>
                        </div>
                    }
                </div>
            </div>
        </div>
    </div>
    
    @code{
    
        private Guid Id => Guid.NewGuid();
        protected string Value { get; set; }
        protected bool MenuVisibility { get; set; }
        protected bool IsContainSpecialCharacter { get; set; }
        protected List<string> Tags { get; set; } = new List<string>();
    
            protected void AddTags(KeyboardEventArgs eventArgs)
            {
                IsContainSpecialCharacter = false;
    
                if (!String.IsNullOrEmpty(Value))
                {
                    if (eventArgs.Key.Equals("Enter"))
                    {
                        var regex = new Regex(@"[^a-zA-Z0-9\s]");
                        if (!regex.IsMatch(Value))
                        {
                            if (!Tags.Exists(t => t.Equals(Value, StringComparison.CurrentCultureIgnoreCase)))
                            {
                                Tags.Add(Value);
                            }
    
                            Value = string.Empty;
                        }
                        else
                        {
                            IsContainSpecialCharacter = true;
                        }
                    }
                }       
            }
        
            protected void DeleteTag(string value)
            {
                if (String.IsNullOrEmpty(value)) return;
    
                var tag = Tags.FirstOrDefault(t => t == value);
                if (tag == null) return;
    
                Tags.Remove(tag);
            }
    }
    

    P.S. What I've shared here is only a code snippet of building tags, it doesn't not contain tags with autocomplete option as it would require time to create a full solution on fiddle with fake data. Thus I'm avoiding that due to time limitation.