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
 * Manage the courses view for the overview block.
18
 *
19
 * @module     tool_courserating/rating
20
 * @copyright  2022 Marina Glancy <marina.glancy@gmail.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import {get_string as getString} from 'core/str';
25
import {add as addToast} from 'core/toast';
26
import ModalForm from "core_form/modalform";
27
import ModalFactory from "core/modal_factory";
28
import Fragment from "core/fragment";
29
import Templates from "core/templates";
30
import ModalEvents from 'core/modal_events';
31
import ajax from 'core/ajax';
32
 
33
const SELECTORS = {
34
    COURSERATING: '.customfield_tool_courserating',
35
    COURSEWIDGET: '.tool_courserating-widget',
36
    ADD_RATING: '[data-action=tool_courserating-addrating][data-courseid]',
37
    VIEW_RATINGS_CFIELD: '.tool_courserating-cfield .tool_courserating-ratings',
38
    VIEW_RATINGS_LINK: '[data-action="tool_courserating-viewratings"]',
39
    FLAG_RATING: '[data-action=tool_courserating-toggleflag]',
40
    DELETE_RATING: `[data-action='tool_courserating-delete-rating']`,
41
    USER_RATING: `[data-for='tool_courserating-user-rating']`,
42
    CFIELD_WRAPPER: `[data-for='tool_courserating-cfield-wrapper'][data-courseid]`,
43
    USER_RATING_FLAG: `[data-for='tool_courserating-user-flag']`,
44
    RATING_POPUP: `.tool_courserating-reviews-popup`,
45
    REVIEWS_LIST: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"]`,
46
    SHOWMORE_WRAPPER: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +
47
        `[data-for="tool_courserating-showmore"]`,
48
    SHOWMORE_BUTTON: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +
49
        `[data-for="tool_courserating-showmore"] [data-action="showmore"]`,
50
    RESET_WITHRATINGS: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +
51
        `[data-for="tool_courserating-resetwithrating"]`,
52
    POPUP_SUMMARY: `.tool_courserating-reviews-popup [data-for="tool_courserating-summary"]`,
53
    SET_WITHRATINGS: `.tool_courserating-reviews-popup [data-for="tool_courserating-summary"] ` +
54
        `[data-for="tool_courserating_setwithrating"]`,
55
    RBCELL: `[data-for="tool_courserating-rbcell"][data-ratingid]`,
56
};
57
 
58
let systemContextId;
59
let viewRatingsModal;
60
let addRatingModal;
61
 
62
/**
63
 * Initialise listeners
64
 *
65
 * @param {Number} systemContextIdParam
66
 * @param {Boolean} useJQuery
67
 */
68
export const init = (systemContextIdParam, useJQuery = false) => {
69
    systemContextId = systemContextIdParam;
70
 
71
    document.addEventListener('click', e => {
72
        if (!e || !e.target || (typeof e.target.closest === "undefined")) {
73
            return;
74
        }
75
 
76
        const addRatingElement = e.target.closest(SELECTORS.ADD_RATING),
77
            viewRatingsElement = e.target.closest(SELECTORS.VIEW_RATINGS_CFIELD),
78
            deleteRatingElement = e.target.closest(SELECTORS.DELETE_RATING);
79
 
80
        if (addRatingElement) {
81
            e.preventDefault();
82
            const courseid = addRatingElement.getAttribute('data-courseid');
83
            if (viewRatingsModal) {
84
                viewRatingsModal.destroy();
85
            }
86
            addRating(courseid);
87
        } else if (viewRatingsElement) {
88
            e.preventDefault();
89
            const classes = (' ' + viewRatingsElement.getAttribute('class') + ' '),
90
                matches = classes.match(/ tool_courserating-ratings-courseid-(\d+) /);
91
            if (matches) {
92
                const widget = viewRatingsElement.closest(SELECTORS.COURSEWIDGET);
93
                if (widget && widget.querySelector(SELECTORS.ADD_RATING)) {
94
                    addRating(matches[1]);
95
                } else {
96
                    viewRatings(matches[1]);
97
                }
98
            }
99
        } else if (deleteRatingElement) {
100
            e.preventDefault();
101
            const ratingid = deleteRatingElement.getAttribute('data-ratingid');
102
            deleteRating(ratingid);
103
        }
104
    });
105
 
106
    if (useJQuery) {
107
        require(['jquery'], function($) {
108
            $('body').on('updated', '[data-inplaceeditable]', e => reloadFlag(e.target));
109
        });
110
    } else {
111
        document.addEventListener('core/inplace_editable:updated', e => reloadFlag(e.target));
112
    }
113
};
114
 
115
/**
116
 * Update the rating flag fragment
117
 *
118
 * @param {Element} inplaceEditable
119
 */
120
const reloadFlag = (inplaceEditable) => {
121
    if (inplaceEditable.dataset.component === 'tool_courserating' && inplaceEditable.dataset.itemtype === 'flag') {
122
        const ratingid = inplaceEditable.dataset.itemid;
123
        const node = document.querySelector(`${SELECTORS.USER_RATING_FLAG}[data-ratingid='${ratingid}']`);
124
        if (node) {
125
            Fragment.loadFragment('tool_courserating', 'rating_flag', systemContextId, {ratingid}).done((html, js) => {
126
                Templates.replaceNode(node, html, js);
127
            });
128
        }
129
    }
130
};
131
 
132
/**
133
 * Add ratings dialogue
134
 *
135
 * @param {Number} courseid
136
 */
137
const addRating = (courseid) => {
138
    addRatingModal = new ModalForm({
139
        formClass: 'tool_courserating\\form\\addrating',
140
        args: {courseid},
141
        modalConfig: {
142
            title: getString('addrating', 'tool_courserating'),
143
        },
144
    });
145
 
146
    // When form is saved, refresh it to remove validation errors, if any:
147
    addRatingModal.addEventListener(addRatingModal.events.FORM_SUBMITTED, () => {
148
        getString('changessaved')
149
            .then(addToast)
150
            .catch(null);
151
        refreshRating(courseid);
152
    });
153
 
154
    addRatingModal.show();
155
};
156
 
157
/**
158
 * View ratings dialogue
159
 *
160
 * @param {Number} courseid
161
 */
162
const viewRatings = (courseid) => {
163
    ModalFactory.create({
164
        type: ModalFactory.types.CANCEL,
165
        title: getString('coursereviews', 'tool_courserating'),
166
        large: true,
167
        buttons: {
168
            cancel: getString('closebuttontitle', 'core'),
169
        },
170
        removeOnClose: true,
171
    })
172
        .then(modal => {
173
            modal.setLarge();
174
            loadCourseRatingPopupContents({courseid})
175
            .done(({html, js}) => {
176
                modal.setBody(html);
177
                Templates.runTemplateJS(js);
178
            });
179
            // Handle hidden event.
180
            modal.getRoot().on(ModalEvents.hidden, function() {
181
                // Destroy when hidden.
182
                modal.destroy();
183
            });
184
            modal.show();
185
            viewRatingsModal = modal;
186
            return modal;
187
        })
188
        .fail(() => null);
189
};
190
 
191
/**
192
 * Delete rating with specified id
193
 *
194
 * @param {Number} ratingid
195
 */
196
const deleteRating = (ratingid) => {
197
    const form = new ModalForm({
198
        formClass: 'tool_courserating\\form\\deleterating',
199
        args: {ratingid},
200
        modalConfig: {
201
            title: getString('deleterating', 'tool_courserating'),
202
        },
203
    });
204
 
205
    // When form is saved, rating should be deleted.
206
    form.addEventListener(form.events.FORM_SUBMITTED, async e => {
207
        const el = document.querySelector(SELECTORS.USER_RATING + `[data-ratingid='${e.detail.ratingid}'`);
208
        if (el) {
209
            el.remove();
210
        }
211
        refreshRating(e.detail.courseid);
212
        if (!el) {
213
            const rbcell = document.querySelector(SELECTORS.RBCELL + `[data-ratingid='${e.detail.ratingid}'`);
214
            if (rbcell) {
215
                rbcell.innerHTML = await getString('ratingdeleted', 'tool_courserating');
216
            }
217
        }
218
    });
219
 
220
    form.show();
221
};
222
 
223
/**
224
 * Refresh course rating summary
225
 *
226
 * @param {Number} courseid
227
 */
228
const refreshRating = (courseid) => {
229
    let el1 = document.getElementsByClassName('tool_courserating-ratings-courseid-' + courseid);
230
    if (el1 && el1.length) {
231
        const cfield = el1[0].closest(SELECTORS.COURSERATING);
232
        Fragment.loadFragment('tool_courserating', 'cfield', systemContextId, {courseid}).done((html, js) => {
233
            Templates.replaceNode(cfield, html, js);
234
        });
235
    }
236
 
237
    const el2 = document.querySelector(SELECTORS.CFIELD_WRAPPER + `[data-courseid='${courseid}']`);
238
    if (el2) {
239
        Fragment.loadFragment('tool_courserating', 'cfield', systemContextId, {courseid}).done((html, js) => {
240
            el2.innerHTML = '';
241
            Templates.appendNodeContents(el2, html, js);
242
        });
243
    }
244
 
245
    const el3 = document.querySelector(`[data-for='tool_courserating-summary'][data-courseid='${courseid}']`);
246
    if (el3) {
247
        Fragment.loadFragment('tool_courserating', 'course_ratings_summary', systemContextId, {courseid}).done((html, js) => {
248
            el3.innerHTML = '';
249
            Templates.appendNodeContents(el3, html, js);
250
        });
251
    }
252
};
253
 
254
/**
255
 * Adds or removes CSS class to/from an element
256
 *
257
 * @param {Element} ratingFormGroup
258
 * @param {String} value
259
 */
260
const setFormGroupClasses = (ratingFormGroup, value) => {
261
    const addRemoveClass = (className, add) => {
262
        if (add && !ratingFormGroup.classList.contains(className)) {
263
            ratingFormGroup.classList.add(className);
264
        } else if (!add && ratingFormGroup.classList.contains(className)) {
265
            ratingFormGroup.classList.remove(className);
266
        }
267
    };
268
    for (let i = 1; i <= 5; i++) {
269
        addRemoveClass('s-' + i, i <= parseInt(value));
270
    }
271
    addRemoveClass('tool_courserating-norating', parseInt(value) === 0);
272
};
273
 
274
/**
275
 * Sets up listeneres for the addRating modal form
276
 *
277
 * @param {String} grpId
278
 */
279
export const setupAddRatingForm = (grpId) => {
280
    const ratingFormGroup = document.getElementById(grpId);
281
    const curchecked = ratingFormGroup.querySelector('input:checked');
282
    setFormGroupClasses(ratingFormGroup, curchecked ? curchecked.value : 0);
283
 
284
    let els = ratingFormGroup.querySelectorAll('input');
285
    for (let i = 0; i < els.length; i++) {
286
        els[i].addEventListener('change', e => setFormGroupClasses(ratingFormGroup, e.target.value));
287
    }
288
 
289
    let labels = ratingFormGroup.querySelectorAll('label');
290
    for (let i = 0; i < labels.length; i++) {
291
        labels[i].addEventListener("mouseover", e => {
292
            const el = e.target.closest('label').querySelector('input');
293
            setFormGroupClasses(ratingFormGroup, el.value);
294
        });
295
        labels[i].addEventListener("mouseleave", () => {
296
            const el = ratingFormGroup.querySelector('input:checked');
297
            setFormGroupClasses(ratingFormGroup, el ? el.value : 0);
298
        });
299
    }
300
 
301
    const form = ratingFormGroup.closest('form');
302
    const viewratingsLink = form.querySelector(SELECTORS.VIEW_RATINGS_LINK);
303
    if (viewratingsLink) {
304
        viewratingsLink.addEventListener('click', e => {
305
            e.preventDefault();
306
            addRatingModal.modal.destroy();
307
            viewRatings(e.target.dataset.courseid);
308
        });
309
    }
310
};
311
 
312
/**
313
 * Sets up the "View course ratings" popup
314
 */
315
export const setupViewRatingsPopup = () => {
316
    const el = document.querySelector(SELECTORS.REVIEWS_LIST);
317
    const reloadReviews = (offset = 0) => {
318
        const params = {
319
            courseid: el.dataset.courseid,
320
            offset,
321
            withrating: el.dataset.withrating
322
        };
323
        return Fragment.loadFragment('tool_courserating', 'course_reviews', el.dataset.systemcontextid, params);
324
    };
325
 
326
    el.addEventListener('click', e => {
327
        const button = e.target.closest(SELECTORS.SHOWMORE_BUTTON);
328
        if (button) {
329
            const wrapper = button.closest(SELECTORS.SHOWMORE_WRAPPER);
330
            e.preventDefault();
331
            reloadReviews(button.dataset.nextoffset).done((html, js) => Templates.replaceNode(wrapper, html, js));
332
        }
333
        const resetLink = e.target.closest(SELECTORS.RESET_WITHRATINGS);
334
        if (resetLink) {
335
            e.preventDefault();
336
            el.dataset.withrating = 0;
337
            reloadReviews(0).done((html, js) => Templates.replaceNodeContents(el, html, js));
338
        }
339
    });
340
 
341
    const elSummary = document.querySelector(SELECTORS.POPUP_SUMMARY);
342
    elSummary.addEventListener('click', e => {
343
        const withRatingButton = e.target.closest(SELECTORS.SET_WITHRATINGS);
344
        if (withRatingButton) {
345
            el.dataset.withrating = (el.dataset.withrating === withRatingButton.dataset.withrating) ?
346
 
347
            reloadReviews(0).done((html, js) => Templates.replaceNodeContents(el, html, js));
348
        }
349
    });
350
};
351
 
352
/**
353
 * Hide the custom field editor on the course edit page
354
 *
355
 * @param {String} fieldname
356
 */
357
export const hideEditField = (fieldname) => {
358
    const s = '#fitem_id_customfield_' + fieldname;
359
    let el = document.querySelector(s + '_editor');
360
    if (el) {
361
        el.style.display = 'none';
362
        el = document.querySelector(s + '_static');
363
        if (el) {
364
            el.style.display = 'none';
365
        }
366
    }
367
};
368
 
369
 
370
/**
371
 * Loads Course Rating popup contents. Allows both loggedin and nonloggedin requests.
372
 *
373
 * @param {object} args Parameters for the callback.
374
 * @return {Promise} JQuery promise object resolved when the fragment has been loaded.
375
 */
376
const loadCourseRatingPopupContents = function(args) {
377
    const isloggedin = !document.body.classList.contains('notloggedin');
378
 
379
    if (isloggedin) {
380
        return Fragment.loadFragment('tool_courserating', 'course_ratings_popup', systemContextId, args)
381
            .then((html, js) => ({html, js}));
382
    }
383
 
384
    return ajax.call([{
385
        methodname: 'tool_courserating_course_rating_popup',
386
        args
387
    }], undefined, false)[0]
388
    .then(function(data) {
389
        return {html: data.html, js: Fragment.processCollectedJavascript(data.javascript)};
390
    });
391
};