Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Highlight GridView rows in JavaScript and CSS

0.00/5 (No votes)
6 Oct 2015 1  
Use JavaScript to manage control layouts, formatting and highlights, without the need for Postback which is not so clean.

Introduction

There are dozens and dozens of examples and articles about how to highlight (background-colour) a row in a GridView or any other table related HTML control.

What's different about this one? For starters the example and code I present below is pure JavaScript (not jQuery as that's another level of expertise!), the code is dynamic (can be used regardless of the ID of your controls), no hard-coding of control IDs, the JavaScript is in a Master file (meaning any auxillary ASPX pages can re-use the same JS code without duplicating it across individual pages), and finally I use the power of CSS (style sheets) to control background colouring without losing previous background effects (like a layer of CSS over another CSS).

Not a single line of code-behind or C#/VB was used to deliver the outcomes of this article. Furthermore, it's a great starting point to those learning JavaScript and to learn the power of client-side functionality.

Background

For days I've racked my brain on how to avoid PostBacks (the dreaded Flasher!). As soon as you touch anything, be it a checkbox, a textbox or a link, the screen flashes as calls are passed and made back on the server., There has to be a better way!

There is.

It's simply JavaScript or client-side programming. The more you can imlement on the client side without the need to do postbacks, the better. You reduce the risk of data "defaults", where data on your form resets itself to the first original load, and you reduce the risk of having to control event flows on the server; as all events are "flat" inside the client's browser. The code runs instantly and directly within your website. No need to debug!

Learning some JavaScript at least, is a must for any web designer in my view.

Enjoy!

Using the code

So lets' get started.

The first thing of course is your common GridView control. I won't go into the details of how you create it at design time or how you load it with data, etc. I'll let you manage the <datasource>, or any other properties associated with a GridView.

Simply said, here we have our simple GridView, along with a nested GridView control which in this example is related to the parents of the students mentioned in the main GridView - gvStudents.

<asp:GridView ID="gvStudents" runat="server" CssClass="gvCSS" DataSourceID="SqlDataSourceStudents" AutoGenerateColumns="False" Width="87%"
    OnRowDataBound="gvStudents_RowDataBound" OnDataBound="gvStudents_DataBound">
    <HeaderStyle BackColor="#5D7B9D" ForeColor="White" />
    <Columns>
        <asp:TemplateField>
            <HeaderTemplate>
                <asp:CheckBox id="CheckBoxAll" runat="server" onClick="gridViewCheckAll(this)" Text="Student" TextAlign="Left"/>
            </HeaderTemplate>
            <ItemTemplate>
                <asp:CheckBox ID="CheckBoxAdd" runat="server" onClick="highlightRow(this)" Visible='<%# Eval("StudentMobilePhone").ToString().Length > 0 %>'/>
            </ItemTemplate>
            <HeaderStyle Wrap="False" Width="200px" HorizontalAlign="Center" />
            <ItemStyle HorizontalAlign="Center"  Width="200px" Wrap="False" />
        </asp:TemplateField>
...
...
    </Columns>
</asp:GridView>

The main points to note here are:

  • the onClick() event calls on the CheckBoxes and their function names (they will be the same for the nested GridView),
  • the CssClass property on each gridview (these won't be the same!),
  • that there is no AlternateRow or Row formatting specified, even though there is, but you will see this in the CSS style sheet references below. The HeaderStyle is purely for the header line only; and,
  • And for further "user friendliness", I've implemented a dynamic Visible property to decide whether to show a checkbox depending on whether the student has a mobile phone number or not. Whether it's a valid number, that's outside the scope of this article.

The nested GridView is again similar and embedded inside a <TemplateField> or column.

<asp:TemplateField>
    <HeaderTemplate>
        <asp:CheckBox ID="cbNOKall" runat="server" Text="Next Of Kin" TextAlign="Left" onClick="gridViewCheckAll(this)"/>
    </HeaderTemplate>
    <ItemTemplate>
        <asp:GridView ID="gvNOKs" runat="server" CssClass="gvCSSsub" AutoGenerateColumns="False" BorderStyle="None" GridLines="None" ShowHeader="false" ShowFooter="false" BackColor="transparent" >
            <Columns>
                <asp:TemplateField HeaderText="Given Name" >
                    <ItemTemplate>
                        <asp:Label ID="lblNOKGivenName" runat="server" Text='<%# Eval("NOKname") %>'></asp:Label>
                    </ItemTemplate>
                    <ControlStyle Width="150px" />
                </asp:TemplateField>
                <asp:TemplateField HeaderText="NoK Type" >
                    <ItemTemplate>
                        <asp:Label ID="lblNOKType" runat="server" Text='<%# Eval("NOKType") %>'>

The main points to note here are:

  • the onClick() event calls on the CheckBoxes and their function names are the same as gvStudents.
  • the CssClass property has a different name; and,
  • again there is no AlternateRow or Row formatting specified.

The nested GridView's CSSClass name is different so that we can highlight its rows Green and not Yellow. Also note the Transparent background? That's so you can see a thin yellow background if the student is selected behind the NOK GridView.

Ok. So far we have a simple Gridview with a nested-Gridview on the NoK's column (Next of Kin) with it's own master and row based CheckBoxes.

So as you've possibly gathered what we're trying to do here is provide data to the user that they can select using CheckBoxes, either on individual rows or all rows (look at the HeaderTemplates).

Show me the JavaScript already!

As mentioned, the other aspect of this article is to have the JavaScript code being modular or held in only one place so that other ASPx pages can use it. Oddly I don't see many JS programmers doing this, and slapping duplicate code all over the place!? Why? Didn't you guys get taught 3G programming at school? ;-)

So without further a-due, place the following code in a Master page (if you need to know what Master pages are - click here), either inside the <body> and <form> tags or in a <ContentPlaceHolder> tag. Do not place it inside the <head> element, as it won't be picked up by the sub programs! If you do not have a Master page, place it inside your ASPx page inside a <div>, <form> or <ContentPlaceHolder> tag.

Now before you look at the script, note there is a lot more here than technically necessary. However, I've included some shims to allow the JavaScript to work on all browsers, including older IE versions from 7 onwards. Just copy them as is. I won't explain how they work here, but you can Google ECMA262-5 for example and find the official specifications.

The real JS code in relation to this article, is below the commented "hash-line".

<script type="text/javascript" >
    // Add ECMA262-5 method binding if not supported natively
    //
    if (!('bind' in Function.prototype)) {
        Function.prototype.bind= function(owner) {
            var that= this;
            if (arguments.length<=1) {
                return function() {
                    return that.apply(owner, arguments);
                };
            } else {
                var args= Array.prototype.slice.call(arguments, 1);
                return function() {
                    return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
                };
            }
        };
    }

    // Add ECMA262-5 string trim if not supported natively
    //
    if (!('trim' in String.prototype)) {
        String.prototype.trim= function() {
            return this.replace(/^\s+/, '').replace(/\s+$/, '');
        };
    }

    // Add ECMA262-5 Array methods if not supported natively
    //
    if (!('indexOf' in Array.prototype)) {
        Array.prototype.indexOf= function(find, i /*opt*/) {
            if (i===undefined) i= 0;
            if (i<0) i+= this.length;
            if (i<0) i= 0;
            for (var n= this.length; i<n; i++)
                if (i in this && this[i]===find)
                    return i;
            return -1;
        };
    }
    if (!('lastIndexOf' in Array.prototype)) {
        Array.prototype.lastIndexOf= function(find, i /*opt*/) {
            if (i===undefined) i= this.length-1;
            if (i<0) i+= this.length;
            if (i>this.length-1) i= this.length-1;
            for (i++; i-->0;) /* i++ because from-argument is sadly inclusive */
                if (i in this && this[i]===find)
                    return i;
            return -1;
        };
    }
    if (!('forEach' in Array.prototype)) {
        Array.prototype.forEach= function(action, that /*opt*/) {
            for (var i= 0, n= this.length; i<n; i++)
                if (i in this)
                    action.call(that, this[i], i, this);
        };
    }
    if (!('map' in Array.prototype)) {
        Array.prototype.map= function(mapper, that /*opt*/) {
            var other= new Array(this.length);
            for (var i= 0, n= this.length; i<n; i++)
                if (i in this)
                    other[i]= mapper.call(that, this[i], i, this);
            return other;
        };
    }
    if (!('filter' in Array.prototype)) {
        Array.prototype.filter= function(filter, that /*opt*/) {
            var other= [], v;
            for (var i=0, n= this.length; i<n; i++)
                if (i in this && filter.call(that, v= this[i], i, this))
                    other.push(v);
            return other;
        };
    }
    if (!('every' in Array.prototype)) {
        Array.prototype.every= function(tester, that /*opt*/) {
            for (var i= 0, n= this.length; i<n; i++)
                if (i in this && !tester.call(that, this[i], i, this))
                    return false;
            return true;
        };
    }
    if (!('some' in Array.prototype)) {
        Array.prototype.some= function(tester, that /*opt*/) {
            for (var i= 0, n= this.length; i<n; i++)
                if (i in this && tester.call(that, this[i], i, this))
                    return true;
            return false;
        };
    }

    function hasClass(ele,cls) {
      return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
    }

    function addClass(ele,cls) {
      if (!hasClass(ele,cls)) ele.className += " "+cls;
    }

    function removeClass(ele,cls) {
      if (hasClass(ele,cls)) {
        var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
        ele.className=ele.className.replace(reg,' ');
      }
    }

    //################################################################################################################
    function gridViewCheckAll(chk) {
      var cell = chk.parentNode;
      var row = cell.parentNode;
      var tbody = row.parentNode;
      var rows = tbody.children;

      var index = [].indexOf.call(row.children, cell);

      for (var i = 1; i < rows.length; i++) {
        var checkBoxes = rows[i].children[index].querySelectorAll('input[type=checkbox]');

        for (var j = 0; j < checkBoxes.length; j++) {
            checkBoxes[j].checked = chk.checked;
            highlightRow(checkBoxes[j]);
        }
      }
    }

    function highlightRow(chk) {
      var row = chk.parentNode.parentNode;  //go to the <td> of the checkbox and then again to the <tr> of the <td>, which is the row
      if (chk.checked)
        addClass(row, 'selected-row');
      else
        removeClass(row, 'selected-row');
    }
</script>

 

The final ingredient is the CSS StyleSheet itself.

<style type="text/css">
    .arial { font-family: Arial; font-size: small; }
    .table { font-family: Arial; font-size: small; background-color: #eeeeee; margin-right: 10px; display: inline; vertical-align: top; }
    .header { text-align: center; background-color: #5D7B9D; color: White; font-weight: bold; font-size: medium; }

    table.gvCSS {
      margin-top: 50px;
      font: 12px Verdana;
    }

    table.gvCSS > tbody > tr {
      background-color: white;
    }

    table.gvCSS > tbody > tr:nth-of-type(odd) {
      background-color: #EEE;
    }

    table.gvCSS > tbody > tr:first-of-type {
      background-color: #5D7B9D;
      color: white;
    }

    table.gvCSS > tbody > tr.selected-row {
      background-color: yellow;
    }

    table.gvCSSsub > tbody > tr.selected-row {
      background-color: lightgreen;
    }

    table.gvCSS tr td {
      padding: 4px 15px;
    }

    /* ******** */

    #gvCSS > tbody > tr > td:nth-of-type(1) {
      width: 100px;
    }

    #gvCSS > tbody > tr > td:nth-of-type(2) {
      width: 70px;
    }

    #gvCSS table > tbody > tr > td:nth-of-type(1) {
      width: 120px;
    }

    #gvCSS table > tbody > tr > td:nth-of-type(2) {
      width: 100px;
    }

    #gvCSS table > tbody > tr > td:nth-of-type(3) {
      width: 100px;
    }
</style>

You will note in the CSS, there is nth-of-type(odd) and nth-of-type references to the <td> tags?

That is what sets the GridView's AlternateRow background colours! Ahhh I hear you say!

So the beauty with the above JavaScript, is that once you've selected a row, the row is highlighted Yellow (or Green for the nested GridView rows), and if you un-tick a checkbox the original AlternateRow background colour are restored. Nice and cleanly.

Points of Interest

In summary, we have "yet another how to highlight a row in a table row" article, but with some extra "user friendly" code. Most other examples have GridView's ID hardcoded into the JavaScript code, which I detest highly and it's generally bad programming practice. All control and web tag IDs can be collected and passed into a function, so why not use it!? Of course I could have saved the JS code in a .js file and simply referenced it on every ASPx page. Yes that's another way of doing it, especially if you don't have a Master page.

I hope you have found this useful, especially the JS script to overlay formats for older IE browsers.

History

Oct 2015: Version 1.0, 1.1 (re-wording and grammar corrections)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here