The wheel isn’t round enough (or, “RTFW, Pete”)

(This has been in my drafts for too long…Wink

I have a need for an auto-complete input on one of the pages that I’m building at work.  I have a love for jQuery, so a quick google around yielded jQuery AutoComplete and CodeAssembly’s, but neither (on cursory inspection; more later…Wink did all of what I wanted (and, to be brutally honest, it’s been a while since I wrote something new in JavaScript, so I had an ulterior motive for concluding that…Wink.

My requirements look something like:

  • Anchor onto an input element (so, in the absence of JS, the user can just type into it)
  • Caches on the client-side – not for performance, but rather for UI responsiveness
  • Should filter and select on the client-side against cache while the user is typing (that is, if possible, narrow down the list)
  • CSS-able – should be possible for it to end up looking and behaving like a select element (or, with a bit of extension, a combobox)
  • Keyboard support
  • Shouldn’t hit the site until the user has finished typing
  • Should assume the site’s going to give back a list of JSON-encoded objects to play with
  • Should have different match-modes (contains, starts-with…Wink

A little while later, I took CodeAssembly’s plugin as a starting point and began to roll my own version.  I should mention that appearances to the contrary, I’m actually far more in favour of “buy-or-find” than “build” most of the time.  So, looking at the starting point (and, remember, this is me reminding myself of how this JS thing works after a few months of nothing but C#), I decided that TypeWatch did a better job of the “wait until the user is done typing before calling back” than what was already present.

Another google flies past, and I remember jQuery has a HotKeys plugin.  This is also duly incorporated (and pretty painlessly).

Now, I want it to cache data on the client-side to not bother the server-side more than it needs to.

Hmm. This is getting a bit long, now. Am I sure the existing wheels aren’t round enough? Hit google. What’s this? jQuery autocomplete + JSON + ASP.NET MVC?  That sounds rather handy.  Oh; look, it’s using the jQuery autocomplete plugin that I initially discarded.  Oh!  There’re some undocumented features!  So instead of writing it myself, I end up with the following in my view:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	<script type="text/javascript" src="~/Scripts/jquery.autocomplete.min.js"></script>
	<script type="text/javascript">
	$(function() {
		$(".country input[name=dto.Country]")
			.bind("result", function(e, data, text) {
				$(".country input[type=hidden]").val(data.id);
			})
			.autocomplete(
			"${Url.RouteUrl(new {Controller='Common', Action='Countries'})}"
			, {
				dataType: "json"
				,parse: function(data) {
					var rows = [];
					$.each(data, function(i) {
						rows[i] = { data: this, value: this.name, result: this.name }
					});
					return rows;
				}
				,formatItem: function(row, i, n) {
					return row.name;
				}
			});
	});
	</script>
1
2
3
4
5
6
7
8
...
<li class="country">
	<label for="dto.Country">Country:</label>
	!{Html.TextBox("dto.Country")}
	!{Html.Hidden("dto.CountryId")}
	!{Html.ValidationMessage("dto.Country")}
</li>
...

(I’m on ASP.NET MVC beta, and I do hope they’re going to fix the Html helper such that it generates id attributes that are actually XHTML compliant – “#dto.Country” as a CSS selector actually means “target the element with id ‘dto’ and class ‘Country’…Wink

The result of this is that the autocomplete is wired to the visible text-box, and when the user selects a value, the hidden input receives the country’s ID. The data-transfer object that the controller-action is using has both Country (in case of a revisit via failed server-side validation) and CountryId (the value we’re actually interested in, business-logic wise) properties. The bind to the “result” event was also undocumented; I had to pore through the source of autocomplete (which was a good move; it’s written really cleanly, and that reinforces the confidence I have in it).

The ~/common/countries controller action looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult Countries(string q, int limit)
{
	var countries = new JsonResult
	{
		Data = Tvsiab.GetTable<country>()
			.Where(c => c.Name.StartsWith(q))
			.Take(limit)
			//.Distinct(countryComparer) // it seems LinqToSql's query-provider doesn't support this part of the Linq API!
			.OrderBy(o => o.Name)
			.Select(c => new { id = c.PKid, name = c.Name })
	};
	return countries;
}

which spits back an array of id,name JSON objects.

Share and Enjoy: These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Digg
  • del.icio.us
  • StumbleUpon
  • Reddit
  • DotNetKicks
  • DZone
  • LinkedIn
  • Technorati

2 Comments

DylanJanuary 22nd, 2009 at 11:34 pm

Re: element IDs in ASP.NET MVC – have you tried passing in your own ID attributes via the attributes {} object in the HTML helper method?

Or skip HTML helpers and just write the code. It’s not that hard Smile

PeteJanuary 23rd, 2009 at 12:22 am

Yeah, that (passing in id attribute) works, but, well… one shouldn’t have to. I expect that a web-framework, especially a ground-up new one like this one, to generate valid XHTML in this day and age. Happily, it’s not like it’s too late to fix it, what with it not being v1 yet Wink

Leave a comment

Your comment

Click to Insert Smiley

SmileBig SmileGrinLaughLOLFrownBig FrownWinkKissRazzAngelAngryReally AngryConfusedNeutralThinkingChicCoolNerdSillyDrunken RazzMad RazzEvil GrinMeanPissed OffReally PissedCurseShoutGrit TeethCryWeepSide FrownWiltSmugDisdainRoll EyesSarcasmLoserTalk to the HandShyBeat UpPainShameBeautyBlushCuteLashesKissingKiss BlowKissedHeh!SmirkSnickerGiggleIn LoveDroolEek!ShockSickSuspenseTrembleDazedHypnotizedFoot in MouthMoney MouthQuietShut MouthDOH!IDKQuestionLyingStruggleSweatStopByeGo AwayWavingTime OutCall MeOn the PhoneMeetingSecretHandshakeHigh FiveHug LeftHug RightClapDanceJumpFingers CrossedVictoryYawnSleepyPrayWorshipWaitingAlienClownCowboyCyclopsDevilDoctorFemale FighterMale FighterMohawkMusicPartyPirateSkywalkerSnowmanSoldierGhostSkeletonEatStarvingVampireZombie KillerBunnyCatCat 2ChickChickenChicken 2CowCow 2DogDog 2DuckGoatHippoKoalaLionMonkeyMonkey 2MousePandaPigPig 2SheepSheep 2ReindeerSnailTigerTurtleFemaleMaleHeartBroken HeartRoseDead RosePeaceYin YangUS FlagMoonStarSunCloudyRainThunderUmbrellaRainbowMusic NoteYesNoAirplaneCarIslandAnnouncebrbBeerDrinkLiquorCakeCoffeePizzaWatermelonBowlPlateCanMailCellPhoneCameraFilmTVClockLampSearchCoinsComputerConsolePresentSoccerCloverPumpkinBombHammerKnifeHandcuffsPillPoopCigarette