Page 1 of 1

Asp.net/JQuery GridView Multi-Selection Snippet

Posted: Tue Aug 11, 2015 11:18 am
by Arce
Here we are concerned with the behaviours of an <asp:GridView> as it relates to selection. This widget by default supports a lightweight selection system...but many people choose not to use it because of the way it looks. What it does is add an extra column to your grid with a hyperlink that says "select" for each row. This is a pretty gritty way to handle things; most users wish to allow for a selection click in any column, not just over a hyperlink. I will detail two different solutions. One in asp.net code-behind, and one in JQuery. The Asp.Net solution is preferable if your GridView has a manageable amount of rows (50-200) or paging; the JQuery solution has better performance on the client side and can manage thousands of records without slowdown.

For the asp solution, the first thing you are going to want to do is enable the OnSelectedIndexChanged event in your grid view's markup. This assures we can assign our event listener properly.

Next, add the following method for your OnRowDataBound event:

Code: Select all

Protected Sub OnRowDataBound(sender As Object, e As GridViewRowEventArgs)
    If e.Row.RowType = DataControlRowType.DataRow Then
        e.Row.Attributes("onclick") = Page.ClientScript.GetPostBackClientHyperlink(GridView1, "Select$" & e.Row.RowIndex)
        e.Row.ToolTip = "Click to select this row."
    End If
End Sub
This code causes the OnSelectedIndexChanged postback event to be fired on each row's "onClick" event. The fact that it uses postbacks instead of everything on client side is why performance becomes an issue with many records.

Next, we are going to use a HiddenField as comma delimiter-ed buffer holding each of our selected rows. Go ahead and add

Code: Select all

<asp:HiddenField id="SelectionHolder" runat="server" />
somewhere in your markup.

Finally, add the following method for your OnSelectedIndexChanged listener:

Code: Select all

requires: gridview has OnSelectedIndexChanged event enabled
    highlights selected row(s) from click/shift-click
    Sub ProjectGridView_SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
        Dim ctrlHeld As Boolean = If(Control.ModifierKeys = Keys.Control, True, False)
        Dim shiftHeld As Boolean = If(Control.ModifierKeys = Keys.Shift, True, False)
        Dim a As String() = Split(selectionHolder.Value.ToString, ",")

        If Not selectionHolder.Value = "" And Not ctrlHeld And Not shiftHeld Then
            Dim count As Integer = 0
            For Each Str As String In a
                If Not String.IsNullOrEmpty(Str) And Not count = 0 Then
                    'Str = Str.Remove(0, 1)
                    Dim num As Integer = Integer.Parse(Str)
                    If Not ctrlHeld Then
                        gvList.Rows(num).BackColor = If(num Mod 2 = 0, ColorTranslator.FromHtml("#FFFFFF"), ColorTranslator.FromHtml("#FFFFd1"))
                        gvList.Rows(num).ToolTip = "Click to select this row."
                    End If
                End If
                count += 1
            Next
            If Not ctrlHeld Then
                selectionHolder.Value = ""
            End If
        End If

        If (shiftHeld) And a.Count > 0 Then
            Dim lastNum As Integer = Integer.Parse(a.Last)
            If (lastNum > gvList.SelectedIndex) Then
                For n As Integer = gvList.SelectedIndex To lastNum - 1
                    gvList.Rows(n).BackColor = ColorTranslator.FromHtml("#A1DCF2")
                    gvList.Rows(n).ToolTip = String.Empty
                    selectionHolder.Value += "," + n.ToString
                Next
            End If
            If (lastNum < gvList.SelectedIndex) Then
                For n As Integer = lastNum To gvList.SelectedIndex
                    gvList.Rows(n).BackColor = ColorTranslator.FromHtml("#A1DCF2")
                    gvList.Rows(n).ToolTip = String.Empty
                    selectionHolder.Value += "," + n.ToString
                Next
            End If
        End If

        selectionHolder.Value += "," + gvList.SelectedIndex.ToString
        gvList.Rows(gvList.SelectedIndex).BackColor = ColorTranslator.FromHtml("#A1DCF2")
        gvList.Rows(gvList.SelectedIndex).ToolTip = String.Empty
    End Sub
and you are good! A suggestion would be to change the FromHtml BackColor to a Css class representing your selection.

Finally, here is the JQuery solution for the same thing. With this solution, you no longer need the OnSelectedIndexChanged event listener so remove it from your markup. Same with the OnRowDataBound. Instead, you want to assure your GridView has the ViewStateMode="Enabled" attribute set.

Next, you are going to want to include in your markup the appropriate references to JQuery if you don't have it already. Something like this in your header content:

Code: Select all

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
Next lets add a CSS class to represent our selected row. Something like this:

Code: Select all

<style type="text/css">
        .row_selected{
            background-color: #A1DCF2;
        }
    </style>
Finally, add the following JavaScript code:

Code: Select all

<script type = "text/javascript">

 //used for shiftselecting
        var shiftPressed = false;
        var previousRow = ""

        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {
            $(document).ready(function () {
                $(window).keydown(function (evt) {
               
                    //alert('Ctrl downX1');
                    if (evt.which == 16) { // ctrl
                        shiftPressed = true;
                        //alert('Ctrl downX');
                    }
                }).keyup(function (evt) {
                    if (evt.which == 16) { // ctrl
                        shiftPressed = false;
                        //alert('Ctrl downX2');
                    }
                });
            });
        });

        //compound selection function for gridview rows
        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {
            $(document).ready(function () {

                $("[id*=gvList] td").click(function () {
                    //is event already handled?
                    var str = $(event.target)[0].id;
                    if (str.indexOf("cbImport") >= 0) {
                        return;
                    }
                    var index = $(this).closest('td').parent()[0].sectionRowIndex;
                    if (previousRow.toString().length == 0) {

                        //alert(index)
                        if (jQuery(this).closest('tr').find('[type=checkbox]').is(":checked")) {
                            jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', false);
                            //add the selected class to selected row
                            $($(this).closest("tr")).removeClass("row_selected");
                            previousRow = "";
                        } else {
                            jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', true);
                            //remove the selected class from selected row
                            $($(this).closest("tr")).addClass("row_selected");
                            previousRow = index.toString();
                        }
                    } else {
                        if (shiftPressed) {
                            var start = (parseInt(previousRow.toString()) < index) ? parseInt(previousRow.toString()) : index;
                            var end = (parseInt(previousRow.toString()) < index) ? index : parseInt(previousRow.toString());

                            var count = 0
                            $("[id*=gvList] tr:has(td)").each(function () {
                                if (count >= start && count <= end) {
                                    if (jQuery(this).closest('tr').find('[type=checkbox]').is(":checked") && count != parseInt(previousRow.toString())) {
                                        jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', false);
                                        //add the selected class to selected row
                                        $($(this).closest("tr")).removeClass("row_selected");
                                    } else if (count != parseInt(previousRow.toString())) {
                                        jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', true);
                                        //remove the selected class from selected row
                                        $($(this).closest("tr")).addClass("row_selected");
                                    }
                                }
                                count++;
                            });


                        } else {
                            if (jQuery(this).closest('tr').find('[type=checkbox]').is(":checked")) {
                                jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', false);
                                //add the selected class to selected row
                                $($(this).closest("tr")).removeClass("row_selected");
                                previousRow = "";
                            } else {
                                jQuery(this).closest('tr').find('[type=checkbox]').prop('checked', true);
                                //remove the selected class from selected row
                                $($(this).closest("tr")).addClass("row_selected");
                                previousRow = index.toString();
                            }
                        }
                    }
                });
            });
        });

        //select all via header checkbox
        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {
            $(document).ready(function () {
                //Checkbox header select all settings
                $('.chkSelectAll').click(function () {
                    if ($(this).is(":checked")) {
                        $('.chkSelect').prop('checked', true);
                        //add the selected class from each row
                        $('.chkSelect').closest("tr").addClass("row_selected");

                    } else {
                        $('.chkSelect').prop('checked', false);
                        //remove selected class from each row
                        $('.chkSelect').closest("tr").removeClass("row_selected");
                    }
                    previousRow = "";
                });
            });
        });

        //selection via checkboxes
        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {
            $(document).ready(function () {
                //Checkbox gridview row select settings
                $('.chkSelect').click(function () {
                    //alert("Message");
                    //remove chkSelectAll checked when any of the child checkbox clicked
                    $('.chkSelectAll').removeAttr("checked");

                    if ($(this).is(":checked")) {
                        //add the selected class to selected row
                        $($(this).closest("tr")).addClass("row_selected");
                        previousRow = $(this).closest('td').parent()[0].sectionRowIndex.toString();
                        //alert(previousRow.toString());
                    } else {
                        //remove the selected class from selected row
                        $($(this).closest("tr")).removeClass("row_selected");
                        previousRow = "";
                        //alert(previousRow.toString());
                    }
                });
            });
        });
    </script>
Please note that you can remove the checkboxes in your application. They are redundant, but give the user an extra layer of control over their selection. If you want to have them, you need to make a column in your gridview have the checkboxes, something like this:
<Columns>

Code: Select all

<asp:TemplateField>
                                <HeaderTemplate>
                                    <input type="checkbox" id="chkSelectAll" class="chkSelectAll" style="width: 50px;" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <input type="checkbox" class="chkSelect" id="cbImport" style="width: 50px;" runat="server"/>
                                </ItemTemplate>
                            </asp:TemplateField>
                            <%--YOUR OTHER COLUMN DATA HERE--%>
</Columns>
And that's it! Pretty simple, hope this is able to help save somebody some time. Thanks for reading

Re: Asp.net/JQuery GridView Multi-Selection Snippet

Posted: Fri Aug 14, 2015 2:34 pm
by Arce
Here is additional code that can be used to disable the default browsers text selection when shift is held.

Code: Select all

        //disable default shift-select of html text
        Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(function (evt, args) {
            $(document).ready(function () {
                    document.onselectstart = function () {
                        return false;
                    }
            });
        });
and in your code where the class assignments occur to designate selected columns, add this line:

Code: Select all

                                        document.getSelection().removeAllRanges();
Does anybody want a JSFiddle example?

Best regards!