Search code examples
jqueryhtmlhtml-tablecellhighlight

Table Cell color highlight and un-highlight on select jQuery


I have designed a tabular calendar with bunch of TD / TR elements under table body.

I want a interaction on each day of table like when I click on one td element (which is one day) it will be highlighted with border and when I moved cursor and click other day this day will be highlighted and previous one will be un-highlighted . My code is like this, but the problem is the .off click function. It is not unhighlighting so all table cells become highlighted and persists. How could I fix this using jQuery?

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>

    $(document).ready(function(){

 $("td.PTDC").on("click",function(){  

  $(this).css("background-color", "#0093e0");
  $(this).css("padding", "5px");
  console.log("onclick");
    }); 

 $("tbody").off("click",function(){  
  $(this).css("background-color", "#ffffff");
  $(this).css("padding", "0px");
  console.log("offclick");
    }); 
});

    </script>

============================ I observed in source that Before click it has :

<td class="PTDC PTLC OOCT" id="db_saw_1883_7_1_6" style="border-style:none;border-right:solid 1px #DADADB;border-bottom:solid 1px #DADADB;">

And after click it has :

<td class="PTDC OOCT" id="db_saw_1883_7_1_5" style="border-style: none solid solid none; border-right-width: 1px; border-right-color: rgb(218, 218, 219); border-bottom-width: 1px; border-bottom-color: rgb(218, 218, 219); background-color: rgb(0, 147, 224); padding: 5px;">

But since all my 30 days in calendar is like one day each td elements it is difficult to de-associate the format when other td elements clicked.


Solution

  • Update 2

    OP is using a primitive application that class styles cannot override. I have deduced from various clues about Tools (OP is vague) it: = generates...HTML tables - it uses inline styles - if so then that would explain why styling with classes is incredibly difficult. - Inline styles (ex. <div style='color:blue'>) can't be overridden by rulesets in a stylesheet or even from a <style> block with !important being the exception. Demo 3 will demonstrate 2 ways to deal with inline style attributes.

    $('td').on('click', function(e) {
      var tgt = e.target;
      e.stopImmediatePropagation();
      $('td').each(function(idx, cell) {
        cell.style.backgroundColor = '#fff';
        cell.style.borderColor = '#000';
        if ($(cell).hasClass('today')) {
          cell.style.backgroundColor = 'rgba(0, 0, 255, 1)';
          cell.style.borderColor = '#aae1ff';
        }
      });
      tgt.style.backgroundColor = '#0093e0';
      tgt.style.borderColor = '#09e';
    });
    
    1. e.target is the <td> that the user has clicked.
    2. e.stopImmediatePropagation(); prevents the event from bubbling and being heard by any other listener as well.
    3. $('td').each(function(idx, cell) {... Every <td> will have a function ran on it.
    4. Each cell (i.e. <td>) will have their inline style attributes set to:

       cell.style.backgroundColor = '#fff';
      
       cell.style.borderColor = '#000';
      
    5. If this particular cell has the .today class, then:

      if ($(cell).hasClass('today')) {
        cell.style.backgroundColor = 'rgba(0, 0, 255, 1)';
        cell.style.borderColor = '#aae1ff';
      }
      
    6. When the for loop is complete, change e.target style:

       tgt.style.backgroundColor = '#0093e0';
      
       tgt.style.borderColor = '#09e';
      


    Update 1

    I misinterpreted the question: OP's desired behavior is that only one cell at a time can have the .lit class. It's an easy modification using .addClass(), .removeClass() and .not(). See Demo 2.

    /* Delegate the click event on all
    || td (cell).
    || Remove the .lit class on every <td>
    || and add .lit class for the clicked <td>
    */
    $('td').on('click', function() {
        var that = $(this);
      $('td').not(that).removeClass('lit');
        that.addClass('lit');
    });
    

    Problem

    "...but the problem is the .off click function. It is not unhighlighting so all table cells become highlighted and persists. How could I fix this using jQuery?"

    The behavior OP mentions is called toggling which is the ability to go back and forth between 2 "states" (e.g. state is in off and on, or light and dark, etc). In this case it is the toggling of 2 backgrounds.

    The .on() method is a function that adds an event listener on any given individual or group of elements (e.g. a $('td')).

    The .off() method is a function that removes an event listener off of any given individual or group of elements. .off() does not undo whatever .on() has done, .off() removes .on(). So every <td> clicked then lost the event listener registered to it.


    Solution

    1. Avoid using .css() method for styling a group of elements
    2. It's far more efficient to manipulate classes. In this demo .lit is the other state and the default state is <td> without class .lit
    3. .toggleClass() is the method used to do this.

    The primary function in the following demo addresses the issue explained by OP. As a bonus I have added the following features:

    • Highlights today's cell
    • Generates a number for each day of the month

    Details are commented in demo

    Demo 1 (Toggle Class)

    // Make a Date Object
    var d = new Date();
    // Get today's day as a number
    var today = d.getDate();
    
    /* Find the cell at the index number 
    || (which is eq -1) and add thr .today class
    */
    $('td').eq(today - 1).addClass('today');
    
    /* On each cell, add the day number, unless
    || the cell has class .empty
    */ // Note: the syntax of string on line 19
    // is ES6 Template Literal see post for ref.
    $('td').each(function(index, day) {
      if ($(this).hasClass('empty')) {
        return
      }
      $(this).append(`<b>${index+1}</b>`);
    });
    
    /* Delegate the click event on all
    || td (cell).
    || callback on each td is to 
    || toggle the .lit class
    */
    $('td').on('click', function() {
      $(this).toggleClass('lit');
    });
    .month {
      table-layout: fixed;
      width: 90%;
      min-height: 250px;
      border-spacing: 1px;
      border: 3px outset grey
    }
    
    caption {
      font-variant: small-caps
    }
    
    .month td {
      border: 2px inset black;
      background: #fff;
      cursor: pointer;
    }
    
    td.lit {
      background-color: #0093e0;
      border-color: #09e;
    }
    
    td.today {
      background: rgba(0, 0, 255, 1);
      border-color: #aae1ff;
    }
    
    td.today.lit {
      background: tomato;
      border-color: red
    }
    
    td b {
      font-size: .3em;
      color: #123;
      vertical-align: top;
      display: inline-block;
      margin: -7px 0 0 -5px;
    }
    
    td.today b {
      color: #fff
    }
    
    .empty {
      /* Prevents any mouse events 
    || i.e unclickable
    */
      pointer-events: none;
      cursor: default;
    }
    <table class='month'>
      <caption>October</caption>
      <thead>
        <tr>
          <th>SUN</th>
          <th>MON</th>
          <th>TUE</th>
          <th>WED</th>
          <th>THU</th>
          <th>FRI</th>
          <th>SAT</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td class='empty' colspan='4'>&nbsp;</td>
        </tr>
      </tbody>
    </table>
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

    Demo 2 (Exclusive Class)

    // Make a Date Object
    var d = new Date();
    // Get today's day as a number
    var today = d.getDate();
    
    /* Find the cell at the index number 
    || (which is eq -1) and add thr .today class
    */
    $('td').eq(today - 1).addClass('today');
    
    /* On each cell, add the day number, unless
    || the cell has class .empty
    */// Note: the syntax of string on line 19
    // is ES6 Template Literal see post for ref.
    $('td').each(function(index, day) {
      if ($(this).hasClass('empty')) {
        return
      }
      $(this).append(`<b>${index+1}</b>`);
    });
    
    /* Delegate the click event on all
    || td (cell).
    || Remove the .lit class on every <td>
    || and add .lit class for the clicked <td>
    */
    $('td').on('click', function() {
    	var that = $(this);
      $('td').not(that).removeClass('lit');
    	that.addClass('lit');
    });
    .month {
      table-layout: fixed;
      width: 90%;
      min-height: 250px;
      border-spacing: 1px;
      border: 3px outset grey
    }
    
    caption {
      font-variant: small-caps
    }
    
    .month td {
      border: 2px inset black;
      background: #fff;
      cursor: pointer;
    }
    
    td.lit {
      background-color: #0093e0;
      border-color: #09e;
    }
    
    td.today {
      background: rgba(0, 0, 255, 1);
      border-color: #aae1ff;
    }
    
    td.today.lit {
      background: tomato;
      border-color: red
    }
    
    td b {
      font-size: .3em;
      color: #123;
      vertical-align: top;
      display: inline-block;
      margin: -7px 0 0 -5px;
    }
    
    td.today b {
      color: #fff
    }
    
    .empty {
    /* Prevents any mouse events 
    || i.e unclickable
    */
      pointer-events: none;
      cursor: default;
    }
    <table class='month'>
      <caption>October</caption>
      <thead>
        <tr>
          <th>SUN</th>
          <th>MON</th>
          <th>TUE</th>
          <th>WED</th>
          <th>THU</th>
          <th>FRI</th>
          <th>SAT</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td class='empty' colspan='4'>&nbsp;</td>
        </tr>
      </tbody>
    </table>
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

    Demo 3

    // Make a Date Object
    var d = new Date();
    // Get today's day as a number
    var today = d.getDate();
    
    /* Find the cell at the index number 
    || (which is eq -1) and add thr .today class
    */
    $('td').eq(today - 1).addClass('today');
    
    /* On each cell, add the day number, unless
    || the cell has class .empty
    */ // Note: the syntax of string on line 19
    // is ES6 Template Literal see post for ref.
    $('td').each(function(index, day) {
      if ($(this).hasClass('empty')) {
        return
      }
      $(this).append(`<b>${index+1}</b>`);
    });
    
    /* Delegate the click event on all
    || td (cell).
    || See post update for details
    || 
    */
    $('td').on('click', function(e) {
      var tgt = e.target;
      e.stopImmediatePropagation();
      $('td').each(function(idx, cell) {
        cell.style.backgroundColor = '#fff';
        cell.style.borderColor = '#000';
        if ($(cell).hasClass('today')) {
          cell.style.backgroundColor = 'rgba(0, 0, 255, 1)';
          cell.style.borderColor = '#aae1ff';
        }
      });
      tgt.style.backgroundColor = '#0093e0';
      tgt.style.borderColor = '#09e';
    });
    .month {
      table-layout: fixed;
      width: 90%;
      min-height: 250px;
      border-spacing: 1px;
      border: 3px outset grey
    }
    
    caption {
      font-variant: small-caps
    }
    
    .month td {
      border: 2px inset black;
      background: #fff;
      cursor: pointer;
    }
    
    td.lit {
      background-color: #0093e0;
      border-color: #09e;
    }
    
    td.today {
      background: rgba(0, 0, 255, 1);
      border-color: #aae1ff;
    }
    
    td.today.lit {
      background: tomato;
      border-color: red
    }
    
    td b {
      font-size: .3em;
      color: #123;
      vertical-align: top;
      display: inline-block;
      margin: -7px 0 0 -5px;
      background: rgba(0, 0, 0, 0);
      pointer-events: none;
    }
    
    td.today b {
      color: #fff
    }
    
    .empty {
      /* Prevents any mouse events 
    || i.e unclickable
    */
      pointer-events: none;
      cursor: default;
    }
    <table class='month'>
      <caption>October</caption>
      <thead>
        <tr>
          <th>SUN</th>
          <th>MON</th>
          <th>TUE</th>
          <th>WED</th>
          <th>THU</th>
          <th>FRI</th>
          <th>SAT</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
        </tr>
        <tr>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td>&nbsp;</td>
          <td class='empty' colspan='4'>&nbsp;</td>
        </tr>
      </tbody>
    </table>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

    Reference

    Time Date(), .getDate()

    DOM Collection .eq(), .each()

    Class Manipulation .toggleClass(), .addClass(), .removeClass(), .hasClass()

    Event Delegation .on(), .off()

    Miscellaneous .append(), ES6 Template Literals .not()