mobrien Posted May 8, 2017 Posted May 8, 2017 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>
pconkie Posted May 8, 2017 Posted May 8, 2017 (edited) 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 May 8, 2017 by pconkie
mobrien Posted May 9, 2017 Author Posted May 9, 2017 @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> 1
Chris.Smith Posted May 9, 2017 Posted May 9, 2017 Hi @mobrien, @pconkie's approach is a good start but there are a couple of things that you should both be aware of: 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. Fortunately, FrogOS already has tablesorter under the hood. Using ID's is problematic, especially if you want to reuse this code in multiple places. Combine this.element and use either a tag lookup or add a class I recommend the using the tag. Using $(document).ready may produce random results as the document is already ready 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 2
mobrien Posted May 9, 2017 Author Posted May 9, 2017 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
pconkie Posted May 9, 2017 Posted May 9, 2017 (edited) 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 May 9, 2017 by pconkie
Chris.Smith Posted May 9, 2017 Posted May 9, 2017 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 1
pconkie Posted May 9, 2017 Posted May 9, 2017 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.....
Chris.Smith Posted May 9, 2017 Posted May 9, 2017 (edited) 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 May 10, 2017 by Chris.Smith Corrected steal.import declarations necessary for forwards-compatibility
pconkie Posted May 9, 2017 Posted May 9, 2017 (edited) @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 May 9, 2017 by pconkie
Chris.Smith Posted May 9, 2017 Posted May 9, 2017 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 2
mobrien Posted May 9, 2017 Author Posted May 9, 2017 @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. 1
Chris.Smith Posted May 10, 2017 Posted May 10, 2017 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
mobrien Posted May 10, 2017 Author Posted May 10, 2017 @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
Chris.Smith Posted May 10, 2017 Posted May 10, 2017 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 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
mobrien Posted May 10, 2017 Author Posted May 10, 2017 10 minutes ago, Chris.Smith said: 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 @Chris.Smith Yes, that's right, a different colour for each House colour
Chris.Smith Posted May 10, 2017 Posted May 10, 2017 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
mobrien Posted May 10, 2017 Author Posted May 10, 2017 @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>
pconkie Posted May 10, 2017 Posted May 10, 2017 (edited) 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 May 10, 2017 by pconkie
mobrien Posted May 11, 2017 Author Posted May 11, 2017 @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 2
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now