Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Course index keyboard navigation and aria-tree compatibility.
18
 *
19
 * Node tree and bootstrap collapsibles don't use the same HTML structure. However,
20
 * all keybindings and logic is compatible. This class translate the primitive opetations
21
 * to a bootstrap collapsible structure.
22
 *
23
 * @module     core_courseformat/local/courseeditor/contenttree
24
 * @class      core_courseformat/local/courseeditor/contenttree
25
 * @copyright  2021 Ferran Recio <ferran@moodle.com>
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 */
28
 
29
// The core/tree uses jQuery to expand all nodes.
1441 ariadna 30
import Collapse from 'theme_boost/bootstrap/collapse';
1 efrain 31
import jQuery from 'jquery';
32
import Tree from 'core/tree';
1441 ariadna 33
import {getList, getFirst} from 'core/normalise';
1 efrain 34
 
35
export default class extends Tree {
36
 
37
    /**
38
     * Setup the core/tree keyboard navigation.
39
     *
40
     * @param {Element|undefined} mainElement an alternative main element in case it is not from the parent component
41
     * @param {Object|undefined} selectors alternative selectors
42
     * @param {boolean} preventcache if the elements cache must be disabled.
43
     */
44
    constructor(mainElement, selectors, preventcache) {
45
        // Init this value with the parent DOM element.
46
        super(mainElement);
47
 
48
        // Get selectors from parent.
49
        this.selectors = {
50
            SECTION: selectors.SECTION,
51
            TOGGLER: selectors.TOGGLER,
52
            COLLAPSE: selectors.COLLAPSE,
53
            ENTER: selectors.ENTER ?? selectors.TOGGLER,
54
        };
55
 
56
        // The core/tree library saves the visible elements cache inside the main tree node.
57
        // However, in edit mode content can change suddenly so we need to refresh caches when needed.
58
        if (preventcache) {
59
            this._getVisibleItems = this.getVisibleItems;
60
            this.getVisibleItems = () => {
61
                this.refreshVisibleItemsCache();
62
                return this._getVisibleItems();
63
            };
64
        }
1441 ariadna 65
        this.treeRoot[0].querySelectorAll(selectors.COLLAPSE).forEach(toggler => {
66
            const collapsible = document.getElementById(toggler.getAttribute('href').replace('#', ''));
67
            collapsible.addEventListener('hidden.bs.collapse', () => this.refreshVisibleItemsCache());
68
            collapsible.addEventListener('shown.bs.collapse', () => this.refreshVisibleItemsCache());
1 efrain 69
        });
70
        // Register a custom callback for pressing enter key.
71
        this.registerEnterCallback(this.enterCallback.bind(this));
72
    }
73
 
74
    /**
75
     * Return the current active node.
76
     *
77
     * @return {Element|undefined} the active item if any
78
     */
79
    getActiveItem() {
80
        const activeItem = this.treeRoot.data('activeItem');
81
        if (activeItem) {
82
            return getList(activeItem)[0];
83
        }
84
        return undefined;
85
    }
86
 
87
    /**
88
     * Handle enter key on a collpasible node.
89
     *
90
     * @param {JQuery} jQueryItem the jQuery object
91
     */
92
    enterCallback(jQueryItem) {
93
        const item = getList(jQueryItem)[0];
94
        if (this.isGroupItem(jQueryItem)) {
95
            // Group elements is like clicking a topic but without loosing the focus.
96
            const enter = item.querySelector(this.selectors.ENTER);
97
            if (enter.getAttribute('href') !== '#') {
98
                window.location.href = enter.getAttribute('href');
99
            }
100
            enter.click();
101
        } else {
102
            // Activity links just follow the link href.
103
            const link = item.querySelector('a');
104
            if (link.getAttribute('href') !== '#') {
105
                window.location.href = link.getAttribute('href');
106
            } else {
107
                link.click();
108
            }
109
            return;
110
        }
111
    }
112
 
113
    /**
114
     * Handle an item click.
115
     *
116
     * @param {Event} event the click event
117
     * @param {jQuery} jQueryItem the item clicked
118
     */
119
    handleItemClick(event, jQueryItem) {
120
        const isChevron = event.target.closest(this.selectors.COLLAPSE);
121
        // Only chevron clicks toogle the sections always.
122
        if (isChevron) {
123
            super.handleItemClick(event, jQueryItem);
124
            return;
125
        }
126
        // This is a title or activity name click.
127
        jQueryItem.focus();
128
        if (this.isGroupItem(jQueryItem)) {
129
            this.expandGroup(jQueryItem);
130
        }
131
    }
132
 
133
    /**
134
     * Check if a gorup item is collapsed.
135
     *
136
     * @param {JQuery} jQueryItem  the jQuery object
137
     * @returns {boolean} if the element is collapsed
138
     */
139
    isGroupCollapsed(jQueryItem) {
140
        const item = getList(jQueryItem)[0];
141
        const toggler = item.querySelector(`[aria-expanded]`);
142
        return toggler.getAttribute('aria-expanded') === 'false';
143
    }
144
 
145
    /**
146
     * Toggle a group item.
147
     *
148
     * @param {JQuery} item  the jQuery object
149
     */
150
    toggleGroup(item) {
1441 ariadna 151
        const toggler = getFirst(item).querySelector(this.selectors.COLLAPSE);
152
        let collapsibleId = toggler.dataset?.target ?? toggler.getAttribute('href');
1 efrain 153
        if (!collapsibleId) {
154
            return;
155
        }
156
        collapsibleId = collapsibleId.replace('#', '');
157
 
1441 ariadna 158
        const collapsible = document.getElementById(collapsibleId);
159
        if (collapsible) {
160
            Collapse.getOrCreateInstance(collapsible).toggle();
1 efrain 161
        }
162
    }
163
 
164
    /**
165
     * Expand a group item.
166
     *
167
     * @param {JQuery} item  the jQuery object
168
     */
169
    expandGroup(item) {
170
        if (this.isGroupCollapsed(item)) {
171
            this.toggleGroup(item);
172
        }
173
    }
174
 
175
    /**
176
     * Collpase a group item.
177
     *
178
     * @param {JQuery} item  the jQuery object
179
     */
180
    collapseGroup(item) {
181
        if (!this.isGroupCollapsed(item)) {
182
            this.toggleGroup(item);
183
        }
184
    }
185
 
186
    /**
187
     * Expand all groups.
188
     */
189
    expandAllGroups() {
190
        const togglers = getList(this.treeRoot)[0].querySelectorAll(this.selectors.SECTION);
191
        togglers.forEach(item => {
192
            this.expandGroup(jQuery(item));
193
        });
194
    }
195
}