Proyectos de Subversion Moodle

Rev

| 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.
30
import jQuery from 'jquery';
31
import Tree from 'core/tree';
32
import {getList} from 'core/normalise';
33
 
34
export default class extends Tree {
35
 
36
    /**
37
     * Setup the core/tree keyboard navigation.
38
     *
39
     * @param {Element|undefined} mainElement an alternative main element in case it is not from the parent component
40
     * @param {Object|undefined} selectors alternative selectors
41
     * @param {boolean} preventcache if the elements cache must be disabled.
42
     */
43
    constructor(mainElement, selectors, preventcache) {
44
        // Init this value with the parent DOM element.
45
        super(mainElement);
46
 
47
        // Get selectors from parent.
48
        this.selectors = {
49
            SECTION: selectors.SECTION,
50
            TOGGLER: selectors.TOGGLER,
51
            COLLAPSE: selectors.COLLAPSE,
52
            ENTER: selectors.ENTER ?? selectors.TOGGLER,
53
        };
54
 
55
        // The core/tree library saves the visible elements cache inside the main tree node.
56
        // However, in edit mode content can change suddenly so we need to refresh caches when needed.
57
        if (preventcache) {
58
            this._getVisibleItems = this.getVisibleItems;
59
            this.getVisibleItems = () => {
60
                this.refreshVisibleItemsCache();
61
                return this._getVisibleItems();
62
            };
63
        }
64
        // All jQuery events can be replaced when MDL-71979 is integrated.
65
        this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => {
66
            this.refreshVisibleItemsCache();
67
        });
68
        // Register a custom callback for pressing enter key.
69
        this.registerEnterCallback(this.enterCallback.bind(this));
70
    }
71
 
72
    /**
73
     * Return the current active node.
74
     *
75
     * @return {Element|undefined} the active item if any
76
     */
77
    getActiveItem() {
78
        const activeItem = this.treeRoot.data('activeItem');
79
        if (activeItem) {
80
            return getList(activeItem)[0];
81
        }
82
        return undefined;
83
    }
84
 
85
    /**
86
     * Handle enter key on a collpasible node.
87
     *
88
     * @param {JQuery} jQueryItem the jQuery object
89
     */
90
    enterCallback(jQueryItem) {
91
        const item = getList(jQueryItem)[0];
92
        if (this.isGroupItem(jQueryItem)) {
93
            // Group elements is like clicking a topic but without loosing the focus.
94
            const enter = item.querySelector(this.selectors.ENTER);
95
            if (enter.getAttribute('href') !== '#') {
96
                window.location.href = enter.getAttribute('href');
97
            }
98
            enter.click();
99
        } else {
100
            // Activity links just follow the link href.
101
            const link = item.querySelector('a');
102
            if (link.getAttribute('href') !== '#') {
103
                window.location.href = link.getAttribute('href');
104
            } else {
105
                link.click();
106
            }
107
            return;
108
        }
109
    }
110
 
111
    /**
112
     * Handle an item click.
113
     *
114
     * @param {Event} event the click event
115
     * @param {jQuery} jQueryItem the item clicked
116
     */
117
    handleItemClick(event, jQueryItem) {
118
        const isChevron = event.target.closest(this.selectors.COLLAPSE);
119
        // Only chevron clicks toogle the sections always.
120
        if (isChevron) {
121
            super.handleItemClick(event, jQueryItem);
122
            return;
123
        }
124
        // This is a title or activity name click.
125
        jQueryItem.focus();
126
        if (this.isGroupItem(jQueryItem)) {
127
            this.expandGroup(jQueryItem);
128
        }
129
    }
130
 
131
    /**
132
     * Check if a gorup item is collapsed.
133
     *
134
     * @param {JQuery} jQueryItem  the jQuery object
135
     * @returns {boolean} if the element is collapsed
136
     */
137
    isGroupCollapsed(jQueryItem) {
138
        const item = getList(jQueryItem)[0];
139
        const toggler = item.querySelector(`[aria-expanded]`);
140
        return toggler.getAttribute('aria-expanded') === 'false';
141
    }
142
 
143
    /**
144
     * Toggle a group item.
145
     *
146
     * @param {JQuery} item  the jQuery object
147
     */
148
    toggleGroup(item) {
149
        // All jQuery in this segment of code can be replaced when MDL-71979 is integrated.
150
        const toggler = item.find(this.selectors.COLLAPSE);
151
        let collapsibleId = toggler.data('target') ?? toggler.attr('href');
152
        if (!collapsibleId) {
153
            return;
154
        }
155
        collapsibleId = collapsibleId.replace('#', '');
156
 
157
        // Bootstrap 4 uses jQuery to interact with collapsibles.
158
        const collapsible = jQuery(`#${collapsibleId}`);
159
        if (collapsible.length) {
160
            jQuery(`#${collapsibleId}`).collapse('toggle');
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
}