Follow me on twitter @yodasmydad
Ahhhh #Fringe can't finish like that!! Latest Tweet:

Hey I'm Lee. My blog was put up to house my strange thoughts, ramblings, nuggets of information I can refer back to and document my learning curves on new dev stuff like Umbraco v5 and other .NET related things.

All thoughts and comments on here are my own, and in no way reflect my employer - I also take no responsibility for spelling, grammar or terminology, so read at your own risk!

Blogs I Read

Sites I Like

Creating A Custom Autocomplete Datatype In Umbraco

We are currently in the middle of building one of the largest Umbraco solutions we have done to date, and part of the build requires a number of custom datatypes as a substantial amount of our data is being held in custom tables.

In one of the tables we have over 7000+ vehicles, and the site requires a user to be able to pick four featured vehicles to appear on the home page - So we came up with the idea of having a custom datatype that let the user start typing the vehicle and then select it from an autocomplete search box.

The datatype would then save the VehicleId's (The primary keys in the DB) in a comma separated list which we could then just loop through in the front end and display the vehicles - I'm going to show you how easy it is to create custom datatypes like this using the 'usercontrol wrapper' datatype (You can read more about it here on Tims blog).  This DataType still needs a bit of work for our solution but should give you an idea on how we implemented it so far and should hopefully give you enough code to make your own version.

Front End

So the User UI for the datatype is pretty simple, I need to have a search box and an add button (Obviously) - Then a Gridview to display the chosen vehicles (Along with a delete option).  For the Autocomplete we are going to use the built in JQuery UI plugin which is already in Umbraco and we can use ClientDependency to load the JS files we need - So here is what I have for the UI the user sees.

<%@ Register TagPrefix="umb" Namespace="umbraco.uicontrols" Assembly="controls" %>

<p><asp:TextBox ID="tbSearch" CssClass="umbEditorTextField" ClientIDMode="Static" runat="server"></asp:TextBox> <asp:Button ID="btnSubmit" runat="server" Text="Add Vehicle" onclick="BtnSubmitClick" /></p>
<asp:GridView GridLines="None" CssClass="bcfgridview" ID="gvVehicles" runat="server" AutoGenerateColumns="false" DataKeyNames="VehicleId">
    <Columns>

        <asp:TemplateField HeaderText="Vehicle">
            <ItemTemplate>
                <%# ((n3oVehicle)Container.DataItem).fullTitle%>
            </ItemTemplate>
        </asp:TemplateField>

        <asp:TemplateField HeaderText="Delete">
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="chkRows" />
            </ItemTemplate>
        </asp:TemplateField>

    </Columns>
    <EmptyDataTemplate>
        <p>No vehicles selected</p>
    </EmptyDataTemplate>
</asp:GridView>
<p><asp:Button ID="btnDelete" runat="server" Text="Delete All Selected Vehicles" onclick="BtnDeleteClick" /></p>
<asp:HiddenField ID="hdnValues" runat="server" />

<script type="text/javascript">
    jQuery(document).ready(function () {jQuery("#tbSearch").autocomplete("vehiclesearch.ashx", { minChars: 2, max: 100 });});
</script>

You can see the JQuery call here at the bottom, we just pass in the text box and also reference a HttpHandler called vehiclesearch.ashx which you can read more about below.

HttpHandler/Web Service

To feed the JQuery autocomplete plugin you just need to create either a webservice of httphandler (Like the one I have created below vehiclesearch.ashx) which just takes the users input and searches your custom table.  We have a vehicle factory we have created which uses the Entity Framework to query our Vehicles tables and return the results as a List<Vehicle> and then I just output the results like so

Vehicle One
Vehicle Two
Vehicle Three

In plain text which is what feeds the autocomplete plugin, very simple and easy to do - The only extra bit of the code below you can see if our custom n3oCacheHelper which just caches the searches so we don't hit the DB every time and speeds it up considerably.  If you want to get the code for the cachehelper then its in the nForum solution on codeplex so you can dig it out of there.

    public class vehiclesearch : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            // Query strings passed by jquery autocomplete:
            // QueryString: {q=a&limit=150&timestamp=1227198175320}
            var searchquery = n3oString.SafePlainText(context.Request.QueryString["q"]);
            var limit = context.Request.QueryString["limit"].ToInt32();
            if(!String.IsNullOrEmpty(searchquery))
            {
                // Check the cache for the search
                List<Vehicle> vs;
                var searchCacheKey = BcfHelpers.CacheNameVehicleSearch(searchquery);
                if (!n3oCacheHelper.Get(searchCacheKey, out vs))
                {
                    // Now get
                    vs = VehicleFactory.Instance.FetchVehiclesByFullTitle(searchquery, limit);
                    // Add to cache
                    n3oCacheHelper.Add(vs, searchCacheKey);
                }

                foreach (var vehicle in vs)
                {
                    context.Response.Write(vehicle.FullTitle + Environment.NewLine);
                }
            }
            else
            {
                context.Response.Write("No search value received");
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }

Back End

This should be pretty easy to follow, but I'll break down what's happening - Obviously I have implemented the IUsercontrolDataEditor interface which you have to use if you want to create a usercontrol wrapper datatype.

Page Load

  • We are using the built in ClientDependencyLoader to add the CSS and JS files we need to power the autocomplete plugin
  • Setter is getting any Vehicle Id's which have been saved and passing the comma separated list of IDs  to a GetVehicles() method which just goes and grabs the vehicles with the IDs and binds them to the gridview

Adding Vehicle

  • We again use the VehicleFactory to match the vehicle the user has selected, get the Vehicle Id and append the values to a Hidden Field and format like so ,[22] (This is what the formatVehicleId method does)
  • A Balloon message is displayed to the user to let them know if the vehicle was added or not (And to tell the to save and publish)
  • Rebind the Gridview to show the vehicle has been selected

On Save

  • We just read in the HiddenField value, strip off the start and end commas and save the value using the default save method which is implemented from the interface.

The reason we store the vehicle Id's in this format ,[22],[33],[44] is to make it easier to delete them from the list in the delete method on the GridView which just loops through the selected checkboxes and deletes based on the DataKey (In this case is the VehiceId)

    public partial class VehiclePicker : UserControl, IUsercontrolDataEditor
    {

        protected void Page_Load(object sender, EventArgs e)
        {
            ClientDependencyLoader.Instance.RegisterDependency("Application/JQuery/jquery.autocomplete.js", "UmbracoClient", ClientDependency.Core.ClientDependencyType.Javascript);
            ClientDependencyLoader.Instance.RegisterDependency("css/umbracoGui.css", "UmbracoRoot", ClientDependency.Core.ClientDependencyType.Css);
        }

        public object value
        {
            get
            {
                var hValues = hdnValues.Value;
                return hValues.TrimStart(',').TrimEnd(',');
            }
            set
            {
                hdnValues.Value = value.ToString();
                GetVehicles(hdnValues.Value);
            }

        }

        private void GetVehicles(string vehiclescsv)
        {
            gvVehicles.DataSource = String.IsNullOrEmpty(vehiclescsv) ? null : VehicleFactory.Instance.FetchVehiclesByIdCsv(vehiclescsv.TrimStart(',').TrimEnd(',').Replace("[", "").Replace("]", ""));
            gvVehicles.DataBind();
        }

        protected void BtnSubmitClick(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(tbSearch.Text))
            {
                //Get the vehicle from the text and save the Id into the hidden field
                var v = VehicleFactory.Instance.FetchVehiclesByFullTitle(tbSearch.Text).FirstOrDefault();
                if(v != null)
                {
                    if (hdnValues.Value.Contains(FormatVehicleId(v.VehicleId)))
                    {
                        BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.info, "Sorry", "This vehicle is already selected");
                        return;
                    }

                    hdnValues.Value += string.Concat(",", FormatVehicleId(v.VehicleId));
                    BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.info, "Added", "You still need to save and publish");
                    GetVehicles(hdnValues.Value);
                    return;
                }

            }
            BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.error, "Error", "No vehicle selected");
        }

        protected void BtnDeleteClick(object sender, EventArgs e)
        {
            foreach (GridViewRow row in gvVehicles.Rows)
            {
                var checkbox = (CheckBox)row.FindControl("chkRows");
                if (checkbox.Checked)
                {
                    var rateId = gvVehicles.DataKeys[row.RowIndex].Value.ToString();
                    RemoveVehicleId(rateId);
                }
            }
            // Show message and rebind
            BasePage.Current.ClientTools.ShowSpeechBubble(BasePage.speechBubbleIcon.info, "Deleted", "Don't forget to SAVE & PUBLISH now");
            // Now update the gridview
            GetVehicles(hdnValues.Value);
        }

        private static string FormatVehicleId(object vehicleid)
        {
            return string.Concat("[", vehicleid, "]");
        }

        private void RemoveVehicleId(string vehicleId)
        {
            var currentvIds = hdnValues.Value;
            currentvIds = currentvIds.Replace(FormatVehicleId(vehicleId), "");
            currentvIds = currentvIds.Replace(",,", ",");
            hdnValues.Value = currentvIds;
        }
    }

The Result

picking

ui

search

Hopefully this gives you an insight how easy it is to create custom datatypes using data from different sources, not just nodes or other umbraco data.

Back to top