Make "Check All" to include only displayed list options in Excel-like filtering

6 posts, 0 answers
  1. Attila Antal
    Admin
    Attila Antal avatar
    425 posts

    Posted 27 Dec 2017 Link to this post

    Requirements

    Telerik Product and Version

    3.5+

    Supported Browsers and Platforms

    All browsers supported by Telerik UI for ASP .NET AJAX

    Components/Widgets used (JS frameworks, etc.)

    JavaScript

    PROJECT DESCRIPTION 

    Clicking on "Check All" checkbox will check only displayed items when CheckList Filtering is enabled.



     

    C# - In the code behind, using the PreRender event of the Page, bind two event handlers to RadListBox holding the checkbox items.

    protected void Page_PreRender(object sender, EventArgs e)
    {
        var lb = RadGrid1.FindControl("filterCheckList") as RadListBox;
        lb.OnClientLoad = "listBoxLoad";
        lb.OnClientCheckAllChecked = "CheckAllChecked";
    }

     

      UPDATED  

    In order to improve performance of this functionality, we have overridden few internal functions, so to use jQuery to do the job instead of accessing and iterating through Object classes.

    With this change, now selection works almost instantly with even 2-3 thousand records. An Updated version of the project is attached.

     

    JavaScript - Client side code to manipulate the checklist items.

    function listBoxLoad(sender, args) {
        // reference to the ListBox
        var listBoxElement = $telerik.$(sender.get_element());
        //listBoxElement.find('input[id$="filterCheckListSearch"]').on('keyup', function () {
        //    debugger;
        //});
        // reference to the List Group
        var listBoxGroup = listBoxElement.find('.rlbGroup');
        // create a custom Check All Checkbox and add it to the list group
        listBoxGroup.prepend('<div class="myChekAllCheckBox" style="display: none;"><label><input type="checkbox" class="customCheckAll">Check All</label></div>');
    
        // wire up the change event to the Custom Check All Checkbox.
        // when changed, execute the script inside the function
        listBoxGroup.find('.customCheckAll').change(function (e) {
            // find only the visible listbox items (li)
            var lbItems = listBoxGroup.find('.rlbList li:visible');
            // find the checkboxes within the visible listbox items and check/uncheck them based on the Check All Checkbox state
            lbItems.find('input').prop('checked', this.checked);
    
            if (this.checked) {
                // uncheck other items
                listBoxGroup.find('.rlbList li:hidden input').prop('checked', false);
            }
        });
    
        // attach the following two events - For the Show Loading indicator functionality
        sender.add_itemsRequested(hideLbLoadingSign);
        sender.add_itemsRequestFailed(hideLbLoadingSign);
    }
    
    function hideLbLoadingSign(sender, args) {
        //remove the custom loading sign class
        $telerik.$(sender.get_element()).find(".rlbGroup").first().removeClass("lbLoadingSign");
        // show the check all checkbox
        $telerik.$(sender.get_element()).find(".rlbGroup .myChekAllCheckBox").first().show();
    }
    
    function showLoadingSign(sender, args) {
        //add the custom loading sign class
        $telerik.$(sender.get_childListElement()).find(".rlbGroup").first().addClass("lbLoadingSign");
    }
    
    function OnClientHidden(sender, args) {
        // Optional: hide the Check All checkbox when the menu closed
    
        // selector to select all occurences of .RadMenu.myCheckAllCheckBox on the page
        $telerik.$(".RadMenu .rgHCMFilter .RadListBox .myChekAllCheckBox").hide();
    
        // get reference to RadGrid this ContextMenu belongs.
        var currentRadGrid = sender.get_parent();
    
        // selector to select only the myCheckAllCheckBox that belongs to the current RadGrid firing opening this ContextMenu.
        $telerik.$(".RadMenu[id^='" + currentRadGrid.ClientID + "'] .rgHCMFilter .RadListBox .myChekAllCheckBox").hide();
    }
    // handle filtering
    Telerik.Web.UI.Grid.FilterSearch = function (grid, input) {
        var ListBox = $telerik.$('#' + grid._filterCheckListClientID)
        // hide all as soon as start searching
        ListBox.find('.rlbList li').hide();
        // show only search results
    
    
        var visibleItems = ListBox.find('.rlbList li').filter(function (index) {
            return $(this).text().toLowerCase().indexOf(input.value.toLowerCase()) > -1;
            //return index % 3 === 2;
        })
    
        visibleItems.show();
    
    
        //var visibleItems = ListBox.find('.rlbList li:contains(' + input.value.toLowerCase() + ')').show();
    
        var visibleItemsCount = visibleItems.length;
        var checkedItemsCount = visibleItems.find('input[type="checkbox"]:checked').length;
        // Check/Uncheck the CheckAll checkbox whether all visible items on the list are checked
        ListBox.find('.myChekAllCheckBox input[type="checkbox"]').prop('checked', visibleItemsCount > 0 && visibleItemsCount == checkedItemsCount);
    }
    
    // handle per item check
    // store a reference to the original method
    var original = Telerik.Web.UI.RadListBox.prototype._onCheck;
    // override the original method
    Telerik.Web.UI.RadListBox.prototype._onCheck = function (e, targetItem) {
        var ListBox = $(this.get_element());
    
        // condition check if ListBox is part of the HeaderContextFilterMenu of RadGrid
        if (ListBox.closest('.rgHCMFilter').length < 1) {
            // if not, call the original method stored above
            original.call(this, e, targetItem);
            // exit the function to prevent the excution of further logic
            return;
        }
    
        // logic below will only be executed if the ListBox is part of the HeaderContextFilterMenu of RadGrid.
    
        var visibleItems = ListBox.find('.rlbList li:visible');
    
        var visibleItemsCount = visibleItems.length;
        var checkedItemsCount = visibleItems.find('input[type="checkbox"]:checked').length;
    
        // Check/Uncheck the CheckAll checkbox whether all visible items on the list are checked
        ListBox.find('.myChekAllCheckBox input[type="checkbox"]').prop('checked', visibleItemsCount > 0 && visibleItemsCount == checkedItemsCount);
    }

     

    Note: This sample project is a custom solution that is beyond the RadGrid's built-in functionality. This was intended to be a demonstration for customizing the RadGrid control. Further customization or improvement of this example will not be guaranteed.

  2. merikmgrasp
    merikmgrasp avatar
    4 posts
    Member since:
    May 2007

    Posted 30 Mar 2018 in reply to Attila Antal Link to this post

    Thanks for this answer.  It does in fact work but unfortunately in our case, it is way too slow to be usable.

    Our databases are quite large and thus the filter list is large too.  In my example I have 1300 items in the filter checkbox list.

    In the provided sample method:

                function CheckAllChecked(sender, args) {
                    var items = sender.get_items();
                    for (var i = 0; i < items.get_count() ; i++) {
                        var item = items.getItem(i);
                        item.set_checked(!checkedState && item.get_visible());
                    }
                    sender.get_checkAllCheckBox().checked = !checkedState;
                }

    The item.set_checked() method called 1300 times in the for loop is taking between 30 and 40 seconds which makes it impracticable for clients to use.

    In the loop I tried bypassing the set_checked() method and instead getting the checkbox element and setting its checked property instead.  The loop then took less than a second to run, but when the grid's NeedDataSource method is called, the grid's filter expression (myGrid.MasterTableView.FilterExpression) is blank.

    I tried another workaround where in the loop I stored the checked item values in a string which I then store into session in an ajax call.  Then in the NeedDataSource method I built the filter expression for those checked items manually.  The problem then was that I needed to store the checked items for each column as multiple columns can be filtered at the same time.  Then there was also a problem with view state and the checked items.

    For now, I have just hidden the "Check All" checkbox in the filter dropdown using the CSS style:

            .rlbCheckAllItems { display: none; }

    If you have any other suggestions as to how to speed up that item.set_checked() method I'd love to hear them. Otherwise I will continue to simply hide the Check All option for now.

    Thanks

    Courtney

     

     

  3. Attila Antal
    Admin
    Attila Antal avatar
    425 posts

    Posted 07 Oct Link to this post

    Hi Courtney,

    Since this improvement has been requested by multiple developer, I've took some time and made few changes to the sample. Content and attachment have been updated accordingly.

    Please note that the sample project is custom and did not go under intensive testing and therefore we cannot guarantee its proper functioning in all scenarios.

    Kind regards,
    Attila Antal
    Progress Telerik

    Get quickly onboarded and successful with your Telerik and/or Kendo UI products with the Virtual Classroom free technical training, available to all active customers. Learn More.
  4. Larry
    Larry avatar
    1 posts
    Member since:
    Aug 2016

    Posted 10 Oct in reply to Attila Antal Link to this post

    Please help me out so I can implement your code. Your code has one, perhaps two, hard-coded names that don't allow me to use it. 

    First, the JS has the name "RadGrid1" hard-coded in the method :

                function OnClientHidden(sender, args) { ...}

    I need to implement this is about a dozen different grids; each with its own name. Can you change this code to derive the name of the grid from the input arguments (sender, args)?

     

    Second, your example doesn't explicitly declare the checkboxes; I assume you are using some sort of default names. However, my code explicitly declares them as the following template. Please tell me what changes need to made to the JS code in this scenario.

    <Columns>
    <telerik:GridTemplateColumn   AllowFiltering="false" EnableHeaderContextMenu="false" HeaderStyle-Width="40px" ItemStyle-Width="40px" UniqueName="CheckBoxTemplateColumn">
    <ItemTemplate>
    <asp:CheckBox ID="CheckBoxProduct" runat="server"  />
    </ItemTemplate>
    <HeaderTemplate>
    <asp:CheckBox ID="CheckBoxAllProducts"  runat="server"   />
    </HeaderTemplate>
    </telerik:GridTemplateColumn>

    Thanks

    Larry

     

  5. Attila Antal
    Admin
    Attila Antal avatar
    425 posts

    Posted 10 Oct Link to this post

    Hi Larry,

    I've changed the code to target the current grid instead of having a hard-coded name.

  6. Attila Antal
    Admin
    Attila Antal avatar
    425 posts

    Posted 05 Nov Link to this post

    I have changed the filtering technique to use the jQuery.filter() method instead of :contains() Selector, thus the filtering will now match any keyword at any position regardless of the character case.

Back to Top