Jump to content

HTML Table Sorting


mobrien

Recommended Posts

Hi Community,

I am just playing with the HTML widget and tables, looking for a way to replace embedding Excel docs with native HTML.

I can get the table to display as I want, however, I cannot seem to get the table to 'sort on click' of the header, as described in many various forums. The code I am using is below, and if I copy this into the live editor on w3schools (https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_sort_table_desc) it works a treat.

But, it does not seem to function in Frog HTML widget.

Any ideas? Thank you in advance for your support.

Michael

 

<style>
table.GeneratedTable {
  width: 100%;
  background-color: #ffffff;
  border-collapse: collapse;
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  color: #000000;
}

table.GeneratedTable td, table.GeneratedTable th {
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  padding: 10px;
}

table.GeneratedTable thead {
  background-color: #52b9e9;
}
</style>

<table class="GeneratedTable" id="myTable">
  <thead>
    <tr>
      <th onclick="sortTable(0)">Name</th>
      <th onclick="sortTable(1)">House</th>
      <th onclick="sortTable(2)">Tutor</th>
      <th onclick="sortTable(3)">Achievement</th>
      <th onclick="sortTable(4)">Behaviour</th>
      <th onclick="sortTable(5)">Total</th>
    </tr>
  </thead>
<tbody>    <tr>
      <td>Student 1</td>
      <td>Bowman</td>
      <td>ACR</td>
      <td>1100</td>
      <td>10</td>
      <td>1090</td>
    </tr>    <tr>
      <td>Student 2</td>
      <td>Fulford</td>
      <td>ACR</td>
      <td>1623</td>
      <td>196</td>
      <td>1427</td>
    </tr>    <tr>
      <td>Student 3</td>
      <td>Netherfield</td>
      <td>MOB</td>
      <td>1507</td>
      <td>198</td>
      <td>1309</td>
    </tr>    <tr>
      <td>Student 4</td>
      <td>Saunderson</td>
      <td>MOB</td>
      <td>1920</td>
      <td>326</td>
      <td>1594</td>
    </tr>    <tr>
      <td>Student 5</td>
      <td>Weirfield</td>
      <td>RBA</td>
      <td>1835</td>
      <td>85</td>
      <td>1750</td>
    </tr>    <tr>
      <td>Student 6</td>
      <td>Bowman</td>
      <td>RBA</td>
      <td>1634</td>
      <td>320</td>
      <td>1314</td>
    </tr>    <tr>
      <td>Student 7</td>
      <td>Fulford</td>
      <td>AHI</td>
      <td>1470</td>
      <td>503</td>
      <td>967</td>
    </tr>    <tr>
      <td>Student 8</td>
      <td>Netherfield</td>
      <td>AHI</td>
      <td>2036</td>
      <td>337</td>
      <td>1699</td>
    </tr>    <tr>
      <td>Student 9</td>
      <td>Saunderson</td>
      <td>AHU</td>
      <td>1342</td>
      <td>134</td>
      <td>1208</td>
    </tr>    <tr>
      <td>Student 10</td>
      <td>Weirfield</td>
      <td>AHU</td>
      <td>1575</td>
      <td>247</td>
      <td>1328</td>
    </tr>    <tr>
      <td>Student 11</td>
      <td>Bowman</td>
      <td>GUN</td>
      <td>1575</td>
      <td>170</td>
      <td>1405</td>
    </tr>    <tr>
      <td>Student 12</td>
      <td>Fulford</td>
      <td>GUN</td>
      <td>1416</td>
      <td>200</td>
      <td>1216</td>
    </tr>    <tr>
      <td>Student 13</td>
      <td>Netherfield</td>
      <td>FNA</td>
      <td>1991</td>
      <td>488</td>
      <td>1503</td>
    </tr>    <tr>
      <td>Student 14</td>
      <td>Saunderson</td>
      <td>FNA</td>
      <td>1596</td>
      <td>90</td>
      <td>1506</td>
    </tr>    <tr>
      <td>Student 15</td>
      <td>Weirfield</td>
      <td>HTA</td>
      <td>1553</td>
      <td>188</td>
      <td>1365</td>
    </tr>    <tr>
      <td>Student 16</td>
      <td>Bowman</td>
      <td>HTA</td>
      <td>1570</td>
      <td>277</td>
      <td>1293</td>
    </tr>    <tr>
      <td>Student 17</td>
      <td>Fulford</td>
      <td>DRO</td>
      <td>1444</td>
      <td>414</td>
      <td>1030</td>
    </tr>    <tr>
      <td>Student 18</td>
      <td>Netherfield</td>
      <td>DRO</td>
      <td>1702</td>
      <td>367</td>
      <td>1335</td>
    </tr>    <tr>
      <td>Student 19</td>
      <td>Saunderson</td>
      <td>SPK</td>
      <td>2027</td>
      <td>387</td>
      <td>1640</td>
    </tr>    <tr>
      <td>Student 20</td>
      <td>Weirfield</td>
      <td>SPK</td>
      <td>1512</td>
      <td>140</td>
      <td>1372</td>
    </tr></tbody>
</table>

<script>
function sortTable(n) {
  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
  table = document.getElementById("myTable");
  switching = true;
  //Set the sorting direction to ascending:
  dir = "asc"; 
  /*Make a loop that will continue until
  no switching has been done:*/
  while (switching) {
    //start by saying: no switching is done:
    switching = false;
    rows = table.getElementsByTagName("TR");
    /*Loop through all table rows (except the
    first, which contains table headers):*/
    for (i = 1; i < (rows.length - 1); i++) {
      //start by saying there should be no switching:
      shouldSwitch = false;
      /*Get the two elements you want to compare,
      one from current row and one from the next:*/
      x = rows[i].getElementsByTagName("TD")[n];
      y = rows[i + 1].getElementsByTagName("TD")[n];
      /*check if the two rows should switch place,
      based on the direction, asc or desc:*/
      if (dir === "asc") {
        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
          //if so, mark as a switch and break the loop:
          shouldSwitch= true;
          break;
        }
      } else if (dir === "desc") {
        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
          //if so, mark as a switch and break the loop:
          shouldSwitch= true;
          break;
        }
      }
    }
    if (shouldSwitch) {
      /*If a switch has been marked, make the switch
      and mark that a switch has been done:*/
      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
      switching = true;
      //Each time a switch is done, increase this count by 1:
      switchcount ++;      
    } else {
      /*If no switching has been done AND the direction is "asc",
      set the direction to "desc" and run the while loop again.*/
      if (switchcount === 0 && dir === "asc") {
        dir = "desc";
        switching = true;
      }
    }
  }
}
</script>

 

Link to comment
Share on other sites

Hi Michael

Although it's not the way i would go about it, I can't see much wrong with your code.  However, I do get an error "sortTable not defined" when i try your code.  Clearly it is defined, so i think this is probably to do with limitations in the frog html widget.

I've got client side sorting working in frog using tablesorter (http://tablesorter.com) which i have used for years and supports a ton of options like multiple column sorting using the ctrl key. Examples on this fiddle http://jsfiddle.net/Mottie/4mVfu/1/

Try this code:

<script type="text/javascript" src="https://mottie.github.io/tablesorter/js/jquery.tablesorter.js"></script>

<script>
$(document).ready(function() 
    { 
        $("#myTable").tablesorter(); 
    } 
); 
</script>

<style>
table.GeneratedTable {
  width: 100%;
  background-color: #ffffff;
  border-collapse: collapse;
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  color: #000000;
}

table.GeneratedTable td, table.GeneratedTable th {
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  padding: 10px;
}

table.GeneratedTable thead {
  background-color: #52b9e9;
}
</style>

<table class="GeneratedTable" id="myTable">
  <thead>
    <tr>
      <th>Name</th>
      <th>House</th>
      <th>Tutor</th>
      <th>Achievement</th>
      <th>Behaviour</th>
      <th>Total</th>
    </tr>
  </thead>
<tbody>    <tr>
      <td>Student 1</td>
      <td>Bowman</td>
      <td>ACR</td>
      <td>1100</td>
      <td>10</td>
      <td>1090</td>
    </tr>    <tr>
      <td>Student 2</td>
      <td>Fulford</td>
      <td>ACR</td>
      <td>1623</td>
      <td>196</td>
      <td>1427</td>
    </tr>    <tr>
      <td>Student 3</td>
      <td>Netherfield</td>
      <td>MOB</td>
      <td>1507</td>
      <td>198</td>
      <td>1309</td>
    </tr>    <tr>
      <td>Student 4</td>
      <td>Saunderson</td>
      <td>MOB</td>
      <td>1920</td>
      <td>326</td>
      <td>1594</td>
    </tr>    <tr>
      <td>Student 5</td>
      <td>Weirfield</td>
      <td>RBA</td>
      <td>1835</td>
      <td>85</td>
      <td>1750</td>
    </tr>    <tr>
      <td>Student 6</td>
      <td>Bowman</td>
      <td>RBA</td>
      <td>1634</td>
      <td>320</td>
      <td>1314</td>
    </tr>    <tr>
      <td>Student 7</td>
      <td>Fulford</td>
      <td>AHI</td>
      <td>1470</td>
      <td>503</td>
      <td>967</td>
    </tr>    <tr>
      <td>Student 8</td>
      <td>Netherfield</td>
      <td>AHI</td>
      <td>2036</td>
      <td>337</td>
      <td>1699</td>
    </tr>    <tr>
      <td>Student 9</td>
      <td>Saunderson</td>
      <td>AHU</td>
      <td>1342</td>
      <td>134</td>
      <td>1208</td>
    </tr>    <tr>
      <td>Student 10</td>
      <td>Weirfield</td>
      <td>AHU</td>
      <td>1575</td>
      <td>247</td>
      <td>1328</td>
    </tr>    <tr>
      <td>Student 11</td>
      <td>Bowman</td>
      <td>GUN</td>
      <td>1575</td>
      <td>170</td>
      <td>1405</td>
    </tr>    <tr>
      <td>Student 12</td>
      <td>Fulford</td>
      <td>GUN</td>
      <td>1416</td>
      <td>200</td>
      <td>1216</td>
    </tr>    <tr>
      <td>Student 13</td>
      <td>Netherfield</td>
      <td>FNA</td>
      <td>1991</td>
      <td>488</td>
      <td>1503</td>
    </tr>    <tr>
      <td>Student 14</td>
      <td>Saunderson</td>
      <td>FNA</td>
      <td>1596</td>
      <td>90</td>
      <td>1506</td>
    </tr>    <tr>
      <td>Student 15</td>
      <td>Weirfield</td>
      <td>HTA</td>
      <td>1553</td>
      <td>188</td>
      <td>1365</td>
    </tr>    <tr>
      <td>Student 16</td>
      <td>Bowman</td>
      <td>HTA</td>
      <td>1570</td>
      <td>277</td>
      <td>1293</td>
    </tr>    <tr>
      <td>Student 17</td>
      <td>Fulford</td>
      <td>DRO</td>
      <td>1444</td>
      <td>414</td>
      <td>1030</td>
    </tr>    <tr>
      <td>Student 18</td>
      <td>Netherfield</td>
      <td>DRO</td>
      <td>1702</td>
      <td>367</td>
      <td>1335</td>
    </tr>    <tr>
      <td>Student 19</td>
      <td>Saunderson</td>
      <td>SPK</td>
      <td>2027</td>
      <td>387</td>
      <td>1640</td>
    </tr>    <tr>
      <td>Student 20</td>
      <td>Weirfield</td>
      <td>SPK</td>
      <td>1512</td>
      <td>140</td>
      <td>1372</td>
    </tr></tbody>
</table>

 

You might not like how your first column is sorted by default, but with tablesorter you can write a custom parser to handle the mixed text/numbers you have in column 0.

Something like this should do it...

<script type="text/javascript" src="https://mottie.github.io/tablesorter/js/jquery.tablesorter.js"></script>

<script>
$(document).ready(function() {    
    $.tablesorter.addParser({ 
        id: 'studentsorting', 
        is: function(s) { 
            return false; 
        }, 
        format: function(s) { 
            return s.toLowerCase().replace(/student /,''); 
        }, 
        type: 'numeric' 
    }); 
    
       $("#myTable").tablesorter({ 
            headers: { 
                0: { sorter:'studentsorting' } 
            } 
        }); 
});  
</script>

<style>
table.GeneratedTable {
  width: 100%;
  background-color: #ffffff;
  border-collapse: collapse;
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  color: #000000;
}

table.GeneratedTable td, table.GeneratedTable th {
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  padding: 10px;
}

table.GeneratedTable thead {
  background-color: #52b9e9;
}
</style>

<table class="GeneratedTable" id="myTable">
  <thead>
    <tr>
      <th>Name</th>
      <th>House</th>
      <th>Tutor</th>
      <th>Achievement</th>
      <th>Behaviour</th>
      <th>Total</th>
    </tr>
  </thead>
<tbody>    <tr>
      <td>Student 1</td>
      <td>Bowman</td>
      <td>ACR</td>
      <td>1100</td>
      <td>10</td>
      <td>1090</td>
    </tr>    <tr>
      <td>Student 2</td>
      <td>Fulford</td>
      <td>ACR</td>
      <td>1623</td>
      <td>196</td>
      <td>1427</td>
    </tr>    <tr>
      <td>Student 3</td>
      <td>Netherfield</td>
      <td>MOB</td>
      <td>1507</td>
      <td>198</td>
      <td>1309</td>
    </tr>    <tr>
      <td>Student 4</td>
      <td>Saunderson</td>
      <td>MOB</td>
      <td>1920</td>
      <td>326</td>
      <td>1594</td>
    </tr>    <tr>
      <td>Student 5</td>
      <td>Weirfield</td>
      <td>RBA</td>
      <td>1835</td>
      <td>85</td>
      <td>1750</td>
    </tr>    <tr>
      <td>Student 6</td>
      <td>Bowman</td>
      <td>RBA</td>
      <td>1634</td>
      <td>320</td>
      <td>1314</td>
    </tr>    <tr>
      <td>Student 7</td>
      <td>Fulford</td>
      <td>AHI</td>
      <td>1470</td>
      <td>503</td>
      <td>967</td>
    </tr>    <tr>
      <td>Student 8</td>
      <td>Netherfield</td>
      <td>AHI</td>
      <td>2036</td>
      <td>337</td>
      <td>1699</td>
    </tr>    <tr>
      <td>Student 9</td>
      <td>Saunderson</td>
      <td>AHU</td>
      <td>1342</td>
      <td>134</td>
      <td>1208</td>
    </tr>    <tr>
      <td>Student 10</td>
      <td>Weirfield</td>
      <td>AHU</td>
      <td>1575</td>
      <td>247</td>
      <td>1328</td>
    </tr>    <tr>
      <td>Student 11</td>
      <td>Bowman</td>
      <td>GUN</td>
      <td>1575</td>
      <td>170</td>
      <td>1405</td>
    </tr>    <tr>
      <td>Student 12</td>
      <td>Fulford</td>
      <td>GUN</td>
      <td>1416</td>
      <td>200</td>
      <td>1216</td>
    </tr>    <tr>
      <td>Student 13</td>
      <td>Netherfield</td>
      <td>FNA</td>
      <td>1991</td>
      <td>488</td>
      <td>1503</td>
    </tr>    <tr>
      <td>Student 14</td>
      <td>Saunderson</td>
      <td>FNA</td>
      <td>1596</td>
      <td>90</td>
      <td>1506</td>
    </tr>    <tr>
      <td>Student 15</td>
      <td>Weirfield</td>
      <td>HTA</td>
      <td>1553</td>
      <td>188</td>
      <td>1365</td>
    </tr>    <tr>
      <td>Student 16</td>
      <td>Bowman</td>
      <td>HTA</td>
      <td>1570</td>
      <td>277</td>
      <td>1293</td>
    </tr>    <tr>
      <td>Student 17</td>
      <td>Fulford</td>
      <td>DRO</td>
      <td>1444</td>
      <td>414</td>
      <td>1030</td>
    </tr>    <tr>
      <td>Student 18</td>
      <td>Netherfield</td>
      <td>DRO</td>
      <td>1702</td>
      <td>367</td>
      <td>1335</td>
    </tr>    <tr>
      <td>Student 19</td>
      <td>Saunderson</td>
      <td>SPK</td>
      <td>2027</td>
      <td>387</td>
      <td>1640</td>
    </tr>    <tr>
      <td>Student 20</td>
      <td>Weirfield</td>
      <td>SPK</td>
      <td>1512</td>
      <td>140</td>
      <td>1372</td>
    </tr></tbody>
</table>

Hope you find that useful.

Edited by pconkie
Link to comment
Share on other sites

@pconkie this is superb! Just what I was looking for.

I suppose I neglected to mention that my HTML skills are basic at best, using a piecemeal approach to building what I need.

I'm interested, however, that you mention you might have done the task differently? Would you be able to elaborate.

I have also been looking at adding a filter function to the table as illustrated here: https://www.w3schools.com/howto/howto_js_filter_table.asp Is this a similar issue to the one I had before?

 

The reason I am doing all this is to replace embedding a clunky Excel file to display rewards points to Tutors, term by term. By writing an Excel formula next to my raw data, I can build HTML code to quickly copy into Frog for updates.

Once my Code is complete here, I'll add a walkthrough and the Excel files to match for anyone else looking for a similar solution.

Thank you for your help - it is much appreciated.

 

PS. here is my updated code removing numerical characters from student names:

<script type="text/javascript" src="https://mottie.github.io/tablesorter/js/jquery.tablesorter.js"></script>

<script>
$(document).ready(function() 
    { 
        $("#myTable").tablesorter(); 
    } 
); 
</script>

<style>
table.GeneratedTable {
  width: 100%;
  background-color: #ffffff;
  border-collapse: collapse;
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  color: #000000;
}

table.GeneratedTable td, table.GeneratedTable th {
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  padding: 10px;
}

table.GeneratedTable thead {
  background-color: #52b9e9;
}
</style>

<table class="GeneratedTable" id="myTable">
  <thead>
    <tr>
      <th>Name</th>
      <th>House</th>
      <th>Tutor</th>
      <th>Achievement</th>
      <th>Behaviour</th>
      <th>Total</th>
    </tr>
  </thead>
<tbody>    <tr>
      <td>Tom Smith</td>
      <td>Bowman</td>
      <td>ACR</td>
      <td>1100</td>
      <td>10</td>
      <td>1090</td>
    </tr>    <tr>
      <td>Harry Jones</td>
      <td>Fulford</td>
      <td>ACR</td>
      <td>1623</td>
      <td>196</td>
      <td>1427</td>
    </tr>    <tr>
      <td>Will Davis</td>
      <td>Netherfield</td>
      <td>MOB</td>
      <td>1507</td>
      <td>198</td>
      <td>1309</td>
    </tr>    <tr>
      <td>Emily O'Brien</td>
      <td>Saunderson</td>
      <td>MOB</td>
      <td>1920</td>
      <td>326</td>
      <td>1594</td>
    </tr>
</tbody>
</table>

 

  • Like 1
Link to comment
Share on other sites

Hi @mobrien,

@pconkie's approach is a good start but there are a couple of things that you should both be aware of:

  1. Linking to external libraries in the HTML Widget works differently to how you expect, so the script tag loading in jquery.tablesorter.js from github won't work.
    1. Fortunately, FrogOS already has tablesorter under the hood.
  2. Using ID's is problematic, especially if you want to reuse this code in multiple places.
    1. Combine this.element and use either a tag lookup or add a class
    2. I recommend the using the tag.
  3. Using $(document).ready may produce random results as the document is already ready
    1. Instead, anything that needs to run at the end put in a script tag at the bottom of the code

Personally, what I would try to do is reduce the amount of data I am storing in HTML by storing my data in JSON. This is a little scary at first but will make modification and maintenance easier.

<style>
    .template {
        display: none;
    }
</style>

<table class="table table-striped">
    <thead>
        <th>Name</th>
        <th>House</th>
        <th>Tutor</th>
        <th>Achievement</th>
        <th>Behaviour</th>
        <th>Total</th>
    </thead>
    <tbody></tbody>
</table>

<table class="template">
    <tbody>
        <tr>
            <td data-field="name"></td>
            <td data-field="house"></td>
            <td data-field="tutor"></td>
            <td data-field="achievement"></td>
            <td data-field="behaviour"></td>
            <td data-field="total"></td>
        </tr>
    </tbody>
</table>

<script>
    var promise, renderData,
        data = [
            { name: 'Student 1', house: 'Bowman', tutor: 'ACR', achievement: 1100, behaviour: 10, total: 1090 },
            { name: 'Student 2', house: 'Fulford', tutor: 'ACR', achievement: 1623, behaviour: 196, total: 1427 }
        ];

    if (steal.then !== undefined) {
        promise = steal('//frogui/components/mysorter/mysorter.js');
    } else {
        promise = steal.import('frogui/components/mysorter/mysorter');
    }

    promise.then(function() {
        var $table = this.element.find('table:first'),
            $tbody = $table.children('tbody'),
            $template = this.element.find('.template tbody > tr');

        $table.frogui_components_mysorter({
            headers: {
                name: 'asc',
                house: 'asc',
                tutor: 'asc',
                achievement: 'asc',
                behaviour: 'asc',
                total: 'asc'
            }
        });

        renderData = function(data) {
            data.forEach(function(datum) {
                var $row = $template.clone();

                for (var key in datum) {
                    $row.find('[data-field='+key+']').text(datum[key]);
                }

                $tbody.append($row);
            }.bind(this));
        }.bind(this);

        $table.on('mysorter.sort', function(ev, sort) {
            var key = sort.key,
                direction = sort.direction,
                sorted_data = data.sort(function(a, b) {
                    if (direction === 'asc') {
                        if (a[key] < b[key]) {
                            return -1;
                        } else if (a[key] > b[key]) {
                            return 1;
                        } else {
                            return 0;
                        }
                    } else {
                        if (a[key] < b[key]) {
                            return 1;
                        } else if (a[key] > b[key]) {
                            return -1;
                        } else {
                            return 0;
                        }
                    }
                });

            $tbody.empty();
            renderData(sorted_data);
        });

        renderData(data);
    }.bind(this));
</script>

From there you could use the built $.fixture to move the data storage and sorting to an API based pattern. Thats a bit more advanced but would allow filtering of data! Let me know if that is of interest.

 

~ Chris

  • Like 2
Link to comment
Share on other sites

Hi @Chris.Smith,

This is much tidier than my original 'attempt'. The organisation of the data rows in the code is fine, and easily manipulated which is what I need.

There are two parts that I would like to add, a Header row colour for our House Groups (should this go within the Styles?) and the filtering function that you describe.

Really grateful for all this help!

Michael

Link to comment
Share on other sites

Hi Chris @Chris.Smith

I've got a couple of questions too....

1. In this line 

$table = this.element.find('table:first'

what does 'this' refer to? - the document, the html widget, something else?

2. Is this also the line showing your preferred way to find an element?

$table = this.element.find('table:first'); - this uses this.element with a tag?

$template = this.element.find('.template tbody > tr'); this uses this.element with a class?
How would  $template = $('.template tbody > tr'); be different?

3. what does .bind(this) do in your script?

Thanks for your help.

Paul

Edited by pconkie
Link to comment
Share on other sites

34 minutes ago, pconkie said:

Hi Chris @Chris.Smith

I've got a couple of questions too....

1. In this line 


$table = this.element.find('table:first'

what does 'this' refer to? - the document, the html widget, something else?

2. Is this also the line showing your preferred way to find an element?

$table = this.element.find('table:first'); - this uses this.element with a tag?

$template = this.element.find('.template tbody > tr'); this uses this.element with a class?
How would  $template = $('.template tbody > tr'); be different?

3. what does .bind(this) do in your script?

Thanks for your help.

Paul

Hi Paul,

this.element does indeed refer to the HTML widget. this refers to the block scope bound to the executing function (code inside the script tag). element is a property of this.

this.element.find(selector) is preferred over $(selector) because it is limited to performing a tree search over just the content within that particular HTML widget. This means you won't get any collisions should two instances of this widget be running at the same time. It is also more performant because it is only searching through a handful of DOM elements versus thousands.

.bind(this) forces a function to operate within a specific block scope. For example:

Class Person {
	constructor(name) {
    	this.name = name;
    }
  
    getName() {
    	return this.name;
    }
}

var paul = new Person("Paul");

// getName returns "Paul"
assertEquals(paul.getName(), "Paul"); // true

// getName with a custom scope returns "Chris"
assertEquals(paul.getName.call({ name: 'Chris' }), "Paul"); // false
assertEquals(paul.getName.call({ name: 'Chris' }), "Chris"); // true

// Binding a function returns a clone of the function
var bound_function = paul.getName.bind({ name: 'Chris' });
assertEquals(paul.getName(), "Paul"); // true
assertEquals(bound_function(), "Chris"); // true

// bound functions can be modified using .call() or .apply()
assertEquals(bound_function.call(paul), "Chris"); // false
assertEquals(bound_function.call(paul), "Paul"); // true

To summarise, .bind() is a static version of .call()  or .apply() however, it does not invoke the function, it merely returns a function that is bound to the provided scope.

Here is a resource which can probably explain it better than I can - http://devdocs.io/javascript/global_objects/function/bind

I hope you find this helpful. .bind(this) is fairly advanced stuff and unless you get into writing complex functional code, I think you probably won't use it that much. this.element is important though, and will help you avoid a number of bugs and errors that the community struggled with for a long time before it came along.

Let me know if you have any more questions,

~ Chris

  • Like 1
Link to comment
Share on other sites

Thanks Chris that makes perfect sense! (the bit about this.element).

Like Doc Brown said to Marty McFly: "You're not thinking fourth-dimensionally!".     Any takers for which BTTF movie?

I had thought that if I had a wrapper with a unique class name that would would avoid potential issues. But of course if there is more than one version of the widget running the class would not be unique.

Ok. I'm changing everything to use this.element.....

Link to comment
Share on other sites

21 hours ago, mobrien said:

Hi @Chris.Smith,

This is much tidier than my original 'attempt'. The organisation of the data rows in the code is fine, and easily manipulated which is what I need.

There are two parts that I would like to add, a Header row colour for our House Groups (should this go within the Styles?) and the filtering function that you describe.

Really grateful for all this help!

Michael

Hi @mobrien,

Here is the code transformed to allow filtering. The styling you can add yourself :)

<style>
    .template {
        display: none;
    }
</style>

<table class="table table-striped">
    <thead>
        <th>Name</th>
        <th>House</th>
        <th>Tutor</th>
        <th>Achievement</th>
        <th>Behaviour</th>
        <th>Total</th>
    </thead>
    <tbody></tbody>
</table>

<table class="template">
    <tbody>
        <tr>
            <td data-field="name"></td>
            <td data-field="house"></td>
            <td data-field="tutor"></td>
            <td data-field="achievement"></td>
            <td data-field="behaviour"></td>
            <td data-field="total"></td>
        </tr>
    </tbody>
</table>

<script type="text/javascript">
    var returnSuccess = function(params, output) {
            return [
                200,
                "success",
                {
                    "data": output,
                    "status": "success",
                    "params": params
                }
            ];
        },
        data = [
            { name: 'Student 1', house: 'Bowman', tutor: 'ACR', achievement: 1100, behaviour: 10, total: 1090 },
            { name: 'Student 2', house: 'Fulford', tutor: 'ACR', achievement: 1623, behaviour: 196, total: 1427 }
        ];

    $.fixture('GET myapi.get', function(original, settings, headers) {
        return returnSuccess(original.data, data);
    });

    $.fixture('GET myapi.sort', function(original, settings, headers) {
        var params = original.data,
            key = params.key,
            direction = params.direction,
            sorted_data = data.sort(function(a, b) {
                if (direction === 'asc') {
                    if (a[key] < b[key]) {
                        return -1;
                    } else if (a[key] > b[key]) {
                        return 1;
                    } else {
                        return 0;
                    }
                } else {
                    if (a[key] < b[key]) {
                        return 1;
                    } else if (a[key] > b[key]) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            });

        return returnSuccess(params, sorted_data);
    });

    $.fixture('GET myapi.filter', function(original, settings, headers) {
        var params = original.data,
            filtered_data = [];

        for (var key in params) {
            filtered_data = data.filter(function(datum) {
                if (datum[key].constructor.name !== "String") {
                    return parseInt(params[key], 10) <= datum[key];
                }
                return (new RegExp(params[key], 'gi')).test(datum[key]);
            });
        }
        filtered_data = Array.prototype.concat.apply([], filtered_data);
        filtered_data = filtered_data.filter(function(datum, index, self) {
            return self.indexOf(datum) === index;
        });

        return returnSuccess(params, filtered_data);
    });
</script>

<script>
    var promise, renderData, applyTableFilter;

    if (steal.then !== undefined) {
        promise = steal('//frogui/components/mysorter/mysorter.js', '//frogui/components/tablefilter/tablefilter.js');
    } else {
        promise = steal.import('frogui/components/mysorter/mysorter!frog-component', 'frogui/components/tablefilter/tablefilter!frog-component');
    }

    promise.then(function() {
        var $table = this.element.find('table:first'),
            $tbody = $table.children('tbody'),
            $template = this.element.find('.template tbody > tr'),
            houses = {}, tutor = {};

        $table.frogui_components_mysorter({
            headers: {
                name: 'asc',
                house: 'asc',
                tutor: 'asc',
                achievement: 'asc',
                behaviour: 'asc',
                total: 'asc'
            }
        });

        applyTableFilter = function() {
            $table.frogui_components_tablefilter({
                filters: {
                    name: {
                        type: 'text',
                        placeholder: 'Student Name',
                        data: [],
                        position: 0
                    },
                    house: {
                        type: 'select',
                        data: Object.keys(houses),
                        placeholder: 'House',
                        position: 1
                    },
                    tutor: {
                        type: 'select',
                        data: Object.keys(tutor),
                        placeholder: 'Tutor',
                        position: 2
                    },
                    achievement: {
                        type: 'text',
                        placeholder: 'Achievement Points',
                        data: [],
                        position: 3
                    },
                    behaviour: {
                        type: 'text',
                        placeholder: 'Behaviour Points',
                        data: [],
                        position: 4
                    },
                    total: {
                        type: 'text',
                        placeholder: 'Total',
                        data: [],
                        position: 5
                    }
                }
            });
            $table.find('.filter-row, .filter-submit-row').removeClass('hide');
        };

        renderData = function(data) {
            data.forEach(function(datum) {
                var $row = $template.clone();

                for (var key in datum) {
                    $row.find('[data-field='+key+']').text(datum[key]);
                }

                houses[datum.house] = null;
                tutor[datum.tutor] = null;
                applyTableFilter();

                $tbody.append($row);
            }.bind(this));
        }.bind(this);

        $table.on('mysorter.sort', function(ev, sort) {
            Frog.Model.api('myapi.sort', sort)
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        $table.on('tablefilter.submit', function(ev, data) {
            Frog.Model.api('myapi.filter', data)
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        $table.on('tablefilter.reset', function(ev, data) {
            Frog.Model.api('myapi.get')
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        Frog.Model.api('myapi.get')
            .done(function(resp) {
                renderData(resp.data);
            });
    }.bind(this));
</script>

Feel free to tweak how it filters. It's just a rough first pass building on what I posted earlier. This has been a pleasant distraction from the project I'm currently on :)

Any thoughts @Graham Quince??

~ Chris

Edited by Chris.Smith
Corrected steal.import declarations necessary for forwards-compatibility
Link to comment
Share on other sites

@Chris.Smith

Trying to change from $(selector) to this.element.find(selector)

Had this which shows week two of a two week timetable (and hides week one)

 

$(".mywrapper #wk1btn").click(function() {

  $(".mywrapper #week1").hide();
  $(".mywrapper #week2").show();

)};

Now i've got this...

var $wk1btn = this.element.find(".mywrapper #wk1btn");
  
$wk1btn.click(function() {
    var $week1 = this.element.find(".mywrapper #week1");	*******
    	$week1.hide();
    var $week2 = this.element.find(".mywrapper #week1");
  	$week2.show();
});

But get an uncaught type error: cannot read property 'find' of undefined on line **** above.

Any thoughts?

Ta  Paul

Edited by pconkie
Link to comment
Share on other sites

7 minutes ago, pconkie said:

@Chris.Smith

Trying to change from $(selector) to this.element.find(selector)

Had this which shows week two of a two week timetable (and hides week one)

 


$(".mywrapper #wk1btn").click(function() {

  $(".mywrapper #week1").hide();
  $(".mywrapper #week2").show();

)};

Now i've got this...


var $wk1btn = this.element.find(".mywrapper #wk1btn");
  
$wk1btn.click(function() {
    var $week1 = this.element.find(".mywrapper #week1");	*******
    	$week1.hide();
    var $week2 = this.element.find(".mywrapper #week1");
  	$week2.show();
});

But get an uncaught type error: cannot read property 'find' of undefined on line **** above.

Any thoughts?

Ta  Paul

Hi Paul,

Remember when I said you probably won't find a need for .bind(this)?! Another alternative would be to "hoist" a variable in.

var $mywrapper = this.element.find(".mywrapper");

$mywrapper.find('#wk1btn').on('click', function(ev) {
  $mywrapper.find('#week1').hide();
  $mywrapper.find('#week2').show();
});

// ############################ OR #######################
var wklbtn = this.element.find(".mywrapper #wk1btn");

$wk1btn.click(function() {
	var $week1 = this.element.find('.mywrapper #week1'),
        $week2 = this.element.find('.mywrapper #week2');
  
  $week1.hide();
  $week2.show();
}.bind(this));

Hope this helps,

 

~ Chris

  • Like 2
Link to comment
Share on other sites

@Chris.Smith Wow. This is superb - far more than I had expected.

It will take me some time to process through, but this is exactly what I needed and more. I am very grateful. Glad it was a welcome distraction along the way too!

@pconkie I hope one day to be at some sort of level where I can decode your comments! Aiming for the sky and hitting the ceiling at the moment.

 

Thank you all for your help today.

M.

  • Like 1
Link to comment
Share on other sites

14 hours ago, mobrien said:

@Chris.Smith Wow. This is superb - far more than I had expected.

It will take me some time to process through, but this is exactly what I needed and more. I am very grateful. Glad it was a welcome distraction along the way too!

@pconkie I hope one day to be at some sort of level where I can decode your comments! Aiming for the sky and hitting the ceiling at the moment.

 

Thank you all for your help today.

M.

@mobrien,

No problems, let me know if you need any of it explaining / if you have any questions.

~ Chris

Link to comment
Share on other sites

@Chris.Smith

I had a good play with this yesterday evening and I'm not far from what appears in my mind's eye (see attachment). I removed some of the filter conditions so that Tutors have only those that they will need.

The final part of the jigsaw is whether there is the possibility of conditionally formatting the 'House' cells/column. i.e. that the 'Bowman' cell appears one colour, 'Fulford' another colour etc. I can see that there is javascript if, then else possibility, but I have no clue how this might be written into the new json code that I am now using.

Do you have any suggestions?

Michael

 

Capture.PNG

Link to comment
Share on other sites

19 minutes ago, mobrien said:

@Chris.Smith

I had a good play with this yesterday evening and I'm not far from what appears in my mind's eye (see attachment). I removed some of the filter conditions so that Tutors have only those that they will need.

The final part of the jigsaw is whether there is the possibility of conditionally formatting the 'House' cells/column. i.e. that the 'Bowman' cell appears one colour, 'Fulford' another colour etc. I can see that there is javascript if, then else possibility, but I have no clue how this might be written into the new json code that I am now using.

Do you have any suggestions?

Michael

 

Capture.PNG

Michael,

Would you want the house cells to be specific colours? i.e. Bowman is always red because that is the house's colour

 

~ Chris

Link to comment
Share on other sites

58 minutes ago, mobrien said:

@Chris.Smith Yes, that's right, a different colour for each House colour

Michael,

Assuming that it the colours are set (i.e. Bowman = red, Fulford = green etc) these could be set in styles as classes:

td.bowman {
  background-color: red;
}
td.fulford {
  background-color: green;
}

or data attributes:

td[data-house="bowman"] {
  background-color: 'red';
}
td[data-house="fulford"] {
  background-color: 'green';
}

Remember of course to set those attributes against the row when preparing the template either using $row.find('td[data-field=house]').addClass(datum.house) or using $row.find('td[data-field=house']).attr('data-house', datum.house).

If this isn't the case you could dynamically generate the colours using Hexadecimal and cache the results to ensure the consistency.

var house_colors = {}, getColorForHouse;

getColorForHouse = function(house_name) {
  var data = {};
  
  if (house_name in house_colors) {
    return house_colors[house_name];
  }
  
  data.color = (Math.floor(Math.random() * 16777215)).toString(16);
  data.text = (parseInt(data.color, 16) > 0xffffff / 2) ? '000000' : 'FFFFFF';
  
  house_colors[house_name] = data;
  
  return data;
};

renderData = function(data) {
  data.forEach(function(datum) {
    var $row = $template.clone(),
        color_data = getColorForHouse(datum.house);

    for (var key in datum) {
      $row.find('[data-field='+key+']').text(datum[key]);
    }

    houses[datum.house] = null;
    tutor[datum.tutor] = null;
    applyTableFilter();

    $row.find('[data-field=house]')
      .css({
        'backgroundColor': '#' + color_data.color,
        'color': '#' + color_data.text
      });
    
    $tbody.append($row);
  }.bind(this));
}.bind(this);

Let me know how you get on :)

 

~ Chris

Link to comment
Share on other sites

@Chris.Smith At the risk of taking far too much of your time here, I am stuck.

Please bear in mind that you are conversing with a total coding beginner here - I was good with formatting an HTML table, largely thanks to Google, but was totally lost within the scripting syntaxes that you have provided me with! I can follow what is taking place within the code, but have zero idea as to how to edit it with what you suggest!

So, Query1: should the definition of the cell colours appear after line 14?

Query 2: Where and how (important...) do I 'set the attributes against the row when preparing the template' as you suggest?

Ever grateful,

Michael

<style>
    .template {
        display: none;   
    }
  thead {
      background-color: red;      
         }
    th, tbody   {
       border-width: 1px;
       border-color: #e6e6e6;
       border-style: double;
       padding: 10px;
       font-size:14px
         }    
</style>

<table class="table table-striped">
  <colgroup>
    <col style="width: 25%">
    <col style="width: 15%">
    <col style="width: 15%">
    <col style="width: 15%">
    <col style="width: 15%">
    <col style="width: 15%">
  </colgroup>
    <thead>
        <th>Name</th>
        <th>House</th>
        <th>Tutor</th>
        <th>Achievement</th>
        <th>Behaviour</th>
        <th>Total</th>
    </thead>  
    <tbody></tbody>
</table>

<table class="template">
    <tbody>
        <tr>
            <td data-field="name"></td>
            <td data-field="house"></td>
            <td data-field="tutor"></td>
            <td data-field="achievement"></td>
            <td data-field="behaviour"></td>
            <td data-field="total"></td>
        </tr>
    </tbody>
</table>

<script type="text/javascript">
    var returnSuccess = function(params, output) {
            return [
                200,
                "success",
                {
                    "data": output,
                    "status": "success",
                    "params": params
                }
            ];
        },
        data = [
            { name: 'Student 1', house: 'Bowman', tutor: 'ACR', achievement: 1100, behaviour: 10, total: 1090 },
            { name: 'Student 2', house: 'Fulford', tutor: 'MOB', achievement: 1623, behaviour: 196, total: 1427 }
        ];

    $.fixture('GET myapi.get', function(original, settings, headers) {
        return returnSuccess(original.data, data);
    });

    $.fixture('GET myapi.sort', function(original, settings, headers) {
        var params = original.data,
            key = params.key,
            direction = params.direction,
            sorted_data = data.sort(function(a, b) {
                if (direction === 'asc') {
                    if (a[key] < b[key]) {
                        return -1;
                    } else if (a[key] > b[key]) {
                        return 1;
                    } else {
                        return 0;
                    }
                } else {
                    if (a[key] < b[key]) {
                        return 1;
                    } else if (a[key] > b[key]) {
                        return -1;
                    } else {
                        return 0;
                    }
                }
            });

        return returnSuccess(params, sorted_data);
    });

    $.fixture('GET myapi.filter', function(original, settings, headers) {
        var params = original.data,
            filtered_data = [];

        for (var key in params) {
            filtered_data = data.filter(function(datum) {
                if (datum[key].constructor.name !== "String") {
                    return parseInt(params[key], 10) <= datum[key];
                }
                return (new RegExp(params[key], 'gi')).test(datum[key]);
            });
        }
        filtered_data = Array.prototype.concat.apply([], filtered_data);
        filtered_data = filtered_data.filter(function(datum, index, self) {
            return self.indexOf(datum) === index;
        });

        return returnSuccess(params, filtered_data);
    });
</script>

<script>
    var promise, renderData, applyTableFilter;

    if (steal.then !== undefined) {
        promise = steal('//frogui/components/mysorter/mysorter.js', '//frogui/components/tablefilter/tablefilter.js');
    } else {
        promise = steal.import('frogui/components/mysorter/mysorter', 'frogui/components/tablefilter/tablefilter');
    }

    promise.then(function() {
        var $table = this.element.find('table:first'),
            $tbody = $table.children('tbody'),
            $template = this.element.find('.template tbody > tr'),
            houses = {}, tutor = {};

        $table.frogui_components_mysorter({
            headers: {
                name: 'asc',
                house: 'asc',
                tutor: 'asc',
                achievement: 'asc',
                behaviour: 'asc',
                total: 'asc'
            }
        });

        applyTableFilter = function() {
            $table.frogui_components_tablefilter({
                filters: {
                    name: {
                        type: 'text',
                        placeholder: 'Student Name',
                        data: [],
                        position: 0
                    },
                    house: {
                        type: 'select',
                        data: Object.keys(houses),
                        placeholder: 'House',
                        position: 1
                    },
                    tutor: {
                        type: 'select',
                        data: Object.keys(tutor),
                        placeholder: 'Tutor',
                        position: 2
                    }
                }
            });
            $table.find('.filter-row, .filter-submit-row').removeClass('hide');
        };

        renderData = function(data) {
            data.forEach(function(datum) {
                var $row = $template.clone();

                for (var key in datum) {
                    $row.find('[data-field='+key+']').text(datum[key]);
                }

                houses[datum.house] = null;
                tutor[datum.tutor] = null;
                applyTableFilter();

                $tbody.append($row);
            }.bind(this));
        }.bind(this);

        $table.on('mysorter.sort', function(ev, sort) {
            Frog.Model.api('myapi.sort', sort)
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        $table.on('tablefilter.submit', function(ev, data) {
            Frog.Model.api('myapi.filter', data)
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        $table.on('tablefilter.reset', function(ev, data) {
            Frog.Model.api('myapi.get')
                .done(function(resp) {
                    $tbody.empty();
                    renderData(resp.data);
                });
        });

        Frog.Model.api('myapi.get')
            .done(function(resp) {
                renderData(resp.data);
            });
    }.bind(this));
</script>

<script> 
var house_colors = {}, getColorForHouse;

getColorForHouse = function(house_name) {
  var data = {};
  
  if (house_name in house_colors) {
    return house_colors[house_name];
  }
  
  data.color = (Math.floor(Math.random() * 16777215)).toString(16);
  data.text = (parseInt(data.color, 16) > 0xffffff / 2) ? '000000' : 'FFFFFF';
  
  house_colors[house_name] = data;
  
  return data;
};

renderData = function(data) {
  data.forEach(function(datum) {
    var $row = $template.clone(),
        color_data = getColorForHouse(datum.house);

    for (var key in datum) {
      $row.find('[data-field='+key+']').text(datum[key]);
    }

    houses[datum.house] = null;
    tutor[datum.tutor] = null;
    applyTableFilter();

    $row.find('[data-field=house]')
      .css({
        'backgroundColor': '#' + color_data.color,
        'color': '#' + color_data.text
      });
    
    $tbody.append($row);
  }.bind(this));
}.bind(this);
</script>

 

Link to comment
Share on other sites

Hi @mobrien

Here are a couple of tips...

Styles need to go inside the <style> tag. So, yes, they could go where you suggested or above .template {, just as long as they come after <style> and before </style>.

<style>
.template {
  display: none;   
}
thead {
  background-color: red;      
}
th, tbody   {
  border-width: 1px;
  border-color: #e6e6e6;
  border-style: double;
  padding: 10px;
  font-size:14px
}
td.bowman {
  background-color: red;
}
td.fulford {
  background-color: green;
}
</style>

 

This is the mark-up for a typical table row in HTML

<tr>
  <td></td>
  <td></td>
  <td></td>
  <td></td>
  <td></td>
  <td></td>
</tr>

<tr></tr> - row (td or th inside)           <td></td> - standard cell (content goes inside)

Like your table the row above contains 6 cells which over a series of rows form the 6 columns.

One of your table rows looks like this:

<tr>
  <td data-field="name">Student 1</td>
  <td data-field="house">Bowman</td>
  <td data-field="tutor">ACR</td>
  <td data-field="achievement">1100</td>
  <td data-field="behaviour">10</td>
  <td data-field="total">1090</td>
</tr>

To add a style such as a specific colour to a cell you would need to give that cell (<td>) a style class (which we have defined in the <style> section of our code). What that looks like is this (look at line 2):

<tr>
  <td data-field="name">Student 1</td>
  <td data-field="house" class="bowman">Bowman</td>
  <td data-field="tutor">ACR</td>
  <td data-field="achievement">1100</td>
  <td data-field="behaviour">10</td>
  <td data-field="total">1090</td>
</tr>

You need to add the correct class to every house cell (<td>). That means when a student in Bowman House appears you add class="bowman" but when a student in Fulford House appears you add class="fulford" etc

Just to make things a bit more complicated:

  • your table is dynamically generated
  • your house names are capitalised but your style names are in lower case (which won't work)
  • your table already has a style applied to it which will over-ride what you want to do with the House <td>'s
//Find this line
$tbody.append($row);
                
//and make a new line below it and add this
$row.find('td[data-field=house]').addClass(datum.house.toLowerCase());
//this is what Chris gave you with .toLowerCase() added to make your class names match your House names.
//this line finds all of your House <td>'s (yes, all of them in one go) and applies the correct house style.

 

Next you have to either take the striped style off the table

/////this line
<table class="table table-striped">
  
////changed to this
<table class="table">

or over-ride the striped style by adding the !important css attribute

td.bowman {
	background-color: red !important;
}
td.fulford {
	background-color: green !important;
}

That should do it.

Let me know if you have any problems.

 

Edited by pconkie
Link to comment
Share on other sites

@pconkie Many thanks for this, that's really clear now. Thank you for taking the time to explain it to me.

So, I think I might have a finished product, as below. This will be used to display students' Rewards to tutors during tutor time. All in one place, with one set of data.

@Chris.Smith and @pconkie, what you have built for me here is far more than I could have expected. Thank you.

Once we have a live version with the Excel part to build the data, I'll post the results here.

Thank you again,

Michael

Capture.PNG

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...