Monday, September 12, 2011

Google-like Filtering with RadGrid and Separate Data Class


I needed to create a Google Suggest style search interface for an ASP.NET page.

Our site recently obtained licenses to the Telerik control suite, and the powerful RadGrid control looked like it was just what I needed. I found this example on the Telerik site.

Key to this setup is having a textbox for the user to input filter criteria, which is then from the user's perspective transformed into a RadComboBox object to house matching results; then once the user selects an item from the dropdown, the RadGrid is bound and displays any matching results.

I encountered a problem because our site uses a centralized class library for CRUD outside of this particular page’s code class, meaning I could not access my application’s data context from within the GridBoundColumn class definition. The Telerik example (specifically in the MyCustomFilteringColumnCS.cs file) performs its own queries as needed using a SqlDataAdapter with a ConnectionString obtained from the application configuration. A simple SELECT statement is executed which returns matching results.

When I tried to reference my centralized data class from within the GridBoundColumn class definition, I got the following error:

Cannot access non-static property … in static context.


Below is the problem code, revealing that the property CurrentUser is inaccessible to the class.

       protected void list_ItemsRequested(object o, RadComboBoxItemsRequestedEventArgs e)
     {
        // Cannot access non-static property 'CurrentUser' in static context.
        using (MyCRUD mc = new MyCRUD(CurrentUser) )
     
  {
              ((RadComboBox)o).DataTextField = DataField;
              ((RadComboBox)o).DataValueField = DataField;
              ((RadComboBox)o).DataSource = mc.GetMatchingAddresses(e.Text);
              ((RadComboBox)o).DataBind();
        }
     }


My GridBoundColumn class does not exist until my application instantiates it with its parent RadGrid object, so I cannot directly assign a property to it. However, I stumbled upon this post which made me realize I could, in the GridBoundColumn class definition, make several changes.

  1. Define a constructor for the class which takes an existing instance of the MyCRUD class as input.
  1. Create a public property in the class definition which can be assigned the MyCRUD object.
  1. Create a private field in the class definition to contain the instance of the MyCRUD object to be utilized by the GridBoundColumn class.
 
Below is the modified class, with additions (*) indicated below.

       public class rgcFilterColumn : GridBoundColumn
      {   
            // * I added a constructor with an input parameter of the type 
            // * corresponding to my app’s CRUD object.       
            public rgcFilterColumn(MyCRUD mycrud)
            {
                TheDataContext = mycrud;
            }

            // * This field provides an instance of the rgcFilterColumn class 
            // * with the corresponding value set for the data context object.
            private readonly MyCRUD TheDataContext;

            // * This property enables the process which instantiates this 
            // * class to assign the MyCRUD object to TheDataContext.
            public MyCRUD thedatacontext
            {
                get { return TheDataContext; }
            }


     // RadGrid will call this method when it initializes
     // the controls inside the filtering item cells
            protected override void SetupFilterControls(TableCell cell)
            {
                base.SetupFilterControls(cell);
                cell.Controls.RemoveAt(0);
                RadComboBox combo = new RadComboBox
                {
                    ID = ("RadComboBox1" + UniqueName),
                    ShowToggleImage = false,
                    Skin = "Office2007",
                    EnableLoadOnDemand = true,
                    AutoPostBack = true,
                    MarkFirstMatch = true,
                    Height = Unit.Pixel(100)
                };
                combo.ItemsRequested += list_ItemsRequested;
                combo.SelectedIndexChanged += list_SelectedIndexChanged;
                cell.Controls.AddAt(0, combo);
                cell.Controls.RemoveAt(1);
            }

   // RadGrid will call this method when the value should
   // be set to the filtering input control(s)
           protected override void SetCurrentFilterValueToControl(TableCell cell)
           {
                base.SetCurrentFilterValueToControl(cell);
                RadComboBox combo = (RadComboBox)cell.Controls[0];
                if ((CurrentFilterValue != string.Empty))
                {
      combo.Text = CurrentFilterValue;
        }
     }

   //
RadGrid will call this method when the filtering value
   // should be extracted from the filtering input control(s)
           protected override string GetCurrentFilterValueFromControl(TableCell cell)
           {
                RadComboBox combo = (RadComboBox)cell.Controls[0];
                return combo.Text;
           }


           protected void list_ItemsRequested(object o, RadComboBoxItemsRequestedEventArgs e)
           {
                // * Below we use the value of field TheDataContext to execute 
                // * a method accesible via the MyCRUD data context for the application.
                using (MyCRUD mc = TheDataContext)         
                {
                     ((RadComboBox)o).DataTextField = DataField;
                     ((RadComboBox)o).DataValueField = DataField;
                     ((RadComboBox)o).DataSource = mc.GetMatchingAddresses(e.Text);
                     ((RadComboBox)o).DataBind();
                }
           }

           private void list_SelectedIndexChanged(object o, RadComboBoxSelectedIndexChangedEventArgs e)
           {
                GridFilteringItem filterItem = (GridFilteringItem)((RadComboBox)o).NamingContainer;
                if ((UniqueName == "Index"))
                {
                    // this is filtering for integer column type
                    filterItem.FireCommandEvent("Filter", new Pair("EqualTo", UniqueName));
                }
                // filtering for string column type
                filterItem.FireCommandEvent("Filter", new Pair("Contains", UniqueName));
           }

      }



Now an instance of the GridBoundColumn class can happily utilize my application's central CRUD class for all its data retrieval operations.


No comments: