This commit is contained in:
Huong Nguyen 2025-01-23 11:15:26 +07:00
commit 5fbd4b6555
No known key found for this signature in database
GPG key ID: 40D88AB693A3E72A
13 changed files with 1031 additions and 0 deletions

View file

@ -0,0 +1,9 @@
issueNumber: MDL-83869
notes:
core:
- message: >
New generic collapsable section output added. Use
core\output\local\collapsable_section or include the
core/local/collapsable_section template to use it. See the full
documentation in the component library.
type: improved

View file

@ -0,0 +1,158 @@
---
layout: docs
title: "Collapsable sections"
description: "A reusable collapsable section component"
date: 2024-12-20T10:10:00+08:00
draft: false
tags:
- MDL-83869
- "5.0"
---
## How it works
The collapsable section component in Moodle allows you to create sections of content that can be expanded or collapsed by the user. This is useful for organizing content in a way that doesn't overwhelm the user with too much information at once. The component is built using a combination of PHP, Mustache templates, and JavaScript.
## Source files
- `lib/templates/local/collapsable_section.mustache`: The Mustache template for rendering the collapsable section.
- `lib/classes/output/local/collapsable_section.php`: The output class for the collapsable section component.
- `lib/amd/src/local/collapsable_section/events.js`: JavaScript module for handling events related to the collapsable section.
- `lib/amd/src/local/collapsable_section/controls.js`: JavaScript module for controlling the collapsable section.
## Usage
To use the collapsable section component, you need to create an instance of the `collapsable_section` class and render it using Moodle's output renderer. You can customize the title content, section content, CSS classes, and additional HTML attributes.
Example:
{{< php >}}
use core\output\local\collapsable_section;
// Create an instance of the collapsable section.
$section = new collapsable_section(
titlecontent: 'Section Title',
sectioncontent: 'This is the content of the section.',
);
echo $OUTPUT->render($section);
{{< / php >}}
{{< mustache template="core/local/collapsable_section" >}}
{
"titlecontent": "Section Title",
"sectioncontent": "This is the content of the section."
}
{{< /mustache >}}
You can also add CSS classes, extra HTML attributes, and customize the expand and collapse labels of the collapsable section:
{{< php >}}
$section = new collapsable_section(
titlecontent: 'Section Title',
sectioncontent: 'This is the content of the section.',
open: true, // Optional parameter to set the section as open by default.
classes: 'p-3 rounded bg-dark text-white', // Optional parameter to add custom CSS classes.
extras: ['id' => 'MyCollapsableSection', 'data-foo' => 'bar'], // Optional HTML attributes.
expandlabel: 'Show more', // Optional label for the expand button.
collapselabel: 'Show less', // Optional label for the collapse button.
);
echo $OUTPUT->render($section);
{{< / php >}}
{{< mustache template="core/local/collapsable_section" >}}
{
"titlecontent": "Section Title",
"sectioncontent": "This is the content of the section.",
"open": true,
"classes": "p-3 rounded bg-dark text-white",
"elementid": "someuniqueid",
"extras": [
{
"attribute": "id",
"value": "MyCollapsableSection"
},
{
"attribute": "data-foo",
"value": "bar"
}
],
"expandlabel": "Show more",
"collapselabel": "Show less"
}
{{< /mustache >}}
## Include a collapsable section from a mustache template
Collapsable sections can also be included from a Mustache template using the `core/local/collapsable_section` template. This template allows you to define the title content and section content within the template.
{{< mustache template="tool_componentlibrary/examples/collapsablesections/includesection" >}}
{
}
{{< /mustache >}}
## JavaScript
### Control a section
The collapsable sections component includes a JavaScript module for controlling the sections. This module provides methods to hide, show, and toggle the visibility of the sections.
To use the JavaScript controls, you need to import the `CollapsableSection` module and create an instance from a selector:
```javascript
import CollapsableSection from 'core/local/collapsable_section/controls';
const section = CollapsableSection.instanceFromSelector('#MyCollapsableSection');
// Use hide, show, and toggle methods to control the section.
section.hide();
section.show();
section.toggle();
```
### Get the state of a section
You can also check the state of a section using the `isHidden` method:
```javascript
import CollapsableSection from 'core/local/collapsable_section/controls';
const section = CollapsableSection.instanceFromSelector('#MyCollapsableSection');
if (section.isVisible()) {
console.log('The section is hidden.');
} else {
console.log('The section is visible.');
}
```
### Events
The collapsable sections component also includes a JavaScript module for handling events. This module wraps the standard Bootstrap collapsable events and provides custom event types for collapsable sections.
The component triggers two main events:
- `core_collapsable_section_shown`: when the collapsed content is shown.
- `core_collapsable_section_hidden`: when the collapsed content is hidden.
For convenience, the `core/local/collapsable_section/events` also list the original Bootstrap events. They should not be needed in most cases, but they are available if you need them:
- `show.bs.collapse`: when the collapse is starting to show.
- `shown.bs.collapse`: when the collapse has been shown.
- `hide.bs.collapse`: when the collapse is starting to hide.
- `hidden.bs.collapse`: when the collapse has been hidden.
To listen for events related to the collapsable sections, you need to import the `eventTypes` from the `events` module and add event listeners:
```javascript
import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';
document.addEventListener(collapsableSectionEventTypes.shown, event => {
console.log(event.target); // The HTMLElement relating to the section that was shown.
});
document.addEventListener(collapsableSectionEventTypes.hidden, event => {
console.log(event.target); // The HTMLElement relating to the section that was hidden.
});
```

View file

@ -0,0 +1,35 @@
{{!
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 tool_componentlibrary/examples/collapsablesections/includesection
TODO describe template includesection
Example context (json):
{
}
}}
{{<core/local/collapsable_section}}
{{$titlecontent}} Section title {{/titlecontent}}
{{! Optional id for the section. }}
{{$elementid}}optionaId{{/elementid}}
{{! Optional classes for the section. }}
{{$extraclasses}}border border-1 rounded-3 p-3{{/extraclasses}}
{{$sectioncontent}}
This is the section content.
{{/sectioncontent}}
{{/core/local/collapsable_section}}

View file

@ -0,0 +1,19 @@
define("core/local/collapsable_section/controls",["exports","core/local/collapsable_section/events","jquery"],(function(_exports,_events,_jquery){var obj;
/**
* The collapsable sections controls.
*
* @module core/local/collapsable_section/controls
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @example <caption>Example of controlling a collapsable section.</caption>
*
* import CollapsableSection from 'core/local/collapsable_section/controls';
*
* const section = CollapsableSection.instanceFromSelector('#MyCollapsableSection');
*
* // Use hide, show and toggle methods to control the section.
* section.hide();
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};let initialized=!1;return _exports.default=class{static instanceFromSelector(selector){const elements=document.querySelector(selector);if(!elements)throw new Error("No elements found with the selector: "+selector);return new this(elements)}static init(){initialized||(initialized=!0,(0,_jquery.default)(document).on(_events.eventTypes.hiddenBsCollapse,(event=>{this.isCollapsableComponent(event.target)&&(0,_events.notifyCollapsableSectionHidden)(event.target)})),(0,_jquery.default)(document).on(_events.eventTypes.shownBsCollapse,(event=>{this.isCollapsableComponent(event.target)&&(0,_events.notifyCollapsableSectionShown)(event.target)})))}static isCollapsableComponent(element){return element.hasAttribute("data-mdl-component")&&"core/local/collapsable_section"===element.getAttribute("data-mdl-component")}constructor(element){this.element=element}hide(){(0,_jquery.default)(this.element).collapse("hide")}show(){(0,_jquery.default)(this.element).collapse("show")}toggle(){(0,_jquery.default)(this.element).collapse("toggle")}isVisible(){return this.element.classList.contains("show")}},_exports.default}));
//# sourceMappingURL=controls.min.js.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,20 @@
define("core/local/collapsable_section/events",["exports","core/event_dispatcher"],(function(_exports,_event_dispatcher){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.notifyCollapsableSectionShown=_exports.notifyCollapsableSectionHidden=_exports.eventTypes=void 0;
/**
* The collapsable section events.
*
* This module wraps the standard bootstrap collapsable events, but for collapsable sections.
*
* @module core/local/collapsable_section/events
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @example <caption>Example of listening to a collapsable section events.</caption>
* import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';
*
* document.addEventListener(collapsableSectionEventTypes.shown, event => {
* window.console.log(event.target); // The HTMLElement relating to the block whose content was updated.
* });
*/
const eventTypes={shown:"core_collapsable_section_shown",hidden:"core_collapsable_section_hidden",hideBsCollapse:"hide.bs.collapse",hiddenBsCollapse:"hidden.bs.collapse",showBsCollapse:"show.bs.collapse",shownBsCollapse:"shown.bs.collapse"};_exports.eventTypes=eventTypes;_exports.notifyCollapsableSectionShown=element=>(0,_event_dispatcher.dispatchEvent)(eventTypes.shown,{},element);_exports.notifyCollapsableSectionHidden=element=>(0,_event_dispatcher.dispatchEvent)(eventTypes.hidden,{},element)}));
//# sourceMappingURL=events.min.js.map

View file

@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../../../src/local/collapsable_section/events.js"],"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 * The collapsable section events.\n *\n * This module wraps the standard bootstrap collapsable events, but for collapsable sections.\n *\n * @module core/local/collapsable_section/events\n * @copyright 2024 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n * @example <caption>Example of listening to a collapsable section events.</caption>\n * import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';\n *\n * document.addEventListener(collapsableSectionEventTypes.shown, event => {\n * window.console.log(event.target); // The HTMLElement relating to the block whose content was updated.\n * });\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\n\n/**\n * Events for `core_block`.\n *\n * @constant\n * @property {String} blockContentUpdated See {@link event:blockContentUpdated}\n */\nexport const eventTypes = {\n /**\n * An event triggered when the content of a block has changed.\n *\n * @event blockContentUpdated\n * @type {CustomEvent}\n * @property {HTMLElement} target The block element that was updated\n * @property {object} detail\n * @property {number} detail.instanceId The block instance id\n */\n shown: 'core_collapsable_section_shown',\n hidden: 'core_collapsable_section_hidden',\n // All Bootstrap 4 jQuery events are wrapped while MDL-71979 is not integrated.\n hideBsCollapse: 'hide.bs.collapse',\n hiddenBsCollapse: 'hidden.bs.collapse',\n showBsCollapse: 'show.bs.collapse',\n shownBsCollapse: 'shown.bs.collapse',\n};\n\n/**\n * Trigger an event to indicate that the content of a block was updated.\n *\n * @method notifyBlockContentUpdated\n * @param {HTMLElement} element The HTMLElement containing the updated block.\n * @returns {CustomEvent}\n * @fires blockContentUpdated\n */\nexport const notifyCollapsableSectionShown = element => dispatchEvent(\n eventTypes.shown,\n {},\n element\n);\n\n/**\n * Trigger an event to indicate that the content of a block was updated.\n *\n * @method notifyBlockContentUpdated\n * @param {HTMLElement} element The HTMLElement containing the updated block.\n * @returns {CustomEvent}\n * @fires blockContentUpdated\n */\nexport const notifyCollapsableSectionHidden = element => dispatchEvent(\n eventTypes.hidden,\n {},\n element\n);\n"],"names":["eventTypes","shown","hidden","hideBsCollapse","hiddenBsCollapse","showBsCollapse","shownBsCollapse","element"],"mappings":";;;;;;;;;;;;;;;;;MAwCaA,WAAa,CAUtBC,MAAO,iCACPC,OAAQ,kCAERC,eAAgB,mBAChBC,iBAAkB,qBAClBC,eAAgB,mBAChBC,gBAAiB,2FAWwBC,UAAW,mCACpDP,WAAWC,MACX,GACAM,iDAW0CA,UAAW,mCACrDP,WAAWE,OACX,GACAK"}

View file

@ -0,0 +1,138 @@
// 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/>.
/**
* The collapsable sections controls.
*
* @module core/local/collapsable_section/controls
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @example <caption>Example of controlling a collapsable section.</caption>
*
* import CollapsableSection from 'core/local/collapsable_section/controls';
*
* const section = CollapsableSection.instanceFromSelector('#MyCollapsableSection');
*
* // Use hide, show and toggle methods to control the section.
* section.hide();
*/
import {
eventTypes,
notifyCollapsableSectionHidden,
notifyCollapsableSectionShown
} from 'core/local/collapsable_section/events';
// The jQuery module is only used for interacting with Boostrap 4. It can we removed when MDL-71979 is integrated.
import jQuery from 'jquery';
let initialized = false;
export default class {
/**
* Create a new instance from a query selector.
*
* @param {String} selector The selector of the collapsable section.
* @return {CollapsableSection} The collapsable section controls.
* @throws {Error} If no elements are found with the selector.
*/
static instanceFromSelector(selector) {
const elements = document.querySelector(selector);
if (!elements) {
throw new Error('No elements found with the selector: ' + selector);
}
return new this(elements);
}
/**
* Initialize the collapsable section controls.
*/
static init() {
if (initialized) {
return;
}
initialized = true;
// We want to add extra events to the standard bootstrap collapsable events.
// TODO: change all jquery events to custom events once MDL-71979 is integrated.
jQuery(document).on(eventTypes.hiddenBsCollapse, event => {
if (!this.isCollapsableComponent(event.target)) {
return;
}
notifyCollapsableSectionHidden(event.target);
});
jQuery(document).on(eventTypes.shownBsCollapse, event => {
if (!this.isCollapsableComponent(event.target)) {
return;
}
notifyCollapsableSectionShown(event.target);
});
}
/**
* Check if the element is a collapsable section.
*
* @private
* @param {HTMLElement} element The element to check.
* @return {boolean} True if the element is a collapsable section.
*/
static isCollapsableComponent(element) {
return element.hasAttribute('data-mdl-component')
&& element.getAttribute('data-mdl-component') === 'core/local/collapsable_section';
}
/**
* Creates an instance of the controls for a collapsable section.
*
* @param {HTMLElement} element - The DOM element that this control will manage.
*/
constructor(element) {
this.element = element;
}
/**
* Hides the collapsible section element.
*/
hide() {
// TODO: change all jquery once MDL-71979 is integrated.
jQuery(this.element).collapse('hide');
}
/**
* Shows the collapsible section element.
*/
show() {
// TODO: change all jquery once MDL-71979 is integrated.
jQuery(this.element).collapse('show');
}
/**
* Toggle the collapsible section element.
*/
toggle() {
// TODO: change all jquery once MDL-71979 is integrated.
jQuery(this.element).collapse('toggle');
}
/**
* Check if the collapsable section is visible.
*
* @return {boolean} True if the collapsable section is visible.
*/
isVisible() {
return this.element.classList.contains('show');
}
}

View file

@ -0,0 +1,86 @@
// 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/>.
/**
* The collapsable section events.
*
* This module wraps the standard bootstrap collapsable events, but for collapsable sections.
*
* @module core/local/collapsable_section/events
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @example <caption>Example of listening to a collapsable section events.</caption>
* import {eventTypes as collapsableSectionEventTypes} from 'core/local/collapsable_section/events';
*
* document.addEventListener(collapsableSectionEventTypes.shown, event => {
* window.console.log(event.target); // The HTMLElement relating to the block whose content was updated.
* });
*/
import {dispatchEvent} from 'core/event_dispatcher';
/**
* Events for `core_block`.
*
* @constant
* @property {String} blockContentUpdated See {@link event:blockContentUpdated}
*/
export const eventTypes = {
/**
* An event triggered when the content of a block has changed.
*
* @event blockContentUpdated
* @type {CustomEvent}
* @property {HTMLElement} target The block element that was updated
* @property {object} detail
* @property {number} detail.instanceId The block instance id
*/
shown: 'core_collapsable_section_shown',
hidden: 'core_collapsable_section_hidden',
// All Bootstrap 4 jQuery events are wrapped while MDL-71979 is not integrated.
hideBsCollapse: 'hide.bs.collapse',
hiddenBsCollapse: 'hidden.bs.collapse',
showBsCollapse: 'show.bs.collapse',
shownBsCollapse: 'shown.bs.collapse',
};
/**
* Trigger an event to indicate that the content of a block was updated.
*
* @method notifyBlockContentUpdated
* @param {HTMLElement} element The HTMLElement containing the updated block.
* @returns {CustomEvent}
* @fires blockContentUpdated
*/
export const notifyCollapsableSectionShown = element => dispatchEvent(
eventTypes.shown,
{},
element
);
/**
* Trigger an event to indicate that the content of a block was updated.
*
* @method notifyBlockContentUpdated
* @param {HTMLElement} element The HTMLElement containing the updated block.
* @returns {CustomEvent}
* @fires blockContentUpdated
*/
export const notifyCollapsableSectionHidden = element => dispatchEvent(
eventTypes.hidden,
{},
element
);

View file

@ -0,0 +1,147 @@
<?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/>.
namespace core\output\local;
use core\output\named_templatable;
use core\output\renderable;
/**
* Collapsable section output.
*
* @package core
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class collapsable_section implements named_templatable, renderable {
/**
* Constructor.
*
* @param string $titlecontent The content to be displayed inside the button.
* @param string $sectioncontent The content to be displayed inside the dialog.
* @param string $classes Additional CSS classes to be applied to the section.
* @param array $extras An attribute => value array to be added to the element.
* @param bool $open If the section is opened by default.
* @param string|null $expandlabel The label for the expand button.
* @param string|null $collapselabel The label for the collapse button.
*/
public function __construct(
/** @var string $titlecontent The content to be displayed inside the button. */
protected string $titlecontent,
/** @var string $sectioncontent The content to be displayed inside the dialog. */
protected string $sectioncontent,
/** @var string $classes Additional CSS classes to be applied to the section. */
protected string $classes = '',
/** @var array $extras A attribute => value array to be added to the element. */
protected array $extras = [],
/** @var bool $open if the section is opened by default. */
protected bool $open = false,
/** @var string|null $expandlabel The label for the expand button. */
protected string|null $expandlabel = null,
/** @var string|null $collapselabel The label for the collapse button. */
protected string|null $collapselabel = null,
) {
}
/**
* Set the title content.
*
* @param string $titlecontent
*/
public function set_title_content(string $titlecontent) {
$this->titlecontent = $titlecontent;
}
/**
* Sets the content for the collapsable section.
*
* @param string $sectioncontent The content to be set for the section.
*/
public function set_section_content(string $sectioncontent) {
$this->sectioncontent = $sectioncontent;
}
/**
* Sets the CSS classes for the collapsable section.
*
* @param string $classes The CSS classes to be applied to the collapsable section.
*/
public function set_classes(string $classes) {
$this->classes = $classes;
}
/**
* Merges the provided extras array with the existing extras array.
*
* @param array $extras The array of extra attributes => extra value.
*/
public function add_extra_attributes(array $extras) {
$this->extras = array_merge($this->extras, $extras);
}
/**
* Sets the default open state of the collapsible section.
*
* @param bool $open
*/
public function set_open(bool $open) {
$this->open = $open;
}
#[\Override]
public function export_for_template(\renderer_base $output): array {
$elementid = $this->extras['id'] ?? \html_writer::random_id('collapsableSection_');
$data = [
'titlecontent' => $this->titlecontent,
'sectioncontent' => $this->sectioncontent,
'classes' => $this->classes,
'extras' => $this->export_extras(),
'elementid' => $elementid,
];
if ($this->open) {
$data['open'] = 'true';
}
if ($this->expandlabel) {
$data['expandlabel'] = $this->expandlabel;
}
if ($this->collapselabel) {
$data['collapselabel'] = $this->collapselabel;
}
return $data;
}
/**
* Exports the extras as an array of attribute-value pairs.
*
* @return array An array of associative arrays, each containing 'attribute' and 'value' keys.
*/
private function export_extras(): array {
$extras = [];
foreach ($this->extras as $attribute => $value) {
$extras[] = [
'attribute' => $attribute,
'value' => $value,
];
}
return $extras;
}
#[\Override]
public function get_template_name(\renderer_base $renderer): string {
return 'core/local/collapsable_section';
}
}

View file

@ -0,0 +1,147 @@
{{!
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/local/collapsable_section
Standard collapsible section.
Optional blocks:
* extraclasses - additional classes.
* elementid - optional element id.
* titlecontent - the collpasible title content.
* sectioncontent - the collapsible content.
* extras - custom HTML attributes for the component.
* expandlabel - the label for the expand icon.
* collapselabel - the label for the collapse icon.
Example context (json):
{
"titlecontent": "New content",
"sectioncontent": "New content",
"classes": "someclass",
"extras": [
{
"attribute": "data-example",
"value": "something"
}
],
"open": true,
"expandlabel": "Expand",
"collapselabel": "Collapse",
"elementid": "someuniqueid"
}
}}
<div
class="collapsable-section mb-3 {{!
}} {{$ extraclasses }} {{!
}} {{#classes}} {{classes}} {{/classes}} {{!
}} {{/ extraclasses }}"
id="{{$ elementid }}{{!
}}{{#elementid}}collapsable_section_{{elementid}}{{/elementid}}{{!
}}{{^elementid}}collapsable_section_{{uniqid}}{{/elementid}}{{!
}}{{/ elementid }}_collapsible"
>
<div class="d-flex">
<div class="d-flex align-items-center position-relative">
<a
role="button"
data-toggle="collapse"
href="#{{$ elementid }}{{!
}}{{#elementid}}{{elementid}}{{/elementid}}{{!
}}{{^elementid}}collapsable_{{uniqid}}{{/elementid}}{{!
}}{{/ elementid }}"
{{#open}} aria-expanded="true" {{/open}}
{{^open}} aria-expanded="false" {{/open}}
aria-controls="{{$ elementid }}{{!
}}{{#elementid}}{{elementid}}{{/elementid}}{{!
}}{{^elementid}}collapsable_{{uniqid}}{{/elementid}}{{!
}}{{/ elementid }}"
class="btn btn-icon me-3 icons-collapse-expand justify-content-center {{!
}} {{^open}} collapsed {{/open}}"
>
<span
class="collapsed-icon icon-no-margin me-1"
title="{{!
}}{{$ expandlabel }}{{!
}}{{#expandlabel}}{{expandlabel}}{{/expandlabel}}{{!
}}{{^expandlabel}}{{#str}} expand, core {{/str}}{{/expandlabel}}{{!
}}{{/ expandlabel }}{{!
}}"
>
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
<span class="sr-only">{{!
}}{{$ expandlabel }}{{!
}}{{#expandlabel}}{{expandlabel}}{{/expandlabel}}{{!
}}{{^expandlabel}}{{#str}} expand, core {{/str}}{{/expandlabel}}{{!
}}{{/ expandlabel }}{{!
}}</span>
</span>
<span
class="expanded-icon icon-no-margin me-1"
title="{{!
}}{{$ collapselabel }}{{!
}}{{#collapselabel}}{{collapselabel}}{{/collapselabel}}{{!
}}{{^collapselabel}}{{#str}} collapse, core {{/str}}{{/collapselabel}}{{!
}}{{/ collapselabel }}{{!
}}"
>
{{#pix}} t/expandedchevron, core {{/pix}}
<span class="sr-only">{{!
}}{{$ collapselabel }}{{!
}}{{#collapselabel}}{{collapselabel}}{{/collapselabel}}{{!
}}{{^collapselabel}}{{#str}} collapse, core {{/str}}{{/collapselabel}}{{!
}}{{/ collapselabel }}{{!
}}</span>
</span>
</a>
<h3
class="d-flex align-self-stretch align-items-center mb-0"
id="{{$ elementid }}{{!
}}{{#elementid}}collapsable_section_{{elementid}}{{/elementid}}{{!
}}{{^elementid}}collapsable_section_{{uniqid}}{{/elementid}}{{!
}}{{/ elementid }}_title"
>
{{$ titlecontent }}
{{{titlecontent}}}
{{/ titlecontent }}
</h3>
</div>
</div>
<div
id="{{$ elementid }}{{!
}}{{#elementid}}{{elementid}}{{/elementid}}{{!
}}{{^elementid}}collapsable_{{uniqid}}{{/elementid}}{{!
}}{{/ elementid }}"
class="content collapse {{#open}}show{{/open}} pt-3"
data-mdl-component="core/local/collapsable_section"
{{$ extras }}
{{#extras}}
{{attribute}}="{{value}}"
{{/extras}}
{{/ extras }}
>
{{$ sectioncontent }}
{{{sectioncontent}}}
{{/ sectioncontent }}
</div>
</div>
{{#js}}
require(['core/local/collapsable_section/controls'], function(Controls) {
Controls.init();
});
{{/js}}

View file

@ -0,0 +1,66 @@
@core @javascript
Feature: Test collapsable section output module
In order to show extra information to the user
As a user
I need to interact with the collapsable section output
Background:
# Get to the fixture page.
Given I log in as "admin"
And I am on fixture page "/lib/tests/behat/fixtures/collapsable_section_output_testpage.php"
Scenario: Collapsable sections can be opened and closed
Given I should not see "Dialog content"
And I should not see "This is the closed section content." in the "closedsection" "region"
And I should see "This is the open section content." in the "opensection" "region"
When I click on "Expand" "button" in the "closedsection" "region"
And I click on "Collapse" "button" in the "opensection" "region"
Then I should see "This is the closed section content." in the "closedsection" "region"
And I should not see "This is the open section content." in the "opensection" "region"
Scenario: Collapsable sections content can have rich content inside
When I click on "Expand" "button" in the "closedsection" "region"
Then I should see "This is the closed section content." in the "closedsection" "region"
And "Link" "link" should exist in the "closedsection" "region"
And "Eye icon" "icon" should exist in the "closedsection" "region"
Scenario: Collapsable sections HTML attributtes can be overriden
When I click on "Expand" "button" in the "extraclasses" "region"
And I click on "Expand" "button" in the "extraattributes" "region"
Then ".extraclass" "css_element" should exist in the "extraclasses" "region"
And "[data-foo='bar']" "css_element" should exist in the "extraattributes" "region"
And "#myid" "css_element" should exist in the "extraattributes" "region"
Scenario: Collapsable sections can have custom labels for expand and collapse
When I click on "Custom expand" "button" in the "customlabels" "region"
Then I should see "This is the custom labels content." in the "customlabels" "region"
And I click on "Custom collapse" "button" in the "customlabels" "region"
And I should not see "This is the custom labels content." in the "customlabels" "region"
Scenario: Collapsable sections can be controlled via javascript
# Toggle.
Given I should not see "This is the javascript controls content." in the "jscontrols" "region"
When I click on "Toggle" "button" in the "jscontrols" "region"
Then I should see "This is the javascript controls content." in the "jscontrols" "region"
And I click on "Toggle" "button" in the "jscontrols" "region"
And I should not see "This is the javascript controls content." in the "jscontrols" "region"
# Show and Hide.
And I click on "Show" "button" in the "jscontrols" "region"
And I should see "This is the javascript controls content." in the "jscontrols" "region"
And I click on "Show" "button" in the "jscontrols" "region"
And I should see "This is the javascript controls content." in the "jscontrols" "region"
And I click on "Hide" "button" in the "jscontrols" "region"
And I should not see "This is the javascript controls content." in the "jscontrols" "region"
And I click on "Hide" "button" in the "jscontrols" "region"
And I should not see "This is the javascript controls content." in the "jscontrols" "region"
# Test state.
And I click on "Test state" "button" in the "jscontrols" "region"
And I should see "hidden" in the "state" "region"
And I click on "Show" "button" in the "jscontrols" "region"
And I click on "Test state" "button" in the "jscontrols" "region"
And I should see "visible" in the "state" "region"
# Events.
And I click on "Show" "button" in the "jscontrols" "region"
And I should see "Last event: Section shown" in the "jscontrols" "region"
And I click on "Hide" "button" in the "jscontrols" "region"
And I should see "Last event: Section hidden" in the "jscontrols" "region"

View file

@ -0,0 +1,204 @@
<?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/>.
/**
* Test page for the collapsable section output component.
*
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @package core
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(__DIR__ . '/../../../../config.php');
use core\output\local\collapsable_section;
defined('BEHAT_SITE_RUNNING') || die();
/**
* Generate the title content.
*
* @param string $content The content to be displayed inside the button.
* @return string
*/
function title_content(string $content): string {
return ucfirst($content) . ' title';
}
/**
* Generate the section content.
*
* @param string $content The content to be displayed inside the dialog.
* @return string
*/
function section_content(string $content): string {
global $OUTPUT;
$icon = $OUTPUT->pix_icon('t/hide', 'Eye icon');
return '
<p>This is the ' . $content . ' content.</p>
<p>Some rich content <a href="">Link</a> ' . $icon . '.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor <b>incididunt ut labore et dolore magna aliqua</b>. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
';
}
global $CFG, $PAGE, $OUTPUT;
$PAGE->set_url('/lib/tests/behat/fixtures/collapsable_section_output_testpage.php');
$PAGE->add_body_class('limitedwidth');
require_login();
$PAGE->set_context(core\context\system::instance());
$PAGE->set_title('Collapsable section test page');
echo $OUTPUT->header();
echo "<h2>Collapsable section test page</h2>";
echo $OUTPUT->paragraph('This page is used to test the collapsable section output component.');
$sample = 'closed section';
echo '<div id="closedsection" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$sample = 'open section';
echo '<div id="opensection" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
open: true,
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$sample = 'extra classes';
echo '<div id="extraclasses" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
classes: 'bg-dark text-white p-3 rounded-3 extraclass',
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$sample = 'extra attributes';
echo '<div id="extraattributes" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
extras: ['data-foo' => 'bar', 'id' => 'myid'],
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$sample = 'custom labels';
echo '<div id="customlabels" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
expandlabel: 'Custom expand',
collapselabel: 'Custom collapse',
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$sample = 'javascript controls';
echo '<div id="jscontrols" class="mb-4 border border-1 rounded-3 p-3">';
echo $OUTPUT->paragraph($sample . ' example');
echo '
<div class="d-flex justify-content-center">
<button class="btn btn-secondary mx-2" id="toggleBtn">Toggle</button>
<button class="btn btn-secondary mx-2" id="showBtn">Show</button>
<button class="btn btn-secondary mx-2" id="hideBtn">Hide</button>
<button class="btn btn-secondary mx-2" id="testBtn">Test state</button>
<div class="d-flex align-content-center mx-2 rounded p-2 border">
Current state: <div class="d-inline-block" id="state">?</div>
</div>
</div>';
echo '<div class="rounded my-2 p-2 border">
Last event: <div class="d-inline-block" id="lastevent">?</div>
</div>';
$collapsable = new collapsable_section(
titlecontent: title_content($sample),
sectioncontent: section_content($sample),
extras: ['id' => 'jsCollapsable'],
);
echo $OUTPUT->render($collapsable);
echo '</div>';
$inlinejs = <<<EOF
require(
[
'core/local/collapsable_section/controls',
'core/local/collapsable_section/events'
],
function(
CollapsableSection,
events
) {
const section = CollapsableSection.instanceFromSelector('#jsCollapsable');
document.getElementById('toggleBtn').addEventListener('click', function() {
section.toggle();
});
document.getElementById('showBtn').addEventListener('click', function() {
section.show();
});
document.getElementById('hideBtn').addEventListener('click', function() {
section.hide();
});
document.getElementById('testBtn').addEventListener('click', function() {
document.getElementById('state').textContent = section.isVisible() ? 'visible' : 'hidden';
});
const jscontrolregion = document.getElementById('jscontrols');
jscontrolregion.addEventListener(events.eventTypes.shown, function() {
document.getElementById('lastevent').textContent = 'Section shown';
});
jscontrolregion.addEventListener(events.eventTypes.hidden, function() {
document.getElementById('lastevent').textContent = 'Section hidden';
});
}
);
EOF;
$PAGE->requires->js_amd_inline($inlinejs);
echo '</div>';
echo $OUTPUT->footer();