dotnetco.de

Tags with autocomplete in asp.net textbox

My last posting showed how to use jQueryUI autocomplete with some individual formatting and an image. Now I have a similar request: I want to build an input for tags (or tokens). It should also provide suggestions from a backend using autocomplete (so that’s similar to previous posting) but the returned list does not need any special formatting or images. Instead I need to have an option to allow multivalue, and it would be nice to have some box around each tag and an ‘x’ to delete a tag.

Requirements

In most of my projects I still use jQuery, jQueryUI and Bootstrap. jQueryUI has already an autocomplete option which allows multi-value input, but the design is not too nice. After some search I found Bootstrap-Tokenfield from Sliptree. It uses jQueryUI autocomplete (or Twitter Typeahead if you like) which has several advantages as it does not reinvent the wheel but uses an existing robust script, and I could reuse my existing autocomplete options and webservice without too many changes :-). Additionally it integrates into Bootstrap design, and further more provides additional options like copy-and-paste.

Bootstrap Tokenfield Configuration

You only need to do 2 things: Inform the tokenfield script to take care of your textbox, and supply information how to get the autocomplete options. The first one is simply done by calling MyTextbox.tokenfield. Then include the autocomplete options there (see below).

Webservice Data Retrieval

The data retrieval in the example is very easy: I hardcoded a table with some usernames (reused from my previous posting). In the live project we have some database where the data is retrieved on the fly but for this example I kept it as simple as possible. So we have a select-statement which uses the supplied user input. If you want to restrict the maximum number of return results you should filter using LINQ or a statement (SELECT TOP 2 …) accordingly. All the data is then placed into a string array and returned to the script. You don’t need to take care about JSON or else.

Get the input

To get the input you could as usual just get the Text of the Textbox as this contains all selected tags separated by comma.

The Code

As usual here is the full source code so you could easily build it on your own.

 Tokenfield.aspx

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="TokenField.aspx.vb" Inherits="WebApplication1.TokenField" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <script src="//code.jquery.com/jquery-1.11.0.min.js" type="text/javascript"></script>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js" type="text/javascript"></script>
    <link href="https://code.jquery.com/ui/1.11.4/themes/start/jquery-ui.css"
        rel="Stylesheet" type="text/css" />
        <script src="js/bootstrap-tokenfield.js" type="text/javascript"></script>
    <link href="css/bootstrap-tokenfield.css" rel="Stylesheet" type="text/css" />
    <title>TokenField</title>
    <script type="text/javascript">
        $(document).ready(function () {
            $("#<%=MyInput.ClientID %>").tokenfield({
                autocomplete: {
                    source: function (request, response) {
                        $.ajax({
                            url: '<%=ResolveUrl("~/Webservices/WebService1.asmx/GetNames") %>',
                            data: "{ 'input': '" + request.term + "'}",
                            dataType: "json",
                            type: "POST",
                            contentType: "application/json; charset=utf-8",
                            success: function (data) {
                                response(data.d)
                            }
                        });
                    },
                    delay: 100
                }
            })
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <div style="margin: 100px">
            <asp:TextBox ID="MyInput" runat="server"></asp:TextBox>
            <asp:Button ID="DoSomething" runat="server" Text="DoSomething" />
        </div>
    </div>
    </form>
</body>
</html>

Tokenfield.aspx.vb

Public Class TokenField
    Inherits System.Web.UI.Page
 
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
 
    End Sub
 
    Private Sub DoSomething_Click(sender As Object, e As System.EventArgs) Handles DoSomething.Click
        Dim test As String = MyInput.Text
 
    End Sub
End Class

Webservice1.asmx.vb

Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
 
' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
<System.Web.Script.Services.ScriptService()> _
<System.Web.Services.WebService(Namespace:="http://tempuri.org/")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
 Public Class WebService1
    Inherits System.Web.Services.WebService
 
    <WebMethod()> _
  <Script.Services.ScriptMethod(ResponseFormat:=Script.Services.ResponseFormat.Json)> _
    Public Function GetNames(ByVal input As String) As String()
        Dim dt As DataTable = CreateDatatable()
        Dim rows As DataRow()
        rows = dt.Select(String.Format("Username like '%{0}%'", input))
        Dim result(rows.Length - 1) As String
        For i As Integer = 0 To rows.Length - 1
            result(i) = rows(i).Item("Username").ToString
        Next
        Return result
    End Function
 
    Private Function CreateDatatable() As DataTable
        Dim dt As New DataTable
        dt.Columns.Add("Username")
 
        Dim row As DataRow
        row = dt.NewRow
        row("Username") = "Mark Zuckerberg"
        dt.Rows.Add(row)
 
        row = dt.NewRow
        row("Username") = "Elon Musk"
        dt.Rows.Add(row)
 
        row = dt.NewRow
        row("Username") = "Bill Gates"
        dt.Rows.Add(row)
 
        row = dt.NewRow
        row("Username") = "Jack Dorsay"
        dt.Rows.Add(row)
 
        Return dt
 
    End Function
End Class

Update: If tag is shown twice..

When upgrading jQueryUI you might experience the problem that a selected tag is shown twice. SemirLab2 posted a solution on github which works quite well so you have to add this code for each of your tokenfields:

$('#tokenfield').on('tokenfield:createtoken', function (event) {
    var existingTokens = $(this).tokenfield('getTokens');
    $.each(existingTokens, function(index, token) {
        if (token.value === event.attrs.value)
            event.preventDefault();
    });
});

11 Comments

  1. I cannot get this to work. What do you mean by “calling MyTextbox.tokenfield”? I do not see how or where the tokenfield is told to handle the textbox accept for the JQuery that makes the Ajax call. I need this functionality so if you can expound on your code above, it would be greatly appreciated.

    1. Hi Beryl,
      have a look at line 16 of Tokenfield.aspx. You only need to define that this textbox is a tokenfield, and the implemented jQuery script will do the rest.
      Hope this helps!
      Kai

  2. Code works perfectly, I just wonder what if I want to prevent duplicates? I mean if one tag is already chosen, it won’t show in the suggestions??

    Also, is it possible to get each tag ID? like two parameters behind each tag Text, and Value?

    Thanks!

    1. Hi Ahmad,
      regarding duplicates: the webservice is triggered with every new character so of course you could add a check there and only return a list with tags not chosen yet.
      regarding IDs: the tags should of course be unique, so that’s already some kind of ID. Technically it would not be necessary to have an additional unique ID as long as the tags are unique. When you further process the input you could of course merge the tags with your ID table.
      Hope this helps!

  3. The sad part is this is not a full example. This is a completelt different ball game when you have to do all this in EDIT mode. Like retrieving a record from the database and binding it to the textbox and then make it work.

    1. Hi, thanks for the info! It seems that the code was not formatted correct in the post. I’ve refreshed the display so please have a look again. Everything is there so that you can copy it to your own projects and it should work fine. At least it did work fine at the time of writing, 6 years ago.

      1. Hi Kaii, thanks for your response. But just by looking at the code, one can tell that you are binding the source with the master list and that’s it. It was more like, what If I have saved ID 1 and 2 in the database on a particular record. I need to createTokens only for those 2 first and set it on that textbox along with calling the master source of course.

        The createtoken call on edit mode seems to work only for and not for a @Html.TextBoxFor (new @id=#autocompleteid}, as it is model bound. I understand this was 6 years ago. I wish I was more recent :-). Thanks though.

  4. Loved it! I am trying to get rid of Telerik and I think it will help me! Thanks!

Leave a Reply to Beryl Small Cancel reply