AutorÃa | Ultima modificación | Ver Log |
// 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/>./*** Manage the courses view for the overview block.** @module tool_courserating/rating* @copyright 2022 Marina Glancy <marina.glancy@gmail.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/import {get_string as getString} from 'core/str';import {add as addToast} from 'core/toast';import ModalForm from "core_form/modalform";import ModalFactory from "core/modal_factory";import Fragment from "core/fragment";import Templates from "core/templates";import ModalEvents from 'core/modal_events';import ajax from 'core/ajax';const SELECTORS = {COURSERATING: '.customfield_tool_courserating',COURSEWIDGET: '.tool_courserating-widget',ADD_RATING: '[data-action=tool_courserating-addrating][data-courseid]',VIEW_RATINGS_CFIELD: '.tool_courserating-cfield .tool_courserating-ratings',VIEW_RATINGS_LINK: '[data-action="tool_courserating-viewratings"]',FLAG_RATING: '[data-action=tool_courserating-toggleflag]',DELETE_RATING: `[data-action='tool_courserating-delete-rating']`,USER_RATING: `[data-for='tool_courserating-user-rating']`,CFIELD_WRAPPER: `[data-for='tool_courserating-cfield-wrapper'][data-courseid]`,USER_RATING_FLAG: `[data-for='tool_courserating-user-flag']`,RATING_POPUP: `.tool_courserating-reviews-popup`,REVIEWS_LIST: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"]`,SHOWMORE_WRAPPER: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +`[data-for="tool_courserating-showmore"]`,SHOWMORE_BUTTON: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +`[data-for="tool_courserating-showmore"] [data-action="showmore"]`,RESET_WITHRATINGS: `.tool_courserating-reviews-popup [data-for="tool_courserating-reviews"] ` +`[data-for="tool_courserating-resetwithrating"]`,POPUP_SUMMARY: `.tool_courserating-reviews-popup [data-for="tool_courserating-summary"]`,SET_WITHRATINGS: `.tool_courserating-reviews-popup [data-for="tool_courserating-summary"] ` +`[data-for="tool_courserating_setwithrating"]`,RBCELL: `[data-for="tool_courserating-rbcell"][data-ratingid]`,};let systemContextId;let viewRatingsModal;let addRatingModal;/*** Initialise listeners** @param {Number} systemContextIdParam* @param {Boolean} useJQuery*/export const init = (systemContextIdParam, useJQuery = false) => {systemContextId = systemContextIdParam;document.addEventListener('click', e => {if (!e || !e.target || (typeof e.target.closest === "undefined")) {return;}const addRatingElement = e.target.closest(SELECTORS.ADD_RATING),viewRatingsElement = e.target.closest(SELECTORS.VIEW_RATINGS_CFIELD),deleteRatingElement = e.target.closest(SELECTORS.DELETE_RATING);if (addRatingElement) {e.preventDefault();const courseid = addRatingElement.getAttribute('data-courseid');if (viewRatingsModal) {viewRatingsModal.destroy();}addRating(courseid);} else if (viewRatingsElement) {e.preventDefault();const classes = (' ' + viewRatingsElement.getAttribute('class') + ' '),matches = classes.match(/ tool_courserating-ratings-courseid-(\d+) /);if (matches) {const widget = viewRatingsElement.closest(SELECTORS.COURSEWIDGET);if (widget && widget.querySelector(SELECTORS.ADD_RATING)) {addRating(matches[1]);} else {viewRatings(matches[1]);}}} else if (deleteRatingElement) {e.preventDefault();const ratingid = deleteRatingElement.getAttribute('data-ratingid');deleteRating(ratingid);}});if (useJQuery) {require(['jquery'], function($) {$('body').on('updated', '[data-inplaceeditable]', e => reloadFlag(e.target));});} else {document.addEventListener('core/inplace_editable:updated', e => reloadFlag(e.target));}};/*** Update the rating flag fragment** @param {Element} inplaceEditable*/const reloadFlag = (inplaceEditable) => {if (inplaceEditable.dataset.component === 'tool_courserating' && inplaceEditable.dataset.itemtype === 'flag') {const ratingid = inplaceEditable.dataset.itemid;const node = document.querySelector(`${SELECTORS.USER_RATING_FLAG}[data-ratingid='${ratingid}']`);if (node) {Fragment.loadFragment('tool_courserating', 'rating_flag', systemContextId, {ratingid}).done((html, js) => {Templates.replaceNode(node, html, js);});}}};/*** Add ratings dialogue** @param {Number} courseid*/const addRating = (courseid) => {addRatingModal = new ModalForm({formClass: 'tool_courserating\\form\\addrating',args: {courseid},modalConfig: {title: getString('addrating', 'tool_courserating'),},});// When form is saved, refresh it to remove validation errors, if any:addRatingModal.addEventListener(addRatingModal.events.FORM_SUBMITTED, () => {getString('changessaved').then(addToast).catch(null);refreshRating(courseid);});addRatingModal.show();};/*** View ratings dialogue** @param {Number} courseid*/const viewRatings = (courseid) => {ModalFactory.create({type: ModalFactory.types.CANCEL,title: getString('coursereviews', 'tool_courserating'),large: true,buttons: {cancel: getString('closebuttontitle', 'core'),},removeOnClose: true,}).then(modal => {modal.setLarge();loadCourseRatingPopupContents({courseid}).done(({html, js}) => {modal.setBody(html);Templates.runTemplateJS(js);});// Handle hidden event.modal.getRoot().on(ModalEvents.hidden, function() {// Destroy when hidden.modal.destroy();});modal.show();viewRatingsModal = modal;return modal;}).fail(() => null);};/*** Delete rating with specified id** @param {Number} ratingid*/const deleteRating = (ratingid) => {const form = new ModalForm({formClass: 'tool_courserating\\form\\deleterating',args: {ratingid},modalConfig: {title: getString('deleterating', 'tool_courserating'),},});// When form is saved, rating should be deleted.form.addEventListener(form.events.FORM_SUBMITTED, async e => {const el = document.querySelector(SELECTORS.USER_RATING + `[data-ratingid='${e.detail.ratingid}'`);if (el) {el.remove();}refreshRating(e.detail.courseid);if (!el) {const rbcell = document.querySelector(SELECTORS.RBCELL + `[data-ratingid='${e.detail.ratingid}'`);if (rbcell) {rbcell.innerHTML = await getString('ratingdeleted', 'tool_courserating');}}});form.show();};/*** Refresh course rating summary** @param {Number} courseid*/const refreshRating = (courseid) => {let el1 = document.getElementsByClassName('tool_courserating-ratings-courseid-' + courseid);if (el1 && el1.length) {const cfield = el1[0].closest(SELECTORS.COURSERATING);Fragment.loadFragment('tool_courserating', 'cfield', systemContextId, {courseid}).done((html, js) => {Templates.replaceNode(cfield, html, js);});}const el2 = document.querySelector(SELECTORS.CFIELD_WRAPPER + `[data-courseid='${courseid}']`);if (el2) {Fragment.loadFragment('tool_courserating', 'cfield', systemContextId, {courseid}).done((html, js) => {el2.innerHTML = '';Templates.appendNodeContents(el2, html, js);});}const el3 = document.querySelector(`[data-for='tool_courserating-summary'][data-courseid='${courseid}']`);if (el3) {Fragment.loadFragment('tool_courserating', 'course_ratings_summary', systemContextId, {courseid}).done((html, js) => {el3.innerHTML = '';Templates.appendNodeContents(el3, html, js);});}};/*** Adds or removes CSS class to/from an element** @param {Element} ratingFormGroup* @param {String} value*/const setFormGroupClasses = (ratingFormGroup, value) => {const addRemoveClass = (className, add) => {if (add && !ratingFormGroup.classList.contains(className)) {ratingFormGroup.classList.add(className);} else if (!add && ratingFormGroup.classList.contains(className)) {ratingFormGroup.classList.remove(className);}};for (let i = 1; i <= 5; i++) {addRemoveClass('s-' + i, i <= parseInt(value));}addRemoveClass('tool_courserating-norating', parseInt(value) === 0);};/*** Sets up listeneres for the addRating modal form** @param {String} grpId*/export const setupAddRatingForm = (grpId) => {const ratingFormGroup = document.getElementById(grpId);const curchecked = ratingFormGroup.querySelector('input:checked');setFormGroupClasses(ratingFormGroup, curchecked ? curchecked.value : 0);let els = ratingFormGroup.querySelectorAll('input');for (let i = 0; i < els.length; i++) {els[i].addEventListener('change', e => setFormGroupClasses(ratingFormGroup, e.target.value));}let labels = ratingFormGroup.querySelectorAll('label');for (let i = 0; i < labels.length; i++) {labels[i].addEventListener("mouseover", e => {const el = e.target.closest('label').querySelector('input');setFormGroupClasses(ratingFormGroup, el.value);});labels[i].addEventListener("mouseleave", () => {const el = ratingFormGroup.querySelector('input:checked');setFormGroupClasses(ratingFormGroup, el ? el.value : 0);});}const form = ratingFormGroup.closest('form');const viewratingsLink = form.querySelector(SELECTORS.VIEW_RATINGS_LINK);if (viewratingsLink) {viewratingsLink.addEventListener('click', e => {e.preventDefault();addRatingModal.modal.destroy();viewRatings(e.target.dataset.courseid);});}};/*** Sets up the "View course ratings" popup*/export const setupViewRatingsPopup = () => {const el = document.querySelector(SELECTORS.REVIEWS_LIST);const reloadReviews = (offset = 0) => {const params = {courseid: el.dataset.courseid,offset,withrating: el.dataset.withrating};return Fragment.loadFragment('tool_courserating', 'course_reviews', el.dataset.systemcontextid, params);};el.addEventListener('click', e => {const button = e.target.closest(SELECTORS.SHOWMORE_BUTTON);if (button) {const wrapper = button.closest(SELECTORS.SHOWMORE_WRAPPER);e.preventDefault();reloadReviews(button.dataset.nextoffset).done((html, js) => Templates.replaceNode(wrapper, html, js));}const resetLink = e.target.closest(SELECTORS.RESET_WITHRATINGS);if (resetLink) {e.preventDefault();el.dataset.withrating = 0;reloadReviews(0).done((html, js) => Templates.replaceNodeContents(el, html, js));}});const elSummary = document.querySelector(SELECTORS.POPUP_SUMMARY);elSummary.addEventListener('click', e => {const withRatingButton = e.target.closest(SELECTORS.SET_WITHRATINGS);if (withRatingButton) {el.dataset.withrating = (el.dataset.withrating === withRatingButton.dataset.withrating) ?0 : withRatingButton.dataset.withrating;reloadReviews(0).done((html, js) => Templates.replaceNodeContents(el, html, js));}});};/*** Hide the custom field editor on the course edit page** @param {String} fieldname*/export const hideEditField = (fieldname) => {const s = '#fitem_id_customfield_' + fieldname;let el = document.querySelector(s + '_editor');if (el) {el.style.display = 'none';el = document.querySelector(s + '_static');if (el) {el.style.display = 'none';}}};/*** Loads Course Rating popup contents. Allows both loggedin and nonloggedin requests.** @param {object} args Parameters for the callback.* @return {Promise} JQuery promise object resolved when the fragment has been loaded.*/const loadCourseRatingPopupContents = function(args) {const isloggedin = !document.body.classList.contains('notloggedin');if (isloggedin) {return Fragment.loadFragment('tool_courserating', 'course_ratings_popup', systemContextId, args).then((html, js) => ({html, js}));}return ajax.call([{methodname: 'tool_courserating_course_rating_popup',args}], undefined, false)[0].then(function(data) {return {html: data.html, js: Fragment.processCollectedJavascript(data.javascript)};});};