My take on implementing searches with AJAX (Part 1)

by Ishai Hachlili 4. February 2008 12:44

I can’t remember a project I worked on that didn’t have at least one search page but recently I built an application where half the pages were search pages and I wanted to make it easy to add new pages and maintain the existing pages. There were also some performance requirements and UI features that we needed to support.

The basic idea is to collect the form input values, send to the server using AJAX and create a grid with the results.
 
Requirements:
-Support all the input controls a search form could have (Text Box, Drop Down, Check Box, Radio Buttons, Hidden Fields)
-Display the results in a grid that supports sorting and column dragging
-Support paging the results
-Support caching result pages and pre-fetching of the next page into the cache 
-Make it simple to add new pages and maintain existing pages.
-Support formatting of the returned data as well as links and other actions on each row

The following code samples were written using VS2008, but should work on previous versions as well, the main difference is the properties that use shorthand here
Also, in this project i used Microsoft Asp.Net AJAX exentsions, the prototype library, a type validation script from SmartWebby and the WebFX column list. In the last post I'll add a file with all the related code.


Collecting the form values

I created a simple search page with a few input controls on it and a search button and added a click event handler for the button.
When the search button is clicked I need to go through all of the input and select elements and collect the values into some object that I can send to the server.

It’s very easy to loop through the elements and get the values, the real question was what’s the best way to send the values to the server.

Asp.Net AJAX extensions has a nice feature that allows generating a javascript class from a server side class. The nice thing about it is that after using this class in the client side you can send it back to the server side and it will be converted to the server side object automatically.
This is done with the span style="font-size: 10pt; color: #2b91af; font-family: 'Courier New'">GenerateScriptType declaration.

I wanted the class on the server side to be generic so that it can be used for all search forms and here’s what I came up with:

namespace IshaiHachlili.MyTakeOnDotNet.Entities
{
public class SearchParameters : QueryParameters
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public string SortColumn { get; set; }
public string SortOrder { get; set; }
}

public class QueryParameters
{
public string QueryName { get; set; }
public string ColumnCollectionName { get; set; }
public List<QueryParameter> Parameters { get; set; }
}

public class QueryParameter{
public string FieldName { get; set; }
public string FieldValue { get; set; }
public DbType FieldType { get; set; }
}
}


SearchParameters is the class I’ll use for queries that need support for paging and sorting
QueryParameters can be used for other queries I might need in the future that don't require paging and sorting
Each QueryParameter holds the name and value of an input element and also the field type (I'll discuss the field type when I get to the server side code in a future post).
The QueryName and ColumnCollectionName properties are used to let the server side know which stored procedure it needs to run with these query parameters and what to do with the returned columns.

I created a separate project for this class because I’m going to use it in all tiers of my application, I called these project Entities.

The next thing I need to do is create a web service and a web method to call when the search button is clicked.

namespace IshaiHachlili.MyTakeOnDotNet.WebServices
{
[WebService(Namespace = http://tempuri.org/)]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]

[GenerateScriptType(typeof(IshaiHachlili.MyTakeOnDotNet.Entities.SearchParameters))]
[GenerateScriptType(typeof(IshaiHachlili.MyTakeOnDotNet.Entities.QueryParameters))]
[GenerateScriptType(typeof(IshaiHachlili.MyTakeOnDotNet.Entities.QueryParameter))]

[ScriptService]
public class ContentsWS : System.Web.Services.WebService
{
[WebMethod]
public string DoSearch(IshaiHachlili.MyTakeOnDotNet.Entities.SearchParameters searchParams)
{
//Call BLL method and pass the search parameters
SearchesBL bl = new SearchesBL();
DataSet ds = bl.DoSearch(searchParams);
//Convert the results of the BLL method to a JSON array using XSLT
//Return the serialized JSON array
string result = GetResultString(ds, searchParams.PageSize);
return result;
}
}
}


You can see I use GenerateScriptType for all three query parameter types, this will allow me to instantiate them in javascript.
My search method, DoSearch, accepts the SearchParameters type and I simply pass it to my business logic layer as is.
The business layer will return a dataset that I want to convert to a string and send back to the client side.

But first, let’s see how we collect the form values and send them to the server. 

function FormController() {}

FormController.prototype.CollectFormValues=function(formContainer, parameters) {
///<summary>Collects the form values using the formcontainer name and returns the entity</summary>
var formContainerEl=$G(formContainer);
if (parameters == null) parameters = new IshaiHachlili.MyTakeOnDotNet.Entities.SearchParameters();
for (var index=0; index<formContainerEl.getElementsByTagName("INPUT").length; index++) {
var el=formContainerEl.getElementsByTagName("INPUT")[index];
this.GetInputControlValue(el, parameters);
}
for (var index=0; index<formContainerEl.getElementsByTagName("TEXTAREA").length; index++) {
var el=formContainerEl.getElementsByTagName("TEXTAREA")[index];
this.GetInputControlValue(el, parameters);
}
for (var index=0; index<formContainerEl.getElementsByTagName("SELECT").length; index++) {
var el=formContainerEl.getElementsByTagName("SELECT")[index];
this.GetInputControlValue(el, parameters);
}
return parameters;
}

FormController.prototype.GetInputControlValue = function(el, formParameters) {
if (el.tagName == 'INPUT') {
var fieldName = this.GetFieldName(el.name);
var fieldType = el.getAttribute('fieldType');
var fieldValue = '';
switch (el.type) {
case "text":
fieldValue=this.ConvertValueToType(el.value,true);
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
break;
case "hidden":
fieldValue=this.ConvertValueToType(el.value,false);
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
break;
case "radio":
if (el.checked) {
fieldValue=el.value;
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
}
break;
case "checkbox":
fieldValue=el.checked;
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
break;
}
} else if (el.tagName == 'TEXTAREA') {
var fieldName = this.GetFieldName(el.name);
var fieldType = el.getAttribute('fieldType');
var fieldValue = el.value;
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
} else if (el.tagName == 'SELECT') {
var fieldName = this.GetFieldName(el.name);
var fieldType = el.getAttribute('fieldType');
var fieldValue = this.getSelectedValues(el);
this.addFormField(formParameters, fieldName, fieldType, fieldValue);
}
}

FormController.prototype.addFormField = function(formParameters, fieldName, fieldType, fieldValue) {
var formField = new IshaiHachlili.MyTakeOnDotNet.Entities.QueryParameter();
formField.FieldName=fieldName;
formField.FieldValue=fieldValue;
formField.FieldType=fieldType;

if (!formParameters.Parameters) formParameters.Parameters=[];
formParameters.Parameters.push(formField);
}


FormController.prototype.GetFieldName = function(controlID) {
var firstPos=controlID.lastIndexOf('$')+1;
var lastPos=controlID.length-firstPos;
return controlID.substr(firstPos, lastPos);
}

FormController.prototype.ConvertValueToType = function(value, isText) {
if (isInteger(value)) {
if (isText) return value;
return this.ConvertToNum(value);
} else if(isDate(value)) {
return this.ConvertToDate(value);
} else {
return value;
}
}

FormController.prototype.ConvertToNum = function(value) {
return eval(value + '+0');
}

FormController.prototype.ConvertToDate = function(value) {
aDateParts=value.split('/');
var oDate = new Date(aDateParts[2],aDateParts[1]-1,aDateParts[0]);
oDate.setMinutes(oDate.getMinutes() - oDate.getTimezoneOffset());
return oDate;
}

FormController.prototype.getSelectedValues = function(select) {
var r = new Array();
for (var i = 0; i < select.options.length; i++)
if (select.options[i].selected)
r[r.length] = select.options[i].value;
return r.join(",");
}



Calling FormController.CollectFormValues with any HTML element that contains input controls will return the parameters object with the form values for these controls.
I use the container to enable multiple forms on the same page, without having to deal with the form tag.

Here’s the code for the search button on click event

function Search() {
var fc = new FormController();
var searchParams = fc.CollectFormValues();
searchParams.PageSize=20;
searchParams.SortColumn=”FirstName”;
searchParams.SortOrder=”ASC”;
searchParams.QueryName=”SearchClients”;
searchParams.ColumnCollectionName=”ClientsSearchResults”;
IshaiHachlili.MyTakeOnDotNet.WebServices.ContentsWS.DoSearch(searchParams, onComplete, onError, this);
}



If you put a break point in the web method, you should get the SearchParameters object with all the form's input control values as well as the paging, sorting and QueryName.

In the next posts I’ll show you what I did on the server side and how I displayed the grid itself as well as adding support for paging and some other cool features that can be built on top of this implementation.

AjaxSearchSamplePart1.zip (63.20 kb)

Tags: , ,

AJAX | Asp.Net | Javascript

Some nice things coming from Israel

by Ishai Hachlili 3. February 2008 17:54

ok, this doesn't have much to do with developement but I just got an email about the Israel Web Tour 2008 and thought some of the companies on ir are pretty interesting.
The tour is basicaly several startups from coming over to the sillicon valley to find investors/partners (they've done it a couple of years ago with FixYa and some others).

Here are some I like:
ClickTale - Their products lets you track what users do on your sites by adding a simple javascript. They track actual mouse movements and they play it back as videos, so you see what each visitor actually did on each page and not just which pages they visited.

8 Hands - This software allows socialites to manage their many sites (myspace, flickr, facebook, etc...) from one place, plus you can easily share things like flickr photos with other friends who use the software.

5 Min - They call it Life Videopedia. It's basically 5 minute videos that answer different questions, mostly guides (how to player guitar, fitness videos, etc...)


And, not from this tour but it's from Israel, Wix
Wix is an online tool that lets you create flash animations easily without knowing how to develop flash and by using a very simple UI.
I got to play around with the beta and I think it's pretty cool, probably better for posting cool message on myspace and other similar sites and not for building flash for your own sites.

 

Tags: , , , , ,

Javascript debugging with visual studio on vista hangs

by Ishai Hachlili 2. February 2008 10:02

Can it be that I'm the only one having this problem? that's what I though until I found another developer who already submitted a bug report to microsoft.

I'm debugging some javascript using Visual Studio 2008 Team System and all of a sudden the IDE hangs, as well as the browser and webserver.
There's no way to get out of it, all I get is a message that asks me if I want to switch back to VS or wait for it to be available, but it won't, no matter how long I wait (I did leave it over night once), so I have to kill the process, it's the only way.

The problem is, this happens in different situations and I can't see any connection other than it's javascript debugging.

This happened to me a couple of times with VSTS 05 on vista, but with 08 it happens a lot

 

if you're developing on XP, DON'T upgrade to vista, this problem might happen on XP as well (never did to me) but I know vista/vsts/ie7 don't like working together too much.

 and another small issue I have with javascript debugging. VS requires the option to enable debugging in IE7, this means that whenever there's a javascript error in a site I'm browsing to I will see the "do you wish to debug?" message. I didn't know so many sites have bugs, don't people test their sites? a lot of bugs have to do with blocked pop ups.
This is really annoying, and it won't be less annoying to change this option everytime I start/finish debugging something.

The real problem is when opening pages in new tabs, if you open more than one page, and both pages have an error, IE7 just hangs and I have to kill the process. and when you kill the process, the history is lost, so go find that site you were on.

Maybe IE8 will solve this problem, I know that it's coming soon (by MS timetable, that probably means 6-12 months) but a patch to VS would be nice, so it can enable/disable debugging in the browser it opens when I select "view in browser" or start the app

I did submit this feature request to MS

 

and now, back to debugging, I'm keeping my fingers crossed for a hang free session

Tags: , , , ,

Javascript

About Me

Ishai Hachlili is a web and mobile application developer.

Currently working on Play The Hunt and The Next Line


Recent Tweets

Twitter October 23, 05:22
@BenThePCGuy a standard where that doesn't matter is better. One more reason to get the #Lumia920, wireless charging, no need for microUSB

Twitter October 23, 05:21
@ManMadeMoon where they dance around the issues and don't really talk about them

Twitter October 23, 05:20
@BenThePCGuy are you a @wpdev ?

Twitter October 23, 04:17
@JonahLupton But if it's black it's usually better

Twitter October 23, 02:58
@jongalloway next time ask your 5 year old how to spell

@EShy