Max
Max

Reputation: 940

Keeping the previous highlighting with jquery

I created a simple table to show the problem I'm having. When a value is highlighted with a checkbox, and if the value exists in the second checkbox it is still highlighted. That's ok. but when I unclick the first or second checkbox, the highlight is gone although it's still in the first checkbox and still clicked. Is there a way to keep the highlight on when the second checkbox is unselected? I hope I made it clear. here is my jsfiddle if you prefer: https://jsfiddle.net/6k5caw8h/19/

Thank you.

Html:

$(document).ready(function() {
  $('.selector').each(function() {
    $(this).on('click', function(e) {
      check($(this).prop("checked") ? $("#nextColor").val() : '#fff', $(this).attr("name"));
    });
  });
  $('.all').each(function() {
    $(this).on('click', all);
  });

  function all(event) {
    if ($(this).is(':checked')) {
      let checked = $("input:checkbox:not(:checked)", $(this).parents('form')).not(this);
      checked.prop("checked", true);
      check($("#nextColor").val(), ...checked.map((i, e) => e.name).get());
    } else {
      let checked = $("input:checkbox(:checked)", $(this).parents('form')).not(this);
      checked.prop("checked", false);
      check('#fff', ...checked.map((i, e) => e.name).get());
    }
  }

  function check(color, ...elements) {
    $('td').each((i, td) => {
      if (elements.includes($(td).text())) $(td).css('background', color);
    });
  }
});
table.tb {
  border: 1px solid #CCC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  border-collapse: collapse;
  table-layout: fixed;
  margin-left: 200px;
}

.tb td {
  padding: 5px;
  margin: 5px;
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<input id="nextColor" type="color" value="#9ac99d">
<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="y" class="selector" />y</label>
</form>

<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="z" class="selector" />z</label>
</form>
<table class="tb">
  <tbody>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
    <tr>
      <td>z</td>
      <td>x</td>
    </tr>
    <tr>
      <td>q</td>
      <td>a</td>
    </tr>
  </tbody>
</table>

Upvotes: 0

Views: 65

Answers (3)

Mark Schultheiss
Mark Schultheiss

Reputation: 34227

I note that the "SelectAll" does not become checked when all the children are checked - I make the assumption that you DO wish to make it "checked" when all the children are checked (a common requirement) so that to uncheck all the children you do not have to check and uncheck the SelectAll.

The other thing is do you wish to keep the related checks checked i.e. when you check "x" do you wish the OTHER "x" to become checked? IF you do, uncomment this line in the code I present here and the like named will check and uncheck in sync:

  //  allChecksNotMe.prop("checked", isChecked);

OK, now that we are past the checkbox management, lets look at keeping the highlight. Now that requirement is slightly unclear - you turn them white #fff when you uncheck. Do you wish that or only when ALL are unchecked? Or never turn white; i.e. once with a color they keep it even if unchecked? My second example has ways to keep the color highlighted when unchecked and to reset to the current color choice as well.

$(function() {
  let cbx = "[type='checkbox']";
  let cbxSel = ".selector" + cbx;
  $(cbxSel) //.add('.all[type="checkbox"]')
    .on('change', function(e) {
      let me = $(this);
      let myname = $(this).attr("name");
      let namesel = "[name='" + myname + "']";
      let allpeer = $(".all" + cbx);
      let allChecksNotMe = $(cbxSel + namesel).not(me);
      let isChecked = this.checked;
      // uncomment to keep like checks in sync
      //  allChecksNotMe.prop("checked", isChecked);
      allpeer.trigger('checkcolor');
      let color = $("#nextColor").val();
      let newColor = isChecked ? color : '#fff';
      check(newColor, myname);
    });
  $('form').on('checkcolor', '.all', function(event) {
    let myForm = $(event.delegateTarget);
    // checks on form
    let formChecks = myForm.find(cbxSel);
    let formChecksChecked = formChecks.filter(':checked');
    let areAllChecked = (formChecks.length == formChecksChecked.length);
    $(this).prop("checked", areAllChecked);
  })
  $('.all' + cbx).on('change', function() {
    let isChecked = this.checked;
    let myForm = $(this).closest('form');
    // checks on form
    myForm.find(cbxSel)
      .prop('checked', isChecked).trigger("change");
  });

  function check(color, ...elements) {
    let newColor = {
      'background-color': color
    };
    $('.tb tbody').find('td').filter(function(index) {
      return elements.includes($(this).text());
    }).css(newColor);
  }
});
table.tb {
  border: 1px solid #CCC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  border-collapse: collapse;
  table-layout: fixed;
  margin-left: 200px;
}

.tb td {
  padding: 5px;
  margin: 5px;
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<input id="nextColor" type="color" value="#9ac99d">
<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="y" class="selector" />y</label>
</form>

<form id="form2" name="form2" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="z" class="selector" />z</label>
</form>
<table class="tb">
  <tbody>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
    <tr>
      <td>z</td>
      <td>x</td>
    </tr>
    <tr>
      <td>q</td>
      <td>a</td>
    </tr>
  </tbody>
</table>

Alternate with highlight differences:

$(function() {
  let cbx = "[type='checkbox']";
  let cbxSel = ".selector" + cbx;
  $(cbxSel) //.add('.all[type="checkbox"]')
    .on('change', function(e) {
      let me = $(this);
      let myname = $(this).attr("name");
      let namesel = "[name='" + myname + "']";
      let allpeer = $(".all" + cbx);
      let namedPeers = $(cbxSel + namesel);
      let allChecksNotMe = namedPeers.not(me);
      let isChecked = this.checked;
      // uncomment to keep like checks in sync
      //  allChecksNotMe.prop("checked", isChecked);
      allpeer.trigger('allchecked');
      let anyNamedChecked = !!namedPeers.filter(":checked").length;
      let keep = $('#keepcolor')[0].checked;
      let reset = $('#resetcolor')[0].checked;
      check(anyNamedChecked, myname, keep, reset);
    });
  $('form').on('allchecked', '.all', function(event) {
    let myForm = $(event.delegateTarget);
    // checks on form
    let formChecks = myForm.find(cbxSel);
    let formChecksChecked = formChecks.filter(':checked');
    let areAllChecked = (formChecks.length == formChecksChecked.length);
    $(this).prop("checked", areAllChecked);
  })
  $('.all' + cbx).on('change', function() {
    let isChecked = this.checked;
    let myForm = $(this).closest('form');
    // checks on form
    myForm.find(cbxSel)
      .prop('checked', isChecked).trigger("change");
  });

  function check(anyNamedChecked, myname, keepColor = false, reset = true) {
    let color = $("#nextColor").val();
    let newColor = anyNamedChecked ? color : '#fff';
    let colorCSS = {
      'background-color': newColor
    };
    $('.tb tbody').find('td').filter(function(index) {
      return myname.includes($(this).text());
    }).each(function(index, element) {
      let me = $(element);
      if (keepColor) {
        let scolor = reset ? color : me.data("savecolor");
        if (!!scolor) {
          colorCSS = {
            'background-color': scolor
          };
        }
        me.data("savecolor", !!scolor ? scolor : color);
      }
      $(element).css(colorCSS);
    });
  }
});
table.tb {
  border: 1px solid #CCC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  border-collapse: collapse;
  table-layout: fixed;
  margin-left: 200px;
}

.tb td {
  padding: 5px;
  margin: 5px;
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>

<input id="nextColor" type="color" value="#9ac99d">
<label><input id="keepcolor" type="checkbox" name="keepcolor" />Keep Color</label>
<label><input id="resetcolor" type="checkbox" name="resetcolor" />Reset Color</label>
<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="y" class="selector" />y</label>
</form>

<form id="form2" name="form2" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="z" class="selector" />z</label>
</form>
<table class="tb">
  <tbody>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
    <tr>
      <td>z</td>
      <td>x</td>
    </tr>
    <tr>
      <td>q</td>
      <td>a</td>
    </tr>
  </tbody>
</table>

Upvotes: 1

Ralph Ritoch
Ralph Ritoch

Reputation: 3440

To keep the previous highlighting you need to keep a history of the checks someplace and the color that was selected when it was checked. A simple way to do this would be to assign the color data and the timestamp directly to the checkbox. The following javascript solves the actual problem of maintaining a history so that when checks are removed the color from previous checks are restored.

I refactored the code to do a rendering loop renderChecks() which is a more maintainable design pattern.

See: https://jsfiddle.net/c6pt0eb5/1/

$(document).ready(function() {

  $('.selector').each(function() {
    $(this).on('click', function(e) {
      if ($(this).is(':checked')) {
      	$(this).data("color",[$("#nextColor").val(),Date.now()]);
      }
      renderChecks();
    });
  });
  $('.all').each(function() {
    $(this).on('click', all);
  });

  function all(event) {
    if ($(this).is(':checked')) {
      let checked = $("input:checkbox", $(this).parents('form')).not(this);
      checked.prop("checked", true);
      checked.data("color",[$("#nextColor").val(),Date.now()]);
    } else {
      let checked = $("input:checkbox(:checked)", $(this).parents('form')).not(this);
      checked.prop("checked", false);      
    }
    renderChecks();
  }

  function renderChecks() {
    const checks = {};
    const names = [];
  	$(".selector:checked").each((i,val) => {
       
       if (checks[$(val).attr('name')]) {
          checks[$(val).attr('name')].push($(val).data('color'));           
          checks[$(val).attr('name')].sort((a,b) => a - b);
       } else {
          checks[$(val).attr('name')] = [$(val).data('color')];
          names.push($(val).attr('name'));
       }});
       
       $('td').each((i, td) => {
           $(td).css('background','#fff');
           const text = $(td).text();
           let t = null;
           names.forEach((name) => {               
               if (text.includes(name)) {
                   if (t == null || t < checks[name][0][1]) {
                      t = checks[name][0][1];
                      $(td).css('background', checks[name][0][0]);
                   }
               }
           });                      
       });       
       
  }

});


  
table.tb {
  border: 1px solid #CCC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  border-collapse: collapse;
  table-layout: fixed;
  margin-left: 200px;
}

.tb td {
  padding: 5px;
  margin: 5px;
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<input id="nextColor" type="color" value="#9ac99d">
<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="y" class="selector" />y</label>
</form>

<form id="form1" name="form1" method="post" action="">
  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="z" class="selector" />z</label>
</form>
<table class="tb">
  <tbody>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
    <tr>
      <td>z</td>
      <td>x</td>
    </tr>
    <tr>
      <td>q</td>
      <td>a</td>
    </tr>
  </tbody>
</table>

Note: The accepted answer doesn't keep the previous highlighting, it simply keeps the current highlighting.

Upvotes: 1

Terry
Terry

Reputation: 66228

Your problem, where unchecking checkboxes that target the same alphabet seems to ignore the state of the others, comes from this problematic line:

check($(this).prop("checked") ? $("#nextColor").val() : '#fff', $(this).attr("name"));

In this line, you are simply checking if the specific checkbox that is being clicked is checked, while you should actually check through all your checkboxes with the same name attribute and see if any of them are checked.

So what you can do is to simply loop through all .selectors and filter them by (1) the value of their name attribute which matches the current checkbox, and (2) if they are checked. If any of them are checked, the first argument passed into check() should be true:

const $sameNameSelector = $('.selector[name="'+$(this).attr('name')+'"]');
const isAnySameNameSelectorChecked = $sameNameSelector.filter(function() { return this.checked }).length;
check(isAnySameNameSelectorChecked ? $("#nextColor").val() : '#fff', $(this).attr("name"));

Update: this does not seem to fix your issue because all() is also calling some convoluted logic. All you need is to:

  1. Check the click event listener on .selector to .change
  2. Simply use the .all checkbox to trigger changes to the .selector checkboxes and manually fire the change event. This causes then individual logic on all the single checkboxes to fire, without needing to duplicate any logic:
function all(event) {
    $(this).closest('form').find('.selector').prop('checked', this.checked).trigger('change');
}

Pro-tip: you don't need to do $('.selector').each({ $(this).on('...'); }) and then bind the event handler. jQuery can handle that intelligently, so you can just do `$('.selector').on(...);'.

$(document).ready(function() {

  $('.selector').on('change', function(e) {
    const $sameNameSelector = $('.selector[name="' + $(this).attr('name') + '"]');
    const isAnySameNameSelectorChecked = $sameNameSelector.filter(function() {
      return this.checked
    }).length;
    check(isAnySameNameSelectorChecked ? $("#nextColor").val() : '#fff', $(this).attr("name"));
  });
  $('.all').each(function() {
    $(this).on('click', all);
  });

  function all(event) {
    $(this).closest('form').find('.selector').prop('checked', this.checked).trigger('change');
  }

  function check(color, ...elements) {
    $('td').each((i, td) => {
      if (elements.includes($(td).text())) $(td).css('background', color);
    });
  }


});
table.tb {
  border: 1px solid #CCC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  border-collapse: collapse;
  table-layout: fixed;
  margin-left: 200px;
}

.tb td {
  padding: 5px;
  margin: 5px;
  border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="nextColor" type="color" value="#9ac99d">

<form id="form1" name="form1" method="post" action="">

  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="y" class="selector" />y</label>

</form>

<form id="form1" name="form1" method="post" action="">

  <label><input type="checkbox" name="SelectAll" class="all" />SelectAll</label>
  <label><input type="checkbox" name="x" class="selector" />x</label>
  <label><input type="checkbox" name="z" class="selector" />z</label>

</form>


<table class="tb">
  <tbody>
    <tr>
      <td>x</td>
      <td>y</td>
    </tr>
    <tr>
      <td>z</td>
      <td>x</td>
    </tr>
    <tr>
      <td>q</td>
      <td>a</td>
    </tr>
  </tbody>
</table>

Upvotes: 2

Related Questions