Click here to Skip to main content
12,449,910 members (44,051 online)
Click here to Skip to main content
Add your own
alternative version

Stats

6.1K views
520 downloads
7 bookmarked
Posted

Filtering search results with multiple CheckBox Lists

, 21 Jul 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Generalised filtering of search results using multiple checkbox lists. Each checkbox list may itself contain multiple selections.

Introduction

I had to write this piece of code for a friend. She was desgining an online shopping cart and a particular problem was that the search results had to be filtered using checkbox lists. Well it could have been done using radio button lists as suggested by many. But you could select only one value in a single Radio Button List and that simply wasn't good enough,. The idea was to select any number of values, in a single checkbox list and then do the same with any number of checkbox lists. Get the picture? There can be N checkboxes with M values in every checkbox and every single one of them can be selected. So the problem was generalizing the entire thing. Since the code was originally written for a shopping cart, you will see I have used a product type 'Laptop' and used five kinds of filters (viz. Brand, Ram, Processor, Screen Size, Hard Drive). The filers themselves have some logical values like Brand - {Hp, Dell, Sony}, Ram - {4GB, 8GB} etc. 
 
 
Background

I have used some very basic stuff here since I am myself a beginner in ASP.NET. Checkbox Lists and Gridviews are the only controls used. A little knowledge of generic lists and three tier architecture will also be helpful. I have followed proper naming conventions and tried to keep things simple. Anyways the entire code can also be downloaded, along with the local database mdf file.

Using the code

    public class FilterList : List<Filter> { }
    
    public Filter() { }
    private String _filterName, _filterValue;
    public Filter(String filterName, String filterValue)
    {
        _filterName = filterName;
        _filterValue = filterValue;
    }
    public String getFilterName
    {
        get { return _filterName; }
        set { _filterName = value; }
    }
    public String getFilterValue
    {
        get { return _filterValue; }
        set { _filterValue = value; }
    } 

Let me tell you at the start that the above code can be cut short by using short hand notation but I like manually typing everything. If you are a beginner, it helps a lot. Anyways what it does is create a Filter class of name-value pairs and another FilterList class which inherits from a list of Filter type objects.

    private DataTable list(String dbObject, String filterName, String filterValue)
    {
        NameValuePairList objNameValuePairList = new NameValuePairList();
        objNameValuePairList.Add(new NameValuePair("@FilterValue", filterValue));
        objNameValuePairList.Add(new NameValuePair("@Action", "FilterBy" + filterName));
        DataTable dt = objDB.getDataTable(dbObject, objNameValuePairList);
        return dt;
    }

As we can see, the method defined above fetches filtered data according to one filter at a time. The idea is to generalise this with respect to N number of filters. Here we go.

public DataTable list(String dbOject, FilterList myFilterList)
    {   
        // gets a collection(dataset) of all unique filters(datatables) and also group all 
        //   subfilters(rows) under each filter
        DataTable dt;
        DataSet ds = new DataSet();             
        // a filter may be a brand or a ram 
        foreach (Filter item in myFilterList)   
        // a subfilter may be hp or dell under the filter brand 
        {                                      
            // another subfilter may be 2gb or 4gb under the filter ram
            dt = list(dbOject, item.getFilterName, item.getFilterValue);
            dt.TableName = item.getFilterName;  
            // datatables are named based on the filters
            if (ds.Tables.Count == 0)
            // so we get a collection of unique filters (datatables) in the dataset
                ds.Tables.Add(dt);
            // add new filter without checking
            // since for the first time, no conflicts are possible
            else
            {
                bool tableMatchFound = false;
                foreach (DataTable newdt in ds.Tables)
                    if (newdt.TableName == dt.TableName)
                    {   
                        // see if filter is already present in the dataset
                        tableMatchFound = true; 
                        // when the current filter is already present in the dataset
                        foreach (DataRow dr in dt.Rows)
                            ds.Tables[newdt.TableName].ImportRow(dr);
                    } 
                // importrow() adds distinct new subfilters to the existing filter
                //duplicate items are not added
                if (!tableMatchFound)
                    ds.Tables.Add(dt);
            }       
            // if the filter does not exist, add the new filter to the collection
        }
        // the entire collection of filters will contain duplicate items
        // distinct items from the entire collection is filtered out in the next section
        dt = ds.Tables[0].Clone(); 
        // get the structure of the first filter as they all apply to the same table object  
        if (ds.Tables.Count == 1)
            dt = ds.Tables[0];  
            // if there is only one filter, no filtering is required
        else 
            // if there are more than one
            //compare each subfilter of every other filter with the subfilters of the first filter
            foreach (DataRow dr in ds.Tables[0].Rows)
            {   
                // each subfilter from the first filter is used as a pivot
                int rowMatchFound = 1;
                for (int i = 1; i < ds.Tables.Count; i++)   
                    // search all filters except the first one
                    foreach (DataRow newdr in ds.Tables[i].Rows) 
                        // select each subfilter from all the filter
                        if ((int)dr["ProductID"] == (int)newdr["ProductID"])
                            rowMatchFound++;
                if (rowMatchFound == ds.Tables.Count)   
                    // a match is found exactly once in all the filters
                    dt.ImportRow(dr);     
                // the final item is selected so that is is present in all the filters   
            }
        return dt;
    }

Now all we need to do is call the list method from the application layer. 

    private FilterList myFilterList = new FilterList();
    LaptopBL objLaptopBL = new LaptopBL();
    protected void Page_Load(object sender, EventArgs e)
    {
        //call addnewfilter(newFilterName, newFilterControl)
        //in case of new filter / prouduct category
        addNewFilter("Brand", ckbBrand);
        addNewFilter("Processor", ckbProcessor);
        addNewFilter("Ram", ckbRam);
        addNewFilter("ScreenSize", ckbScreenSize);
        addNewFilter("HardDrive", ckbHardDrive);
        if (myFilterList.Count == 0)
            gvLaptopList.DataSource = objLaptopBL.list();
        else
            gvLaptopList.DataSource = objLaptopBL.list(myFilterList);
        bindGridView();
    }

So, in the application layer we create a FilterList object and keep on adding the filters by calling the addNewFilter method described below.

    private void addNewFilter(String filterName, ListControl lc)
    {
        foreach (ListItem item in lc.Items)
            if (item.Selected)
                myFilterList.Add(new Filter(filterName, item.Text));
    }

Points of Interest

The ImportRow() method saved me a lot of trouble and processing time by ignoring duplicates. In absence of that, I would have to manually check for duplicates by writing a few more lines of code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Abhijit Ghosh (Subho)
Software Developer (Junior) ICRA Online Limited
India India
About Me:
I am a junior software developer who is still learning how to write code that won't give others a headache. I love biking and travelling. I am also a big foodie and a cinephile.

Coding Style:
I am not a ninja programmer and I don't want to be one. I am the slowest developer you may ever encounter. I take a lot of time to develop code and I spend even more time reviewing it. My codes are always full of comments and summaries. Sometimes there are more comments than actual lines of code.

Pet Peeve:
A great software with zero or very little documentation. That is simply a great idea wasted, not to mention the time and labor of the dev team. Normally I wouldn't care unless I have to maintain it.

Liberal Views:
Free sharing of knowledge by the developer community has always amazed me. I do what I can to contribute. I believe that the 'end-user' rules. because his disturbingly feeble-minded needs drive me to build something better.

Egoistic Views:
I take pride in what I develop and the way I do it but I still bow down to all who can provide a better solution.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
GeneralNice one. Pin
mazharkhan12330-Oct-15 0:38
membermazharkhan12330-Oct-15 0:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160826.1 | Last Updated 21 Jul 2014
Article Copyright 2014 by Abhijit Ghosh (Subho)
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid