MDL-83869 output: new collapsable section component

This commit is contained in:
ferran 2024-12-20 11:54:32 +01:00
parent a2653cc924
commit 46318e53d0
11 changed files with 838 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,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();