diff --git a/amd/build/table_sort.min.js b/amd/build/table_sort.min.js new file mode 100644 index 00000000..deaca994 --- /dev/null +++ b/amd/build/table_sort.min.js @@ -0,0 +1,2 @@ +define ("mod_questionnaire/table_sort",["jquery"],function(c){var a={init:function init(){M.util.js_pending("mod_questionnaire_tablesort");c(".qn-handcursor").on("click",a.sortcolumn);M.util.js_complete("mod_questionnaire_tablesort")},sortcolumn:function sortcolumn(a){a.preventDefault();var d=c(this).index(),e=c(this).closest("table").attr("id");c(this).siblings().find("span[class^=\"icon-container-\"]").hide();c(this).siblings().removeClass("asc desc");c(this).find("span[class^=\"icon-container-\"]").removeAttr("style");if(c(this).is(".asc")){c(this).removeClass("asc").addClass("desc");sortOrder=-1}else{c(this).addClass("asc").removeClass("desc");sortOrder=1}var b=c(this).closest("table").find("tbody >tr:has(td.cell)").get();b.sort(function(e,a){var b=c(e).children("td").eq(d).text(),f=c(a).children("td").eq(d).text(),g=/^\d{2}.*\d{4},/;if(g.test(b)&&g.test(f)){b=new Date(b);f=new Date(f);return bf?sortOrder:0}else if(c.isNumeric(b)&&c.isNumeric(f)){return 1==sortOrder?b-f:f-b}else{return bf?sortOrder:0}});c.each(b,function(a,b){tableid=c("#"+e+" tbody");tableid.append(b)})}};return a}); +//# sourceMappingURL=table_sort.min.js.map diff --git a/amd/build/table_sort.min.js.map b/amd/build/table_sort.min.js.map new file mode 100644 index 00000000..514a2a85 --- /dev/null +++ b/amd/build/table_sort.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/table_sort.js"],"names":["define","$","t","init","M","util","js_pending","on","sortcolumn","js_complete","e","preventDefault","col","index","id","closest","attr","siblings","find","hide","removeClass","removeAttr","is","addClass","sortOrder","arrData","get","sort","a","b","val1","children","eq","text","val2","dateregx","test","Date","isNumeric","each","row","tableid","append"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JavaScript library for questionnaire response table sorting.\n *\n * @module mod_questionnaire/table_sort\n * @package mod_questionnaire\n * @copyright\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n\n var t = {\n /**\n * Initialise the event listeners.\n *\n */\n init: function() {\n M.util.js_pending('mod_questionnaire_tablesort');\n $(\".qn-handcursor\").on('click', t.sortcolumn);\n M.util.js_complete('mod_questionnaire_tablesort');\n },\n\n /**\n * Javascript for sorting s'Text Box' Response.\n *\n */\n sortcolumn: function(e) {\n e.preventDefault();\n var col = $(this).index();\n var id = $(this).closest('table').attr('id');\n $(this).siblings().find('span[class^=\"icon-container-\"]').hide();\n $(this).siblings().removeClass('asc desc');\n $(this).find('span[class^=\"icon-container-\"]').removeAttr('style');\n if ($(this).is('.asc')) {\n $(this).removeClass('asc').addClass('desc');\n sortOrder = -1;\n } else {\n $(this).addClass('asc').removeClass('desc');\n sortOrder = 1;\n }\n var arrData = $(this).closest('table').find('tbody >tr:has(td.cell)').get();\n arrData.sort(function (a, b) {\n var val1 = $(a).children('td').eq(col).text();\n var val2 = $(b).children('td').eq(col).text();\n // Regex to check for date sorting.\n var dateregx = /^\\d{2}.*\\d{4},/;\n if (dateregx.test(val1) && dateregx.test(val2)) {\n val1 = new Date(val1);\n val2 = new Date(val2);\n return (val1 < val2) ? -sortOrder : (val1 > val2) ? sortOrder : 0;\n } else if ($.isNumeric(val1) && $.isNumeric(val2)) {\n return sortOrder == 1 ? val1 - val2 : val2 - val1;\n } else {\n return (val1 < val2) ? -sortOrder : (val1 > val2) ? sortOrder : 0;\n }\n });\n /* Append the sorted rows to tbody*/\n $.each(arrData, function (index, row) {\n tableid = $('#' + id + ' tbody');\n tableid.append(row);\n });\n },\n };\n return t;\n});"],"file":"table_sort.min.js"} \ No newline at end of file diff --git a/amd/src/table_sort.js b/amd/src/table_sort.js new file mode 100644 index 00000000..e95a3d59 --- /dev/null +++ b/amd/src/table_sort.js @@ -0,0 +1,79 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * JavaScript library for questionnaire response table sorting. + * + * @module mod_questionnaire/table_sort + * @package mod_questionnaire + * @copyright + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery'], function($) { + + var t = { + /** + * Initialise the event listeners. + * + */ + init: function() { + M.util.js_pending('mod_questionnaire_tablesort'); + $(".qn-handcursor").on('click', t.sortcolumn); + M.util.js_complete('mod_questionnaire_tablesort'); + }, + + /** + * Javascript for sorting s'Text Box' Response. + * + */ + sortcolumn: function(e) { + e.preventDefault(); + var col = $(this).index(); + var id = $(this).closest('table').attr('id'); + $(this).siblings().find('span[class^="icon-container-"]').hide(); + $(this).siblings().removeClass('asc desc'); + $(this).find('span[class^="icon-container-"]').removeAttr('style'); + if ($(this).is('.asc')) { + $(this).removeClass('asc').addClass('desc'); + sortOrder = -1; + } else { + $(this).addClass('asc').removeClass('desc'); + sortOrder = 1; + } + var arrData = $(this).closest('table').find('tbody >tr:has(td.cell)').get(); + arrData.sort(function (a, b) { + var val1 = $(a).children('td').eq(col).text(); + var val2 = $(b).children('td').eq(col).text(); + // Regex to check for date sorting. + var dateregx = /^\d{2}.*\d{4},/; + if (dateregx.test(val1) && dateregx.test(val2)) { + val1 = new Date(val1); + val2 = new Date(val2); + return (val1 < val2) ? -sortOrder : (val1 > val2) ? sortOrder : 0; + } else if ($.isNumeric(val1) && $.isNumeric(val2)) { + return sortOrder == 1 ? val1 - val2 : val2 - val1; + } else { + return (val1 < val2) ? -sortOrder : (val1 > val2) ? sortOrder : 0; + } + }); + /* Append the sorted rows to tbody*/ + $.each(arrData, function (index, row) { + tableid = $('#' + id + ' tbody'); + tableid.append(row); + }); + }, + }; + return t; +}); \ No newline at end of file diff --git a/classes/responsetype/text.php b/classes/responsetype/text.php index b03b7d47..5a6cc66f 100644 --- a/classes/responsetype/text.php +++ b/classes/responsetype/text.php @@ -116,7 +116,7 @@ public function get_results($rids=false, $anonymous=false) { 'WHERE question_id=' . $this->question->id . $rsql . ' AND t.response_id = r.id' . ' AND u.id = r.userid ' . - 'ORDER BY u.lastname, u.firstname, r.submitted'; + 'ORDER BY r.submitted DESC'; } return $DB->get_records_sql($sql, $params); } @@ -204,10 +204,15 @@ public function get_results_tags($weights, $participants, $respondents, $showtot } // The 'evencolor' attribute is used by the PDF template. $response->evencolor = $evencolor; + $response->date = userdate($row->submitted, get_string('strftimedatetime')); $pagetags->responses[] = (object)['response' => $response]; $evencolor = !$evencolor; } - + // sort table only when row count is greater than one. + if (count($weights) > 1) { + $pagetags->sortresponse = true; + } + $pagetags->tableid = $this->question->id; if ($showtotals == 1) { $pagetags->total = new \stdClass(); $pagetags->total->total = "$respondents/$participants"; diff --git a/styles.css b/styles.css index 41dcd324..ad8906ec 100644 --- a/styles.css +++ b/styles.css @@ -426,7 +426,23 @@ td.selected { #page-mod-questionnaire-report .generaltable.questionnairereport td { border: 1px solid silver; } +#page-mod-questionnaire-report .generaltable[id] th.qn-handcursor { + cursor: pointer; +} + +#page-mod-questionnaire-report th.c0 a span[class^='icon-container-'], +#page-mod-questionnaire-report th.c1 a span.icon-container-asc, +#page-mod-questionnaire-report th.asc a span.icon-container-desc, +#page-mod-questionnaire-report th.desc a span.icon-container-asc { + display: none; +} + +#page-mod-questionnaire-report th.asc a span.icon-container-asc, +#page-mod-questionnaire-report th.desc a span.icon-container-desc { + display: inline-block; +} +#page-mod-questionnaire-report .frtlst, .qn-container .smalltext { font-size: 0.75em; } diff --git a/templates/reportpage.mustache b/templates/reportpage.mustache index e6c17b49..ad6c97bc 100644 --- a/templates/reportpage.mustache +++ b/templates/reportpage.mustache @@ -83,4 +83,11 @@ {{#responses}}{{{.}}}{{/responses}} {{#bottomnavigationbar}}
{{{.}}}
{{/bottomnavigationbar}} - \ No newline at end of file + +{{#js}} + require(['jquery', 'mod_questionnaire/table_sort'], function($, TableSort) { + $(document).ready(function() { + TableSort.init(); + }); + }); +{{/js}} \ No newline at end of file diff --git a/templates/results_text.mustache b/templates/results_text.mustache index 662e9052..6abc511b 100644 --- a/templates/results_text.mustache +++ b/templates/results_text.mustache @@ -50,11 +50,51 @@ }} {{#responses.0}} - +
- - + + {{/tableid}} + + @@ -63,21 +103,29 @@ {{#response}} - + {{#tableid}} + + {{/tableid}} + {{/response}} {{/responses}} +{{#responses.0}} + + +{{/responses.0}} {{#total}} - + - + {{/total}} {{#responses.0}} - +
{{#str}} respondent, mod_questionnaire {{/str}}{{#str}} response, mod_questionnaire {{/str}} + {{#sortresponse}} + + {{#str}} respondent, mod_questionnaire {{/str}} + + {{#pix}} t/sort_asc, moodle, {{#str}} sortbyx {{/str}}{{/pix}} + + + {{#pix}} t/sort_desc, moodle, {{#str}} sortbyxreverse {{/str}}{{/pix}} + + + {{/sortresponse}} + {{^sortresponse}} + {{#str}} respondent, mod_questionnaire {{/str}} + {{/sortresponse}} + {{#tableid}} +
{{#str}} firstname {{/str}}/{{#str}} lastname {{/str}}
+
+ {{#sortresponse}} + + {{#str}} date {{/str}} + + {{#pix}} t/sort_asc, moodle, {{#str}} sortbyx {{/str}}{{/pix}} + + + {{#pix}} t/sort_desc, moodle, {{#str}} sortbyxreverse {{/str}}{{/pix}} + + + {{/sortresponse}} + {{^sortresponse}} + {{#str}} date {{/str}} + {{/sortresponse}} + {{#str}} response, mod_questionnaire{{/str}}
{{{response.respondent}}}{{{response.text}}}{{{response.date}}}{{{response.text}}}
{{#str}} totalresponses, mod_questionnaire{{/str}} + {{#str}} totalresponses, mod_questionnaire{{/str}} {{total}}
{{/responses.0}} {{^responses}} diff --git a/tests/behat/table_sort.feature b/tests/behat/table_sort.feature new file mode 100644 index 00000000..d45d1a5c --- /dev/null +++ b/tests/behat/table_sort.feature @@ -0,0 +1,119 @@ +@mod @mod_questionnaire +Feature: In questionnaire, Teacher should be able to sort Text box responses on date and username. + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Wendi | Blake | student1@example.com | + | student2 | Jim | Lai | student2@example.com | + | student3 | Stephan | Parker | student3@example.com | + | student4 | Bobby | Anderson | student4@example.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | + | student4 | C1 | student | + And the following "activities" exist: + | activity | name | description | course | idnumber | resume | navigate | progressbar | + | questionnaire | Test questionnaire | Test questionnaire description | C1 | questionnaire0 | 1 | 1 | 1 | + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + And I navigate to "Questions" in current page administration + And I add a "Text Box" question and I fill the form with: + | Question Name | Q1 | + | Yes | y | + | Question Text | Enter testbox question | + And I add a "Numeric" question and I fill the form with: + | Question Name | Q2 | + | No | y | + | Question Text | Enter a number | + And I add a "Yes/No" question and I fill the form with: + | Question Name | Q3 | + | Yes | y | + | Question Text | Would you like to answer another question? | + And I log out + + @javascript + Scenario: No sorting for table with single row + And I log in as "student1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + When I navigate to "Answer the questions..." in current page administration + And I set the field "Enter testbox question" to "Test 1" + And I set the field "Enter a number" to "2" + And I click on "Yes" "radio" + And I press "Submit questionnaire" + And I press "Continue" + And I log out + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + And I navigate to "View All Responses" in current page administration + And "(//table[string-length(@id) > 1]//thead//th[2]//span[contains(@class,'icon-container-desc')])[1]" "xpath_element" should not exist + And "(//table[string-length(@id) > 1]//thead//th[2]//span[contains(@class,'icon-container-asc')])[1]" "xpath_element" should not exist + + @javascript + Scenario: Sort the table with multiple row + And I log in as "student1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + When I navigate to "Answer the questions..." in current page administration + And I set the field "Enter testbox question" to "Test 1" + And I set the field "Enter a number" to "2" + And I click on "Yes" "radio" + And I press "Submit questionnaire" + And I press "Continue" + And I log out + And I log in as "student2" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + When I navigate to "Answer the questions..." in current page administration + And I set the field "Enter testbox question" to "StuTest 2" + And I set the field "Enter a number" to "1" + And I click on "Yes" "radio" + And I press "Submit questionnaire" + And I press "Continue" + And I log out + And I log in as "student3" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + When I navigate to "Answer the questions..." in current page administration + And I set the field "Enter testbox question" to "Test 3" + And I set the field "Enter a number" to "3" + And I click on "Yes" "radio" + And I press "Submit questionnaire" + And I press "Continue" + And I log out + And I log in as "student4" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + When I navigate to "Answer the questions..." in current page administration + And I set the field "Enter testbox question" to "Test 4" + And I set the field "Enter a number" to "4" + And I click on "Yes" "radio" + And I press "Submit questionnaire" + And I press "Continue" + And I log out + And I log in as "teacher1" + And I am on "Course 1" course homepage + And I follow "Test questionnaire" + And I navigate to "View All Responses" in current page administration + And I click on "(//table[string-length(@id) > 1]//thead//th[contains(@class, 'c0 qn-handcursor')])[1]" "xpath_element" + And "(//table[string-length(@id) > 1]//thead//th[contains(@class, 'asc')])[1]" "xpath_element" should exist + And I should see "Bobby Anderson" in the "(//table[string-length(@id) > 1]//tbody//tr[1]//td[1])[1]" "xpath_element" + And I should see "Jim Lai" in the "(//table[string-length(@id) > 1]//tbody//tr[2]//td[1])[1]" "xpath_element" + And I should see "Stephan Parker" in the "(//table[string-length(@id) > 1]//tbody//tr[3]//td[1])[1]" "xpath_element" + And I should see "Wendi Blake" in the "(//table[string-length(@id) > 1]//tbody//tr[4]//td[1])[1]" "xpath_element" + And I click on "(//table[string-length(@id) > 1]//thead//th[contains(@class, 'c0 qn-handcursor')])[1]" "xpath_element" + And "(//table[string-length(@id) > 1]//thead//th[contains(@class, 'desc')])[1]" "xpath_element" should exist + And I should see "Wendi Blake" in the "(//table[string-length(@id) > 1]//tbody//tr[1]//td[1])[1]" "xpath_element" + And I should see "Stephan Parker" in the "(//table[string-length(@id) > 1]//tbody//tr[2]//td[1])[1]" "xpath_element" + And I should see "Jim Lai" in the "(//table[string-length(@id) > 1]//tbody//tr[3]//td[1])[1]" "xpath_element" + And I should see "Bobby Anderson" in the "(//table[string-length(@id) > 1]//tbody//tr[4]//td[1])[1]" "xpath_element"