ASP.Net AJAX Extender - List Search Box


If you do web development and work with ASP.Net, most likely you've also been using their AJAX implementation and probably also their Control Toolkit. I've been using it for a while now and I have to say that most of the time I have to tweek or even reinvent one of their items because of their implementation. One such item was the ListSearchExtender. It worked perfectly well but I found that quite a few people never used the feature because they didn't pay attention to the text that popped up even if an explanation was above the list.

To mitigate that issue I found that using a text box with text above it explaining its purpose fixed that problem. Another issue that I found was that sometimes a design called for an item in a list to be indented. This can be done by adding code similar to this:

ListItem IndentedItem = new ListItem();
string EncodedString = "    ";
System.IO.StringWriter Writer = new System.IO.StringWriter();
HttpUtility.HtmlDecode(EncodedString, Writer);
string DecodedString = Writer.ToString();
IndentedItem.Text = DecodedString + DecodedString+"Item Name";

And adding that to the list. This however makes the item completely unsearchable as the ListSearchExtender does not take into account HTML encoding nor are you capable of specifying characters to ignore from what I can tell. This also meant that I had to develop my own version of that item. And to help others so that they don't have to reinvent the wheel (although they may still want to tweak this code as there is little optimization done), I'm giving you the code:

FilteredSearchBoxExtender.cs

using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using AjaxControlToolkit;

[assembly: System.Web.UI.WebResource("AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.js", "text/javascript")]

namespace AJAXControls.FilteredSearchBox
{
    [Designer(typeof(FilteredSearchBoxDesigner))]
    [ClientScriptResource("AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior", "AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.js")]
    [TargetControlType(typeof(Control))]
    public class FilteredSearchBoxExtender : ExtenderControlBase
    {
        /// <summary>
        /// The id of the control to update
        /// </summary>
        [ExtenderControlProperty()]
        [RequiredProperty()]
        [IDReferenceProperty(typeof(TextBox))]
        public string SearchBoxID
        {
            get
            {
                return GetPropertyValue("SearchBoxID", "");
            }
            set
            {
                SetPropertyValue("SearchBoxID", value);
            }
        }
    }
}

FilteredSearchBoxBehavior.js

Type.registerNamespace('AJAXControls.FilteredSearchBox');

AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior = function(element) {
    AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.initializeBase(this, [element]);

    // TODO : (Step 1) Add your property variables here
    this._SearchBoxIDValue=null;
    this._CurrentPosition=0;
}
AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.prototype = {
    initialize : function() {
        AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.callBaseMethod(this, 'initialize');

        // TODO: Add your initalization code here
        Sys.UI.DomEvent.addHandler($get(this._SearchBoxIDValue), 'keyup',Function.createDelegate(this, this._onkeyup));
        Sys.UI.DomEvent.addHandler($get(this._SearchBoxIDValue), 'keydown',Function.createDelegate(this, this._onkeydown));
    },
   
    _onkeyup : function(e) {
        var SearchText = $get(this._SearchBoxIDValue).value;
        var NumKeys = $get(this._SearchBoxIDValue).value.length;
       
        var Text = SearchText ? SearchText.toLowerCase() : "";
       
        if(Text.length == 0)
        {
            this.get_element().selectedIndex = 0;
            this._CurrentPosition=0;
            return;
        }
        else
        {
            for(var i = this._CurrentPosition; i <= this.get_element().options.length-1; i++)
            {
                var Item=this.get_element()[i].text.toLowerCase();
                while(escape(Item).indexOf("%A0")==0)                //This is where we remove all of the leading spaces
                {
                    Item=Item.slice(1);
                }
                if(Item.startsWith(Text))
                {
                    this.get_element().selectedIndex=i;
                    this._CurrentPosition=i;
                    return;
                }
            }
        }
    },
   
    _onkeydown : function(e) {
        if(e.keyCode == Sys.UI.Key.backspace)
        {
            this._CurrentPosition=0;
        }
    },

    dispose : function() {
        // TODO: Add your cleanup code here

        AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.callBaseMethod(this, 'dispose');
    },

    // TODO: (Step 2) Add your property accessors here
    get_SearchBoxID : function() {
        return this._SearchBoxIDValue;
    },

    set_SearchBoxID : function(value) {
        this._SearchBoxIDValue = value;
    }
}
AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior.registerClass('AJAXControls.FilteredSearchBox.FilteredSearchBoxBehavior', AjaxControlToolkit.BehaviorBase);

You might want to note that the extender was created in a seperate project for my various AJAX extenders called AJAXControls and also within its own directory called FilteredSearchBox. So you might have to change some stuff around to use this. Also the designer file was left blank. So when you create the extender you can pretty much ignore that portion. Anyway, I have quite a few other extenders that I've created and as far as I'm aware they should all work with 2.0 as well as 3.5, so expect every once in a while to pepper the site with these from time to time.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: General | Web Design
Posted by James Craig on Friday, February 29, 2008 4:00 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Lunar Engine Error Logging System


One of the main problems with working on any sort of project, whether it is a game or not, is how to detect and report bugs to the developers in an easy/straightforward manner.  In a previous post I talked a bit about various options out there and proposed using a database. Using a database for this sort of work has a number of advantages (some of which I mentioned previously and some that I didn't):

  • It's quick and designed specifically to store information
  • It is easy to search through and gather information from
  • It's easy to setup and get running

So with that in mind, I've decided to use this approach within the Lunar Engine. I also decided that I was going to use MySQL as the backend portion. And while I'm not going to go over setting it up (although there is a good tutorial here), I will go over the code and database itself:

The Table

The table that I'm using for saving the errors is rather straightforward and used the following statement to set it up

CREATE TABLE  errors (
  `ErrorID` int(10) unsigned NOT NULL auto_increment,
  `ErrorText` varchar(5000) NOT NULL,
  `ErrorType` int(10) unsigned NOT NULL,
  `File` varchar(1024) NOT NULL,
  `Line` int(10) unsigned NOT NULL,
  `StartTime` datetime NOT NULL,
  `Method` varchar(1024) NOT NULL,
  PRIMARY KEY  (`ErrorID`)
)

This allows us to store information about the error including the error message, error type (general, lost input device, etc.), file it occurred in, line it occurred on, time that the program was started, and the method in which the error occurred. There are a few other items that we could save, but this will do for our purposes. The next step is setting up the code:

The Code

 

using System;
using System.IO;

namespace LunarEngine
{
    public class ErrorLog
    {
        public ErrorLog()
        {
#if DEBUG
            try
            {
                string ConnectionString = "DRIVER={MySQL ODBC 3.51 Driver};" +
                          "SERVER=localhost;" +        //The server location should go here
                          "DATABASE=errorlog;" +     //The database name, in this case ErrorLog
                          "UID=user;" +                     //Whatever you've setup as the user name for the database
                          "PASSWORD=password;"+   //Whatever you've setup as the password for the database
                          "OPTION=3";

                Connection = new System.Data.Odbc.OdbcConnection(ConnectionString);
                Connection.Open();

                Run = DateTime.Now;
            }
            catch (Exception a)
            {
                throw a;
            }
#else
            File = new FileStream("./Logs/Errors.txt", FileMode.CreateNew,FileAccess.Write);
            Writer = new StreamWriter(File);
#endif
        }

        public void Write(string Message, ErrorType ErrorType)
        {
            try
            {
#if DEBUG
                System.Diagnostics.StackFrame CallStack = new System.Diagnostics.StackFrame(1, true);
                string File = CallStack.GetFileName().Replace("\\","\\\\");
                string Method = CallStack.GetMethod().Name;
                int Line = CallStack.GetFileLineNumber();


                System.Data.Odbc.OdbcCommand Command;
                string CommandString = "insert into errors(ErrorText,ErrorType,File,Line,StartTime,Method)"+

                         "values (\"" +
                         Message + "\"," + ((int)ErrorType).ToString() + ",\"" +
                         File + "\"," + Line.ToString() + ",\""+
                         Run.ToString("yyyy-MM-dd hh:mm:ss")+"\",\""+Method+"\");";
                Command = new System.Data.Odbc.OdbcCommand(CommandString, Connection);
                int LinesInserted = Command.ExecuteNonQuery();
#else
                Writer.Write(((int)ErrorType).ToString()+" "+Message);
#endif
            }
            catch (Exception a)
            {
                throw a;
            }
        }

        public void Cleanup()
        {
#if DEBUG
            if(Connection!=null)
            {


            if(Connection.State!=System.Data.ConnectionState.Closed)
            {
                Connection.Close();
            }


            }
#else
            Writer.Close();
            File.Close();
#endif
        }

        private System.Data.Odbc.OdbcConnection Connection;
        private FileStream File;
        private StreamWriter Writer;
        private DateTime Run;


        public enum ErrorType
        {
            General = 1
        };
    }
}

 

Final Word

There are a couple of areas where the code might confuse you:

Why am I not passing in the file, method, and line information to the function? 

Well in C# there is no __FILE__ or __LINE__ constants. The only way to really get that information is to use the StackFrame class. It's a bit awkward but it will let you know what you need. Also note though that it will not work in release mode as the debug information that it uses isn't available.

Why are you using this system only in debug builds?

The reasons for using a more traditional file system to record this information in release is two fold. First, we would have to setup the ODBC driver on the end user's machine. The second issue is that it could be just a bit too slow. While it is fast enough when the database is on the same machine as the application, the error message would have to be sent to the server, a response sent back, etc. if it were left in during release. To be honest that isn't really something we want to deal with. There are ways we could address that issue, run the error logging system in its own thread for example, but we should save those threads for items that would enhance our game.

This approach as you can see is easy to setup though and fast enough for our purposes when it comes to debugging the system. There's another advantage that I haven't talked much about yet, our ability to search through the information. We can easily setup a website to use the information to present it in a readable manner that can allow us to filter out the noise and get to the info we want. I will say that I've talked enough though and I'll go over this further in a future post.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by James Craig on Tuesday, February 26, 2008 5:19 PM
Permalink | Comments (0) | Post RSSRSS comment feed

New Website


You may have noticed that the website has been redone. I'm trying to update everything and keep myself motivated when it comes to programming. First and foremost though I'm going to try to be more helpful and upload actual articles with code that might be of use to you. In fact every component that I create for the Lunar Engine will be shown here and explained although expect a good deal of web development code to be here as well. So take a look around and see what there is.

In other news I've discovered a couple of products that are helping me a great deal. First is what I'm powering this site with: BlogEngine.net. It's one of the better systems that I've seen out there, open source, very easy to update. In fact I've even written a couple of my own extensions for it that I'm using here along with my own theme. So I'm quite pleased with it.

The second item that I've found is MOGRE it's a .Net wrapper for Ogre3D. I really didn't want to spend tons of time creating my own rendering system and I had yet to find a decent .Net one out there that met my needs. Thankfully though I find this and I have to say that it seems to do everything I need, although there is one issue. It was compiled using VC++ 2005 and not VC++ 2008... Seems like a small issue but it causes quite a few issues if you want to use their debug libraries. Thankfully using their release libraries fixes the issues. So expect the Lunar Engine to be using MOGRE for the graphics portion of the engine.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: General
Posted by James Craig on Sunday, February 24, 2008 10:34 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Recent comments

None

Calendar

<<  January 2009  >>
SuMoTuWeThFrSa
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567

Sponsors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009