diff --git a/admin/admin_settings_search_form.php b/admin/admin_settings_search_form.php deleted file mode 100644 index ad42300b853..00000000000 --- a/admin/admin_settings_search_form.php +++ /dev/null @@ -1,48 +0,0 @@ -. - -/** - * Admin settings search form - * - * @package admin - * @copyright 2016 Damyon Wiese - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once $CFG->libdir.'/formslib.php'; - -/** - * Admin settings search form - * - * @package admin - * @copyright 2016 Damyon Wiese - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class admin_settings_search_form extends moodleform { - function definition () { - $mform = $this->_form; - - //$mform->addElement('header', 'settingsheader', get_string('search', 'admin')); - $elements = []; - $elements[] = $mform->createElement('text', 'query', get_string('query', 'admin')); - $elements[] = $mform->createElement('submit', 'search', get_string('search')); - $mform->addGroup($elements); - $mform->setType('query', PARAM_RAW); - $mform->setDefault('query', optional_param('query', '', PARAM_RAW)); - } -} diff --git a/admin/search.php b/admin/search.php index 55395171b53..98ec900ed44 100644 --- a/admin/search.php +++ b/admin/search.php @@ -68,9 +68,16 @@ if ($errormsg !== '') { $showsettingslinks = true; if ($hassiteconfig) { - require_once("admin_settings_search_form.php"); - $form = new admin_settings_search_form(); - $form->display(); + $data = [ + 'action' => new moodle_url('/admin/search.php'), + 'btnclass' => 'btn-primary', + 'inputname' => 'query', + 'searchstring' => get_string('search'), + 'query' => $query, + 'extraclasses' => 'd-flex justify-content-center' + ]; + echo $OUTPUT->render_from_template('core/search_input', $data); + echo '
'; if ($query) { echo admin_search_settings_html($query); diff --git a/admin/tests/behat/behat_admin.php b/admin/tests/behat/behat_admin.php index af027dec4c8..98f5d91de10 100644 --- a/admin/tests/behat/behat_admin.php +++ b/admin/tests/behat/behat_admin.php @@ -56,7 +56,7 @@ class behat_admin extends behat_base { $this->execute('behat_navigation::i_select_from_flat_navigation_drawer', [get_string('administrationsite')]); // Search by label. - $this->execute('behat_forms::i_set_the_field_to', [get_string('query', 'admin'), $label]); + $this->execute('behat_forms::i_set_the_field_to', [get_string('search'), $label]); $this->execute("behat_forms::press_button", get_string('search', 'admin')); // Admin settings does not use the same DOM structure than other moodle forms diff --git a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js index 1c0553405bc..ed3d79cd36e 100644 --- a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js +++ b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js @@ -53,6 +53,13 @@ SEARCH.prototype = { * @protected */ button: null, + /** + * The cancel button for the form. + * @property button + * @type Node + * @protected + */ + cancel: null, /** * The last search node if there is one. * If there is a last search node then the last search term will be persisted between requests. @@ -76,17 +83,28 @@ SEARCH.prototype = { this.button = this.form.all('input[type=submit]'); this.lastsearch = this.form.one('input[name=search]'); - var div = Y.Node.create('
'), - label = Y.Node.create(''); - this.input = Y.Node.create(''); + var div = Y.Node.create('
'), + label = Y.Node.create(''); + this.cancel = Y.Node.create('' + + '' + + ''); + this.input = Y.Node.create(''); - div.append(label).append(this.input); + div.append(label).append(this.input).append(this.cancel); this.select.insert(div, 'before'); this.input.on('keyup', this.typed, this); this.select.on('change', this.validate, this); + this.cancel.on('click', function() { + this.input.set('value', ''); + this.typed(); + }, this); + if (this.lastsearch) { this.input.set('value', this.lastsearch.get('value')); this.typed(); @@ -133,6 +151,11 @@ SEARCH.prototype = { last.set('selected', true); } } + if (search !== '') { + this.cancel.removeClass("d-none"); + } else { + this.cancel.addClass("d-none"); + } this.validate(); } }; diff --git a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js index 6520ca28100..30e97fccc72 100644 --- a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js +++ b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js @@ -1 +1 @@ -YUI.add("moodle-tool_capability-search",function(i,t){var e=function(){e.superclass.constructor.apply(this,arguments)};e.prototype={form:null,select:null,selectoptions:{},input:null,button:null,lastsearch:null,initializer:function(){this.form=i.one("#capability-overview-form"),this.select=this.form.one("select[data-search=capability]"),this.select.setStyle("minWidth",this.select.get("offsetWidth")),this.select.get("options").each(function(t){var e=t.get("value");this.selectoptions[e]=t},this),this.button=this.form.all("input[type=submit]"),this.lastsearch=this.form.one("input[name=search]");var t=i.Node.create('
'),e=i.Node.create('");this.input=i.Node.create(''),t.append(e).append(this.input),this.select.insert(t,"before"),this.input.on("keyup",this.typed,this),this.select.on("change",this.validate,this),this.lastsearch&&(this.input.set("value",this.lastsearch.get("value")),this.typed(),this.select.one("option[selected]")&&this.select.set("scrollTop",this.select.one("option[selected]").get("getX"))),this.validate()},validate:function(){this.button.set("disabled",""===this.select.get("value"))},typed:function(){var t,e=this.input.get("value"),i=0,s=null;for(t in this.lastsearch&&this.lastsearch.set("value",e),this.select.all("option").remove(),this.selectoptions)0<=t.indexOf(e)&&(i++,s=this.selectoptions[t],this.select.append(this.selectoptions[t]));0===i?this.input.addClass("error"):(this.input.removeClass("error"),1===i&&s.set("selected",!0)),this.validate()}},i.extend(e,i.Base,e.prototype,{NAME:"tool_capability-search",ATTRS:{strsearch:{}}}),M.tool_capability=M.tool_capability||{},M.tool_capability.init_capability_search=function(t){new e(t)}},"@VERSION@",{requires:["base","node"]}); \ No newline at end of file +YUI.add("moodle-tool_capability-search",function(s,t){var e=function(){e.superclass.constructor.apply(this,arguments)};e.prototype={form:null,select:null,selectoptions:{},input:null,button:null,cancel:null,lastsearch:null,initializer:function(){this.form=s.one("#capability-overview-form"),this.select=this.form.one("select[data-search=capability]"),this.select.setStyle("minWidth",this.select.get("offsetWidth")),this.select.get("options").each(function(t){var e=t.get("value");this.selectoptions[e]=t},this),this.button=this.form.all("input[type=submit]"),this.lastsearch=this.form.one("input[name=search]");var t=s.Node.create('
'),e=s.Node.create('");this.cancel=s.Node.create(''),this.input=s.Node.create(''),t.append(e).append(this.input).append(this.cancel),this.select.insert(t,"before"),this.input.on("keyup",this.typed,this),this.select.on("change",this.validate,this),this.cancel.on("click",function(){this.input.set("value",""),this.typed()},this),this.lastsearch&&(this.input.set("value",this.lastsearch.get("value")),this.typed(),this.select.one("option[selected]")&&this.select.set("scrollTop",this.select.one("option[selected]").get("getX"))),this.validate()},validate:function(){this.button.set("disabled",""===this.select.get("value"))},typed:function(){var t,e=this.input.get("value"),s=0,i=null;for(t in this.lastsearch&&this.lastsearch.set("value",e),this.select.all("option").remove(),this.selectoptions)0<=t.indexOf(e)&&(s++,i=this.selectoptions[t],this.select.append(this.selectoptions[t]));0===s?this.input.addClass("error"):(this.input.removeClass("error"),1===s&&i.set("selected",!0)),""!==e?this.cancel.removeClass("d-none"):this.cancel.addClass("d-none"),this.validate()}},s.extend(e,s.Base,e.prototype,{NAME:"tool_capability-search",ATTRS:{strsearch:{}}}),M.tool_capability=M.tool_capability||{},M.tool_capability.init_capability_search=function(t){new e(t)}},"@VERSION@",{requires:["base","node"]}); \ No newline at end of file diff --git a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js index 1c0553405bc..ed3d79cd36e 100644 --- a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js +++ b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js @@ -53,6 +53,13 @@ SEARCH.prototype = { * @protected */ button: null, + /** + * The cancel button for the form. + * @property button + * @type Node + * @protected + */ + cancel: null, /** * The last search node if there is one. * If there is a last search node then the last search term will be persisted between requests. @@ -76,17 +83,28 @@ SEARCH.prototype = { this.button = this.form.all('input[type=submit]'); this.lastsearch = this.form.one('input[name=search]'); - var div = Y.Node.create('
'), - label = Y.Node.create(''); - this.input = Y.Node.create(''); + var div = Y.Node.create('
'), + label = Y.Node.create(''); + this.cancel = Y.Node.create('' + + '' + + ''); + this.input = Y.Node.create(''); - div.append(label).append(this.input); + div.append(label).append(this.input).append(this.cancel); this.select.insert(div, 'before'); this.input.on('keyup', this.typed, this); this.select.on('change', this.validate, this); + this.cancel.on('click', function() { + this.input.set('value', ''); + this.typed(); + }, this); + if (this.lastsearch) { this.input.set('value', this.lastsearch.get('value')); this.typed(); @@ -133,6 +151,11 @@ SEARCH.prototype = { last.set('selected', true); } } + if (search !== '') { + this.cancel.removeClass("d-none"); + } else { + this.cancel.addClass("d-none"); + } this.validate(); } }; diff --git a/admin/tool/capability/yui/src/search/js/search.js b/admin/tool/capability/yui/src/search/js/search.js index 1e4b96c9d91..71bd1525766 100644 --- a/admin/tool/capability/yui/src/search/js/search.js +++ b/admin/tool/capability/yui/src/search/js/search.js @@ -51,6 +51,13 @@ SEARCH.prototype = { * @protected */ button: null, + /** + * The cancel button for the form. + * @property button + * @type Node + * @protected + */ + cancel: null, /** * The last search node if there is one. * If there is a last search node then the last search term will be persisted between requests. @@ -74,17 +81,28 @@ SEARCH.prototype = { this.button = this.form.all('input[type=submit]'); this.lastsearch = this.form.one('input[name=search]'); - var div = Y.Node.create('
'), - label = Y.Node.create(''); - this.input = Y.Node.create(''); + var div = Y.Node.create('
'), + label = Y.Node.create(''); + this.cancel = Y.Node.create('' + + '' + + ''); + this.input = Y.Node.create(''); - div.append(label).append(this.input); + div.append(label).append(this.input).append(this.cancel); this.select.insert(div, 'before'); this.input.on('keyup', this.typed, this); this.select.on('change', this.validate, this); + this.cancel.on('click', function() { + this.input.set('value', ''); + this.typed(); + }, this); + if (this.lastsearch) { this.input.set('value', this.lastsearch.get('value')); this.typed(); @@ -131,6 +149,11 @@ SEARCH.prototype = { last.set('selected', true); } } + if (search !== '') { + this.cancel.removeClass("d-none"); + } else { + this.cancel.addClass("d-none"); + } this.validate(); } }; diff --git a/admin/tool/templatelibrary/amd/build/search.min.js b/admin/tool/templatelibrary/amd/build/search.min.js index ea1aac52977..74f8c50e9b3 100644 --- a/admin/tool/templatelibrary/amd/build/search.min.js +++ b/admin/tool/templatelibrary/amd/build/search.min.js @@ -1,2 +1,2 @@ -define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-field=\"search\"]").val();document.location.hash=f;b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-field=\"search\"]",k);a("[data-field=\"search\"]").val(document.location.hash.replace("#",""));h(f.theme);return{}}); +define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-region=\"list-templates\"] [data-region=\"input\"]").val();if(""!==f){a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").removeClass("d-none")}else{a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").addClass("d-none")}document.location.hash=f;b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-region=\"input\"]",k);a("[data-action=\"clearsearch\"]").on("click",function(){a("[data-region=\"input\"]").val("");h(f.theme);a(this).addClass("d-none")});a("[data-region=\"input\"]").val(document.location.hash.replace("#",""));h(f.theme);return{}}); //# sourceMappingURL=search.min.js.map diff --git a/admin/tool/templatelibrary/amd/build/search.min.js.map b/admin/tool/templatelibrary/amd/build/search.min.js.map index 1611c7e245d..0245bb86ee5 100644 --- a/admin/tool/templatelibrary/amd/build/search.min.js.map +++ b/admin/tool/templatelibrary/amd/build/search.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/search.js"],"names":["define","$","ajax","log","notification","templates","config","reloadListTemplate","templateList","render","done","result","js","replaceNode","fail","exception","refreshSearch","themename","componentStr","val","searchStr","document","location","hash","call","methodname","args","component","search","throttle","queueRefresh","callback","delay","window","clearTimeout","setTimeout","changeHandler","bind","theme","on","replace"],"mappings":"AAuBAA,OAAM,+BAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,UAAxB,CAAoC,mBAApC,CAAyD,gBAAzD,CAA2E,aAA3E,CAAD,CACC,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAAuBC,CAAvB,CAAqCC,CAArC,CAAgDC,CAAhD,CAAwD,IAQvDC,CAAAA,CAAkB,CAAG,SAASC,CAAT,CAAuB,CAC5CH,CAAS,CAACI,MAAV,CAAiB,qCAAjB,CAAwD,CAACJ,SAAS,CAAEG,CAAZ,CAAxD,EACKE,IADL,CACU,SAASC,CAAT,CAAiBC,CAAjB,CAAqB,CACvBP,CAAS,CAACQ,WAAV,CAAsBZ,CAAC,CAAC,iCAAD,CAAvB,CAA0DU,CAA1D,CAAkEC,CAAlE,CACH,CAHL,EAGOE,IAHP,CAGYV,CAAY,CAACW,SAHzB,CAIH,CAb0D,CAqBvDC,CAAa,CAAG,SAASC,CAAT,CAAoB,IAChCC,CAAAA,CAAY,CAAGjB,CAAC,CAAC,4BAAD,CAAD,CAA8BkB,GAA9B,EADiB,CAEhCC,CAAS,CAAGnB,CAAC,CAAC,yBAAD,CAAD,CAA2BkB,GAA3B,EAFoB,CAKpCE,QAAQ,CAACC,QAAT,CAAkBC,IAAlB,CAAyBH,CAAzB,CAEAlB,CAAI,CAACsB,IAAL,CAAU,CACN,CAACC,UAAU,CAAE,qCAAb,CACEC,IAAI,CAAE,CAACC,SAAS,CAAET,CAAZ,CAA0BU,MAAM,CAAER,CAAlC,CAA6CH,SAAS,CAAEA,CAAxD,CADR,CAEEP,IAAI,CAAEH,CAFR,CAGEO,IAAI,CAAEV,CAAY,CAACW,SAHrB,CADM,CAAV,OAMH,CAlC0D,CAoCvDc,CAAQ,CAAG,IApC4C,CA8CvDC,CAAY,CAAG,SAASC,CAAT,CAAmBC,CAAnB,CAA0B,CACzC,GAAiB,IAAb,GAAAH,CAAJ,CAAuB,CACnBI,MAAM,CAACC,YAAP,CAAoBL,CAApB,CACH,CAEDA,CAAQ,CAAGI,MAAM,CAACE,UAAP,CAAkB,UAAW,CACpCJ,CAAQ,GACRF,CAAQ,CAAG,IACd,CAHU,CAGRG,CAHQ,CAId,CAvD0D,CAyDvDI,CAAa,CAAG,UAAW,CAC3BN,CAAY,CAACd,CAAa,CAACqB,IAAd,CAAmB,IAAnB,CAAyB/B,CAAM,CAACgC,KAAhC,CAAD,CAAyC,GAAzC,CACf,CA3D0D,CA6D3DrC,CAAC,CAAC,kCAAD,CAAD,CAAoCsC,EAApC,CAAuC,QAAvC,CAAiD,4BAAjD,CAA6EH,CAA7E,EACAnC,CAAC,CAAC,kCAAD,CAAD,CAAoCsC,EAApC,CAAuC,OAAvC,CAAgD,yBAAhD,CAAyEH,CAAzE,EAEAnC,CAAC,CAAC,yBAAD,CAAD,CAA2BkB,GAA3B,CAA+BE,QAAQ,CAACC,QAAT,CAAkBC,IAAlB,CAAuBiB,OAAvB,CAA+B,GAA/B,CAAoC,EAApC,CAA/B,EACAxB,CAAa,CAACV,CAAM,CAACgC,KAAR,CAAb,CACA,MAAO,EACV,CApEK,CAAN","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 * This module adds ajax search functions to the template library page.\n *\n * @module tool_templatelibrary/search\n * @package tool_templatelibrary\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates', 'core/config'],\n function($, ajax, log, notification, templates, config) {\n\n /**\n * The ajax call has returned with a new list of templates.\n *\n * @method reloadListTemplate\n * @param {String[]} templateList List of template ids.\n */\n var reloadListTemplate = function(templateList) {\n templates.render('tool_templatelibrary/search_results', {templates: templateList})\n .done(function(result, js) {\n templates.replaceNode($('[data-region=\"searchresults\"]'), result, js);\n }).fail(notification.exception);\n };\n\n /**\n * Get the current values for the form inputs and refresh the list of matching templates.\n *\n * @method refreshSearch\n * @param {String} themename The naeme of the theme.\n */\n var refreshSearch = function(themename) {\n var componentStr = $('[data-field=\"component\"]').val();\n var searchStr = $('[data-field=\"search\"]').val();\n\n // Trigger the search.\n document.location.hash = searchStr;\n\n ajax.call([\n {methodname: 'tool_templatelibrary_list_templates',\n args: {component: componentStr, search: searchStr, themename: themename},\n done: reloadListTemplate,\n fail: notification.exception}\n ], true, false);\n };\n\n var throttle = null;\n\n /**\n * Call the specified function after a delay. If this function is called again before the function is executed,\n * the function will only be executed once.\n *\n * @method queueRefresh\n * @param {function} callback\n * @param {Number} delay The time in milliseconds to delay.\n */\n var queueRefresh = function(callback, delay) {\n if (throttle !== null) {\n window.clearTimeout(throttle);\n }\n\n throttle = window.setTimeout(function() {\n callback();\n throttle = null;\n }, delay);\n };\n\n var changeHandler = function() {\n queueRefresh(refreshSearch.bind(this, config.theme), 400);\n };\n // Add change handlers to refresh the list.\n $('[data-region=\"list-templates\"]').on('change', '[data-field=\"component\"]', changeHandler);\n $('[data-region=\"list-templates\"]').on('input', '[data-field=\"search\"]', changeHandler);\n\n $('[data-field=\"search\"]').val(document.location.hash.replace('#', ''));\n refreshSearch(config.theme);\n return {};\n});\n"],"file":"search.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/search.js"],"names":["define","$","ajax","log","notification","templates","config","reloadListTemplate","templateList","render","done","result","js","replaceNode","fail","exception","refreshSearch","themename","componentStr","val","searchStr","removeClass","addClass","document","location","hash","call","methodname","args","component","search","throttle","queueRefresh","callback","delay","window","clearTimeout","setTimeout","changeHandler","bind","theme","on","replace"],"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 * This module adds ajax search functions to the template library page.\n *\n * @module tool_templatelibrary/search\n * @package tool_templatelibrary\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates', 'core/config'],\n function($, ajax, log, notification, templates, config) {\n\n /**\n * The ajax call has returned with a new list of templates.\n *\n * @method reloadListTemplate\n * @param {String[]} templateList List of template ids.\n */\n var reloadListTemplate = function(templateList) {\n templates.render('tool_templatelibrary/search_results', {templates: templateList})\n .done(function(result, js) {\n templates.replaceNode($('[data-region=\"searchresults\"]'), result, js);\n }).fail(notification.exception);\n };\n\n /**\n * Get the current values for the form inputs and refresh the list of matching templates.\n *\n * @method refreshSearch\n * @param {String} themename The naeme of the theme.\n */\n var refreshSearch = function(themename) {\n var componentStr = $('[data-field=\"component\"]').val();\n var searchStr = $('[data-region=\"list-templates\"] [data-region=\"input\"]').val();\n\n if (searchStr !== '') {\n $('[data-region=\"list-templates\"] [data-action=\"clearsearch\"]').removeClass('d-none');\n } else {\n $('[data-region=\"list-templates\"] [data-action=\"clearsearch\"]').addClass('d-none');\n }\n // Trigger the search.\n document.location.hash = searchStr;\n\n ajax.call([\n {methodname: 'tool_templatelibrary_list_templates',\n args: {component: componentStr, search: searchStr, themename: themename},\n done: reloadListTemplate,\n fail: notification.exception}\n ], true, false);\n };\n\n var throttle = null;\n\n /**\n * Call the specified function after a delay. If this function is called again before the function is executed,\n * the function will only be executed once.\n *\n * @method queueRefresh\n * @param {function} callback\n * @param {Number} delay The time in milliseconds to delay.\n */\n var queueRefresh = function(callback, delay) {\n if (throttle !== null) {\n window.clearTimeout(throttle);\n }\n\n throttle = window.setTimeout(function() {\n callback();\n throttle = null;\n }, delay);\n };\n\n var changeHandler = function() {\n queueRefresh(refreshSearch.bind(this, config.theme), 400);\n };\n // Add change handlers to refresh the list.\n $('[data-region=\"list-templates\"]').on('change', '[data-field=\"component\"]', changeHandler);\n $('[data-region=\"list-templates\"]').on('input', '[data-region=\"input\"]', changeHandler);\n $('[data-action=\"clearsearch\"]').on('click', function() {\n $('[data-region=\"input\"]').val('');\n refreshSearch(config.theme);\n $(this).addClass('d-none');\n });\n\n $('[data-region=\"input\"]').val(document.location.hash.replace('#', ''));\n refreshSearch(config.theme);\n return {};\n});\n"],"file":"search.min.js"} \ No newline at end of file diff --git a/admin/tool/templatelibrary/amd/src/search.js b/admin/tool/templatelibrary/amd/src/search.js index 18623dffc0c..9caf34416b2 100644 --- a/admin/tool/templatelibrary/amd/src/search.js +++ b/admin/tool/templatelibrary/amd/src/search.js @@ -45,8 +45,13 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates' */ var refreshSearch = function(themename) { var componentStr = $('[data-field="component"]').val(); - var searchStr = $('[data-field="search"]').val(); + var searchStr = $('[data-region="list-templates"] [data-region="input"]').val(); + if (searchStr !== '') { + $('[data-region="list-templates"] [data-action="clearsearch"]').removeClass('d-none'); + } else { + $('[data-region="list-templates"] [data-action="clearsearch"]').addClass('d-none'); + } // Trigger the search. document.location.hash = searchStr; @@ -84,9 +89,14 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates' }; // Add change handlers to refresh the list. $('[data-region="list-templates"]').on('change', '[data-field="component"]', changeHandler); - $('[data-region="list-templates"]').on('input', '[data-field="search"]', changeHandler); + $('[data-region="list-templates"]').on('input', '[data-region="input"]', changeHandler); + $('[data-action="clearsearch"]').on('click', function() { + $('[data-region="input"]').val(''); + refreshSearch(config.theme); + $(this).addClass('d-none'); + }); - $('[data-field="search"]').val(document.location.hash.replace('#', '')); + $('[data-region="input"]').val(document.location.hash.replace('#', '')); refreshSearch(config.theme); return {}; }); diff --git a/admin/tool/templatelibrary/templates/list_templates_page.mustache b/admin/tool/templatelibrary/templates/list_templates_page.mustache index 0526c94f1f1..c5a454e8698 100644 --- a/admin/tool/templatelibrary/templates/list_templates_page.mustache +++ b/admin/tool/templatelibrary/templates/list_templates_page.mustache @@ -30,26 +30,67 @@ Context variables required for this template: * allcomponents - array of components containing templates. Each component has a name and a component attribute. +}} +{{! + 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 . +}} +{{! + @template tool_templatelibrary/list_templates_page + + Moodle template to the template library + + The purpose of this template is build the entire page for the template library (by including smaller templates). + + Classes required for JS: + * none + + Data attributes required for JS: + * data-region, data-field + + Context variables required for this template: + * allcomponents - array of components containing templates. Each component has a name and a component attribute. + }}
-
- -
- -
-
-
- -
- -
-
+ {{< core_form/element-template }} + {{$label}} +
{{#str}}component, tool_templatelibrary{{/str}}
+ {{/label}} + + {{$element}} + + {{/element}} + {{/ core_form/element-template }} + + {{< core_form/element-template }} + {{$element}} + {{< core/search_input_auto }} + {{$label}}{{{ searchstring }}}{{/label}} + {{$placeholder}}{{#str}} + search, core + {{/str}}{{/placeholder}} + {{/ core/search_input_auto }} + {{/element}} + {{/ core_form/element-template }}

{{> tool_templatelibrary/search_results }} diff --git a/backup/util/ui/renderer.php b/backup/util/ui/renderer.php index d5b44ce9189..4a1f187f921 100644 --- a/backup/util/ui/renderer.php +++ b/backup/util/ui/renderer.php @@ -720,7 +720,7 @@ class core_backup_renderer extends plugin_renderer_base { public function render_restore_course_search(restore_course_search $component) { $url = $component->get_url(); - $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline mb-1')); + $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); $table = new html_table(); @@ -759,22 +759,14 @@ class core_backup_renderer extends plugin_renderer_base { $output .= html_writer::table($table); $output .= html_writer::end_tag('div'); - $output .= html_writer::start_tag('div', array('class' => 'rcs-search')); - $attrs = array( - 'type' => 'text', - 'name' => restore_course_search::$VAR_SEARCH, - 'value' => $component->get_search(), - 'class' => 'form-control' - ); - $output .= html_writer::empty_tag('input', $attrs); - $attrs = array( - 'type' => 'submit', - 'name' => 'searchcourses', - 'value' => get_string('search'), - 'class' => 'btn btn-secondary' - ); - $output .= html_writer::empty_tag('input', $attrs); - $output .= html_writer::end_tag('div'); + $data = [ + 'inform' => true, + 'extraclasses' => 'rcs-search mb-3 w-25', + 'inputname' => restore_course_search::$VAR_SEARCH, + 'searchstring' => get_string('searchcourses'), + 'query' => $component->get_search(), + ]; + $output .= $this->output->render_from_template('core/search_input', $data); $output .= html_writer::end_tag('div'); return $output; @@ -880,7 +872,7 @@ class core_backup_renderer extends plugin_renderer_base { public function render_restore_category_search(restore_category_search $component) { $url = $component->get_url(); - $output = html_writer::start_tag('div', array('class' => 'restore-course-search form-inline mb-1')); + $output = html_writer::start_tag('div', array('class' => 'restore-course-search mb-1')); $output .= html_writer::start_tag('div', array('class' => 'rcs-results table-sm w-75')); $table = new html_table(); @@ -922,22 +914,14 @@ class core_backup_renderer extends plugin_renderer_base { $output .= html_writer::table($table); $output .= html_writer::end_tag('div'); - $output .= html_writer::start_tag('div', array('class' => 'rcs-search')); - $attrs = array( - 'type' => 'text', - 'name' => restore_category_search::$VAR_SEARCH, - 'value' => $component->get_search(), - 'class' => 'form-control' - ); - $output .= html_writer::empty_tag('input', $attrs); - $attrs = array( - 'type' => 'submit', - 'name' => 'searchcourses', - 'value' => get_string('search'), - 'class' => 'btn btn-secondary' - ); - $output .= html_writer::empty_tag('input', $attrs); - $output .= html_writer::end_tag('div'); + $data = [ + 'inform' => true, + 'extraclasses' => 'rcs-search mb-3 w-25', + 'inputname' => restore_category_search::$VAR_SEARCH, + 'searchstring' => get_string('search'), + 'query' => $component->get_search(), + ]; + $output .= $this->output->render_from_template('core/search_input', $data); $output .= html_writer::end_tag('div'); return $output; diff --git a/blocks/blog_menu/block_blog_menu.php b/blocks/blog_menu/block_blog_menu.php index 7bc53b130c9..3c4d9c9166c 100644 --- a/blocks/blog_menu/block_blog_menu.php +++ b/blocks/blog_menu/block_blog_menu.php @@ -50,7 +50,7 @@ class block_blog_menu extends block_base { } function get_content() { - global $CFG; + global $CFG, $OUTPUT; // detect if blog enabled if ($this->content !== NULL) { @@ -98,15 +98,14 @@ class block_blog_menu extends block_base { // Prepare the footer for this block if (has_capability('moodle/blog:search', context_system::instance())) { - // Full-text search field - $form = html_writer::tag('label', get_string('search', 'admin'), array('for' => 'blogsearchquery', - 'class' => 'accesshide')); - $form .= html_writer::empty_tag('input', array('id' => 'blogsearchquery', 'class' => 'form-control mr-1', - 'type' => 'text', 'name' => 'search')); - $form .= html_writer::empty_tag('input', array('type' => 'submit', 'class' => 'btn btn-secondary', - 'value' => get_string('search'))); - $this->content->footer = html_writer::tag('form', html_writer::tag('div', $form), array( - 'class' => 'blogsearchform form-inline', 'method' => 'get', 'action' => new moodle_url('/blog/index.php'))); + + $data = [ + 'action' => new moodle_url('/blog/index.php'), + 'inputname' => 'search', + 'searchstring' => get_string('search', 'admin'), + 'extraclasses' => 'mt-3' + ]; + $this->content->footer = $OUTPUT->render_from_template('core/search_input', $data); } else { // No footer to display $this->content->footer = ''; diff --git a/blocks/blog_menu/tests/behat/block_blog_menu_activity.feature b/blocks/blog_menu/tests/behat/block_blog_menu_activity.feature index 9a342d231c4..586e00c51ec 100644 --- a/blocks/blog_menu/tests/behat/block_blog_menu_activity.feature +++ b/blocks/blog_menu/tests/behat/block_blog_menu_activity.feature @@ -206,7 +206,7 @@ Feature: Enable Block blog menu in an activity When I log in as "teacher1" And I am on "Course 1" course homepage And I follow "Test assignment 1" - And I set the field "blogsearchquery" to "First" + And I set the field "Search" to "First" And I press "Search" Then I should see "S1 First Blog" And I should see "S2 First Blog" diff --git a/blocks/blog_menu/tests/behat/block_blog_menu_course.feature b/blocks/blog_menu/tests/behat/block_blog_menu_course.feature index 39821d29276..11d9625b02e 100644 --- a/blocks/blog_menu/tests/behat/block_blog_menu_course.feature +++ b/blocks/blog_menu/tests/behat/block_blog_menu_course.feature @@ -183,7 +183,7 @@ Feature: Students can use block blog menu in a course And I log out When I log in as "teacher1" And I am on "Course 1" course homepage - And I set the field "blogsearchquery" to "First" + And I set the field "Search" to "First" And I press "Search" Then I should see "S1 First Blog" And I should see "S2 First Blog" diff --git a/blocks/globalsearch/block_globalsearch.php b/blocks/globalsearch/block_globalsearch.php index d3d96b20662..91892ae26eb 100644 --- a/blocks/globalsearch/block_globalsearch.php +++ b/blocks/globalsearch/block_globalsearch.php @@ -64,32 +64,17 @@ class block_globalsearch extends block_base { return $this->content; } - $url = new moodle_url('/search/index.php'); - $this->content->footer .= html_writer::link($url, get_string('advancedsearch', 'search')); + $data = [ + 'action' => new moodle_url('/search/index.php'), + 'inputname' => 'q', + 'searchstring' => get_string('search'), + ]; - $this->content->text = html_writer::start_tag('div', array('class' => 'searchform')); - $this->content->text .= html_writer::start_tag('form', array('action' => $url->out())); - $this->content->text .= html_writer::start_tag('fieldset', array('action' => 'invisiblefieldset')); - - // Input. - $this->content->text .= html_writer::tag('label', get_string('search', 'search'), - array('for' => 'searchform_search', 'class' => 'accesshide')); - $inputoptions = array('id' => 'searchform_search', 'name' => 'q', 'class' => 'form-control', - 'type' => 'text', 'size' => '15'); - $this->content->text .= html_writer::empty_tag('input', $inputoptions); - - // Context id. if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) { - $this->content->text .= html_writer::empty_tag('input', ['type' => 'hidden', - 'name' => 'context', 'value' => $this->page->context->id]); + $data['hiddenfields'] = (object) ['name' => 'context', 'value' => $this->page->context->id]; } - // Search button. - $this->content->text .= html_writer::tag('button', get_string('search', 'search'), - array('id' => 'searchform_button', 'type' => 'submit', 'title' => 'globalsearch', 'class' => 'btn btn-secondary')); - $this->content->text .= html_writer::end_tag('fieldset'); - $this->content->text .= html_writer::end_tag('form'); - $this->content->text .= html_writer::end_tag('div'); + $this->content->text = $OUTPUT->render_from_template('core/search_input', $data); return $this->content; } diff --git a/blocks/search_forums/classes/output/search_form.php b/blocks/search_forums/classes/output/search_form.php index 8213abe987f..76b41a025be 100644 --- a/blocks/search_forums/classes/output/search_form.php +++ b/blocks/search_forums/classes/output/search_form.php @@ -44,8 +44,6 @@ class search_form implements renderable, templatable { protected $courseid; /** @var moodle_url The form action URL. */ protected $actionurl; - /** @var moodle_url The advanced search URL. */ - protected $advancedsearchurl; /** @var help_icon The help icon. */ protected $helpicon; @@ -56,17 +54,17 @@ class search_form implements renderable, templatable { */ public function __construct($courseid) { $this->courseid = $courseid; - $this->actionurl = new moodle_url('/mod/forum/search.php'); - $this->advancedsearchurl = new moodle_url('/mod/forum/search.php', ['id' => $this->courseid]); + $this->actionurl = new moodle_url('/mod/forum/search.php', ['id' => $courseid]); $this->helpicon = new help_icon('search', 'core'); } public function export_for_template(renderer_base $output) { $data = [ - 'actionurl' => $this->actionurl->out(false), - 'courseid' => $this->courseid, - 'advancedsearchurl' => $this->advancedsearchurl->out(false), + 'action' => $this->actionurl, 'helpicon' => $this->helpicon->export_for_template($output), + 'hiddenfields' => (object) ['name' => 'id', 'value' => $this->courseid], + 'inputname' => 'search', + 'searchstring' => get_string('search') ]; return $data; } diff --git a/blocks/search_forums/templates/search_form.mustache b/blocks/search_forums/templates/search_form.mustache index b5f813325b4..320a5463365 100644 --- a/blocks/search_forums/templates/search_form.mustache +++ b/blocks/search_forums/templates/search_form.mustache @@ -21,25 +21,23 @@ Example context (json): { - "actionurl": "https://domain.example/mod/forum/search.php", - "courseid": "2", - "advancedsearchurl": "https://domain.example/mod/forum/search.php?id=2", + "action": "https://moodle.local/admin/search.php", + "inputname": "search", + "searchstring": "Search settings", + "value": "policy", + "hiddenfields": [ + { + "name": "course", + "value": "11" + } + ], "helpicon": "" } }}
-
- -
- - -
- -
-
-
+ {{>core/search_input}}
- {{#str}}advancedsearch, block_search_forums{{/str}} + {{#str}}advancedsearch, block_search_forums{{/str}} {{#helpicon}} {{>core/help_icon}} {{/helpicon}} diff --git a/blocks/search_forums/tests/behat/block_search_forums_course.feature b/blocks/search_forums/tests/behat/block_search_forums_course.feature index dfdeb6f53ed..9444846f6fa 100644 --- a/blocks/search_forums/tests/behat/block_search_forums_course.feature +++ b/blocks/search_forums/tests/behat/block_search_forums_course.feature @@ -29,9 +29,8 @@ Feature: The search forums block allows users to search for forum posts on cours Scenario: Use the search forum block in a course without any forum posts Given I log in as "student1" And I am on "Course 1" course homepage - When I set the following fields to these values: - | searchform_search | Moodle | - And I press "Go" + When I set the field "Search" to "Moodle" + And I press "Search" Then I should see "No posts" Scenario: Use the search forum block in a course with a hidden forum and search for posts @@ -50,9 +49,8 @@ Feature: The search forums block allows users to search for forum posts on cours When I log in as "student1" And I am on "Course 1" course homepage And "Search forums" "block" should exist - And I set the following fields to these values: - | searchform_search | message | - And I press "Go" + When I set the field "Search" to "message" + And I press "Search" Then I should see "No posts" Scenario: Use the search forum block in a course and search for posts @@ -65,7 +63,6 @@ Feature: The search forums block allows users to search for forum posts on cours When I log in as "student1" And I am on "Course 1" course homepage And "Search forums" "block" should exist - And I set the following fields to these values: - | searchform_search | message | - And I press "Go" + When I set the field "Search" to "message" + And I press "Search" Then I should see "My subject" diff --git a/blocks/search_forums/tests/behat/block_search_forums_frontpage.feature b/blocks/search_forums/tests/behat/block_search_forums_frontpage.feature index 53f3ba95767..31d868376cd 100644 --- a/blocks/search_forums/tests/behat/block_search_forums_frontpage.feature +++ b/blocks/search_forums/tests/behat/block_search_forums_frontpage.feature @@ -17,15 +17,13 @@ Feature: The search forums block allows users to search for forum posts on front Scenario: Use the search forum block on the frontpage and search for posts as a user Given I log in as "student1" And I am on site homepage - When I set the following fields to these values: - | searchform_search | Moodle | - And I press "Go" + When I set the field "Search" to "Moodle" + And I press "Search" Then I should see "No posts" Scenario: Use the search forum block on the frontpage and search for posts as a guest Given I log in as "guest" And I am on site homepage - When I set the following fields to these values: - | searchform_search | Moodle | - And I press "Go" + When I set the field "Search" to "Moodle" + And I press "Search" Then I should see "No posts" diff --git a/blocks/settings/renderer.php b/blocks/settings/renderer.php index bceefd8a5d8..3e928483a8d 100644 --- a/blocks/settings/renderer.php +++ b/blocks/settings/renderer.php @@ -144,11 +144,12 @@ class block_settings_renderer extends plugin_renderer_base { public function search_form(moodle_url $formtarget, $searchvalue) { $data = [ - 'action' => $formtarget->out(false), - 'label' => get_string('searchinsettings', 'admin'), - 'searchvalue' => $searchvalue + 'action' => $formtarget, + 'inputname' => 'query', + 'searchstring' => get_string('searchinsettings', 'admin'), + 'query' => $searchvalue ]; - return $this->render_from_template('block_settings/search_form', $data); + return $this->render_from_template('core/search_input', $data); } } diff --git a/cohort/index.php b/cohort/index.php index 0341bf96385..02d14341f0d 100644 --- a/cohort/index.php +++ b/cohort/index.php @@ -103,19 +103,21 @@ if ($editcontrols = cohort_edit_controls($context, $baseurl)) { } // Add search form. -$search = html_writer::start_tag('form', array('id'=>'searchcohortquery', 'method'=>'get', 'class' => 'form-inline search-cohort')); -$search .= html_writer::start_div('mb-1'); -$search .= html_writer::label(get_string('searchcohort', 'cohort'), 'cohort_search_q', true, - array('class' => 'mr-1')); // No : in form labels! -$search .= html_writer::empty_tag('input', array('id' => 'cohort_search_q', 'type' => 'text', 'name' => 'search', - 'value' => $searchquery, 'class' => 'form-control mr-1')); -$search .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('search', 'cohort'), - 'class' => 'btn btn-secondary')); -$search .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'contextid', 'value'=>$contextid)); -$search .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'showall', 'value'=>$showall)); -$search .= html_writer::end_div(); -$search .= html_writer::end_tag('form'); -echo $search; +$hiddenfields = [ + (object) ['name' => 'contextid', 'value' => $contextid], + (object) ['name' => 'showall', 'value' => $showall] +]; + +$data = [ + 'action' => new moodle_url('/cohort/index.php'), + 'inputname' => 'search', + 'searchstring' => get_string('search', 'cohort'), + 'query' => $searchquery, + 'hiddenfields' => $hiddenfields, + 'extraclasses' => 'mb-3' +]; + +echo $OUTPUT->render_from_template('core/search_input', $data); // Output pagination bar. echo $OUTPUT->paging_bar($cohorts['totalcohorts'], $page, 25, $baseurl); diff --git a/contentbank/amd/build/search.min.js b/contentbank/amd/build/search.min.js index 16c80b09dfd..8309c2460c1 100644 --- a/contentbank/amd/build/search.min.js +++ b/contentbank/amd/build/search.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/search",["exports","jquery","core_contentbank/selectors","core/str","core/pending","core/utils"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=g(b);c=g(c);e=g(e);function g(a){return a&&a.__esModule?a:{default:a}}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}var j=function(){var a=new e.default,d=(0,b.default)(c.default.regions.contentbank);k(d);a.resolve()};a.init=j;var k=function(a){var b=a.find(c.default.elements.searchinput)[0];a.on("click",c.default.actions.search,function(c){c.preventDefault();l(a,b.value)});a.on("click",c.default.actions.clearSearch,function(c){c.preventDefault();b.value="";b.focus();l(a,b.value)});b.addEventListener("input",(0,f.debounce)(function(){l(a,b.value)},300))},l=function(){var a=i(regeneratorRuntime.mark(function a(b,e){var f,g,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=b.find(c.default.elements.clearsearch)[0];g=b.find(c.default.elements.searchicon)[0];h=b.find(c.default.elements.cbnavbarbreadcrumb)[0];i=b.find(c.default.elements.cbnavbartotalsearch)[0];j=m(b,e);if(!(0"+a.substr(d,b.length)+""+a.substr(d+b.length)}}return c}}); +define ("core_contentbank/search",["exports","jquery","core_contentbank/selectors","core/str","core/pending","core/utils"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=g(b);c=g(c);e=g(e);function g(a){return a&&a.__esModule?a:{default:a}}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}var j=function(){var a=new e.default,d=(0,b.default)(c.default.regions.contentbank);k(d);a.resolve()};a.init=j;var k=function(a){var b=a.find(c.default.elements.searchinput)[0];a.on("click",c.default.actions.search,function(c){c.preventDefault();l(a,b.value)});a.on("click",c.default.actions.clearSearch,function(c){c.preventDefault();b.value="";b.focus();l(a,b.value)});b.addEventListener("input",(0,f.debounce)(function(){l(a,b.value)},300))},l=function(){var a=i(regeneratorRuntime.mark(function a(b,e){var f,g,h,i;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:f=b.find(c.default.actions.clearSearch)[0];g=b.find(c.default.elements.cbnavbarbreadcrumb)[0];h=b.find(c.default.elements.cbnavbartotalsearch)[0];i=m(b,e);if(!(0"+a.substr(d,b.length)+""+a.substr(d+b.length)}}return c}}); //# sourceMappingURL=search.min.js.map diff --git a/contentbank/amd/build/search.min.js.map b/contentbank/amd/build/search.min.js.map index 0e0625892fe..ae3bad772ca 100644 --- a/contentbank/amd/build/search.min.js.map +++ b/contentbank/amd/build/search.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/search.js"],"names":["init","pendingPromise","Pending","root","selectors","regions","contentbank","registerListenerEvents","resolve","searchInput","find","elements","searchinput","on","actions","search","e","preventDefault","toggleSearchResultsView","value","clearSearch","focus","addEventListener","body","searchQuery","clearSearchButton","clearsearch","searchIcon","searchicon","navbarBreadcrumb","cbnavbarbreadcrumb","navbarTotal","cbnavbartotalsearch","filteredContents","filterContents","length","classList","add","remove","innerHTML","searchTerm","contents","Array","from","listitem","searchResults","forEach","content","contentName","getAttribute","toLowerCase","includes","push","contentNameElement","querySelector","cbcontentname","highlight","text","highlightText","result","pos","indexOf","substr"],"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 * Search methods for finding contents in the content bank.\n *\n * @module core_contentbank/search\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport selectors from 'core_contentbank/selectors';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\nimport {debounce} from 'core/utils';\n\n/**\n * Set up the search.\n *\n * @method init\n */\nexport const init = () => {\n const pendingPromise = new Pending();\n\n const root = $(selectors.regions.contentbank);\n registerListenerEvents(root);\n\n pendingPromise.resolve();\n};\n\n/**\n * Register contentbank search related event listeners.\n *\n * @method registerListenerEvents\n * @param {Object} root The root element for the contentbank.\n */\nconst registerListenerEvents = (root) => {\n\n const searchInput = root.find(selectors.elements.searchinput)[0];\n\n root.on('click', selectors.actions.search, function(e) {\n e.preventDefault();\n toggleSearchResultsView(root, searchInput.value);\n });\n\n root.on('click', selectors.actions.clearSearch, function(e) {\n e.preventDefault();\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(root, searchInput.value);\n });\n\n // The search input is also triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(root, searchInput.value);\n }, 300));\n\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query.\n *\n * @method toggleSearchResultsView\n * @param {HTMLElement} body The root element for the contentbank.\n * @param {String} searchQuery The search query.\n */\nconst toggleSearchResultsView = async(body, searchQuery) => {\n const clearSearchButton = body.find(selectors.elements.clearsearch)[0];\n const searchIcon = body.find(selectors.elements.searchicon)[0];\n\n const navbarBreadcrumb = body.find(selectors.elements.cbnavbarbreadcrumb)[0];\n const navbarTotal = body.find(selectors.elements.cbnavbartotalsearch)[0];\n // Update the results.\n const filteredContents = filterContents(body, searchQuery);\n if (searchQuery.length > 0) {\n // As the search query is present, search results should be displayed.\n\n // Display the \"clear\" search button in the activity chooser search bar.\n searchIcon.classList.add('d-none');\n clearSearchButton.classList.remove('d-none');\n\n // Change the cb-navbar to display total items found.\n navbarBreadcrumb.classList.add('d-none');\n navbarTotal.innerHTML = await getString('itemsfound', 'core_contentbank', filteredContents.length);\n navbarTotal.classList.remove('d-none');\n } else {\n // As search query is not present, the search results should be removed.\n\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n searchIcon.classList.remove('d-none');\n\n // Display again the breadcrumb in the navbar.\n navbarBreadcrumb.classList.remove('d-none');\n navbarTotal.classList.add('d-none');\n }\n};\n\n/**\n * Return the list of contents which have a name that matches the given search term.\n *\n * @method filterContents\n * @param {HTMLElement} body The root element for the contentbank.\n * @param {String} searchTerm The search term to match.\n * @return {Array}\n */\nconst filterContents = (body, searchTerm) => {\n const contents = Array.from(body.find(selectors.elements.listitem));\n const searchResults = [];\n contents.forEach((content) => {\n const contentName = content.getAttribute('data-name');\n if (searchTerm === '' || contentName.toLowerCase().includes(searchTerm.toLowerCase())) {\n // The content matches the search criteria so it should be displayed and hightlighted.\n searchResults.push(content);\n const contentNameElement = content.querySelector(selectors.regions.cbcontentname);\n contentNameElement.innerHTML = highlight(contentName, searchTerm);\n content.classList.remove('d-none');\n } else {\n content.classList.add('d-none');\n }\n });\n\n return searchResults;\n};\n\n/**\n * Highlight a given string in a text.\n *\n * @method highlight\n * @param {String} text The whole text.\n * @param {String} highlightText The piece of text to highlight.\n * @return {String}\n */\nconst highlight = (text, highlightText) => {\n let result = text;\n if (highlightText !== '') {\n const pos = text.toLowerCase().indexOf(highlightText.toLowerCase());\n if (pos > -1) {\n result = text.substr(0, pos) + '' + text.substr(pos, highlightText.length) + '' +\n text.substr(pos + highlightText.length);\n }\n }\n\n return result;\n};\n"],"file":"search.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/search.js"],"names":["init","pendingPromise","Pending","root","selectors","regions","contentbank","registerListenerEvents","resolve","searchInput","find","elements","searchinput","on","actions","search","e","preventDefault","toggleSearchResultsView","value","clearSearch","focus","addEventListener","body","searchQuery","clearSearchButton","navbarBreadcrumb","cbnavbarbreadcrumb","navbarTotal","cbnavbartotalsearch","filteredContents","filterContents","length","classList","remove","add","innerHTML","searchTerm","contents","Array","from","listitem","searchResults","forEach","content","contentName","getAttribute","toLowerCase","includes","push","contentNameElement","querySelector","cbcontentname","highlight","text","highlightText","result","pos","indexOf","substr"],"mappings":"6NAwBA,OACA,OAEA,O,kXAQO,GAAMA,CAAAA,CAAI,CAAG,UAAM,IAChBC,CAAAA,CAAc,CAAG,GAAIC,UADL,CAGhBC,CAAI,CAAG,cAAEC,UAAUC,OAAV,CAAkBC,WAApB,CAHS,CAItBC,CAAsB,CAACJ,CAAD,CAAtB,CAEAF,CAAc,CAACO,OAAf,EACH,CAPM,C,YAeDD,CAAAA,CAAsB,CAAG,SAACJ,CAAD,CAAU,CAErC,GAAMM,CAAAA,CAAW,CAAGN,CAAI,CAACO,IAAL,CAAUN,UAAUO,QAAV,CAAmBC,WAA7B,EAA0C,CAA1C,CAApB,CAEAT,CAAI,CAACU,EAAL,CAAQ,OAAR,CAAiBT,UAAUU,OAAV,CAAkBC,MAAnC,CAA2C,SAASC,CAAT,CAAY,CACnDA,CAAC,CAACC,cAAF,GACAC,CAAuB,CAACf,CAAD,CAAOM,CAAW,CAACU,KAAnB,CAC1B,CAHD,EAKAhB,CAAI,CAACU,EAAL,CAAQ,OAAR,CAAiBT,UAAUU,OAAV,CAAkBM,WAAnC,CAAgD,SAASJ,CAAT,CAAY,CACxDA,CAAC,CAACC,cAAF,GACAR,CAAW,CAACU,KAAZ,CAAoB,EAApB,CACAV,CAAW,CAACY,KAAZ,GACAH,CAAuB,CAACf,CAAD,CAAOM,CAAW,CAACU,KAAnB,CAC1B,CALD,EAQAV,CAAW,CAACa,gBAAZ,CAA6B,OAA7B,CAAsC,eAAS,UAAM,CAEjDJ,CAAuB,CAACf,CAAD,CAAOM,CAAW,CAACU,KAAnB,CAC1B,CAHqC,CAGnC,GAHmC,CAAtC,CAKH,C,CASKD,CAAuB,4CAAG,WAAMK,CAAN,CAAYC,CAAZ,+FACtBC,CADsB,CACFF,CAAI,CAACb,IAAL,CAAUN,UAAUU,OAAV,CAAkBM,WAA5B,EAAyC,CAAzC,CADE,CAGtBM,CAHsB,CAGHH,CAAI,CAACb,IAAL,CAAUN,UAAUO,QAAV,CAAmBgB,kBAA7B,EAAiD,CAAjD,CAHG,CAItBC,CAJsB,CAIRL,CAAI,CAACb,IAAL,CAAUN,UAAUO,QAAV,CAAmBkB,mBAA7B,EAAkD,CAAlD,CAJQ,CAMtBC,CANsB,CAMHC,CAAc,CAACR,CAAD,CAAOC,CAAP,CANX,MAOH,CAArB,CAAAA,CAAW,CAACQ,MAPY,mBAWxBP,CAAiB,CAACQ,SAAlB,CAA4BC,MAA5B,CAAmC,QAAnC,EAGAR,CAAgB,CAACO,SAAjB,CAA2BE,GAA3B,CAA+B,QAA/B,EAdwB,eAeM,iBAAU,YAAV,CAAwB,kBAAxB,CAA4CL,CAAgB,CAACE,MAA7D,CAfN,QAexBJ,CAAW,CAACQ,SAfY,QAgBxBR,CAAW,CAACK,SAAZ,CAAsBC,MAAtB,CAA6B,QAA7B,EAhBwB,wBAqBxBT,CAAiB,CAACQ,SAAlB,CAA4BE,GAA5B,CAAgC,QAAhC,EAGAT,CAAgB,CAACO,SAAjB,CAA2BC,MAA3B,CAAkC,QAAlC,EACAN,CAAW,CAACK,SAAZ,CAAsBE,GAAtB,CAA0B,QAA1B,EAzBwB,yCAAH,uD,CAqCvBJ,CAAc,CAAG,SAACR,CAAD,CAAOc,CAAP,CAAsB,IACnCC,CAAAA,CAAQ,CAAGC,KAAK,CAACC,IAAN,CAAWjB,CAAI,CAACb,IAAL,CAAUN,UAAUO,QAAV,CAAmB8B,QAA7B,CAAX,CADwB,CAEnCC,CAAa,CAAG,EAFmB,CAGzCJ,CAAQ,CAACK,OAAT,CAAiB,SAACC,CAAD,CAAa,CAC1B,GAAMC,CAAAA,CAAW,CAAGD,CAAO,CAACE,YAAR,CAAqB,WAArB,CAApB,CACA,GAAmB,EAAf,GAAAT,CAAU,EAAWQ,CAAW,CAACE,WAAZ,GAA0BC,QAA1B,CAAmCX,CAAU,CAACU,WAAX,EAAnC,CAAzB,CAAuF,CAEnFL,CAAa,CAACO,IAAd,CAAmBL,CAAnB,EACA,GAAMM,CAAAA,CAAkB,CAAGN,CAAO,CAACO,aAAR,CAAsB/C,UAAUC,OAAV,CAAkB+C,aAAxC,CAA3B,CACAF,CAAkB,CAACd,SAAnB,CAA+BiB,CAAS,CAACR,CAAD,CAAcR,CAAd,CAAxC,CACAO,CAAO,CAACX,SAAR,CAAkBC,MAAlB,CAAyB,QAAzB,CACH,CAND,IAMO,CACHU,CAAO,CAACX,SAAR,CAAkBE,GAAlB,CAAsB,QAAtB,CACH,CACJ,CAXD,EAaA,MAAOO,CAAAA,CACV,C,CAUKW,CAAS,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAyB,CACvC,GAAIC,CAAAA,CAAM,CAAGF,CAAb,CACA,GAAsB,EAAlB,GAAAC,CAAJ,CAA0B,CACtB,GAAME,CAAAA,CAAG,CAAGH,CAAI,CAACP,WAAL,GAAmBW,OAAnB,CAA2BH,CAAa,CAACR,WAAd,EAA3B,CAAZ,CACA,GAAU,CAAC,CAAP,CAAAU,CAAJ,CAAc,CACVD,CAAM,CAAGF,CAAI,CAACK,MAAL,CAAY,CAAZ,CAAeF,CAAf,EAAsB,4BAAtB,CAAmDH,CAAI,CAACK,MAAL,CAAYF,CAAZ,CAAiBF,CAAa,CAACvB,MAA/B,CAAnD,CAA4F,SAA5F,CACLsB,CAAI,CAACK,MAAL,CAAYF,CAAG,CAAGF,CAAa,CAACvB,MAAhC,CACP,CACJ,CAED,MAAOwB,CAAAA,CACV,C","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 * Search methods for finding contents in the content bank.\n *\n * @module core_contentbank/search\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport selectors from 'core_contentbank/selectors';\nimport {get_string as getString} from 'core/str';\nimport Pending from 'core/pending';\nimport {debounce} from 'core/utils';\n\n/**\n * Set up the search.\n *\n * @method init\n */\nexport const init = () => {\n const pendingPromise = new Pending();\n\n const root = $(selectors.regions.contentbank);\n registerListenerEvents(root);\n\n pendingPromise.resolve();\n};\n\n/**\n * Register contentbank search related event listeners.\n *\n * @method registerListenerEvents\n * @param {Object} root The root element for the contentbank.\n */\nconst registerListenerEvents = (root) => {\n\n const searchInput = root.find(selectors.elements.searchinput)[0];\n\n root.on('click', selectors.actions.search, function(e) {\n e.preventDefault();\n toggleSearchResultsView(root, searchInput.value);\n });\n\n root.on('click', selectors.actions.clearSearch, function(e) {\n e.preventDefault();\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(root, searchInput.value);\n });\n\n // The search input is also triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(root, searchInput.value);\n }, 300));\n\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query.\n *\n * @method toggleSearchResultsView\n * @param {HTMLElement} body The root element for the contentbank.\n * @param {String} searchQuery The search query.\n */\nconst toggleSearchResultsView = async(body, searchQuery) => {\n const clearSearchButton = body.find(selectors.actions.clearSearch)[0];\n\n const navbarBreadcrumb = body.find(selectors.elements.cbnavbarbreadcrumb)[0];\n const navbarTotal = body.find(selectors.elements.cbnavbartotalsearch)[0];\n // Update the results.\n const filteredContents = filterContents(body, searchQuery);\n if (searchQuery.length > 0) {\n // As the search query is present, search results should be displayed.\n\n // Display the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.remove('d-none');\n\n // Change the cb-navbar to display total items found.\n navbarBreadcrumb.classList.add('d-none');\n navbarTotal.innerHTML = await getString('itemsfound', 'core_contentbank', filteredContents.length);\n navbarTotal.classList.remove('d-none');\n } else {\n // As search query is not present, the search results should be removed.\n\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n\n // Display again the breadcrumb in the navbar.\n navbarBreadcrumb.classList.remove('d-none');\n navbarTotal.classList.add('d-none');\n }\n};\n\n/**\n * Return the list of contents which have a name that matches the given search term.\n *\n * @method filterContents\n * @param {HTMLElement} body The root element for the contentbank.\n * @param {String} searchTerm The search term to match.\n * @return {Array}\n */\nconst filterContents = (body, searchTerm) => {\n const contents = Array.from(body.find(selectors.elements.listitem));\n const searchResults = [];\n contents.forEach((content) => {\n const contentName = content.getAttribute('data-name');\n if (searchTerm === '' || contentName.toLowerCase().includes(searchTerm.toLowerCase())) {\n // The content matches the search criteria so it should be displayed and hightlighted.\n searchResults.push(content);\n const contentNameElement = content.querySelector(selectors.regions.cbcontentname);\n contentNameElement.innerHTML = highlight(contentName, searchTerm);\n content.classList.remove('d-none');\n } else {\n content.classList.add('d-none');\n }\n });\n\n return searchResults;\n};\n\n/**\n * Highlight a given string in a text.\n *\n * @method highlight\n * @param {String} text The whole text.\n * @param {String} highlightText The piece of text to highlight.\n * @return {String}\n */\nconst highlight = (text, highlightText) => {\n let result = text;\n if (highlightText !== '') {\n const pos = text.toLowerCase().indexOf(highlightText.toLowerCase());\n if (pos > -1) {\n result = text.substr(0, pos) + '' + text.substr(pos, highlightText.length) + '' +\n text.substr(pos + highlightText.length);\n }\n }\n\n return result;\n};\n"],"file":"search.min.js"} \ No newline at end of file diff --git a/contentbank/amd/build/selectors.min.js b/contentbank/amd/build/selectors.min.js index 26786ecc15c..5aba987d6d3 100644 --- a/contentbank/amd/build/selectors.min.js +++ b/contentbank/amd/build/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_contentbank/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{cbcontentname:b("region","cb-content-name"),contentbank:b("region","contentbank"),filearea:b("region","filearea")},actions:{search:b("action","searchcontent"),clearSearch:b("action","clearsearchcontent"),viewgrid:b("action","viewgrid"),viewlist:b("action","viewlist"),sortname:b("action","sortname"),sortdate:b("action","sortdate"),sortsize:b("action","sortsize"),sorttype:b("action","sorttype"),sortauthor:b("action","sortauthor")},elements:{listitem:".cb-listitem",cbnavbarbreadcrumb:".cb-navbar-breadbrumb",cbnavbartotalsearch:".cb-navbar-totalsearch",clearsearch:".input-group-append .clear-icon",searchicon:".input-group-append .search-icon",searchinput:"#searchinput",sortbutton:".cb-btnsort"}};a.default=c;return a.default}); +define ("core_contentbank/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{cbcontentname:b("region","cb-content-name"),contentbank:b("region","contentbank"),filearea:b("region","filearea")},actions:{search:b("action","searchcontent"),clearSearch:b("action","clearsearch"),viewgrid:b("action","viewgrid"),viewlist:b("action","viewlist"),sortname:b("action","sortname"),sortdate:b("action","sortdate"),sortsize:b("action","sortsize"),sorttype:b("action","sorttype"),sortauthor:b("action","sortauthor")},elements:{listitem:".cb-listitem",cbnavbarbreadcrumb:".cb-navbar-breadbrumb",cbnavbartotalsearch:".cb-navbar-totalsearch",searchinput:"#searchinput",sortbutton:".cb-btnsort"}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/contentbank/amd/build/selectors.min.js.map b/contentbank/amd/build/selectors.min.js.map index 2efa6a5ed18..62be4440b62 100644 --- a/contentbank/amd/build/selectors.min.js.map +++ b/contentbank/amd/build/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/selectors.js"],"names":["getDataSelector","name","value","regions","cbcontentname","contentbank","filearea","actions","search","clearSearch","viewgrid","viewlist","sortname","sortdate","sortsize","sorttype","sortauthor","elements","listitem","cbnavbarbreadcrumb","cbnavbartotalsearch","clearsearch","searchicon","searchinput","sortbutton"],"mappings":"+IAgCMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,aAAa,CAAEJ,CAAe,CAAC,QAAD,CAAW,iBAAX,CADzB,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,QAAQ,CAAEN,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CADE,CAMXO,OAAO,CAAE,CACLC,MAAM,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CADlB,CAELS,WAAW,CAAET,CAAe,CAAC,QAAD,CAAW,oBAAX,CAFvB,CAGLU,QAAQ,CAAEV,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CAILW,QAAQ,CAAEX,CAAe,CAAC,QAAD,CAAW,UAAX,CAJpB,CAKLY,QAAQ,CAAEZ,CAAe,CAAC,QAAD,CAAW,UAAX,CALpB,CAMLa,QAAQ,CAAEb,CAAe,CAAC,QAAD,CAAW,UAAX,CANpB,CAOLc,QAAQ,CAAEd,CAAe,CAAC,QAAD,CAAW,UAAX,CAPpB,CAQLe,QAAQ,CAAEf,CAAe,CAAC,QAAD,CAAW,UAAX,CARpB,CASLgB,UAAU,CAAEhB,CAAe,CAAC,QAAD,CAAW,YAAX,CATtB,CANE,CAiBXiB,QAAQ,CAAE,CACNC,QAAQ,CAAE,cADJ,CAENC,kBAAkB,CAAE,uBAFd,CAGNC,mBAAmB,CAAE,wBAHf,CAINC,WAAW,CAAE,iCAJP,CAKNC,UAAU,CAAE,kCALN,CAMNC,WAAW,CAAE,cANP,CAONC,UAAU,CAAE,aAPN,CAjBC,C","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 * Define all of the selectors we will be using on the contentbank interface.\n *\n * @module core_contentbank/selectors\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n *\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n cbcontentname: getDataSelector('region', 'cb-content-name'),\n contentbank: getDataSelector('region', 'contentbank'),\n filearea: getDataSelector('region', 'filearea')\n },\n actions: {\n search: getDataSelector('action', 'searchcontent'),\n clearSearch: getDataSelector('action', 'clearsearchcontent'),\n viewgrid: getDataSelector('action', 'viewgrid'),\n viewlist: getDataSelector('action', 'viewlist'),\n sortname: getDataSelector('action', 'sortname'),\n sortdate: getDataSelector('action', 'sortdate'),\n sortsize: getDataSelector('action', 'sortsize'),\n sorttype: getDataSelector('action', 'sorttype'),\n sortauthor: getDataSelector('action', 'sortauthor'),\n },\n elements: {\n listitem: '.cb-listitem',\n cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',\n cbnavbartotalsearch: '.cb-navbar-totalsearch',\n clearsearch: '.input-group-append .clear-icon',\n searchicon: '.input-group-append .search-icon',\n searchinput: '#searchinput',\n sortbutton: '.cb-btnsort'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/selectors.js"],"names":["getDataSelector","name","value","regions","cbcontentname","contentbank","filearea","actions","search","clearSearch","viewgrid","viewlist","sortname","sortdate","sortsize","sorttype","sortauthor","elements","listitem","cbnavbarbreadcrumb","cbnavbartotalsearch","searchinput","sortbutton"],"mappings":"+IAgCMA,CAAAA,CAAe,CAAG,SAACC,CAAD,CAAOC,CAAP,CAAiB,CACrC,sBAAgBD,CAAhB,eAAyBC,CAAzB,OACH,C,GAEc,CACXC,OAAO,CAAE,CACLC,aAAa,CAAEJ,CAAe,CAAC,QAAD,CAAW,iBAAX,CADzB,CAELK,WAAW,CAAEL,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLM,QAAQ,CAAEN,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CADE,CAMXO,OAAO,CAAE,CACLC,MAAM,CAAER,CAAe,CAAC,QAAD,CAAW,eAAX,CADlB,CAELS,WAAW,CAAET,CAAe,CAAC,QAAD,CAAW,aAAX,CAFvB,CAGLU,QAAQ,CAAEV,CAAe,CAAC,QAAD,CAAW,UAAX,CAHpB,CAILW,QAAQ,CAAEX,CAAe,CAAC,QAAD,CAAW,UAAX,CAJpB,CAKLY,QAAQ,CAAEZ,CAAe,CAAC,QAAD,CAAW,UAAX,CALpB,CAMLa,QAAQ,CAAEb,CAAe,CAAC,QAAD,CAAW,UAAX,CANpB,CAOLc,QAAQ,CAAEd,CAAe,CAAC,QAAD,CAAW,UAAX,CAPpB,CAQLe,QAAQ,CAAEf,CAAe,CAAC,QAAD,CAAW,UAAX,CARpB,CASLgB,UAAU,CAAEhB,CAAe,CAAC,QAAD,CAAW,YAAX,CATtB,CANE,CAiBXiB,QAAQ,CAAE,CACNC,QAAQ,CAAE,cADJ,CAENC,kBAAkB,CAAE,uBAFd,CAGNC,mBAAmB,CAAE,wBAHf,CAINC,WAAW,CAAE,cAJP,CAKNC,UAAU,CAAE,aALN,CAjBC,C","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 * Define all of the selectors we will be using on the contentbank interface.\n *\n * @module core_contentbank/selectors\n * @package core_contentbank\n * @copyright 2020 Sara Arjona \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n *\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n cbcontentname: getDataSelector('region', 'cb-content-name'),\n contentbank: getDataSelector('region', 'contentbank'),\n filearea: getDataSelector('region', 'filearea')\n },\n actions: {\n search: getDataSelector('action', 'searchcontent'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n viewgrid: getDataSelector('action', 'viewgrid'),\n viewlist: getDataSelector('action', 'viewlist'),\n sortname: getDataSelector('action', 'sortname'),\n sortdate: getDataSelector('action', 'sortdate'),\n sortsize: getDataSelector('action', 'sortsize'),\n sorttype: getDataSelector('action', 'sorttype'),\n sortauthor: getDataSelector('action', 'sortauthor'),\n },\n elements: {\n listitem: '.cb-listitem',\n cbnavbarbreadcrumb: '.cb-navbar-breadbrumb',\n cbnavbartotalsearch: '.cb-navbar-totalsearch',\n searchinput: '#searchinput',\n sortbutton: '.cb-btnsort'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/contentbank/amd/src/search.js b/contentbank/amd/src/search.js index e604abc037b..40a99a5a0dc 100644 --- a/contentbank/amd/src/search.js +++ b/contentbank/amd/src/search.js @@ -80,8 +80,7 @@ const registerListenerEvents = (root) => { * @param {String} searchQuery The search query. */ const toggleSearchResultsView = async(body, searchQuery) => { - const clearSearchButton = body.find(selectors.elements.clearsearch)[0]; - const searchIcon = body.find(selectors.elements.searchicon)[0]; + const clearSearchButton = body.find(selectors.actions.clearSearch)[0]; const navbarBreadcrumb = body.find(selectors.elements.cbnavbarbreadcrumb)[0]; const navbarTotal = body.find(selectors.elements.cbnavbartotalsearch)[0]; @@ -91,7 +90,6 @@ const toggleSearchResultsView = async(body, searchQuery) => { // As the search query is present, search results should be displayed. // Display the "clear" search button in the activity chooser search bar. - searchIcon.classList.add('d-none'); clearSearchButton.classList.remove('d-none'); // Change the cb-navbar to display total items found. @@ -103,7 +101,6 @@ const toggleSearchResultsView = async(body, searchQuery) => { // Hide the "clear" search button in the activity chooser search bar. clearSearchButton.classList.add('d-none'); - searchIcon.classList.remove('d-none'); // Display again the breadcrumb in the navbar. navbarBreadcrumb.classList.remove('d-none'); diff --git a/contentbank/amd/src/selectors.js b/contentbank/amd/src/selectors.js index 60fe955b91d..f6de1b2a0fd 100644 --- a/contentbank/amd/src/selectors.js +++ b/contentbank/amd/src/selectors.js @@ -42,7 +42,7 @@ export default { }, actions: { search: getDataSelector('action', 'searchcontent'), - clearSearch: getDataSelector('action', 'clearsearchcontent'), + clearSearch: getDataSelector('action', 'clearsearch'), viewgrid: getDataSelector('action', 'viewgrid'), viewlist: getDataSelector('action', 'viewlist'), sortname: getDataSelector('action', 'sortname'), @@ -55,8 +55,6 @@ export default { listitem: '.cb-listitem', cbnavbarbreadcrumb: '.cb-navbar-breadbrumb', cbnavbartotalsearch: '.cb-navbar-totalsearch', - clearsearch: '.input-group-append .clear-icon', - searchicon: '.input-group-append .search-icon', searchinput: '#searchinput', sortbutton: '.cb-btnsort' }, diff --git a/contentbank/templates/bankcontent/search.mustache b/contentbank/templates/bankcontent/search.mustache index 8d028633290..6fc2e582775 100644 --- a/contentbank/templates/bankcontent/search.mustache +++ b/contentbank/templates/bankcontent/search.mustache @@ -18,34 +18,16 @@ @template core_contentbank/bankcontent/search Example context (json): - {} + { + } }} - \ No newline at end of file + +{{< core/search_input_auto }} + {{$label}}{{#str}} + searchcontentbankbyname, contentbank + {{/str}}{{/label}} + {{$placeholder}}{{#str}} + search, core + {{/str}}{{/placeholder}} +{{/ core/search_input_auto }} diff --git a/course/amd/build/local/activitychooser/dialogue.min.js b/course/amd/build/local/activitychooser/dialogue.min.js index dbb33f45fbe..d517d5b68fa 100644 --- a/course/amd/build/local/activitychooser/dialogue.min.js +++ b/course/amd/build/local/activitychooser/dialogue.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_course/local/activitychooser/dialogue",["exports","jquery","core/modal_events","core_course/local/activitychooser/selectors","core/templates","core/key_codes","core/loadingicon","core_course/local/activitychooser/repository","core/notification","core/utils"],function(a,b,c,d,e,f,g,h,i,j){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.displayChooser=void 0;b=m(b);c=l(c);d=m(d);e=l(e);h=l(h);i=m(i);var v="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a){return a&&a.__esModule?a:{default:a}}function n(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function o(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){n(h,d,e,f,g,"next",a)}function g(a){n(h,d,e,f,g,"throw",a)}f(void 0)})}}function p(a,b){return u(a)||t(a,b)||r(a,b)||q()}function q(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function r(a,b){if(!a)return;if("string"==typeof a)return s(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return s(a,b)}function s(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\nimport {debounce} from 'core/utils';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n * @param {jQuery} modal We need to figure out if the current modal has a footer.\n */\nconst showModuleHelp = (carousel, moduleData, modal = null) => {\n // If we have a real footer then we need to change temporarily.\n if (modal !== null && moduleData.showFooter === true) {\n modal.setFooter(Templates.render('core_course/local/activitychooser/footer_partial', moduleData));\n }\n const help = carousel.find(selectors.regions.help)[0];\n help.innerHTML = '';\n help.classList.add('m-auto');\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise('core_course/local/activitychooser/help', moduleData);\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.header).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite, footerData) => {\n const bodyClickListener = async(e) => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n await manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n const activeSectionId = modal.getBody()[0].querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = modal.getBody()[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions, modal);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n\n // The \"clear search\" button is triggered.\n if (e.target.closest(selectors.actions.clearSearch)) {\n // Clear the entered search query in the search bar and hide the search results container.\n const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }\n };\n\n // We essentially have two types of footer.\n // A fake one that is handled within the template for chooser_help and then all of the stuff for\n // modal.footer. We need to ensure we know exactly what type of footer we are using so we know what we\n // need to manage. The below code handles a real footer going to a mnet carousel item.\n const footerClickListener = async(e) => {\n if (footerData.footer === true) {\n const footerjs = await getPlugin(footerData.customfooterjs);\n await footerjs.footerClickListener(e, footerData, modal);\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Add a listener for an input change in the activity chooser's search bar.\n .then(body => {\n const searchInput = body.querySelector(selectors.actions.search);\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }, 300));\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n // Get the active chooser options section.\n const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);\n\n toggleFocusableChooserOption(firstChooserOption, true);\n initTabsKeyboardNavigation(body);\n initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);\n\n return body;\n })\n .catch();\n\n modal.getFooterPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(footer => footer[0])\n // Add the listener for clicks on the footer.\n .then(footer => {\n footer.addEventListener('click', footerClickListener);\n return footer;\n })\n .catch();\n};\n\n/**\n * Initialise the keyboard navigation controls for the tab list items.\n *\n * @method initTabsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n */\nconst initTabsKeyboardNavigation = (body) => {\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const activityTabNav = body.querySelector(selectors.regions.activityTabNav);\n const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n // The first visible navigation tab link.\n const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);\n // The last navigation tab link. It would always be the default activities tab link.\n const lastLink = e.target.parentElement.lastElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.nextElementSibling;\n if (nextLink === null) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.target.tabIndex = -1;\n nextLink.tabIndex = 0;\n nextLink.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.previousElementSibling;\n if (previousLink === null) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.target.tabIndex = -1;\n previousLink.tabIndex = 0;\n previousLink.focus();\n }\n }\n if (e.keyCode === home) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser options.\n *\n * @method initChooserOptionsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items\n * @param {Object} modal Our created modal for the section\n */\nconst initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer, modal = null) => {\n const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n\n // Check for enter/ space triggers for showing the help.\n if (e.keyCode === enter || e.keyCode === space) {\n if (e.target.matches(selectors.actions.optionActions.showSummary)) {\n e.preventDefault();\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptionsContainer.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptionsContainer.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptionsContainer.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptionsContainer.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement|null} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = null) => {\n if (previousChooserOption !== null) {\n toggleFocusableChooserOption(previousChooserOption, false);\n }\n\n toggleFocusableChooserOption(currentChooserOption, true);\n currentChooserOption.focus();\n};\n\n/**\n * Add or remove a chooser option from the focus order.\n *\n * @method toggleFocusableChooserOption\n * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order\n * @param {Boolean} isFocusable Whether the chooser element is focusable or not\n */\nconst toggleFocusableChooserOption = (chooserOption, isFocusable) => {\n const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);\n const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n\n if (isFocusable) {\n // Set tabindex to 0 to add current chooser option element to the focus order.\n chooserOption.tabIndex = 0;\n chooserOptionLink.tabIndex = 0;\n chooserOptionHelp.tabIndex = 0;\n chooserOptionFavourite.tabIndex = 0;\n } else {\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n chooserOption.tabIndex = -1;\n chooserOptionLink.tabIndex = -1;\n chooserOptionHelp.tabIndex = -1;\n chooserOptionFavourite.tabIndex = -1;\n }\n};\n\n/**\n * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Render the search results in a defined container\n *\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The container where the data should be rendered\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresultsnumber': searchResultsData.length,\n 'searchresults': searchResultsData\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/search_results', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {Object} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {String} searchQuery The search query\n */\nconst toggleSearchResultsView = async(modal, mappedModules, searchQuery) => {\n const modalBody = modal.getBody()[0];\n const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);\n const chooserContainer = modalBody.querySelector(selectors.regions.chooser);\n const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);\n const searchIcon = modalBody.querySelector(selectors.elements.searchicon);\n\n if (searchQuery.length > 0) { // Search query is present.\n const searchResultsData = searchModules(mappedModules, searchQuery);\n await renderSearchResults(searchResultsContainer, searchResultsData);\n const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);\n const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal);\n }\n // Display the \"clear\" search button in the activity chooser search bar.\n searchIcon.classList.add('d-none');\n clearSearchButton.classList.remove('d-none');\n // Hide the default chooser options container.\n chooserContainer.setAttribute('hidden', 'hidden');\n // Display the search results container.\n searchResultsContainer.removeAttribute('hidden');\n } else { // Search query is not present.\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n searchIcon.classList.remove('d-none');\n // Hide the search results container.\n searchResultsContainer.setAttribute('hidden', 'hidden');\n // Display the default chooser options container.\n chooserContainer.removeAttribute('hidden');\n }\n};\n\n/**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {Array} modules List of available modules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchModules = (modules, searchTerm) => {\n if (searchTerm === '') {\n return modules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n modules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n};\n\n/**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n * @param {Promise} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the built module information\n */\nconst setupKeyboardAccessibility = (modal, mappedModules) => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise().then(body => {\n $(selectors.elements.tab).on('shown.bs.tab', (e) => {\n const activeSectionId = e.target.getAttribute(\"href\");\n const activeSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = activeSectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n const prevActiveSectionId = e.relatedTarget.getAttribute(\"href\");\n const prevActiveSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));\n\n // Disable the focus of every chooser option in the previous active section.\n disableFocusAllChooserOptions(prevActiveSectionChooserOptions);\n // Enable the focus of the first chooser option in the current active section.\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions, modal);\n });\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items\n */\nconst disableFocusAllChooserOptions = (sectionChooserOptions) => {\n const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n toggleFocusableChooserOption(chooserOption, false);\n });\n};\n\n/**\n * Display the module chooser.\n *\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n modalPromise.then(modal => {\n registerListenerEvents(modal, mappedModules, partialFavourite, footerData);\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n setupKeyboardAccessibility(modal, mappedModules);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n }).catch();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/dialogue.js"],"names":["getPlugin","pluginName","showModuleHelp","carousel","moduleData","modal","showFooter","setFooter","Templates","render","help","find","selectors","regions","innerHTML","classList","add","spinnerPromise","transitionPromiseResolver","transitionPromise","Promise","resolve","contentPromise","renderForPromise","all","then","html","js","replaceNodeContents","querySelector","chooserSummary","header","focus","catch","Notification","exception","one","manageFavouriteState","modalBody","caller","partialFavourite","isFavourite","dataset","favourited","id","name","internal","Repository","unfavouriteModule","favouriteModule","registerListenerEvents","mappedModules","footerData","bodyClickListener","e","target","closest","actions","optionActions","showSummary","getBody","module","chooserOption","container","moduleName","modname","get","hasFooterContent","manageFavourite","activeSectionId","elements","activetab","getAttribute","sectionChooserOptions","getSectionChooserOptions","firstChooserOption","toggleFocusableChooserOption","initChooserOptionsKeyboardNavigation","matches","closeOption","on","allModules","modules","getModuleSelector","clearSearch","searchInput","search","value","toggleSearchResultsView","footerClickListener","footer","customfooterjs","footerjs","getBodyPromise","body","interval","pause","keyboard","addEventListener","initTabsKeyboardNavigation","getFooterPromise","favTabNav","favouriteTabNav","recommendedTabNav","defaultTabNav","activityTabNav","resourceTabNav","forEach","element","firstLink","parentElement","visibletabs","lastLink","lastElementChild","keyCode","arrowRight","nextLink","nextElementSibling","tabIndex","contains","arrowLeft","previousLink","previousElementSibling","home","end","space","preventDefault","click","chooserOptionsContainer","chooserOptions","querySelectorAll","Array","from","enter","currentOption","nextOption","firstOption","firstElementChild","toFocusOption","clickErrorHandler","focusChooserOption","previousOption","lastOption","currentChooserOption","previousChooserOption","isFocusable","chooserOptionLink","addChooser","chooserOptionHelp","chooserOptionFavourite","item","fallback","renderSearchResults","searchResultsContainer","searchResultsData","templateData","length","searchQuery","searchResults","chooserContainer","chooser","clearSearchButton","searchModules","searchResultItemsContainer","searchResultItems","firstSearchResultItem","remove","setAttribute","removeAttribute","searchTerm","toLowerCase","activity","activityName","title","activityDesc","includes","push","setupKeyboardAccessibility","getModal","tab","activeSectionChooserOptions","prevActiveSectionId","relatedTarget","prevActiveSectionChooserOptions","disableFocusAllChooserOptions","allChooserOptions","displayChooser","modalPromise","sectionModules","Map","set","componentname","link","getRoot","ModalEvents","hidden","destroy"],"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 * A type of dialogue used as for choosing options.\n *\n * @module core_course/local/chooser/dialogue\n * @package core\n * @copyright 2019 Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as ModalEvents from 'core/modal_events';\nimport selectors from 'core_course/local/activitychooser/selectors';\nimport * as Templates from 'core/templates';\nimport {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';\nimport {addIconToContainer} from 'core/loadingicon';\nimport * as Repository from 'core_course/local/activitychooser/repository';\nimport Notification from 'core/notification';\nimport {debounce} from 'core/utils';\nconst getPlugin = pluginName => import(pluginName);\n\n/**\n * Given an event from the main module 'page' navigate to it's help section via a carousel.\n *\n * @method showModuleHelp\n * @param {jQuery} carousel Our initialized carousel to manipulate\n * @param {Object} moduleData Data of the module to carousel to\n * @param {jQuery} modal We need to figure out if the current modal has a footer.\n */\nconst showModuleHelp = (carousel, moduleData, modal = null) => {\n // If we have a real footer then we need to change temporarily.\n if (modal !== null && moduleData.showFooter === true) {\n modal.setFooter(Templates.render('core_course/local/activitychooser/footer_partial', moduleData));\n }\n const help = carousel.find(selectors.regions.help)[0];\n help.innerHTML = '';\n help.classList.add('m-auto');\n\n // Add a spinner.\n const spinnerPromise = addIconToContainer(help);\n\n // Used later...\n let transitionPromiseResolver = null;\n const transitionPromise = new Promise(resolve => {\n transitionPromiseResolver = resolve;\n });\n\n // Build up the html & js ready to place into the help section.\n const contentPromise = Templates.renderForPromise('core_course/local/activitychooser/help', moduleData);\n\n // Wait for the content to be ready, and for the transition to be complet.\n Promise.all([contentPromise, spinnerPromise, transitionPromise])\n .then(([{html, js}]) => Templates.replaceNodeContents(help, html, js))\n .then(() => {\n help.querySelector(selectors.regions.chooserSummary.header).focus();\n return help;\n })\n .catch(Notification.exception);\n\n // Move to the next slide, and resolve the transition promise when it's done.\n carousel.one('slid.bs.carousel', () => {\n transitionPromiseResolver();\n });\n // Trigger the transition between 'pages'.\n carousel.carousel('next');\n};\n\n/**\n * Given a user wants to change the favourite state of a module we either add or remove the status.\n * We also propergate this change across our map of modals.\n *\n * @method manageFavouriteState\n * @param {HTMLElement} modalBody The DOM node of the modal to manipulate\n * @param {HTMLElement} caller\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n */\nconst manageFavouriteState = async(modalBody, caller, partialFavourite) => {\n const isFavourite = caller.dataset.favourited;\n const id = caller.dataset.id;\n const name = caller.dataset.name;\n const internal = caller.dataset.internal;\n // Switch on fave or not.\n if (isFavourite === 'true') {\n await Repository.unfavouriteModule(name, id);\n\n partialFavourite(internal, false, modalBody);\n } else {\n await Repository.favouriteModule(name, id);\n\n partialFavourite(internal, true, modalBody);\n }\n\n};\n\n/**\n * Register chooser related event listeners.\n *\n * @method registerListenerEvents\n * @param {Promise} modal Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nconst registerListenerEvents = (modal, mappedModules, partialFavourite, footerData) => {\n const bodyClickListener = async(e) => {\n if (e.target.closest(selectors.actions.optionActions.showSummary)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n\n if (e.target.closest(selectors.actions.optionActions.manageFavourite)) {\n const caller = e.target.closest(selectors.actions.optionActions.manageFavourite);\n await manageFavouriteState(modal.getBody()[0], caller, partialFavourite);\n const activeSectionId = modal.getBody()[0].querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = modal.getBody()[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(modal.getBody()[0], mappedModules, sectionChooserOptions, modal);\n }\n\n // From the help screen go back to the module overview.\n if (e.target.matches(selectors.actions.closeOption)) {\n const carousel = $(modal.getBody()[0].querySelector(selectors.regions.carousel));\n\n // Trigger the transition between 'pages'.\n carousel.carousel('prev');\n carousel.on('slid.bs.carousel', () => {\n const allModules = modal.getBody()[0].querySelector(selectors.regions.modules);\n const caller = allModules.querySelector(selectors.regions.getModuleSelector(e.target.dataset.modname));\n caller.focus();\n });\n }\n\n // The \"clear search\" button is triggered.\n if (e.target.closest(selectors.actions.clearSearch)) {\n // Clear the entered search query in the search bar and hide the search results container.\n const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);\n searchInput.value = \"\";\n searchInput.focus();\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }\n };\n\n // We essentially have two types of footer.\n // A fake one that is handled within the template for chooser_help and then all of the stuff for\n // modal.footer. We need to ensure we know exactly what type of footer we are using so we know what we\n // need to manage. The below code handles a real footer going to a mnet carousel item.\n const footerClickListener = async(e) => {\n if (footerData.footer === true) {\n const footerjs = await getPlugin(footerData.customfooterjs);\n await footerjs.footerClickListener(e, footerData, modal);\n }\n };\n\n modal.getBodyPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(body => body[0])\n\n // Set up the carousel.\n .then(body => {\n $(body.querySelector(selectors.regions.carousel))\n .carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n return body;\n })\n\n // Add the listener for clicks on the body.\n .then(body => {\n body.addEventListener('click', bodyClickListener);\n return body;\n })\n\n // Add a listener for an input change in the activity chooser's search bar.\n .then(body => {\n const searchInput = body.querySelector(selectors.actions.search);\n // The search input is triggered.\n searchInput.addEventListener('input', debounce(() => {\n // Display the search results.\n toggleSearchResultsView(modal, mappedModules, searchInput.value);\n }, 300));\n return body;\n })\n\n // Register event listeners related to the keyboard navigation controls.\n .then(body => {\n // Get the active chooser options section.\n const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute(\"href\");\n const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);\n\n toggleFocusableChooserOption(firstChooserOption, true);\n initTabsKeyboardNavigation(body);\n initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions, modal);\n\n return body;\n })\n .catch();\n\n modal.getFooterPromise()\n\n // The return value of getBodyPromise is a jquery object containing the body NodeElement.\n .then(footer => footer[0])\n // Add the listener for clicks on the footer.\n .then(footer => {\n footer.addEventListener('click', footerClickListener);\n return footer;\n })\n .catch();\n};\n\n/**\n * Initialise the keyboard navigation controls for the tab list items.\n *\n * @method initTabsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n */\nconst initTabsKeyboardNavigation = (body) => {\n // Set up the tab handlers.\n const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);\n const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);\n const defaultTabNav = body.querySelector(selectors.regions.defaultTabNav);\n const activityTabNav = body.querySelector(selectors.regions.activityTabNav);\n const resourceTabNav = body.querySelector(selectors.regions.resourceTabNav);\n const tabNavArray = [favTabNav, recommendedTabNav, defaultTabNav, activityTabNav, resourceTabNav];\n tabNavArray.forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n // The first visible navigation tab link.\n const firstLink = e.target.parentElement.querySelector(selectors.elements.visibletabs);\n // The last navigation tab link. It would always be the default activities tab link.\n const lastLink = e.target.parentElement.lastElementChild;\n\n if (e.keyCode === arrowRight) {\n const nextLink = e.target.nextElementSibling;\n if (nextLink === null) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else if (nextLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else {\n e.target.tabIndex = -1;\n nextLink.tabIndex = 0;\n nextLink.focus();\n }\n }\n if (e.keyCode === arrowLeft) {\n const previousLink = e.target.previousElementSibling;\n if (previousLink === null) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n } else if (previousLink.classList.contains('d-none')) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n } else {\n e.target.tabIndex = -1;\n previousLink.tabIndex = 0;\n previousLink.focus();\n }\n }\n if (e.keyCode === home) {\n e.target.tabIndex = -1;\n firstLink.tabIndex = 0;\n firstLink.focus();\n }\n if (e.keyCode === end) {\n e.target.tabIndex = -1;\n lastLink.tabIndex = 0;\n lastLink.focus();\n }\n if (e.keyCode === space) {\n e.preventDefault();\n e.target.click();\n }\n });\n });\n};\n\n/**\n * Initialise the keyboard navigation controls for the chooser options.\n *\n * @method initChooserOptionsKeyboardNavigation\n * @param {HTMLElement} body Our modal that we are working with\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items\n * @param {Object} modal Our created modal for the section\n */\nconst initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer, modal = null) => {\n const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);\n\n Array.from(chooserOptions).forEach((element) => {\n return element.addEventListener('keydown', (e) => {\n\n // Check for enter/ space triggers for showing the help.\n if (e.keyCode === enter || e.keyCode === space) {\n if (e.target.matches(selectors.actions.optionActions.showSummary)) {\n e.preventDefault();\n const module = e.target.closest(selectors.regions.chooserOption.container);\n const moduleName = module.dataset.modname;\n const moduleData = mappedModules.get(moduleName);\n const carousel = $(body.querySelector(selectors.regions.carousel));\n carousel.carousel({\n interval: false,\n pause: true,\n keyboard: false\n });\n\n // We need to know if the overall modal has a footer so we know when to show a real / vs fake footer.\n moduleData.showFooter = modal.hasFooterContent();\n showModuleHelp(carousel, moduleData, modal);\n }\n }\n\n // Next.\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const nextOption = currentOption.nextElementSibling;\n const firstOption = chooserOptionsContainer.firstElementChild;\n const toFocusOption = clickErrorHandler(nextOption, firstOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n // Previous.\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const previousOption = currentOption.previousElementSibling;\n const lastOption = chooserOptionsContainer.lastElementChild;\n const toFocusOption = clickErrorHandler(previousOption, lastOption);\n focusChooserOption(toFocusOption, currentOption);\n }\n\n if (e.keyCode === home) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const firstOption = chooserOptionsContainer.firstElementChild;\n focusChooserOption(firstOption, currentOption);\n }\n\n if (e.keyCode === end) {\n e.preventDefault();\n const currentOption = e.target.closest(selectors.regions.chooserOption.container);\n const lastOption = chooserOptionsContainer.lastElementChild;\n focusChooserOption(lastOption, currentOption);\n }\n });\n });\n};\n\n/**\n * Focus on a chooser option element and remove the previous chooser element from the focus order\n *\n * @method focusChooserOption\n * @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus\n * @param {HTMLElement|null} previousChooserOption The previous focused option element\n */\nconst focusChooserOption = (currentChooserOption, previousChooserOption = null) => {\n if (previousChooserOption !== null) {\n toggleFocusableChooserOption(previousChooserOption, false);\n }\n\n toggleFocusableChooserOption(currentChooserOption, true);\n currentChooserOption.focus();\n};\n\n/**\n * Add or remove a chooser option from the focus order.\n *\n * @method toggleFocusableChooserOption\n * @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order\n * @param {Boolean} isFocusable Whether the chooser element is focusable or not\n */\nconst toggleFocusableChooserOption = (chooserOption, isFocusable) => {\n const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);\n const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);\n const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);\n\n if (isFocusable) {\n // Set tabindex to 0 to add current chooser option element to the focus order.\n chooserOption.tabIndex = 0;\n chooserOptionLink.tabIndex = 0;\n chooserOptionHelp.tabIndex = 0;\n chooserOptionFavourite.tabIndex = 0;\n } else {\n // Set tabindex to -1 to remove the previous chooser option element from the focus order.\n chooserOption.tabIndex = -1;\n chooserOptionLink.tabIndex = -1;\n chooserOptionHelp.tabIndex = -1;\n chooserOptionFavourite.tabIndex = -1;\n }\n};\n\n/**\n * Small error handling function to make sure the navigated to object exists\n *\n * @method clickErrorHandler\n * @param {HTMLElement} item What we want to check exists\n * @param {HTMLElement} fallback If we dont match anything fallback the focus\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Render the search results in a defined container\n *\n * @method renderSearchResults\n * @param {HTMLElement} searchResultsContainer The container where the data should be rendered\n * @param {Object} searchResultsData Data containing the module items that satisfy the search criteria\n */\nconst renderSearchResults = async(searchResultsContainer, searchResultsData) => {\n const templateData = {\n 'searchresultsnumber': searchResultsData.length,\n 'searchresults': searchResultsData\n };\n // Build up the html & js ready to place into the help section.\n const {html, js} = await Templates.renderForPromise('core_course/local/activitychooser/search_results', templateData);\n await Templates.replaceNodeContents(searchResultsContainer, html, js);\n};\n\n/**\n * Toggle (display/hide) the search results depending on the value of the search query\n *\n * @method toggleSearchResultsView\n * @param {Object} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}\n * @param {String} searchQuery The search query\n */\nconst toggleSearchResultsView = async(modal, mappedModules, searchQuery) => {\n const modalBody = modal.getBody()[0];\n const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);\n const chooserContainer = modalBody.querySelector(selectors.regions.chooser);\n const clearSearchButton = modalBody.querySelector(selectors.actions.clearSearch);\n\n if (searchQuery.length > 0) { // Search query is present.\n const searchResultsData = searchModules(mappedModules, searchQuery);\n await renderSearchResults(searchResultsContainer, searchResultsData);\n const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);\n const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);\n if (firstSearchResultItem) {\n // Set the first result item to be focusable.\n toggleFocusableChooserOption(firstSearchResultItem, true);\n // Register keyboard events on the created search result items.\n initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal);\n }\n // Display the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.remove('d-none');\n // Hide the default chooser options container.\n chooserContainer.setAttribute('hidden', 'hidden');\n // Display the search results container.\n searchResultsContainer.removeAttribute('hidden');\n } else { // Search query is not present.\n // Hide the \"clear\" search button in the activity chooser search bar.\n clearSearchButton.classList.add('d-none');\n // Hide the search results container.\n searchResultsContainer.setAttribute('hidden', 'hidden');\n // Display the default chooser options container.\n chooserContainer.removeAttribute('hidden');\n }\n};\n\n/**\n * Return the list of modules which have a name or description that matches the given search term.\n *\n * @method searchModules\n * @param {Array} modules List of available modules\n * @param {String} searchTerm The search term to match\n * @return {Array}\n */\nconst searchModules = (modules, searchTerm) => {\n if (searchTerm === '') {\n return modules;\n }\n searchTerm = searchTerm.toLowerCase();\n const searchResults = [];\n modules.forEach((activity) => {\n const activityName = activity.title.toLowerCase();\n const activityDesc = activity.help.toLowerCase();\n if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {\n searchResults.push(activity);\n }\n });\n\n return searchResults;\n};\n\n/**\n * Set up our tabindex information across the chooser.\n *\n * @method setupKeyboardAccessibility\n * @param {Promise} modal Our created modal for the section\n * @param {Map} mappedModules A map of all of the built module information\n */\nconst setupKeyboardAccessibility = (modal, mappedModules) => {\n modal.getModal()[0].tabIndex = -1;\n\n modal.getBodyPromise().then(body => {\n $(selectors.elements.tab).on('shown.bs.tab', (e) => {\n const activeSectionId = e.target.getAttribute(\"href\");\n const activeSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));\n const firstChooserOption = activeSectionChooserOptions\n .querySelector(selectors.regions.chooserOption.container);\n const prevActiveSectionId = e.relatedTarget.getAttribute(\"href\");\n const prevActiveSectionChooserOptions = body[0]\n .querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));\n\n // Disable the focus of every chooser option in the previous active section.\n disableFocusAllChooserOptions(prevActiveSectionChooserOptions);\n // Enable the focus of the first chooser option in the current active section.\n toggleFocusableChooserOption(firstChooserOption, true);\n initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions, modal);\n });\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Disable the focus of all chooser options in a specific container (section).\n *\n * @method disableFocusAllChooserOptions\n * @param {HTMLElement} sectionChooserOptions The section that contains the chooser items\n */\nconst disableFocusAllChooserOptions = (sectionChooserOptions) => {\n const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);\n allChooserOptions.forEach((chooserOption) => {\n toggleFocusableChooserOption(chooserOption, false);\n });\n};\n\n/**\n * Display the module chooser.\n *\n * @method displayChooser\n * @param {Promise} modalPromise Our created modal for the section\n * @param {Array} sectionModules An array of all of the built module information\n * @param {Function} partialFavourite Partially applied function we need to manage favourite status\n * @param {Object} footerData Our base footer object.\n */\nexport const displayChooser = (modalPromise, sectionModules, partialFavourite, footerData) => {\n // Make a map so we can quickly fetch a specific module's object for either rendering or searching.\n const mappedModules = new Map();\n sectionModules.forEach((module) => {\n mappedModules.set(module.componentname + '_' + module.link, module);\n });\n\n // Register event listeners.\n modalPromise.then(modal => {\n registerListenerEvents(modal, mappedModules, partialFavourite, footerData);\n\n // We want to focus on the first chooser option element as soon as the modal is opened.\n setupKeyboardAccessibility(modal, mappedModules);\n\n // We want to focus on the action select when the dialog is closed.\n modal.getRoot().on(ModalEvents.hidden, () => {\n modal.destroy();\n });\n\n return modal;\n }).catch();\n};\n"],"file":"dialogue.min.js"} \ No newline at end of file diff --git a/course/amd/build/local/activitychooser/selectors.min.js b/course/amd/build/local/activitychooser/selectors.min.js index 0b4a6edf1ea..45f8f4fd399 100644 --- a/course/amd/build/local/activitychooser/selectors.min.js +++ b/course/amd/build/local/activitychooser/selectors.min.js @@ -1,2 +1,2 @@ -define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),header:b("region","summary-header"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),activityTabNav:b("region","activity-tab-nav"),resourceTabNav:b("region","resources-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),activityTab:b("region","activity"),resourceTab:b("region","resources"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",visibletabs:"a[data-toggle=\"tab\"]:not(.d-none)",searchicon:".input-group-append .search-icon",clearsearch:".input-group-append .clear"}};a.default=c;return a.default}); +define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),header:b("region","summary-header"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),activityTabNav:b("region","activity-tab-nav"),resourceTabNav:b("region","resources-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),activityTab:b("region","activity"),resourceTab:b("region","resources"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",visibletabs:"a[data-toggle=\"tab\"]:not(.d-none)"}};a.default=c;return a.default}); //# sourceMappingURL=selectors.min.js.map diff --git a/course/amd/build/local/activitychooser/selectors.min.js.map b/course/amd/build/local/activitychooser/selectors.min.js.map index 177b53e8f34..13f9e9f9fcc 100644 --- a/course/amd/build/local/activitychooser/selectors.min.js.map +++ b/course/amd/build/local/activitychooser/selectors.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/activitychooser/selectors.js"],"names":["getDataSelector","name","value","regions","chooser","getSectionChooserOptions","containerid","chooserOption","container","actions","info","chooserSummary","content","header","carousel","help","modules","favouriteTabNav","recommendedTabNav","defaultTabNav","activityTabNav","resourceTabNav","favouriteTab","recommendedTab","defaultTab","activityTab","resourceTab","getModuleSelector","modname","searchResults","searchResultItems","optionActions","showSummary","manageFavourite","addChooser","closeOption","hide","search","clearSearch","render","favourites","elements","section","sectionmodchooser","sitemenu","sitetopic","tab","activetab","visibletabs","searchicon","clearsearch"],"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 * Define all of the selectors we will be using on the grading interface.\n *\n * @module core_course/local/chooser/selectors\n * @package core_course\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n chooser: getDataSelector('region', 'chooser-container'),\n getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n header: getDataSelector('region', 'summary-header'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n modules: getDataSelector('region', 'modules'),\n favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),\n recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),\n defaultTabNav: getDataSelector('region', 'default-tab-nav'),\n activityTabNav: getDataSelector('region', 'activity-tab-nav'),\n resourceTabNav: getDataSelector('region', 'resources-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n recommendedTab: getDataSelector('region', 'recommended'),\n defaultTab: getDataSelector('region', 'default'),\n activityTab: getDataSelector('region', 'activity'),\n resourceTab: getDataSelector('region', 'resources'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`,\n searchResults: getDataSelector('region', 'search-results-container'),\n searchResultItems: getDataSelector('region', 'search-result-items-container'),\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide'),\n search: getDataSelector('action', 'search'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n },\n render: {\n favourites: getDataSelector('render', 'favourites-area'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n sitemenu: '.block_site_main_menu',\n sitetopic: 'div.sitetopic',\n tab: 'a[data-toggle=\"tab\"]',\n activetab: 'a[data-toggle=\"tab\"][aria-selected=\"true\"]',\n visibletabs: 'a[data-toggle=\"tab\"]:not(.d-none)',\n searchicon: '.input-group-append .search-icon',\n clearsearch: '.input-group-append .clear'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/activitychooser/selectors.js"],"names":["getDataSelector","name","value","regions","chooser","getSectionChooserOptions","containerid","chooserOption","container","actions","info","chooserSummary","content","header","carousel","help","modules","favouriteTabNav","recommendedTabNav","defaultTabNav","activityTabNav","resourceTabNav","favouriteTab","recommendedTab","defaultTab","activityTab","resourceTab","getModuleSelector","modname","searchResults","searchResultItems","optionActions","showSummary","manageFavourite","addChooser","closeOption","hide","search","clearSearch","render","favourites","elements","section","sectionmodchooser","sitemenu","sitetopic","tab","activetab","visibletabs"],"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 * Define all of the selectors we will be using on the grading interface.\n *\n * @module core_course/local/chooser/selectors\n * @package core_course\n * @copyright 2019 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * A small helper function to build queryable data selectors.\n * @method getDataSelector\n * @param {String} name\n * @param {String} value\n * @return {string}\n */\nconst getDataSelector = (name, value) => {\n return `[data-${name}=\"${value}\"]`;\n};\n\nexport default {\n regions: {\n chooser: getDataSelector('region', 'chooser-container'),\n getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,\n chooserOption: {\n container: getDataSelector('region', 'chooser-option-container'),\n actions: getDataSelector('region', 'chooser-option-actions-container'),\n info: getDataSelector('region', 'chooser-option-info-container'),\n },\n chooserSummary: {\n container: getDataSelector('region', 'chooser-option-summary-container'),\n content: getDataSelector('region', 'chooser-option-summary-content-container'),\n header: getDataSelector('region', 'summary-header'),\n actions: getDataSelector('region', 'chooser-option-summary-actions-container'),\n },\n carousel: getDataSelector('region', 'carousel'),\n help: getDataSelector('region', 'help'),\n modules: getDataSelector('region', 'modules'),\n favouriteTabNav: getDataSelector('region', 'favourite-tab-nav'),\n recommendedTabNav: getDataSelector('region', 'recommended-tab-nav'),\n defaultTabNav: getDataSelector('region', 'default-tab-nav'),\n activityTabNav: getDataSelector('region', 'activity-tab-nav'),\n resourceTabNav: getDataSelector('region', 'resources-tab-nav'),\n favouriteTab: getDataSelector('region', 'favourites'),\n recommendedTab: getDataSelector('region', 'recommended'),\n defaultTab: getDataSelector('region', 'default'),\n activityTab: getDataSelector('region', 'activity'),\n resourceTab: getDataSelector('region', 'resources'),\n getModuleSelector: modname => `[role=\"menuitem\"][data-modname=\"${modname}\"]`,\n searchResults: getDataSelector('region', 'search-results-container'),\n searchResultItems: getDataSelector('region', 'search-result-items-container'),\n },\n actions: {\n optionActions: {\n showSummary: getDataSelector('action', 'show-option-summary'),\n manageFavourite: getDataSelector('action', 'manage-module-favourite'),\n },\n addChooser: getDataSelector('action', 'add-chooser-option'),\n closeOption: getDataSelector('action', 'close-chooser-option-summary'),\n hide: getDataSelector('action', 'hide'),\n search: getDataSelector('action', 'search'),\n clearSearch: getDataSelector('action', 'clearsearch'),\n },\n render: {\n favourites: getDataSelector('render', 'favourites-area'),\n },\n elements: {\n section: '.section',\n sectionmodchooser: 'button.section-modchooser-link',\n sitemenu: '.block_site_main_menu',\n sitetopic: 'div.sitetopic',\n tab: 'a[data-toggle=\"tab\"]',\n activetab: 'a[data-toggle=\"tab\"][aria-selected=\"true\"]',\n visibletabs: 'a[data-toggle=\"tab\"]:not(.d-none)'\n },\n};\n"],"file":"selectors.min.js"} \ No newline at end of file diff --git a/course/amd/src/local/activitychooser/dialogue.js b/course/amd/src/local/activitychooser/dialogue.js index cd045a3de0d..a3bde76d89f 100644 --- a/course/amd/src/local/activitychooser/dialogue.js +++ b/course/amd/src/local/activitychooser/dialogue.js @@ -466,8 +466,7 @@ const toggleSearchResultsView = async(modal, mappedModules, searchQuery) => { const modalBody = modal.getBody()[0]; const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults); const chooserContainer = modalBody.querySelector(selectors.regions.chooser); - const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch); - const searchIcon = modalBody.querySelector(selectors.elements.searchicon); + const clearSearchButton = modalBody.querySelector(selectors.actions.clearSearch); if (searchQuery.length > 0) { // Search query is present. const searchResultsData = searchModules(mappedModules, searchQuery); @@ -481,7 +480,6 @@ const toggleSearchResultsView = async(modal, mappedModules, searchQuery) => { initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer, modal); } // Display the "clear" search button in the activity chooser search bar. - searchIcon.classList.add('d-none'); clearSearchButton.classList.remove('d-none'); // Hide the default chooser options container. chooserContainer.setAttribute('hidden', 'hidden'); @@ -490,7 +488,6 @@ const toggleSearchResultsView = async(modal, mappedModules, searchQuery) => { } else { // Search query is not present. // Hide the "clear" search button in the activity chooser search bar. clearSearchButton.classList.add('d-none'); - searchIcon.classList.remove('d-none'); // Hide the search results container. searchResultsContainer.setAttribute('hidden', 'hidden'); // Display the default chooser options container. diff --git a/course/amd/src/local/activitychooser/selectors.js b/course/amd/src/local/activitychooser/selectors.js index 6b54bbcde26..dd20e756c9f 100644 --- a/course/amd/src/local/activitychooser/selectors.js +++ b/course/amd/src/local/activitychooser/selectors.js @@ -86,8 +86,6 @@ export default { sitetopic: 'div.sitetopic', tab: 'a[data-toggle="tab"]', activetab: 'a[data-toggle="tab"][aria-selected="true"]', - visibletabs: 'a[data-toggle="tab"]:not(.d-none)', - searchicon: '.input-group-append .search-icon', - clearsearch: '.input-group-append .clear' + visibletabs: 'a[data-toggle="tab"]:not(.d-none)' }, }; diff --git a/course/classes/management_renderer.php b/course/classes/management_renderer.php index 25340f849f6..3de3c7a5bbf 100644 --- a/course/classes/management_renderer.php +++ b/course/classes/management_renderer.php @@ -1291,56 +1291,19 @@ class core_course_management_renderer extends plugin_renderer_base { * Renders html to display a course search form * * @param string $value default value to populate the search field - * @param string $format display format - 'plain' (default), 'short' or 'navbar' * @return string */ - public function course_search_form($value = '', $format = 'plain') { - static $count = 0; - $formid = 'coursesearch'; - if ((++$count) > 1) { - $formid .= $count; - } + public function course_search_form($value = '') { - switch ($format) { - case 'navbar' : - $formid = 'coursesearchnavbar'; - $inputid = 'navsearchbox'; - $inputsize = 20; - break; - case 'short' : - $inputid = 'shortsearchbox'; - $inputsize = 12; - break; - default : - $inputid = 'coursesearchbox'; - $inputsize = 30; - } - - $strsearchcourses = get_string("searchcourses"); - $searchurl = new moodle_url('/course/management.php'); - - $output = html_writer::start_div('row'); - $output .= html_writer::start_div('col-md-12'); - $output .= html_writer::start_tag('form', array('class' => 'card', 'id' => $formid, - 'action' => $searchurl, 'method' => 'get')); - $output .= html_writer::start_tag('fieldset', array('class' => 'coursesearchbox invisiblefieldset')); - $output .= html_writer::tag('legend', $this->output->heading($strsearchcourses.': ', 2, 'm-0'), - array('class' => 'card-header')); - $output .= html_writer::start_div('card-body'); - $output .= html_writer::start_div('input-group col-sm-6 col-lg-4 m-auto'); - $output .= html_writer::empty_tag('input', array('class' => 'form-control', 'type' => 'text', 'id' => $inputid, - 'size' => $inputsize, 'name' => 'search', 'value' => s($value), 'aria-label' => get_string('searchcourses'))); - $output .= html_writer::start_tag('span', array('class' => 'input-group-btn')); - $output .= html_writer::tag('button', get_string('go'), array('class' => 'btn btn-primary', 'type' => 'submit')); - $output .= html_writer::end_tag('span'); - $output .= html_writer::end_div(); - $output .= html_writer::end_div(); - $output .= html_writer::end_tag('fieldset'); - $output .= html_writer::end_tag('form'); - $output .= html_writer::end_div(); - $output .= html_writer::end_div(); - - return $output; + $data = [ + 'action' => new moodle_url('/course/management.php'), + 'btnclass' => 'btn-primary', + 'extraclasses' => 'my-3 d-flex justify-content-center', + 'inputname' => 'search', + 'searchstring' => get_string('searchcourses'), + 'value' => $value + ]; + return $this->render_from_template('core/search_input', $data); } /** diff --git a/course/management.php b/course/management.php index 39ad5a77b7b..5afc685bb5b 100644 --- a/course/management.php +++ b/course/management.php @@ -487,6 +487,9 @@ if (count($notificationsfail) > 0) { } // Start the management form. + +echo $renderer->course_search_form($search); + echo $renderer->management_form_start(); echo $renderer->accessible_skipto_links($displaycategorylisting, $displaycourselisting, $displaycoursedetail); @@ -518,6 +521,5 @@ echo $renderer->grid_end(); // End of the management form. echo $renderer->management_form_end(); -echo $renderer->course_search_form($search); echo $renderer->footer(); diff --git a/course/renderer.php b/course/renderer.php index da82415caf2..89b7e0c7566 100644 --- a/course/renderer.php +++ b/course/renderer.php @@ -399,48 +399,22 @@ class core_course_renderer extends plugin_renderer_base { } /** - * Renders html to display a course search form. + * Renders html to display a course search form * * @param string $value default value to populate the search field - * @param string $format display format - 'plain' (default), 'short' or 'navbar' * @return string */ - public function course_search_form($value = '', $format = 'plain') { - static $count = 0; - $formid = 'coursesearch'; - if ((++$count) > 1) { - $formid .= $count; - } + public function course_search_form($value = '') { - switch ($format) { - case 'navbar' : - $formid = 'coursesearchnavbar'; - $inputid = 'navsearchbox'; - $inputsize = 20; - break; - case 'short' : - $inputid = 'shortsearchbox'; - $inputsize = 12; - break; - default : - $inputid = 'coursesearchbox'; - $inputsize = 30; - } - - $data = new stdClass(); - $data->searchurl = \core_search\manager::get_course_search_url()->out(false); - $data->id = $formid; - $data->inputid = $inputid; - $data->inputsize = $inputsize; - $data->value = $value; - $data->areaids = 'core_course-course'; - - if ($format != 'navbar') { - $helpicon = new \help_icon('coursesearch', 'core'); - $data->helpicon = $helpicon->export_for_template($this); - } - - return $this->render_from_template('core_course/course_search_form', $data); + $data = [ + 'action' => \core_search\manager::get_course_search_url(), + 'btnclass' => 'btn-primary', + 'inputname' => 'q', + 'searchstring' => get_string('searchcourses'), + 'hiddenfields' => (object) ['name' => 'areaids', 'value' => 'core_course-course'], + 'query' => $value + ]; + return $this->render_from_template('core/search_input', $data); } /** @@ -1890,6 +1864,13 @@ class core_course_renderer extends plugin_renderer_base { public function search_courses($searchcriteria) { global $CFG; $content = ''; + + $search = ''; + if (!empty($searchcriteria['search'])) { + $search = $searchcriteria['search']; + } + $content .= $this->course_search_form($search); + if (!empty($searchcriteria)) { // print search results @@ -1931,18 +1912,6 @@ class core_course_renderer extends plugin_renderer_base { $content .= $this->heading(get_string('searchresults'). ": $totalcount"); $content .= $courseslist; } - - if (!empty($searchcriteria['search'])) { - // print search form only if there was a search by search string, otherwise it is confusing - $content .= $this->box_start('generalbox mdl-align'); - $content .= $this->course_search_form($searchcriteria['search']); - $content .= $this->box_end(); - } - } else { - // just print search form - $content .= $this->box_start('generalbox mdl-align'); - $content .= $this->course_search_form(); - $content .= $this->box_end(); } return $content; } @@ -2464,7 +2433,7 @@ class core_course_renderer extends plugin_renderer_base { break; case FRONTPAGECOURSESEARCH: - $output .= $this->box($this->course_search_form('', 'short'), 'mdl-align'); + $output .= $this->box($this->course_search_form(''), 'd-flex justify-content-center'); break; } diff --git a/course/search.php b/course/search.php index 144767d452f..ccaf4b302a9 100644 --- a/course/search.php +++ b/course/search.php @@ -95,7 +95,7 @@ if (empty($searchcriteria)) { $aurl = new moodle_url('/course/management.php', $searchcriteria); $searchform = $OUTPUT->single_button($aurl, get_string('managecourses'), 'get'); } else { - $searchform = $courserenderer->course_search_form($search, 'navbar'); + $searchform = $courserenderer->course_search_form($search); } $PAGE->set_button($searchform); diff --git a/course/templates/local/activitychooser/search.mustache b/course/templates/local/activitychooser/search.mustache index 92839805e96..70dfac6b560 100644 --- a/course/templates/local/activitychooser/search.mustache +++ b/course/templates/local/activitychooser/search.mustache @@ -22,31 +22,14 @@ Example context (json): {} }} - +{{< core/search_input_auto }} + {{$label}}{{#str}} + searchactivities, core + {{/str}}{{/label}} + {{$placeholder}}{{#str}} + search, core + {{/str}}{{/placeholder}} +{{/ core/search_input_auto }} + diff --git a/course/tests/behat/course_search.feature b/course/tests/behat/course_search.feature index 635bc2d1d91..bd5c99c5f67 100644 --- a/course/tests/behat/course_search.feature +++ b/course/tests/behat/course_search.feature @@ -20,8 +20,8 @@ Feature: Courses can be searched for and moved in bulk. Scenario: Search courses finds correct results Given I log in as "admin" And I go to the courses management page - When I set the field "coursesearchbox" to "Biology" - And I press "Go" + When I set the field "Search" to "Biology" + And I press "Search" Then I should see "Biology Y1" And I should see "Biology Y2" And I should not see "English Y1" @@ -31,8 +31,8 @@ Feature: Courses can be searched for and moved in bulk. Scenario: Search courses and move results in bulk Given I log in as "admin" And I go to the courses management page - And I set the field "coursesearchbox" to "Biology" - And I press "Go" + And I set the field "Search" to "Biology" + And I press "Search" When I select course "Biology Y1" in the management interface And I select course "Biology Y2" in the management interface And I set the field "menumovecoursesto" to "Science" diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 2d8bd7267fe..602f1baeb2b 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -2096,6 +2096,7 @@ $string['today'] = 'Today'; $string['todaylogs'] = 'Today\'s logs'; $string['toeveryone'] = 'to everyone'; $string['toggleemojipicker'] = 'Toggle emoji picker'; +$string['togglesearch'] = 'Toggle search input'; $string['toomanybounces'] = 'That email address has had too many bounces. You must change it to continue.'; $string['toomanytags'] = 'This search included too many tags; some will have been ignored.'; $string['toomanytoshow'] = 'There are too many users to show.'; diff --git a/lib/amd/build/search-input.min.js b/lib/amd/build/search-input.min.js deleted file mode 100644 index 5e2f8db12b9..00000000000 --- a/lib/amd/build/search-input.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("core/search-input",["jquery"],function(a){var b=null,c=function(a){if(b.hasClass("expanded")){e()}else{d(a)}},d=function(c){var d=a(document).width();if("keydown"===c.type&&13!==c.keyCode&&32!==c.keyCode){return}if(767>=d&&("click"===c.type||"keydown"===c.type)){f();return}else if(767>=d){return}if("keydown"===c.type){c.preventDefault()}b.addClass("expanded");b.find("form").addClass("expanded");b.find("input").focus()},e=function(){b.removeClass("expanded");b.find("form").removeClass("expanded")},f=function(){b.find("form").submit()};return{init:function init(d){b=a("#"+d);b.on("click mouseover keydown","div",c)}}}); -//# sourceMappingURL=search-input.min.js.map diff --git a/lib/amd/build/search-input.min.js.map b/lib/amd/build/search-input.min.js.map deleted file mode 100644 index f6a55ed1d8e..00000000000 --- a/lib/amd/build/search-input.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/search-input.js"],"names":["define","$","wrapper","toggleForm","ev","hasClass","hideForm","showForm","windowWidth","document","width","type","keyCode","submitForm","preventDefault","addClass","find","focus","removeClass","submit","init","id","on"],"mappings":"AAyBAA,OAAM,qBAAC,CAAC,QAAD,CAAD,CAAa,SAASC,CAAT,CAAY,IAOvBC,CAAAA,CAAO,CAAG,IAPa,CAgBvBC,CAAU,CAAG,SAASC,CAAT,CAAa,CAE1B,GAAIF,CAAO,CAACG,QAAR,CAAiB,UAAjB,CAAJ,CAAkC,CAC9BC,CAAQ,EACX,CAFD,IAEO,CACHC,CAAQ,CAACH,CAAD,CACX,CACJ,CAvB0B,CAgCvBG,CAAQ,CAAG,SAASH,CAAT,CAAa,CAExB,GAAII,CAAAA,CAAW,CAAGP,CAAC,CAACQ,QAAD,CAAD,CAAYC,KAAZ,EAAlB,CAGA,GAAgB,SAAZ,GAAAN,CAAE,CAACO,IAAH,EAAwC,EAAf,GAAAP,CAAE,CAACQ,OAA5B,EAA6D,EAAf,GAAAR,CAAE,CAACQ,OAArD,CAAqE,CACjE,MACH,CAED,GAAmB,GAAf,EAAAJ,CAAW,GAAwB,OAAZ,GAAAJ,CAAE,CAACO,IAAH,EAAmC,SAAZ,GAAAP,CAAE,CAACO,IAAtC,CAAf,CAA0E,CAEtEE,CAAU,GACV,MACH,CAJD,IAIO,IAAmB,GAAf,EAAAL,CAAJ,CAAwB,CAE3B,MACH,CAED,GAAgB,SAAZ,GAAAJ,CAAE,CAACO,IAAP,CAA2B,CAEvBP,CAAE,CAACU,cAAH,EACH,CAEDZ,CAAO,CAACa,QAAR,CAAiB,UAAjB,EACAb,CAAO,CAACc,IAAR,CAAa,MAAb,EAAqBD,QAArB,CAA8B,UAA9B,EACAb,CAAO,CAACc,IAAR,CAAa,OAAb,EAAsBC,KAAtB,EACH,CA1D0B,CAkEvBX,CAAQ,CAAG,UAAW,CACtBJ,CAAO,CAACgB,WAAR,CAAoB,UAApB,EACAhB,CAAO,CAACc,IAAR,CAAa,MAAb,EAAqBE,WAArB,CAAiC,UAAjC,CACH,CArE0B,CA8EvBL,CAAU,CAAG,UAAW,CACxBX,CAAO,CAACc,IAAR,CAAa,MAAb,EAAqBG,MAArB,EACH,CAhF0B,CAkF3B,MAA8C,CAS1CC,IAAI,CAAE,cAASC,CAAT,CAAa,CACfnB,CAAO,CAAGD,CAAC,CAAC,IAAMoB,CAAP,CAAX,CACAnB,CAAO,CAACoB,EAAR,CAAW,yBAAX,CAAsC,KAAtC,CAA6CnB,CAA7C,CACH,CAZyC,CAcjD,CAhGK,CAAN","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 * Search box.\n *\n * @module core/search-input\n * @class search-input\n * @package core\n * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since Moodle 3.1\n */\ndefine(['jquery'], function($) {\n\n /**\n * This search box div node.\n *\n * @private\n */\n var wrapper = null;\n\n /**\n * Toggles the form visibility.\n *\n * @param {Event} ev\n * @method toggleForm\n * @private\n */\n var toggleForm = function(ev) {\n\n if (wrapper.hasClass('expanded')) {\n hideForm();\n } else {\n showForm(ev);\n }\n };\n\n /**\n * Shows the form or submits it depending on the window size.\n *\n * @param {Event} ev\n * @method showForm\n * @private\n */\n var showForm = function(ev) {\n\n var windowWidth = $(document).width();\n\n // We are only interested in enter and space keys (accessibility).\n if (ev.type === 'keydown' && ev.keyCode !== 13 && ev.keyCode !== 32) {\n return;\n }\n\n if (windowWidth <= 767 && (ev.type === 'click' || ev.type === 'keydown')) {\n // Move to the search page when using small window sizes as the input requires too much space.\n submitForm();\n return;\n } else if (windowWidth <= 767) {\n // Ignore mousedown events in while using small window sizes.\n return;\n }\n\n if (ev.type === 'keydown') {\n // We don't want to submit the form unless the user hits enter.\n ev.preventDefault();\n }\n\n wrapper.addClass('expanded');\n wrapper.find('form').addClass('expanded');\n wrapper.find('input').focus();\n };\n\n /**\n * Hides the form.\n *\n * @method hideForm\n * @private\n */\n var hideForm = function() {\n wrapper.removeClass('expanded');\n wrapper.find('form').removeClass('expanded');\n };\n\n /**\n * Submits the form.\n *\n * @param {Event} ev\n * @method submitForm\n * @private\n */\n var submitForm = function() {\n wrapper.find('form').submit();\n };\n\n return /** @alias module:core/search-input */ {\n // Public variables and functions.\n\n /**\n * Assigns listeners to the requested select box.\n *\n * @method init\n * @param {Number} id The search wrapper div id\n */\n init: function(id) {\n wrapper = $('#' + id);\n wrapper.on('click mouseover keydown', 'div', toggleForm);\n }\n };\n});\n"],"file":"search-input.min.js"} \ No newline at end of file diff --git a/lib/amd/src/search-input.js b/lib/amd/src/search-input.js deleted file mode 100644 index 85f9bb14946..00000000000 --- a/lib/amd/src/search-input.js +++ /dev/null @@ -1,122 +0,0 @@ -// 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 . - -/** - * Search box. - * - * @module core/search-input - * @class search-input - * @package core - * @copyright 2016 David Monllao {@link http://www.davidmonllao.com} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @since Moodle 3.1 - */ -define(['jquery'], function($) { - - /** - * This search box div node. - * - * @private - */ - var wrapper = null; - - /** - * Toggles the form visibility. - * - * @param {Event} ev - * @method toggleForm - * @private - */ - var toggleForm = function(ev) { - - if (wrapper.hasClass('expanded')) { - hideForm(); - } else { - showForm(ev); - } - }; - - /** - * Shows the form or submits it depending on the window size. - * - * @param {Event} ev - * @method showForm - * @private - */ - var showForm = function(ev) { - - var windowWidth = $(document).width(); - - // We are only interested in enter and space keys (accessibility). - if (ev.type === 'keydown' && ev.keyCode !== 13 && ev.keyCode !== 32) { - return; - } - - if (windowWidth <= 767 && (ev.type === 'click' || ev.type === 'keydown')) { - // Move to the search page when using small window sizes as the input requires too much space. - submitForm(); - return; - } else if (windowWidth <= 767) { - // Ignore mousedown events in while using small window sizes. - return; - } - - if (ev.type === 'keydown') { - // We don't want to submit the form unless the user hits enter. - ev.preventDefault(); - } - - wrapper.addClass('expanded'); - wrapper.find('form').addClass('expanded'); - wrapper.find('input').focus(); - }; - - /** - * Hides the form. - * - * @method hideForm - * @private - */ - var hideForm = function() { - wrapper.removeClass('expanded'); - wrapper.find('form').removeClass('expanded'); - }; - - /** - * Submits the form. - * - * @param {Event} ev - * @method submitForm - * @private - */ - var submitForm = function() { - wrapper.find('form').submit(); - }; - - return /** @alias module:core/search-input */ { - // Public variables and functions. - - /** - * Assigns listeners to the requested select box. - * - * @method init - * @param {Number} id The search wrapper div id - */ - init: function(id) { - wrapper = $('#' + id); - wrapper.on('click mouseover keydown', 'div', toggleForm); - } - }; -}); diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index ba5366fc048..dd9bd247977 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -3222,31 +3222,13 @@ EOD; return ''; } - if ($id == false) { - $id = uniqid(); - } else { - // Needs to be cleaned, we use it for the input id. - $id = clean_param($id, PARAM_ALPHANUMEXT); - } - - // JS to animate the form. - $this->page->requires->js_call_amd('core/search-input', 'init', array($id)); - - $searchicon = html_writer::tag('div', $this->pix_icon('a/search', get_string('search', 'search'), 'moodle'), - array('role' => 'button', 'tabindex' => 0)); - $formattrs = array('class' => 'search-input-form', 'action' => $CFG->wwwroot . '/search/index.php'); - $inputattrs = array('type' => 'text', 'name' => 'q', 'placeholder' => get_string('search', 'search'), - 'size' => 13, 'tabindex' => -1, 'id' => 'id_q_' . $id, 'class' => 'form-control'); - - $contents = html_writer::tag('label', get_string('enteryoursearchquery', 'search'), - array('for' => 'id_q_' . $id, 'class' => 'accesshide')) . html_writer::empty_tag('input', $inputattrs); - if ($this->page->context && $this->page->context->contextlevel !== CONTEXT_SYSTEM) { - $contents .= html_writer::empty_tag('input', ['type' => 'hidden', - 'name' => 'context', 'value' => $this->page->context->id]); - } - $searchinput = html_writer::tag('form', $contents, $formattrs); - - return html_writer::tag('div', $searchicon . $searchinput, array('class' => 'search-input-wrapper nav-link', 'id' => $id)); + $data = [ + 'action' => new moodle_url('/search/index.php'), + 'hiddenfields' => (object) ['name' => 'context', 'value' => $this->page->context->id], + 'inputname' => 'q', + 'searchstring' => get_string('search'), + ]; + return $this->render_from_template('core/search_input_navbar', $data); } /** diff --git a/blocks/settings/templates/search_form.mustache b/lib/templates/checkbox.mustache similarity index 52% rename from blocks/settings/templates/search_form.mustache rename to lib/templates/checkbox.mustache index 2ae6011112e..518e662b2cf 100644 --- a/blocks/settings/templates/search_form.mustache +++ b/lib/templates/checkbox.mustache @@ -15,22 +15,20 @@ along with Moodle. If not, see . }} {{! - @template block_settings/search_form + @template core/checkbox - This template renders the search form. + Chooser search template. Example context (json): { - "action": "https://domain.example/admin/search.php", - "label": "Search in settings", - "searchvalue": "Find this setting", - "quote": "Search" + "name": "fullsearch", + "id": "fullsearch", + "checked": true, + "value": "1", + "label": "Reset options" } }} - +
+ + +
\ No newline at end of file diff --git a/lib/templates/popover_region.mustache b/lib/templates/popover_region.mustache index 2047541cfab..610681e5324 100644 --- a/lib/templates/popover_region.mustache +++ b/lib/templates/popover_region.mustache @@ -36,7 +36,7 @@