MDL-71943 core: Dynamic (AJAX) tabs in Moodle LMS

This commit is contained in:
David Matamoros 2021-08-31 15:06:31 +02:00
parent 214adb7984
commit 5909d5b0ce
16 changed files with 755 additions and 1 deletions

View File

@ -451,6 +451,7 @@ $string['nonmeaningfulcontent'] = 'Non meaningful content';
$string['noparticipants'] = 'No participants found for this course';
$string['noparticipatorycms'] = 'Sorry, but you have no participatory course modules to report on';
$string['nopermissions'] = 'Sorry, but you do not currently have permissions to do that ({$a}).';
$string['nopermissiontoaccesspage'] = 'You don\'t have permission to access this page.';
$string['nopermissiontocomment'] = 'You can\'t add comments';
$string['nopermissiontodelentry'] = 'You can\'t delete this comment!';
$string['nopermissiontoeditcomment'] = 'You can\'t edit other people\'s comments!';

2
lib/amd/build/dynamic_tabs.min.js vendored Normal file
View File

@ -0,0 +1,2 @@
define ("core/dynamic_tabs",["exports","jquery","core/templates","core/notification","core/pending","core/local/repository/dynamic_tabs"],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);d=g(d);e=g(e);function g(a){return a&&a.__esModule?a:{default:a}}function h(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);if(b)d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable});c.push.apply(c,d)}return c}function i(a){for(var b=1,c;b<arguments.length;b++){c=null!=arguments[b]?arguments[b]:{};if(b%2){h(Object(c),!0).forEach(function(b){j(a,b,c[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(c))}else{h(Object(c)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(c,b))})}}return a}function j(a,b,c){if(b in a){Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0})}else{a[b]=c}return a}var k={dynamicTabs:".dynamictabs",activeTab:".dynamictabs .nav-link.active",allActiveTabs:".dynamictabs .nav-link[data-toggle=\"tab\"]:not(.disabled)",tabContent:".dynamictabs .tab-pane [data-tab-content]",tabToggle:"a[data-toggle=\"tab\"]",tabPane:".dynamictabs .tab-pane",forTabName:function(a){return".dynamictabs [data-tab-content=\"".concat(a,"\"]")},forTabId:function(a){return".dynamictabs [data-toggle=\"tab\"][href=\"#".concat(a,"\"]")}},l=function(){(0,b.default)(k.tabToggle).on("shown.bs.tab",function(){var a=(0,b.default)((0,b.default)(this).attr("href"));if(1!==a.length){return}p(a.attr("id"))});if(!t()){var a=document.querySelector(k.allActiveTabs);if(a){s(a.getAttribute("aria-controls"))}else{var c=document.querySelector(k.tabPane);if(c){c.classList.add("active","show");p(c.getAttribute("id"))}}}};a.init=l;var m=function(a){return c.default.render("core/loading",{}).then(function(b,d){return c.default.replaceNodeContents(a,b,d)}).catch(d.default.exception)},n=function(){var a=document.querySelector(k.activeTab);return(null===a||void 0===a?void 0:a.getAttribute("aria-controls"))||null},o=function(){var a=document.querySelector(k.tabContent);return(null===a||void 0===a?void 0:a.dataset.tabContent)||null},p=function(a){var b,g,h=1<arguments.length&&arguments[1]!==void 0?arguments[1]:{};a=null!==(b=null!==(g=a)&&void 0!==g?g:n())&&void 0!==b?b:o();var j=document.querySelector(k.forTabName(a));if(!j){return}var l=new e.default("core/dynamic_tabs:loadTab:"+a),p=j.closest(k.dynamicTabs),q=i({reportid:p.dataset.reportid,id:p.dataset.id},h),r="";j.textContent="";m(j).then(function(){return(0,f.getContent)(j.dataset.tabClass,JSON.stringify(q))}).then(function(a){r=a.javascript;return c.default.render(a.template,JSON.parse(a.content))}).then(function(a,b){return c.default.replaceNodeContents(j,a,b+r)}).then(function(){l.resolve();return null}).catch(d.default.exception)},q=function(a){return document.querySelector(k.forTabId(a))},r=function(a){return document.getElementById(a)},s=function(a){var b=q(a);if(!b){return!1}p(a);b.classList.add("active");r(a).classList.add("active","show");return!0},t=function(){var a=document.location.hash;if(a.match(/^#\w+$/g)){return s(a.replace(/^#/g,""))}return!1}});
//# sourceMappingURL=dynamic_tabs.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
define ("core/local/repository/dynamic_tabs",["exports","core/ajax"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getContent=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a,c){return b.default.call([{methodname:"core_dynamic_tabs_get_content",args:{tab:a,jsondata:c}}])[0]};a.getContent=c});
//# sourceMappingURL=dynamic_tabs.min.js.map

View File

@ -0,0 +1 @@
{"version":3,"sources":["../../../src/local/repository/dynamic_tabs.js"],"names":["getContent","tab","jsondata","Ajax","call","methodname","args"],"mappings":"qKAuBA,uDASO,GAAMA,CAAAA,CAAU,CAAG,SAACC,CAAD,CAAMC,CAAN,CAAmB,CAMzC,MAAOC,WAAKC,IAAL,CAAU,CALD,CACZC,UAAU,CAAE,+BADA,CAEZC,IAAI,CAAE,CAACL,GAAG,CAAEA,CAAN,CAAWC,QAAQ,CAAEA,CAArB,CAFM,CAKC,CAAV,EAAqB,CAArB,CACV,CAPM,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 <http://www.gnu.org/licenses/>.\n\n/**\n* Module to handle dynamic tabs AJAX requests\n*\n* @module core/local/repository/dynamic_tabs\n* @copyright 2021 David Matamoros <davidmc@moodle.com>\n* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n*/\n\nimport Ajax from 'core/ajax';\n\n/**\n* Return tab content\n*\n* @param {String} tab\n* @param {String} jsondata\n* @return {Promise}\n*/\nexport const getContent = (tab, jsondata) => {\n const request = {\n methodname: 'core_dynamic_tabs_get_content',\n args: {tab: tab, jsondata: jsondata}\n };\n\n return Ajax.call([request])[0];\n};\n"],"file":"dynamic_tabs.min.js"}

197
lib/amd/src/dynamic_tabs.js Normal file
View File

@ -0,0 +1,197 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Dynamic Tabs UI element with AJAX loading of tabs content
*
* @module core/dynamic_tabs
* @copyright 2021 David Matamoros <davidmc@moodle.com> based on code from Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Templates from 'core/templates';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {getContent} from 'core/local/repository/dynamic_tabs';
const SELECTORS = {
dynamicTabs: '.dynamictabs',
activeTab: '.dynamictabs .nav-link.active',
allActiveTabs: '.dynamictabs .nav-link[data-toggle="tab"]:not(.disabled)',
tabContent: '.dynamictabs .tab-pane [data-tab-content]',
tabToggle: 'a[data-toggle="tab"]',
tabPane: '.dynamictabs .tab-pane',
};
SELECTORS.forTabName = tabName => `.dynamictabs [data-tab-content="${tabName}"]`;
SELECTORS.forTabId = tabName => `.dynamictabs [data-toggle="tab"][href="#${tabName}"]`;
/**
* Initialises the tabs view on the page (only one tabs view per page is supported)
*/
export const init = () => {
// This code listens to Bootstrap event 'shown.bs.tab' which is triggered using JQuery and
// can not be converted yet to native events.
$(SELECTORS.tabToggle).on('shown.bs.tab', function() {
const tab = $($(this).attr('href'));
if (tab.length !== 1) {
return;
}
loadTab(tab.attr('id'));
});
if (!openTabFromHash()) {
const tabs = document.querySelector(SELECTORS.allActiveTabs);
if (tabs) {
openTab(tabs.getAttribute('aria-controls'));
} else {
// We may hide tabs if there is only one available, just load the contents of the first tab.
const tabPane = document.querySelector(SELECTORS.tabPane);
if (tabPane) {
tabPane.classList.add('active', 'show');
loadTab(tabPane.getAttribute('id'));
}
}
}
};
/**
* Show "loading" template instead of a node
*
* @param {HTMLElement} node
* @return {Promise}
*/
const indicateNodeIsLoading = (node) => {
return Templates.render('core/loading', {})
.then((html, js) => {
return Templates.replaceNodeContents(node, html, js);
}).catch(Notification.exception);
};
/**
* Returns id/name of the currently active tab
*
* @return {String|null}
*/
const getActiveTabName = () => {
const element = document.querySelector(SELECTORS.activeTab);
return element?.getAttribute('aria-controls') || null;
};
/**
* Returns the id/name of the first tab
*
* @return {String|null}
*/
const getFirstTabName = () => {
const element = document.querySelector(SELECTORS.tabContent);
return element?.dataset.tabContent || null;
};
/**
* Loads contents of a tab using an AJAX request
*
* @param {String} tabName
* @param {Object} additionalData additional data to pass to WS
*/
const loadTab = (tabName, additionalData = {}) => {
// If tabName is not specified find the active tab, or if is not defined, the first available tab.
tabName = tabName ?? getActiveTabName() ?? getFirstTabName();
const tab = document.querySelector(SELECTORS.forTabName(tabName));
if (!tab) {
return;
}
const pendingPromise = new Pending('core/dynamic_tabs:loadTab:' + tabName);
const tabdata = tab.closest(SELECTORS.dynamicTabs);
const wsData = {
'reportid': tabdata.dataset.reportid,
'id': tabdata.dataset.id,
...additionalData
};
let tabjs = '';
tab.textContent = '';
indicateNodeIsLoading(tab)
.then(() => {
return getContent(tab.dataset.tabClass, JSON.stringify(wsData));
})
.then((data) => {
tabjs = data.javascript;
return Templates.render(data.template, JSON.parse(data.content));
})
.then((html, js) => {
return Templates.replaceNodeContents(tab, html, js + tabjs);
})
.then(() => {
pendingPromise.resolve();
return null;
})
.catch(Notification.exception);
};
/**
* Return the tab given the tab name
*
* @param {String} tabName
* @return {HTMLElement}
*/
const getTab = (tabName) => {
return document.querySelector(SELECTORS.forTabId(tabName));
};
/**
* Return the tab pane given the tab name
*
* @param {String} tabName
* @return {HTMLElement}
*/
const getTabPane = (tabName) => {
return document.getElementById(tabName);
};
/**
* Open the tab on page load. If this script loads before theme_boost/tab we need to open tab ourselves
*
* @param {String} tabName
* @return {Boolean}
*/
const openTab = (tabName) => {
const tab = getTab(tabName);
if (!tab) {
return false;
}
loadTab(tabName);
tab.classList.add('active');
getTabPane(tabName).classList.add('active', 'show');
return true;
};
/**
* If there is a location hash that is the same as the tab name - open this tab.
*
* @return {Boolean}
*/
const openTabFromHash = () => {
const hash = document.location.hash;
if (hash.match(/^#\w+$/g)) {
return openTab(hash.replace(/^#/g, ''));
}
return false;
};

View File

@ -0,0 +1,40 @@
// 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 <http://www.gnu.org/licenses/>.
/**
* Module to handle dynamic tabs AJAX requests
*
* @module core/local/repository/dynamic_tabs
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Return tab content
*
* @param {String} tab
* @param {String} jsondata
* @return {Promise}
*/
export const getContent = (tab, jsondata) => {
const request = {
methodname: 'core_dynamic_tabs_get_content',
args: {tab: tab, jsondata: jsondata}
};
return Ajax.call([request])[0];
};

View File

@ -0,0 +1,113 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core\external;
use coding_exception;
use context_system;
use core\output\dynamic_tabs\base;
use external_api;
use external_function_parameters;
use external_single_structure;
use external_value;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/externallib.php");
/**
* External method for getting tab contents
*
* @package core
* @copyright 2021 David Matamoros <davidmc@moodle.com> based on code from Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dynamic_tabs_get_content extends external_api {
/**
* External method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'tab' => new external_value(PARAM_RAW_TRIMMED, 'Tab class', VALUE_REQUIRED),
'jsondata' => new external_value(PARAM_RAW, 'Json-encoded data', VALUE_REQUIRED),
]);
}
/**
* Tab content
*
* @param string $tabclass class of the tab
* @param string $jsondata
* @return array
*/
public static function execute(string $tabclass, string $jsondata): array {
global $PAGE, $OUTPUT;
[
'tab' => $tabclass,
'jsondata' => $jsondata,
] = self::validate_parameters(self::execute_parameters(), [
'tab' => $tabclass,
'jsondata' => $jsondata,
]);
$data = @json_decode($jsondata, true);
$context = context_system::instance();
self::validate_context($context);
// This call is needed to avoid debug messages on webserver log.
$PAGE->set_url('/');
// This call is needed to initiate moodle page.
$OUTPUT->header();
if (!class_exists($tabclass) || !is_subclass_of($tabclass, base::class)) {
throw new coding_exception('unknown dynamic tab class', $tabclass);
}
/** @var base $tab */
$tab = new $tabclass($data);
$tab->require_access();
$PAGE->start_collecting_javascript_requirements();
$content = $tab->export_for_template($PAGE->get_renderer('core'));
$jsfooter = $PAGE->requires->get_end_code();
return [
'template' => $tab->get_template(),
'content' => json_encode($content),
'javascript' => $jsfooter,
];
}
/**
* External method return value
*
* @return external_single_structure
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'template' => new external_value(PARAM_PATH, 'Template name'),
'content' => new external_value(PARAM_RAW, 'JSON-encoded data for template'),
'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment'),
]);
}
}

View File

@ -0,0 +1,90 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core\output;
use core\output\dynamic_tabs\base;
use renderer_base;
use templatable;
/**
* Class dynamic tabs
*
* @package core
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dynamic_tabs implements templatable {
/** @var array */
protected $attributes;
/** @var base[] */
protected $tabs = [];
/**
* tabs constructor.
*
* @param array $attributes additional attributes that will be passed to the webservice
* @param base[] $tabs array of tab
*/
public function __construct(array $attributes = [], array $tabs = []) {
$this->attributes = $attributes;
foreach ($tabs as $tab) {
$this->add_tab($tab);
}
}
/**
* Add a tab
*
* @param base $tab
*/
public function add_tab(base $tab): void {
$this->tabs[] = $tab;
}
/**
* Implementation of exporter from templatable interface
*
* @param renderer_base $output
* @return array
*/
public function export_for_template(renderer_base $output): array {
$data = [
'dataattributes' => [],
'tabs' => []
];
foreach ($this->attributes as $name => $value) {
$data['dataattributes'][] = ['name' => $name, 'value' => $value];
}
foreach ($this->tabs as $tab) {
$data['tabs'][] = [
'shortname' => $tab->get_tab_id(),
'displayname' => $tab->get_tab_label(),
'enabled' => $tab->is_available(),
'tabclass' => get_class($tab)
];
}
$data['showtabsnavigation'] = (count($data['tabs']) > 1) ? 1 : 0;
return $data;
}
}

View File

@ -0,0 +1,86 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core\output\dynamic_tabs;
use moodle_exception;
use templatable;
/**
* Class tab_base
*
* @package core
* @copyright 2018 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base implements templatable {
/** @var array */
protected $data;
/**
* tab constructor.
*
* @param array $data
*/
final public function __construct(array $data) {
$this->data = $data;
}
/**
* HTML "id" attribute that should be used for this tab, by default the last part of class name
*
* @return string
*/
public function get_tab_id(): string {
$parts = preg_split('/\\\\/', static::class);
return array_pop($parts);
}
/**
* The label to be displayed on the tab
*
* @return string
*/
abstract public function get_tab_label(): string;
/**
* Check permission of the current user to access this tab
*
* @return bool
*/
abstract public function is_available(): bool;
/**
* Check that tab is accessible, throw exception otherwise - used from WS requesting tab contents
*
* @throws moodle_exception
*/
final public function require_access() {
if (!$this->is_available()) {
throw new moodle_exception('nopermissiontoaccesspage', 'error');
}
}
/**
* Template to use to display tab contents
*
* @return string
*/
abstract public function get_template(): string;
}

View File

@ -2779,6 +2779,13 @@ $functions = array(
'type' => 'write',
'ajax' => true,
],
'core_dynamic_tabs_get_content' => [
'classname' => 'core\external\dynamic_tabs_get_content',
'description' => 'Returns the content for a dynamic tab',
'type' => 'read',
'ajax' => true,
],
);
$services = array(

View File

@ -0,0 +1,75 @@
{{!
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 <http://www.gnu.org/licenses/>.
}}
{{!
@template core/dynamic_tabs
Template for showing dynamic tabs
Example context (json):
{
"dataattributes": [ {"name": "programid", "value": "13"} ],
"showtabsnavigation": "1",
"tabs": [
{
"shortname": "tab1",
"displayname": "Tab 1",
"content": "Content of tab 1",
"enabled": "1"
},
{
"shortname": "tab2",
"displayname": "Tab 2",
"content": "Content of tab 2",
"enabled": "1"
}
]
}
}}
{{! We must not use the JS helper otherwise this gets executed too late. Tell behat to wait. }}
<script>
M.util.js_pending('core_dynamic_tabs_init');
</script>
<div class="dynamictabs" {{#dataattributes}}data-{{name}}="{{value}}" {{/dataattributes}} >
{{#showtabsnavigation}}
<ul class="nav nav-tabs mb-4" id="dynamictabs-tabs" role="tablist">
{{#tabs}}
<li class="nav-item">
<a class="nav-link {{#active}}active{{/active}} {{^enabled}}disabled{{/enabled}}" id="{{shortname}}-tab" data-toggle="tab" href="#{{shortname}}" role="tab" aria-controls="{{shortname}}">
{{{displayname}}}
</a>
</li>
{{/tabs}}
</ul>
{{/showtabsnavigation}}
<div class="tab-content" id="dynamictabs-content">
{{#tabs}}
<div class="tab-pane fade {{#active}}show active{{/active}}" id="{{shortname}}" role="tabpanel" aria-labelledby="{{shortname}}-tab">
<div class="container-fluid" data-tab-content="{{shortname}}" data-tab-class="{{tabclass}}">
{{{content}}}
</div>
</div>
{{/tabs}}
</div>
</div>
{{#js}}
require(['core/dynamic_tabs'], function(Tabs) {
Tabs.init();
M.util.js_complete('core_dynamic_tabs_init');
});
{{/js}}

View File

@ -2084,4 +2084,17 @@ EOF;
public function i_mark_this_test_as_long_running(int $factor = 2): void {
$this->set_test_timeout_factor($factor);
}
/**
* Click on a dynamic tab to load its content
*
* @Given /^I click on the "(?P<tab_string>(?:[^"]|\\")*)" dynamic tab$/
*
* @param string $tabname
*/
public function i_click_on_the_dynamic_tab(string $tabname): void {
$xpath = "//*[@id='dynamictabs-tabs'][descendant::a[contains(text(), '" . $this->escape($tabname) . "')]]";
$this->execute('behat_general::i_click_on_in_the',
[$tabname, 'link', $xpath, 'xpath_element']);
}
}

View File

@ -0,0 +1,53 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core\external;
use external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/lib/tests/fixtures/testeable_dynamic_tab.php');
/**
* Unit tests external dynamic tabs get content
*
* @package core
* @covers core\external\dynamic_tabs_get_content
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class dynamic_tabs_get_content_testcase extends \externallib_advanced_testcase {
/**
* Text execute method
*/
public function test_execute(): void {
$this->resetAfterTest();
$this->setAdminUser();
$result = dynamic_tabs_get_content::execute(testeable_dynamic_tab::class, json_encode([]));
$result = external_api::clean_returnvalue(dynamic_tabs_get_content::execute_returns(), $result);
$this->assertEquals('templates/tabs/mytab', $result['template']);
$this->assertEquals(json_encode(['content' => get_string('content')]), $result['content']);
$this->assertNotEmpty($result['javascript']);
$this->assertStringStartsWith('<script>', $result['javascript']);
}
}

View File

@ -0,0 +1,73 @@
<?php
// 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 <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core\external;
use core\output\dynamic_tabs\base;
use renderer_base;
use stdClass;
/**
* Dynamic tab class fixture
*
* @package core
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testeable_dynamic_tab extends base {
/**
* Export this for use in a mustache template context.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$content = (object)[];
$content->content = get_string('content');
return $content;
}
/**
* The label to be displayed on the tab
*
* @return string
*/
public function get_tab_label(): string {
return get_string('myhome');
}
/**
* Check permission of the current user to access this tab
*
* @return bool
*/
public function is_available(): bool {
// Define the correct permissions here.
return true;
}
/**
* Template to use to display tab contents
*
* @return string
*/
public function get_template(): string {
return 'templates/tabs/mytab';
}
}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2021092400.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2021092400.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.0dev (Build: 20210924)'; // Human-friendly version name