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×tamp=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



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.