2

I have a sort function which uses data-attributes to re-arrange the items in the list for each table. The problem with my code is that if the value of the data-head isn't the same for each list the sort breaks down and appends all of the second column items into the same list.

In this example when I click Price or Size, both types items are appended to each list even though they have a different data-status.

How do I change the sort to only apply to each table when the data-sort matches the data-head?

var keys = [...$(".table .btn")].map((i) => $(i).text());
var arr = [...$(".list .item")].map((i) => {
  var data = $(i).find("[data-sort]");
  var obj = {};
  for (let id = 0; id < data.length; id++) {
    obj[keys[id]] = data.eq(id).text();
    obj.line = $(i)[0].outerHTML;
  }
  return obj;
});

//console.log("arr",  arr);        

var lastkey = -1;
$(".table .btn").on("click", function() {
  var keytosort = keys[Number($(".table .btn").index(this))];
  var desc = true;
  if (keytosort == lastkey) {
    desc = true;
    lastkey = -1;
  } else {
    desc = false;
    lastkey = keytosort;
  }


  arr = arr.sort((a, b) => {
    if (a[keytosort].startsWith("$")) {
      let an = a[keytosort].substring(1); //put off $
      let bn = b[keytosort].substring(1);
      return Number(an) - Number(bn);
    } else {
      return a[keytosort].localeCompare(b[keytosort]);
    }
  }); //end sort

  if (desc) arr = arr.reverse();

  //recreate the different items
  $(".list").children().remove();

  for (let i = 0; i < arr.length; i++) {
    $(".list").append(arr[i].line);
  }
});
.list {
  display: flex;
  flex-direction: column;
}

.item,
.header {
  display: flex;
}

.btn {
  cursor: pointer;
}

[data-sort] {
  border: 1px solid;
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="table">
  <div class="header">
    <div class="btn" data-head='country'>Country</div>
    <div class="btn" data-head='price'>Price</div>
  </div>
  <div>
    <div class="list">
      <div class="item">
        <div data-sort="country">France</div>
        <div data-sort="price">$25</div>
      </div>
      <div class="item">
        <div data-sort="country">Spain</div>
        <div data-sort="price">$30</div>
      </div>
    </div>
  </div>
</div>
<div class="table">
  <div class="header">
    <div class="btn" data-head='country'>Country</div>
    <div class="btn" data-head='type'>Size</div>
  </div>
  <div>
    <div class="list">
      <div class="item">
        <div data-sort="country">France</div>
        <div data-sort="type">small</div>
      </div>
      <div class="item">
        <div data-sort="country">spain</div>
        <div data-sort="price">large</div>
      </div>
    </div>
  </div>
</div>
isherwood
  • 58,414
  • 16
  • 114
  • 157
Kyle Underhill
  • 89
  • 15
  • 43

1 Answers1

1

By creating an array of tables, it is possible to separate the keys and arr values for each table so that they can be sorted independently.

The below example also adds logic to adjust for cases where the sort had no effect.

EDIT: Updated to include an example table with numeric prices only.

var tables = [...$(".table")].map((t) => {
  var keys = [...$(t).find(".btn")].map((i) => $(i).text());
  var arr = [...$(t).find(".list .item")].map((i) => {
    var data = $(i).find("[data-sort]");
    var obj = {};
    for (let id = 0; id < data.length; id++) {
      obj[keys[id]] = data.eq(id).text();
      obj.line = $(i)[0].outerHTML;
    }
    return obj;
  });
  
  return { keys: keys, arr: arr, lastkey: -1 };
})
    

$(".table .btn").on("click", function() {
  var table = $(this.closest(".table"));
  var i = Number($(".table").index(table));
  var tabletosort = tables[i];
  var keytosort = tabletosort.keys[Number(table.find(".btn").index(this))];
  var desc = true;
  
  if (keytosort == tables[i].lastkey) {
    desc = true;
    tables[i].lastkey = -1;
  } else {
    desc = false;
    tables[i].lastkey = keytosort;
  }

  tabletosort.arr = tabletosort.arr.sort((a, b) => {
    if (a[keytosort].startsWith("$")) {
      let an = a[keytosort].substring(1); //put off $
      let bn = b[keytosort].substring(1);
      return Number(an) - Number(bn);
    } else if (!isNaN(parseFloat(a[keytosort])) && isFinite(a[keytosort]) && !isNaN(parseFloat(b[keytosort])) && isFinite(b[keytosort])) {
      return Number(a[keytosort]) - Number(b[keytosort]);
    } else {
      return a[keytosort].localeCompare(b[keytosort]);
    }
  }); //end sort
  
  //if sorting made no difference, reverse the result
  if(0 == tabletosort.arr.reduce((total, item, j) => total + (item == tables[i].arr[j] ? 0 : 1), 0)) {
    tabletosort.arr = tabletosort.arr.reverse();
  }

  if (desc) tabletosort.arr = tabletosort.arr.reverse();

  //recreate the different items
  table.find(".list").children().remove();

  for (let i = 0; i < tabletosort.arr.length; i++) {
    table.find(".list").append(tabletosort.arr[i].line);
  }
});
.list {
  display: flex;
  flex-direction: column;
}

.item,
.header {
  display: flex;
}

.btn {
  cursor: pointer;
}

[data-sort] {
  border: 1px solid;
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="table">
  <div class="header">
    <div class="btn" data-head='country'>Country</div>
    <div class="btn" data-head='price'>Price</div>
  </div>
  <div>
    <div class="list">
      <div class="item">
        <div data-sort="country">France</div>
        <div data-sort="price">$25</div>
      </div>
      <div class="item">
        <div data-sort="country">Spain</div>
        <div data-sort="price">$30</div>
      </div>
    </div>
  </div>
</div>
<div class="table">
  <div class="header">
    <div class="btn" data-head='country'>Country</div>
    <div class="btn" data-head='type'>Size</div>
  </div>
  <div>
    <div class="list">
      <div class="item">
        <div data-sort="country">France</div>
        <div data-sort="type">small</div>
      </div>
      <div class="item">
        <div data-sort="country">spain</div>
        <div data-sort="price">large</div>
      </div>
    </div>
  </div>
</div>
<div class="table">
  <div class="header">
    <div class="btn" data-head='country'>Country</div>
    <div class="btn" data-head='price'>Price</div>
  </div>
  <div>
    <div class="list">
      <div class="item">
        <div data-sort="country">France</div>
        <div data-sort="price">150</div>
      </div>
      <div class="item">
        <div data-sort="country">Spain</div>
        <div data-sort="price">250</div>
      </div>
      <div class="item">
        <div data-sort="country">Germany</div>
        <div data-sort="price">30</div>
      </div>
    </div>
  </div>
</div>
sbgib
  • 5,580
  • 3
  • 19
  • 26
  • I just noticed that if you remove the "$" from the numbers in the table it doesn't sort based on the value of the number. It only takes the first digit and not the entire number. – Kyle Underhill Mar 22 '21 at 14:26
  • @KyleUnderhill good point! I've updated the sorting logic (added the `else if`) with a numeric check based on [this](https://stackoverflow.com/questions/9716468/pure-javascript-a-function-like-jquerys-isnumeric) to handle that case. – sbgib Mar 22 '21 at 16:39
  • I tested your updated code using the numbers 150, 250, and 30 and it sorts in this exact order as it takes the first integer only and not the entire number. – Kyle Underhill Mar 22 '21 at 17:20