Proyectos de Subversion Moodle

Rev

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)};
    });
};