/** * MeetLet mod form participants — page-local only (not AMD). */ require(['core/str', 'core/notification', 'core/templates', 'core/ajax'], function(Str, Notification, Templates, Ajax) { const ELEMENT_SELECTOR = { participantData: () => document.querySelector('[data-participant-data]'), participantList: () => document.getElementsByName('participants')[0], participantTable: () => document.getElementById('participant_list_table'), participantSelectionType: () => document.getElementsByName('meetlet_participant_selection_type')[0], participantSelection: () => document.getElementsByName('meetlet_participant_selection')[0], participantAddButton: () => document.getElementsByName('meetlet_participant_selection_add')[0], }; const getParticipantList = () => { const value = ELEMENT_SELECTOR.participantList().value; return value ? JSON.parse(value) : []; }; const participantListUpdate = (list) => { ELEMENT_SELECTOR.participantList().value = JSON.stringify(list); }; const participantRemove = (selectionTypeValue, selectionValue) => { const pList = getParticipantList(); const id = 'participant_list_tr_' + selectionTypeValue + '-' + selectionValue; const table = ELEMENT_SELECTOR.participantTable(); for (let i = 0; i < pList.length; i++) { if (pList[i].selectiontype === selectionTypeValue && String(pList[i].selectionid) === String(selectionValue)) { pList.splice(i, 1); break; } } for (let i = 0; i < table.rows.length; i++) { if (table.rows[i].id === id) { table.deleteRow(i); break; } } participantListUpdate(pList); }; const participantListRoleUpdate = (type, id) => { const select = document.querySelector('#participant_list_tr_' + type + '-' + id + ' .participant-select'); const pList = getParticipantList(); for (let i = 0; i < pList.length; i++) { if (pList[i].selectiontype === type && String(pList[i].selectionid) === String(id)) { pList[i].role = select.value; } } participantListUpdate(pList); }; const participantAddToForm = async(selectionTypeValue, selectionValue, selectedRole, canRemove) => { const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData); const [shost, spresenter, sparticipant] = await Promise.all([ Str.get_string('role_host', 'meetletacbfree'), Str.get_string('role_presenter', 'meetletacbfree'), Str.get_string('role_participant', 'meetletacbfree'), ]); const roles = { host: {id: 'host', label: shost}, presenter: {id: 'presenter', label: spresenter}, participant: {id: 'participant', label: sparticipant}, }; if (roles[selectedRole]) { roles[selectedRole].isselected = true; } else { roles.participant.isselected = true; } try { const listTable = document.querySelector('#participant_list_table tbody'); const templateContext = { selectiontypevalue: selectionTypeValue, selectionvalue: selectionValue, participanttype: participantData[selectionTypeValue].name, participantvalue: (selectionTypeValue !== 'all') ? participantData[selectionTypeValue].children[selectionValue].name : null, roles: Object.values(roles), canRemove: canRemove, }; const {html, js} = await Templates.renderForPromise('mod_meetletacbfree/participant_form_add', templateContext); const newNode = Templates.appendNodeContents(listTable, html, js)[0]; newNode.querySelector('.participant-select').addEventListener('change', () => { participantListRoleUpdate(selectionTypeValue, selectionValue); }); const removeNode = newNode.querySelector('.remove-button'); if (removeNode) { removeNode.addEventListener('click', (e) => { e.preventDefault(); participantRemove(selectionTypeValue, selectionValue); }); } } catch (e) { Notification.exception(e); } }; const participantAddFromCurrentSelection = () => { const selectionType = ELEMENT_SELECTOR.participantSelectionType(); const selection = ELEMENT_SELECTOR.participantSelection(); const pList = getParticipantList(); for (let i = 0; i < pList.length; i++) { if (pList[i].selectiontype === selectionType.value && String(pList[i].selectionid) === String(selection.value)) { return; } } const role = 'participant'; pList.push({ selectiontype: selectionType.value, selectionid: selection.value, role: role, }); participantAddToForm(selectionType.value, selection.value, role, true); participantListUpdate(pList); }; const updateSelectionFromType = (currentTypeSelect) => { const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData); const participantSelect = ELEMENT_SELECTOR.participantSelection(); while (participantSelect.firstChild) { participantSelect.removeChild(participantSelect.firstChild); } if (currentTypeSelect.value === 'all') { const option = document.createElement('option'); option.text = '---------------'; option.value = 'all'; participantSelect.add(option); return; } const options = Object.values(participantData[currentTypeSelect.value].children || {}); options.forEach((opt) => { const option = document.createElement('option'); option.text = opt.name; option.value = opt.id; participantSelect.add(option); }); }; const participantListInit = () => { const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData); getParticipantList().forEach((participant) => { if (participant.selectiontype === 'all' || (participantData[participant.selectiontype] && typeof participantData[participant.selectiontype].children[participant.selectionid] !== 'undefined')) { participantAddToForm(participant.selectiontype, participant.selectionid, participant.role, true); } }); }; const syncAccessMatrix = () => { // Moodle advcheckbox renders a hidden input with the same name first — use the real checkbox. const override = document.querySelector('#id_featureaccess_override') || document.querySelector('input[type="checkbox"][name="featureaccess_override"]'); const matrix = document.querySelector('.meetlet-access-matrix'); if (!override || !matrix) { return; } const enabled = !!override.checked; matrix.classList.toggle('is-disabled', !enabled); matrix.querySelectorAll('input.meetlet-access-check').forEach((el) => { el.disabled = !enabled; }); matrix.querySelectorAll('.meetlet-access-toggle').forEach((el) => { el.disabled = !enabled; }); }; const accessOverride = document.querySelector('#id_featureaccess_override') || document.querySelector('input[type="checkbox"][name="featureaccess_override"]'); if (accessOverride) { accessOverride.addEventListener('change', syncAccessMatrix); accessOverride.addEventListener('click', syncAccessMatrix); syncAccessMatrix(); } const setDatetimeFields = (basename, unix) => { if (!unix) { return; } const d = new Date(unix * 1000); const set = (suffix, value) => { const el = document.querySelector('[name="' + basename + '[' + suffix + ']"]'); if (el) { el.value = String(value); el.dispatchEvent(new Event('change', {bubbles: true})); } }; set('year', d.getFullYear()); set('month', d.getMonth() + 1); set('day', d.getDate()); set('hour', d.getHours()); set('minute', d.getMinutes()); }; const applyExistingMeeting = () => { const picker = document.querySelector('[data-meeting-picker]'); const metaEl = document.querySelector('[data-meetings-meta]'); const scoInput = document.querySelector('#id_existingscoid') || document.querySelector('input[name="existingscoid"]'); const linkmode = document.querySelector('#id_linkmode') || document.querySelector('[name="linkmode"]'); const nameInput = document.querySelector('#id_name') || document.querySelector('[name="name"]'); if (!picker || !scoInput || !linkmode) { return; } const toggle = picker.querySelector('[data-meeting-toggle]'); const panel = picker.querySelector('[data-meeting-panel]'); const searchInput = picker.querySelector('[data-meeting-search]'); const listEl = picker.querySelector('[data-meeting-list]'); const hint = picker.querySelector('[data-meeting-hint]'); const labelEl = picker.querySelector('[data-meeting-label]'); const readonly = picker.getAttribute('data-readonly') === '1'; const strChoose = picker.getAttribute('data-str-choose') || '---'; const strLoading = picker.getAttribute('data-str-loading') || '...'; const strEmpty = picker.getAttribute('data-str-empty') || ''; const courseid = parseInt(picker.getAttribute('data-courseid') || '0', 10); let meta = {}; try { meta = JSON.parse((metaEl && metaEl.getAttribute('data-meetings-meta')) || '{}'); } catch (e) { meta = {}; } let loadedOnce = false; let searchTimer = null; let requestSeq = 0; const setOpen = (open) => { if (!panel || !toggle || readonly) { return; } if (open) { panel.removeAttribute('hidden'); toggle.setAttribute('aria-expanded', 'true'); picker.classList.add('is-open'); if (searchInput) { searchInput.focus(); } } else { panel.setAttribute('hidden', 'hidden'); toggle.setAttribute('aria-expanded', 'false'); picker.classList.remove('is-open'); } }; const applySelected = (scoid) => { const info = meta[String(scoid)]; if (!info || !nameInput || String(linkmode.value) !== '1') { return; } if (info.name) { nameInput.value = info.name; nameInput.dispatchEvent(new Event('input', {bubbles: true})); nameInput.dispatchEvent(new Event('change', {bubbles: true})); } if (info.starttime) { setDatetimeFields('starttime', info.starttime); } if (info.endtime) { const endEnable = document.querySelector('[name="endtimeenable"]'); if (endEnable && !endEnable.checked) { endEnable.checked = true; endEnable.dispatchEvent(new Event('change', {bubbles: true})); } setDatetimeFields('endtime', info.endtime); } }; const selectMeeting = (m) => { const scoid = String(m.scoid || '0'); scoInput.value = scoid; meta[scoid] = { name: m.name || '', urlpath: m.urlpath || '', starttime: m.starttime || 0, endtime: m.endtime || 0, }; if (metaEl) { metaEl.setAttribute('data-meetings-meta', JSON.stringify(meta)); } if (labelEl) { labelEl.textContent = m.label || m.name || strChoose; } if (listEl) { listEl.querySelectorAll('.meetlet-combobox__option').forEach((btn) => { btn.classList.toggle('is-selected', btn.getAttribute('data-scoid') === scoid); }); } setOpen(false); applySelected(scoid); }; const renderList = (meetings) => { if (!listEl) { return; } const selected = String(scoInput.value || '0'); listEl.innerHTML = ''; const rows = meetings || []; if (!rows.length) { return; } rows.forEach((m) => { const scoid = String(m.scoid); meta[scoid] = { name: m.name || '', urlpath: m.urlpath || '', starttime: m.starttime || 0, endtime: m.endtime || 0, }; const li = document.createElement('li'); const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'meetlet-combobox__option' + (scoid === selected ? ' is-selected' : ''); btn.setAttribute('data-scoid', scoid); btn.textContent = m.label || m.name || scoid; btn.addEventListener('click', (e) => { e.preventDefault(); selectMeeting(m); }); li.appendChild(btn); listEl.appendChild(li); }); if (metaEl) { metaEl.setAttribute('data-meetings-meta', JSON.stringify(meta)); } }; const fetchMeetings = (query) => { if (String(linkmode.value) !== '1' || readonly) { return Promise.resolve(); } const seq = ++requestSeq; if (hint) { hint.textContent = strLoading; } return Ajax.call([{ methodname: 'mod_meetletacbfree_search_server_meetings', args: { query: query || '', limit: 10, courseid: courseid, }, }])[0].then((res) => { if (seq !== requestSeq) { return null; } const meetings = (res && res.meetings) ? res.meetings : []; renderList(meetings); if (hint) { hint.textContent = meetings.length ? '' : strEmpty; } loadedOnce = true; return res; }).catch((err) => { if (hint) { hint.textContent = (err && err.message) ? err.message : strEmpty; } Notification.exception(err); }); }; if (toggle) { toggle.addEventListener('click', (e) => { e.preventDefault(); if (readonly || String(linkmode.value) !== '1') { return; } const willOpen = panel.hasAttribute('hidden'); setOpen(willOpen); if (willOpen) { fetchMeetings(searchInput ? String(searchInput.value || '').trim() : ''); } }); } if (searchInput) { searchInput.addEventListener('input', () => { window.clearTimeout(searchTimer); searchTimer = window.setTimeout(() => { fetchMeetings(String(searchInput.value || '').trim()); }, 350); }); } document.addEventListener('click', (e) => { if (!picker.contains(e.target)) { setOpen(false); } }); linkmode.addEventListener('change', () => { if (String(linkmode.value) === '1') { loadedOnce = false; if (!readonly) { setOpen(true); fetchMeetings(''); } } else { setOpen(false); scoInput.value = '0'; if (labelEl) { labelEl.textContent = strChoose; } } }); if (String(linkmode.value) === '1' && !readonly) { // Prefetch quietly so first open is fast; do not force panel open on edit. fetchMeetings(''); } }; applyExistingMeeting(); const syncSettingOverrideInputs = () => { document.querySelectorAll('[data-meetlet-setting-radio]').forEach((input) => { if (input.getAttribute('data-meetlet-setting-bound') === '1') { return; } input.setAttribute('data-meetlet-setting-bound', '1'); input.addEventListener('change', () => { const baseName = input.name.replace(/_state$/, ''); const hidden = document.querySelector('input[type="hidden"][name="' + baseName + '"]'); if (hidden && input.checked) { if (baseName === 'participantsmode') { hidden.value = input.value; } else { hidden.value = input.value === '2' ? '1' : '0'; } } syncParticipantOverride(); }); }); }; const participantModeInput = () => document.querySelector('input[name="participantsmode_state"]:checked'); const syncParticipantOverride = () => { const mode = participantModeInput(); const enabled = !mode || mode.value === '1'; const container = document.querySelector('#fitem_id_static_participant_list'); if (container) { container.classList.toggle('is-disabled', !enabled); } const participantForm = document.querySelector('.meetlet-participant-form') || container; if (participantForm) { participantForm.querySelectorAll('select, button, input.participant-select').forEach((el) => { el.disabled = !enabled; }); } const table = ELEMENT_SELECTOR.participantTable(); if (table) { table.querySelectorAll('select, button').forEach((el) => { el.disabled = !enabled; }); } }; syncSettingOverrideInputs(); syncParticipantOverride(); const typeSelect = ELEMENT_SELECTOR.participantSelectionType(); const addButton = ELEMENT_SELECTOR.participantAddButton(); if (!typeSelect || !addButton || !ELEMENT_SELECTOR.participantData()) { return; } typeSelect.addEventListener('change', (e) => updateSelectionFromType(e.target)); addButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); participantAddFromCurrentSelection(); }); document.querySelectorAll('input[name="participantsmode_state"]').forEach((input) => { input.addEventListener('change', syncParticipantOverride); }); updateSelectionFromType(typeSelect); participantListInit(); });