From 235ef57a3d3e9eb2a3e3ad37c9067127eb88aec3 Mon Sep 17 00:00:00 2001 From: Damyon Wiese Date: Fri, 4 Sep 2015 14:59:04 +0800 Subject: [PATCH] MDL-51324 forms: Add a new course selector This is a squashed commit containing a number of changes: This is an ajax driven course selector that has searching etc. It can select single, or multiple courses. Make course selector accept a list of courses to exclude courseselector - lookup coursename on setValue Use the get_course_display_name_in_list function to generate the course names Add a throttle to auto-complete to reduce spamming the server Do a single query to fetch all the courses in the mform element when validation fails Fix core course search function to return results when there are less than 2 chars in the query. Handle setData with an empty array in new course selector --- course/externallib.php | 27 ++-- lib/amd/build/form-autocomplete.min.js | 2 +- lib/amd/build/form-course-selector.min.js | 1 + lib/amd/src/form-autocomplete.js | 13 +- lib/amd/src/form-course-selector.js | 76 ++++++++++++ lib/coursecatlib.php | 34 ++++-- lib/datalib.php | 20 +-- lib/db/services.php | 2 + lib/form/course.php | 142 ++++++++++++++++++++++ lib/formslib.php | 1 + lib/upgrade.txt | 5 + 11 files changed, 298 insertions(+), 25 deletions(-) create mode 100644 lib/amd/build/form-course-selector.min.js create mode 100644 lib/amd/src/form-course-selector.js create mode 100644 lib/form/course.php diff --git a/course/externallib.php b/course/externallib.php index da480bbe24a..62148282aba 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -2135,7 +2135,11 @@ class core_course_external extends external_api { (search, modulelist (only admins), blocklist (only admins), tagid)'), 'criteriavalue' => new external_value(PARAM_RAW, 'criteria value'), 'page' => new external_value(PARAM_INT, 'page number (0 based)', VALUE_DEFAULT, 0), - 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0) + 'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0), + 'requiredcapabilities' => new external_multiple_structure( + new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'), + VALUE_OPTIONAL + ) ) ); } @@ -2147,11 +2151,16 @@ class core_course_external extends external_api { * @param string $criteriavalue Criteria value * @param int $page Page number (for pagination) * @param int $perpage Items per page + * @param array $requiredcapabilities Optional list of required capabilities (used to filter the list). * @return array of course objects and warnings * @since Moodle 3.0 * @throws moodle_exception */ - public static function search_courses($criterianame, $criteriavalue, $page=0, $perpage=0) { + public static function search_courses($criterianame, + $criteriavalue, + $page=0, + $perpage=0, + $requiredcapabilities=array()) { global $CFG; require_once($CFG->libdir . '/coursecatlib.php'); @@ -2161,7 +2170,8 @@ class core_course_external extends external_api { 'criterianame' => $criterianame, 'criteriavalue' => $criteriavalue, 'page' => $page, - 'perpage' => $perpage + 'perpage' => $perpage, + 'requiredcapabilities' => $requiredcapabilities ); $params = self::validate_parameters(self::search_courses_parameters(), $parameters); @@ -2194,8 +2204,8 @@ class core_course_external extends external_api { } // Search the courses. - $courses = coursecat::search_courses($searchcriteria, $options); - $totalcount = coursecat::search_courses_count($searchcriteria); + $courses = coursecat::search_courses($searchcriteria, $options, $params['requiredcapabilities']); + $totalcount = coursecat::search_courses_count($searchcriteria, $options, $params['requiredcapabilities']); $finalcourses = array(); $categoriescache = array(); @@ -2244,10 +2254,12 @@ class core_course_external extends external_api { list($summary, $summaryformat) = external_format_text($course->summary, $course->summaryformat, $coursecontext->id, 'course', 'summary', null); + $displayname = get_course_display_name_for_list($course); $coursereturns = array(); $coursereturns['id'] = $course->id; - $coursereturns['fullname'] = $course->get_formatted_fullname(); - $coursereturns['shortname'] = $course->get_formatted_shortname(); + $coursereturns['fullname'] = external_format_string($course->fullname, $coursecontext->id); + $coursereturns['displayname'] = external_format_string($displayname, $coursecontext->id); + $coursereturns['shortname'] = external_format_string($course->shortname, $coursecontext->id); $coursereturns['categoryid'] = $course->category; $coursereturns['categoryname'] = $category->name; $coursereturns['summary'] = $summary; @@ -2281,6 +2293,7 @@ class core_course_external extends external_api { array( 'id' => new external_value(PARAM_INT, 'course id'), 'fullname' => new external_value(PARAM_TEXT, 'course full name'), + 'displayname' => new external_value(PARAM_TEXT, 'course display name'), 'shortname' => new external_value(PARAM_TEXT, 'course short name'), 'categoryid' => new external_value(PARAM_INT, 'category id'), 'categoryname' => new external_value(PARAM_TEXT, 'category name'), diff --git a/lib/amd/build/form-autocomplete.min.js b/lib/amd/build/form-autocomplete.min.js index 1adc2b0f5ea..9b1d49754bb 100644 --- a/lib/amd/build/form-autocomplete.min.js +++ b/lib/amd/build/form-autocomplete.min.js @@ -1 +1 @@ -define(["jquery","core/log","core/str","core/templates","core/notification"],function(a,b,c,d,e){var f={DOWN:40,ENTER:13,SPACE:32,ESCAPE:27,COMMA:188,UP:38},g=function(b,c){var d=a(document.getElementById(c.selectionId)),e=d.children("[aria-selected=true]").length;for(b%=e;0>b;)b+=e;var f=a(d.children("[aria-selected=true]").get(b)),g=c.selectionId+"-"+b;d.children().attr("data-active-selection",!1).attr("id",""),f.attr("data-active-selection",!0).attr("id",g),d.attr("aria-activedescendant",g)},h=function(b,c,f){var h=[],i=a(document.getElementById(c.selectionId)),j=i.attr("aria-activedescendant"),k=!1;j&&(k=a(document.getElementById(j)).attr("data-value")),f.children("option").each(function(b,c){a(c).prop("selected")&&h.push({label:a(c).html(),value:a(c).attr("value")})});var l=a.extend({items:h},b,c);d.render("core/form_autocomplete_selection",l).done(function(b){i.empty().append(a(b).html()),k!==!1&&i.children("[aria-selected=true]").each(function(b,d){a(d).attr("data-value")===k&&g(b,c)})}).fail(e.exception),f.change()},i=function(b,c,d,e){var f=a(d).attr("data-value");b.multiple&&e.children("option").each(function(b,c){a(c).attr("value")==f&&(a(c).prop("selected",!1),a(c).attr("data-iscustom")&&a(c).remove())}),h(b,c,e)},j=function(b,c){var d=a(document.getElementById(c.inputId)),e=a(document.getElementById(c.suggestionsId)),f=e.children("[aria-hidden=false]").length;for(b%=f;0>b;)b+=f;var g=a(e.children("[aria-hidden=false]").get(b)),h=a(e.children("[role=option]")).index(g),i=c.suggestionsId+"-"+h;e.children().attr("aria-selected",!1).attr("id",""),g.attr("aria-selected",!0).attr("id",i),d.attr("aria-activedescendant",i);var j=g.offset().top-e.offset().top+e.scrollTop()-e.height()/2;e.animate({scrollTop:j},100)},k=function(b){var c=a(document.getElementById(b.suggestionsId)),d=c.children("[aria-selected=true]"),e=c.children("[aria-hidden=false]").index(d);j(e+1,b)},l=function(b){var c=a(document.getElementById(b.selectionId)),d=c.children("[data-active-selection=true]");if(!d)return void g(0,b);var e=c.children("[aria-selected=true]").index(d);g(e-1,b)},m=function(b){var c=a(document.getElementById(b.selectionId)),d=c.children("[data-active-selection=true]");if(!d)return void g(0,b);var e=c.children("[aria-selected=true]").index(d);g(e+1,b)},n=function(b){var c=a(document.getElementById(b.suggestionsId)),d=c.children("[aria-selected=true]"),e=c.children("[aria-hidden=false]").index(d);j(e-1,b)},o=function(b){var c=a(document.getElementById(b.inputId)),d=a(document.getElementById(b.suggestionsId));c.attr("aria-expanded",!1).attr("aria-activedescendant",b.selectionId),d.hide().attr("aria-hidden",!0)},p=function(b,f,g,h){var i=a(document.getElementById(f.inputId)),k=a(document.getElementById(f.suggestionsId)),l=!1,m=[];h.children("option").each(function(b,c){a(c).prop("selected")!==!0&&(m[m.length]={label:c.innerHTML,value:a(c).attr("value")})});var n=f.caseSensitive?g:g.toLocaleLowerCase(),o=a.extend({options:m},b,f);d.render("core/form_autocomplete_suggestions",o).done(function(d){k.replaceWith(d),k=a(document.getElementById(f.suggestionsId)),k.show().attr("aria-hidden",!1),k.children().each(function(c,d){d=a(d),b.caseSensitive&&d.text().indexOf(n)>-1||!b.caseSensitive&&d.text().toLocaleLowerCase().indexOf(n)>-1?(d.show().attr("aria-hidden",!1),l=!0):d.hide().attr("aria-hidden",!0)}),i.attr("aria-expanded",!0),l?b.tags||j(0,f):c.get_string("nosuggestions","form").done(function(a){k.html(a)})}).fail(e.exception)},q=function(b,c,d){var e=a(document.getElementById(c.inputId)),f=e.val(),g=f.split(","),i=!1;a.each(g,function(c,e){if(e=e.trim(),""!==e&&(b.multiple||d.children("option").prop("selected",!1),d.children("option").each(function(b,c){a(c).attr("value")==e&&(i=!0,a(c).prop("selected",!0))}),!i)){var f=a("