ÿØÿà JFIF ÿÛ „ ( %"1!%)+...383,7(-.+
-+++--++++---+-+-----+---------------+---+-++7-----ÿÀ ß â" ÿÄ ÿÄ H !1AQaq"‘¡2B±ÁÑð#R“Ò Tbr‚²á3csƒ’ÂñDS¢³$CÿÄ ÿÄ % !1AQa"23‘ÿÚ ? ôÿ ¨pŸªáÿ —åYõõ\?àÒü©ŠÄï¨pŸªáÿ —åYõõ\?àÓü©ŠÄá 0Ÿªáÿ Ÿå[úƒ ú®ði~TÁbqÐ8OÕpÿ ƒOò¤Oè`–RÂáœá™êi€ßÉ< FtŸI“öÌ8úDf´°å}“¾œ6
öFá°y¥jñÇh†ˆ¢ã/ÃÐ:ªcÈ
"Y¡ðÑl>ÿ ”ÏËte:qž\oäŠe÷ó²·˜HT4&ÿ ÓÐü6ö®¿øþßèô Ÿ•7Ñi’•j|“ñì>b…þS?*Óôÿ ÓÐü*h¥£ír¶ü UãS炟[AÐaè[ûª•õ&õj?†Éö+EzP—WeÒírJFt ‘BŒ†Ï‡%#tE Øz ¥OÛ«!1›üä±Í™%ºÍãö]°î(–:@<‹ŒÊö×òÆt¦ãº+‡¦%Ìòh´OƒJŒtMÜ>ÀÜÊw3Y´•牋4ÇýÊTì>œú=Íwhyë,¾Ôò×õ¿ßÊa»«þˆÑªQ|%6ž™A õ%:øj<>É—ÿ Å_ˆCbõ¥š±ý¯Ýƒï…¶|RëócÍf溪“t.СøTÿ *Ä¿-{†çàczůŽ_–^XþŒ±miB[X±d 1,é”zEù»&
î9gœf™9Ð'.;—™i}!ôšåîqêÛ٤ёý£½ÆA–àôe"A$ËÚsäÿ
÷Û #°xŸëí(l
»ý3—¥5m!
rt`†0~'j2(]S¦¦kv,ÚÇl¦øJA£Šƒ
J3E8ÙiŽ:cÉžúeZ°€¯\®kÖ(79«Ž:¯X”¾³Š&¡* ….‰Ž(ÜíŸ2¥ª‡×Hi²TF¤ò[¨íÈRëÉä¢mgÑ.Ÿ<öäS0í„ǹÁU´f#Vß;Õ–…P@3ío<ä-±»Ž.L|kªÀê›fÂ6@»eu‚|ÓaÞÆŸ…¨ááå>åŠ?cKü6ùTÍÆ”†sĤÚ;H2RÚ†õ\Ö·Ÿn'¾ ñ#ºI¤Å´%çÁ‚â7›‹qT3Iï¨ÖÚ5I7Ë!ÅOóŸ¶øÝñØôת¦$Tcö‘[«Ö³šÒ';Aþ ¸èíg
A2Z"i¸vdÄ÷.iõ®§)¿]¤À†–‡É&ä{V¶iŽ”.Ó×Õÿ û?h¬Mt–íª[ÿ Ñÿ ÌV(í}=ibÔ¡›¥¢±b Lô¥‡piη_Z<‡z§èŒ)iÖwiÇ 2hÙ3·=’d÷8éŽ1¦¸c¤µ€7›7Ø ð\á)} ¹fËí›pAÃL%âc2 í§æQz¿;T8sæ°qø)QFMð‰XŒÂ±N¢aF¨…8¯!U Z©RÊ ÖPVÄÀÍin™Ì-GˆªÅËŠ›•zË}º±ŽÍFò¹}Uw×#ä5B¤{î}Ð<ÙD
é©¤&‡ïDbàÁôMÁ.// 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 .
/**
* Report builder audiences
*
* @module core_reportbuilder/audience
* @copyright 2021 David Matamoros
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
"use strict";
import 'core/inplace_editable';
import Templates from 'core/templates';
import Notification from 'core/notification';
import Pending from 'core/pending';
import {prefetchStrings} from 'core/prefetch';
import {getString} from 'core/str';
import DynamicForm from 'core_form/dynamicform';
import {add as addToast} from 'core/toast';
import {deleteAudience} from 'core_reportbuilder/local/repository/audiences';
import * as reportSelectors from 'core_reportbuilder/local/selectors';
import {loadFragment} from 'core/fragment';
import {markFormAsDirty} from 'core_form/changechecker';
let reportId = 0;
let contextId = 0;
/**
* Add audience card
*
* @param {String} className
* @param {String} title
*/
const addAudienceCard = (className, title) => {
const pendingPromise = new Pending('core_reportbuilder/audience:add');
const audiencesContainer = document.querySelector(reportSelectors.regions.audiencesContainer);
const audienceCardLength = audiencesContainer.querySelectorAll(reportSelectors.regions.audienceCard).length;
const params = {
classname: className,
reportid: reportId,
showormessage: (audienceCardLength > 0),
title: title,
};
// Load audience card fragment, render and then initialise the form within.
loadFragment('core_reportbuilder', 'audience_form', contextId, params)
.then((html, js) => {
const audienceCard = Templates.appendNodeContents(audiencesContainer, html, js)[0];
const audienceEmptyMessage = audiencesContainer.querySelector(reportSelectors.regions.audienceEmptyMessage);
const audienceForm = initAudienceCardForm(audienceCard);
// Mark as dirty new audience form created to prevent users leaving the page without saving it.
markFormAsDirty(audienceForm.getFormNode());
audienceEmptyMessage.classList.add('hidden');
return getString('audienceadded', 'core_reportbuilder', title);
})
.then(addToast)
.then(() => pendingPromise.resolve())
.catch(Notification.exception);
};
/**
* Edit audience card
*
* @param {Element} audienceCard
*/
const editAudienceCard = audienceCard => {
const pendingPromise = new Pending('core_reportbuilder/audience:edit');
// Load audience form with data for editing, then toggle visible controls in the card.
const audienceForm = initAudienceCardForm(audienceCard);
audienceForm.load({id: audienceCard.dataset.audienceId})
.then(() => {
const audienceFormContainer = audienceCard.querySelector(reportSelectors.regions.audienceFormContainer);
const audienceDescription = audienceCard.querySelector(reportSelectors.regions.audienceDescription);
const audienceEdit = audienceCard.querySelector(reportSelectors.actions.audienceEdit);
audienceFormContainer.classList.remove('hidden');
audienceDescription.classList.add('hidden');
audienceEdit.disabled = true;
return pendingPromise.resolve();
})
.catch(Notification.exception);
};
/**
* Initialise dynamic form within given audience card
*
* @param {Element} audienceCard
* @return {DynamicForm}
*/
const initAudienceCardForm = audienceCard => {
const audienceFormContainer = audienceCard.querySelector(reportSelectors.regions.audienceFormContainer);
const audienceForm = new DynamicForm(audienceFormContainer, '\\core_reportbuilder\\form\\audience');
// After submitting the form, update the card instance and description properties.
audienceForm.addEventListener(audienceForm.events.FORM_SUBMITTED, data => {
const audienceHeading = audienceCard.querySelector(reportSelectors.regions.audienceHeading);
const audienceDescription = audienceCard.querySelector(reportSelectors.regions.audienceDescription);
audienceCard.dataset.audienceId = data.detail.instanceid;
audienceHeading.innerHTML = data.detail.heading;
audienceDescription.innerHTML = data.detail.description;
closeAudienceCardForm(audienceCard);
return getString('audiencesaved', 'core_reportbuilder')
.then(addToast);
});
// If cancelling the form, close the card or remove it if it was never created.
audienceForm.addEventListener(audienceForm.events.FORM_CANCELLED, () => {
if (audienceCard.dataset.audienceId > 0) {
closeAudienceCardForm(audienceCard);
} else {
removeAudienceCard(audienceCard);
}
});
return audienceForm;
};
/**
* Delete audience card
*
* @param {Element} audienceDelete
*/
const deleteAudienceCard = audienceDelete => {
const audienceCard = audienceDelete.closest(reportSelectors.regions.audienceCard);
const {audienceId, audienceTitle, audienceEditWarning = false} = audienceCard.dataset;
// The edit warning indicates the audience is in use in a report schedule.
const audienceDeleteConfirmation = audienceEditWarning ? 'audienceusedbyschedule' : 'deleteaudienceconfirm';
Notification.saveCancelPromise(
getString('deleteaudience', 'core_reportbuilder', audienceTitle),
getString(audienceDeleteConfirmation, 'core_reportbuilder', audienceTitle),
getString('delete', 'core'),
{triggerElement: audienceDelete}
).then(() => {
const pendingPromise = new Pending('core_reportbuilder/audience:delete');
return deleteAudience(reportId, audienceId)
.then(() => addToast(getString('audiencedeleted', 'core_reportbuilder', audienceTitle)))
.then(() => {
removeAudienceCard(audienceCard);
return pendingPromise.resolve();
})
.catch(Notification.exception);
}).catch(() => {
return;
});
};
/**
* Close audience card form
*
* @param {Element} audienceCard
*/
const closeAudienceCardForm = audienceCard => {
// Remove the [data-region="audience-form-container"] (with all the event listeners attached to it), and create it again.
const audienceFormContainer = audienceCard.querySelector(reportSelectors.regions.audienceFormContainer);
const NewAudienceFormContainer = audienceFormContainer.cloneNode(false);
audienceCard.querySelector(reportSelectors.regions.audienceForm).replaceChild(NewAudienceFormContainer, audienceFormContainer);
// Show the description container and enable the action buttons.
audienceCard.querySelector(reportSelectors.regions.audienceDescription).classList.remove('hidden');
audienceCard.querySelector(reportSelectors.actions.audienceEdit).disabled = false;
audienceCard.querySelector(reportSelectors.actions.audienceDelete).disabled = false;
};
/**
* Remove audience card
*
* @param {Element} audienceCard
*/
const removeAudienceCard = audienceCard => {
audienceCard.remove();
const audiencesContainer = document.querySelector(reportSelectors.regions.audiencesContainer);
const audienceCards = audiencesContainer.querySelectorAll(reportSelectors.regions.audienceCard);
// Show message if there are no cards remaining, ensure first card's separator is not present.
if (audienceCards.length === 0) {
const audienceEmptyMessage = document.querySelector(reportSelectors.regions.audienceEmptyMessage);
audienceEmptyMessage.classList.remove('hidden');
} else {
const audienceFirstCardSeparator = audienceCards[0].querySelector('.audience-separator');
audienceFirstCardSeparator?.remove();
}
};
let initialized = false;
/**
* Initialise audiences tab.
*
* @param {Number} id
* @param {Number} contextid
*/
export const init = (id, contextid) => {
prefetchStrings('core_reportbuilder', [
'audienceadded',
'audiencedeleted',
'audiencesaved',
'audienceusedbyschedule',
'deleteaudience',
'deleteaudienceconfirm',
]);
prefetchStrings('core', [
'delete',
]);
reportId = id;
contextId = contextid;
if (initialized) {
// We already added the event listeners (can be called multiple times by mustache template).
return;
}
document.addEventListener('click', event => {
// Add instance.
const audienceAdd = event.target.closest(reportSelectors.actions.audienceAdd);
if (audienceAdd) {
event.preventDefault();
addAudienceCard(audienceAdd.dataset.uniqueIdentifier, audienceAdd.dataset.name);
}
// Edit instance.
const audienceEdit = event.target.closest(reportSelectors.actions.audienceEdit);
if (audienceEdit) {
const audienceEditCard = audienceEdit.closest(reportSelectors.regions.audienceCard);
event.preventDefault();
editAudienceCard(audienceEditCard);
}
// Delete instance.
const audienceDelete = event.target.closest(reportSelectors.actions.audienceDelete);
if (audienceDelete) {
event.preventDefault();
deleteAudienceCard(audienceDelete);
}
});
initialized = true;
};