MDL-79769 login: Fix focus loss of loginpageautofocus setting

Added new form event to fix login fields focus loss when DOM is
manipulated by togglesensitive.
This commit is contained in:
David Woloszyn
2024-03-08 15:54:37 +11:00
parent cb6acfc46a
commit cd935eb3e2
7 changed files with 48 additions and 11 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
define("core/togglesensitive",["exports","core/pagehelpers","core/templates","core/pending","core/prefetch","core/notification"],(function(_exports,_pagehelpers,_templates,_pending,_prefetch,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
define("core/togglesensitive",["exports","core/pagehelpers","core/templates","core/pending","core/prefetch","core/notification","core_form/events"],(function(_exports,_pagehelpers,_templates,_pending,_prefetch,_notification,_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* 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
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending),_prefetch=_interopRequireDefault(_prefetch),_notification=_interopRequireDefault(_notification);const SELECTORS_BUTTON=".toggle-sensitive-btn",PIX_EYE="t/hide",PIX_EYE_SLASH="t/show";let sensitiveElementId,smallScreensOnly;_exports.init=function(elementId){let isSmallScreensOnly=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const sensitiveInput=document.getElementById(elementId);null!==sensitiveInput&&(sensitiveElementId=elementId,smallScreensOnly=isSmallScreensOnly,_prefetch.default.prefetchTemplate("core/form_input_toggle_sensitive"),renderSensitiveToggle(sensitiveInput).then((()=>{})).catch(_notification.default.exception),registerListenerEvents())};const renderSensitiveToggle=async sensitiveInput=>{const{html:html}=await _templates.default.renderForPromise("core/form_input_toggle_sensitive",{smallscreensonly:smallScreensOnly,sensitiveinput:sensitiveInput.outerHTML});sensitiveInput.outerHTML=html},registerListenerEvents=()=>{document.addEventListener("click",handleButtonInteraction),smallScreensOnly&&window.addEventListener("resize",handleScreenResizing)},handleButtonInteraction=event=>{const toggleButton=event.target.closest(SELECTORS_BUTTON);if(toggleButton){const sensitiveInput=document.getElementById(sensitiveElementId);toggleSensitiveVisibility(sensitiveInput,toggleButton)}},handleScreenResizing=()=>{if(!(0,_pagehelpers.isExtraSmall)()){const sensitiveInput=document.getElementById(sensitiveElementId),toggleButton=sensitiveInput.parentNode.querySelector(SELECTORS_BUTTON);toggleSensitiveVisibility(sensitiveInput,toggleButton,!0)}},toggleSensitiveVisibility=function(sensitiveInput,toggleButton){let force=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const pendingPromise=new _pending.default("core/togglesensitive:toggle");let type,icon;!0===force?(type="password",icon=PIX_EYE):(type="password"===sensitiveInput.getAttribute("type")?"text":"password",icon="password"===sensitiveInput.getAttribute("type")?PIX_EYE_SLASH:PIX_EYE),sensitiveInput.setAttribute("type",type),_templates.default.renderPix(icon,"core").then((icon=>{toggleButton.innerHTML=icon,pendingPromise.resolve()})).catch(_notification.default.exception)}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending),_prefetch=_interopRequireDefault(_prefetch),_notification=_interopRequireDefault(_notification);const SELECTORS_BUTTON=".toggle-sensitive-btn",PIX_EYE="t/hide",PIX_EYE_SLASH="t/show";let sensitiveElementId,smallScreensOnly;_exports.init=function(elementId){let isSmallScreensOnly=arguments.length>1&&void 0!==arguments[1]&&arguments[1];const sensitiveInput=document.getElementById(elementId);null!==sensitiveInput&&(sensitiveElementId=elementId,smallScreensOnly=isSmallScreensOnly,_prefetch.default.prefetchTemplate("core/form_input_toggle_sensitive"),renderSensitiveToggle(sensitiveInput).then((()=>{(0,_events.notifyFieldStructureChanged)(sensitiveInput.id)})).catch(_notification.default.exception),registerListenerEvents())};const renderSensitiveToggle=async sensitiveInput=>{const{html:html}=await _templates.default.renderForPromise("core/form_input_toggle_sensitive",{smallscreensonly:smallScreensOnly,sensitiveinput:sensitiveInput.outerHTML});sensitiveInput.outerHTML=html},registerListenerEvents=()=>{document.addEventListener("click",handleButtonInteraction),smallScreensOnly&&window.addEventListener("resize",handleScreenResizing)},handleButtonInteraction=event=>{const toggleButton=event.target.closest(SELECTORS_BUTTON);if(toggleButton){const sensitiveInput=document.getElementById(sensitiveElementId);toggleSensitiveVisibility(sensitiveInput,toggleButton)}},handleScreenResizing=()=>{if(!(0,_pagehelpers.isExtraSmall)()){const sensitiveInput=document.getElementById(sensitiveElementId),toggleButton=sensitiveInput.parentNode.querySelector(SELECTORS_BUTTON);toggleSensitiveVisibility(sensitiveInput,toggleButton,!0)}},toggleSensitiveVisibility=function(sensitiveInput,toggleButton){let force=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const pendingPromise=new _pending.default("core/togglesensitive:toggle");let type,icon;!0===force?(type="password",icon=PIX_EYE):(type="password"===sensitiveInput.getAttribute("type")?"text":"password",icon="password"===sensitiveInput.getAttribute("type")?PIX_EYE_SLASH:PIX_EYE),sensitiveInput.setAttribute("type",type),_templates.default.renderPix(icon,"core").then((icon=>{toggleButton.innerHTML=icon,pendingPromise.resolve()})).catch(_notification.default.exception)}}));
//# sourceMappingURL=togglesensitive.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -26,6 +26,7 @@ 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',
@@ -58,6 +59,8 @@ export const init = (elementId, isSmallScreensOnly = false) => {
Prefetch.prefetchTemplate('core/form_input_toggle_sensitive');
// Render the sensitive input with a toggle button.
renderSensitiveToggle(sensitiveInput).then(() => {
// Dispatch the event indicating the sensitive input has changed.
notifyFieldStructureChanged(sensitiveInput.id);
return;
}).catch(Notification.exception);
// Register event listeners.
+1 -1
View File
@@ -14,6 +14,6 @@ define("core_form/events",["exports","core/str","core/event_dispatcher","jquery"
* window.console.log(e.target); // The form that was submitted.
* window.console.log(e.detail.skipValidation); // Whether form validation was skipped.
* });
*/let changesMadeString;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.types=_exports.triggerUploadStarted=_exports.triggerUploadCompleted=_exports.notifyUploadStarted=_exports.notifyUploadCompleted=_exports.notifyUploadChanged=_exports.notifyFormSubmittedByJavascript=_exports.notifyFormError=_exports.notifyFieldValidationFailure=_exports.eventTypes=void 0,_jquery=_interopRequireDefault(_jquery),_yui=_interopRequireDefault(_yui);const changesMadeCheck=e=>{e&&(e.returnValue=changesMadeString)},eventTypes={formError:"core_form/error",formSubmittedByJavascript:"core_form/submittedByJavascript",formFieldValidationFailed:"core_form/fieldValidationFailed",uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted",uploadChanged:"core_form/uploadChanged"};_exports.eventTypes=eventTypes;_exports.notifyFormError=field=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formError,{},field);_exports.notifyFormSubmittedByJavascript=function(form){let skipValidation=arguments.length>1&&void 0!==arguments[1]&&arguments[1],fallbackHandled=arguments.length>2&&void 0!==arguments[2]&&arguments[2];skipValidation&&(window.skipClientValidation=!0);const customEvent=(0,_event_dispatcher.dispatchEvent)(eventTypes.formSubmittedByJavascript,{skipValidation:skipValidation,fallbackHandled:fallbackHandled},form);return skipValidation&&(window.skipClientValidation=!1),customEvent};_exports.notifyFieldValidationFailure=(field,message)=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formFieldValidationFailed,{message:message},field,{cancelable:!0});const notifyUploadStarted=async elementId=>(changesMadeString=await(0,_str.getString)("changesmadereallygoaway","moodle"),window.addEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadStarted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadStarted=notifyUploadStarted;const notifyUploadCompleted=elementId=>(window.removeEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadCompleted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadCompleted=notifyUploadCompleted;const triggerUploadStarted=notifyUploadStarted;_exports.triggerUploadStarted=triggerUploadStarted;const triggerUploadCompleted=notifyUploadCompleted;_exports.triggerUploadCompleted=triggerUploadCompleted;_exports.types={uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted"};let legacyEventsRegistered=!1;legacyEventsRegistered||(_yui.default.use("event","moodle-core-event",(()=>{document.addEventListener(eventTypes.formError,(e=>{const element=_yui.default.one(e.target),formElement=_yui.default.one(e.target.closest("form"));_yui.default.Global.fire(M.core.globalEvents.FORM_ERROR,{formid:formElement.generateID(),elementid:element.generateID()})})),document.addEventListener(eventTypes.formSubmittedByJavascript,(e=>{if(e.detail.fallbackHandled)return;e.skipValidation&&(window.skipClientValidation=!0);const form=_yui.default.one(e.target);form.fire(M.core.event.FORM_SUBMIT_AJAX,{currentTarget:form,fallbackHandled:!0}),e.skipValidation&&(window.skipClientValidation=!1)}))})),document.addEventListener(eventTypes.formFieldValidationFailed,(e=>{const legacyEvent=_jquery.default.Event("core_form-field-validation");(0,_jquery.default)(e.target).trigger(legacyEvent,e.detail.message)})),legacyEventsRegistered=!0);_exports.notifyUploadChanged=elementId=>(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadChanged,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1})}));
*/let changesMadeString;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.types=_exports.triggerUploadStarted=_exports.triggerUploadCompleted=_exports.notifyUploadStarted=_exports.notifyUploadCompleted=_exports.notifyUploadChanged=_exports.notifyFormSubmittedByJavascript=_exports.notifyFormError=_exports.notifyFieldValidationFailure=_exports.notifyFieldStructureChanged=_exports.eventTypes=void 0,_jquery=_interopRequireDefault(_jquery),_yui=_interopRequireDefault(_yui);const changesMadeCheck=e=>{e&&(e.returnValue=changesMadeString)},eventTypes={formError:"core_form/error",formSubmittedByJavascript:"core_form/submittedByJavascript",formFieldValidationFailed:"core_form/fieldValidationFailed",uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted",uploadChanged:"core_form/uploadChanged",fieldStructureChanged:"core_form/fieldStructureChanged"};_exports.eventTypes=eventTypes;_exports.notifyFormError=field=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formError,{},field);_exports.notifyFormSubmittedByJavascript=function(form){let skipValidation=arguments.length>1&&void 0!==arguments[1]&&arguments[1],fallbackHandled=arguments.length>2&&void 0!==arguments[2]&&arguments[2];skipValidation&&(window.skipClientValidation=!0);const customEvent=(0,_event_dispatcher.dispatchEvent)(eventTypes.formSubmittedByJavascript,{skipValidation:skipValidation,fallbackHandled:fallbackHandled},form);return skipValidation&&(window.skipClientValidation=!1),customEvent};_exports.notifyFieldValidationFailure=(field,message)=>(0,_event_dispatcher.dispatchEvent)(eventTypes.formFieldValidationFailed,{message:message},field,{cancelable:!0});const notifyUploadStarted=async elementId=>(changesMadeString=await(0,_str.getString)("changesmadereallygoaway","moodle"),window.addEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadStarted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadStarted=notifyUploadStarted;const notifyUploadCompleted=elementId=>(window.removeEventListener("beforeunload",changesMadeCheck),(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadCompleted,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1}));_exports.notifyUploadCompleted=notifyUploadCompleted;const triggerUploadStarted=notifyUploadStarted;_exports.triggerUploadStarted=triggerUploadStarted;const triggerUploadCompleted=notifyUploadCompleted;_exports.triggerUploadCompleted=triggerUploadCompleted;_exports.types={uploadStarted:"core_form/uploadStarted",uploadCompleted:"core_form/uploadCompleted"};let legacyEventsRegistered=!1;legacyEventsRegistered||(_yui.default.use("event","moodle-core-event",(()=>{document.addEventListener(eventTypes.formError,(e=>{const element=_yui.default.one(e.target),formElement=_yui.default.one(e.target.closest("form"));_yui.default.Global.fire(M.core.globalEvents.FORM_ERROR,{formid:formElement.generateID(),elementid:element.generateID()})})),document.addEventListener(eventTypes.formSubmittedByJavascript,(e=>{if(e.detail.fallbackHandled)return;e.skipValidation&&(window.skipClientValidation=!0);const form=_yui.default.one(e.target);form.fire(M.core.event.FORM_SUBMIT_AJAX,{currentTarget:form,fallbackHandled:!0}),e.skipValidation&&(window.skipClientValidation=!1)}))})),document.addEventListener(eventTypes.formFieldValidationFailed,(e=>{const legacyEvent=_jquery.default.Event("core_form-field-validation");(0,_jquery.default)(e.target).trigger(legacyEvent,e.detail.message)})),legacyEventsRegistered=!0);_exports.notifyUploadChanged=elementId=>(0,_event_dispatcher.dispatchEvent)(eventTypes.uploadChanged,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1});_exports.notifyFieldStructureChanged=elementId=>(0,_event_dispatcher.dispatchEvent)(eventTypes.fieldStructureChanged,{},document.getElementById(elementId),{bubbles:!0,cancellable:!1})}));
//# sourceMappingURL=events.min.js.map
File diff suppressed because one or more lines are too long
+28
View File
@@ -53,6 +53,7 @@ const changesMadeCheck = e => {
* @property {String} formFieldValidationFailed See {@link event:core_form/fieldValidationFailed}
* @property {String} formSubmittedByJavascript See {@link event:core_form/submittedByJavascript}
* @property {String} uploadChanged See {@link event:core_form/uploadChanged}
* @property {String} fieldStructureChanged See {@link event:core_form/fieldStructureChanged}
*/
export const eventTypes = {
/**
@@ -113,6 +114,15 @@ export const eventTypes = {
* @property {HTMLElement} target The form field which was changed
*/
uploadChanged: 'core_form/uploadChanged',
/**
* An event triggered when a form field structure has changed.
*
* @event core_form/fieldStructureChanged
* @type {CustomEvent}
* @property {HTMLElement} target The form field that has changed
*/
fieldStructureChanged: 'core_form/fieldStructureChanged',
};
// These are only imported for legacy.
@@ -337,3 +347,21 @@ export const notifyUploadChanged = elementId => dispatchEvent(
cancellable: false,
}
);
/**
* Trigger an event to notify the field structure has changed.
*
* @method
* @param {string} elementId The element which was changed
* @returns {CustomEvent}
* @fires fieldStructureChanged
*/
export const notifyFieldStructureChanged = elementId => dispatchEvent(
eventTypes.fieldStructureChanged,
{},
document.getElementById(elementId),
{
bubbles: true,
cancellable: false,
}
);
+12 -6
View File
@@ -222,12 +222,18 @@
});
{{/togglepassword}}
{{#autofocusform}}
var userNameField = document.getElementById('username');
if (userNameField.value.length == 0) {
userNameField.focus();
} else {
document.getElementById('password').focus();
}
require(['core_form/events'], function(FormEvent) {
function autoFocus() {
const userNameField = document.getElementById('username');
if (userNameField.value.length == 0) {
userNameField.focus();
} else {
document.getElementById('password').focus();
}
}
autoFocus();
window.addEventListener(FormEvent.eventTypes.fieldStructureChanged, autoFocus);
});
{{/autofocusform}}
{{/error}}
{{#error}}