// 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/>.
/**
* JS module for toggling the sensitive input visibility (e.g. passwords, keys).
*
* @module core/togglesensitive
* @copyright 2023 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {isExtraSmall} from 'core/pagehelpers';
import Templates from 'core/templates';
import Pending from 'core/pending';
import Prefetch from 'core/prefetch';
import Notification from 'core/notification';
import {notifyFieldStructureChanged} from 'core_form/events';
const SELECTORS = {
BUTTON: '.toggle-sensitive-btn',
ICON: '.toggle-sensitive-btn .icon',
};
const PIX = {
EYE: 't/hide',
EYE_SLASH: 't/show',
};
let sensitiveElementId;
let smallScreensOnly;
/**
* Entrypoint of the js.
*
* @method init
* @param {String} elementId Form button element.
* @param {boolean} isSmallScreensOnly Is this for small screens only?
*/
export const init = (elementId, isSmallScreensOnly = false) => {
const sensitiveInput = document.getElementById(elementId);
if (sensitiveInput === null) {
// Exit early if invalid element id passed.
return;
}
sensitiveElementId = elementId;
smallScreensOnly = isSmallScreensOnly;
Prefetch.prefetchTemplate('core/form_input_toggle_sensitive');
// Render the sensitive input with a toggle button.
renderSensitiveToggle(sensitiveInput);
// Register event listeners.
registerListenerEvents();
};
/**
* Render the new input html with toggle button and update the incoming html.
*
* @method renderSensitiveToggle
* @param {HTMLElement} sensitiveInput HTML element for the sensitive input.
*/
const renderSensitiveToggle = (sensitiveInput) => {
Templates.render(
'core/form_input_toggle_sensitive',
{
smallscreensonly: smallScreensOnly,
sensitiveinput: sensitiveInput.outerHTML,
}
).then((html) => {
sensitiveInput.outerHTML = html;
// Dispatch the event indicating the sensitive input has changed.
notifyFieldStructureChanged(sensitiveInput.id);
return;
}).catch(Notification.exception);
};
/**
* Register event listeners.
*
* @method registerListenerEvents
*/
const registerListenerEvents = () => {
// Toggle the sensitive input visibility when interacting with the toggle button.
document.addEventListener('click', handleButtonInteraction);
// For small screens only, hide all sensitive inputs when the screen is enlarged.
if (smallScreensOnly) {
window.addEventListener('resize', handleScreenResizing);
}
};
/**
* Handle events trigger by interacting with the toggle button.
*
* @method handleButtonInteraction
* @param {Event} event The button event.
*/
const handleButtonInteraction = (event) => {
const toggleButton = event.target.closest(SELECTORS.BUTTON);
if (toggleButton) {
const sensitiveInput = document.getElementById(sensitiveElementId);
if (sensitiveInput) {
toggleSensitiveVisibility(sensitiveInput, toggleButton);
}
}
};
/**
* Handle events trigger by resizing the screen.
*
* @method handleScreenResizing
*/
const handleScreenResizing = () => {
if (!isExtraSmall()) {
const sensitiveInput = document.getElementById(sensitiveElementId);
if (sensitiveInput) {
const toggleButton = sensitiveInput.parentNode.querySelector(SELECTORS.BUTTON);
if (toggleButton) {
toggleSensitiveVisibility(sensitiveInput, toggleButton, true);
}
}
}
};
/**
* Toggle the sensitive input visibility and its associated icon.
*
* @method toggleSensitiveVisibility
* @param {HTMLInputElement} sensitiveInput The sensitive input element.
* @param {HTMLElement} toggleButton The toggle button.
* @param {boolean} force Force the input back to password type.
*/
const toggleSensitiveVisibility = (sensitiveInput, toggleButton, force = false) => {
const pendingPromise = new Pending('core/togglesensitive:toggle');
let type;
let icon;
if (force === true) {
type = 'password';
icon = PIX.EYE;
} else {
type = sensitiveInput.getAttribute('type') === 'password' ? 'text' : 'password';
icon = sensitiveInput.getAttribute('type') === 'password' ? PIX.EYE_SLASH : PIX.EYE;
}
sensitiveInput.setAttribute('type', type);
Templates.renderPix(icon, 'core').then((icon) => {
toggleButton.innerHTML = icon;
pendingPromise.resolve();
return;
}).catch(Notification.exception);
};