mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 04:22:07 +02:00
MDL-71943 core: Dynamic (AJAX) tabs in Moodle LMS
This commit is contained in:
parent
214adb7984
commit
5909d5b0ce
@ -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
2
lib/amd/build/dynamic_tabs.min.js
vendored
Normal 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
|
1
lib/amd/build/dynamic_tabs.min.js.map
Normal file
1
lib/amd/build/dynamic_tabs.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
lib/amd/build/local/repository/dynamic_tabs.min.js
vendored
Normal file
2
lib/amd/build/local/repository/dynamic_tabs.min.js
vendored
Normal 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
|
1
lib/amd/build/local/repository/dynamic_tabs.min.js.map
Normal file
1
lib/amd/build/local/repository/dynamic_tabs.min.js.map
Normal 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
197
lib/amd/src/dynamic_tabs.js
Normal 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;
|
||||
};
|
40
lib/amd/src/local/repository/dynamic_tabs.js
Normal file
40
lib/amd/src/local/repository/dynamic_tabs.js
Normal 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];
|
||||
};
|
113
lib/classes/external/dynamic_tabs_get_content.php
vendored
Normal file
113
lib/classes/external/dynamic_tabs_get_content.php
vendored
Normal 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'),
|
||||
]);
|
||||
}
|
||||
}
|
90
lib/classes/output/dynamic_tabs.php
Normal file
90
lib/classes/output/dynamic_tabs.php
Normal 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;
|
||||
}
|
||||
}
|
86
lib/classes/output/dynamic_tabs/base.php
Normal file
86
lib/classes/output/dynamic_tabs/base.php
Normal 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;
|
||||
}
|
@ -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(
|
||||
|
75
lib/templates/dynamic_tabs.mustache
Normal file
75
lib/templates/dynamic_tabs.mustache
Normal 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}}
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
53
lib/tests/external/dynamic_tabs_get_content_test.php
vendored
Normal file
53
lib/tests/external/dynamic_tabs_get_content_test.php
vendored
Normal 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']);
|
||||
}
|
||||
}
|
73
lib/tests/fixtures/testeable_dynamic_tab.php
vendored
Normal file
73
lib/tests/fixtures/testeable_dynamic_tab.php
vendored
Normal 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';
|
||||
}
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user