MDL-69918 core: Rewrite Form Change Checker a ES6
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
define ("core_form/changechecker",["exports","core_editor/events","core/str"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.markFormAsDirtyById=a.resetFormDirtyStateById=a.watchFormById=a.startWatching=a.markAllFormsSubmitted=a.markFormSubmitted=a.markFormChangedFromNode=a.disableAllChecks=a.markFormAsDirty=a.markAllFormsAsDirty=a.resetFormDirtyState=a.resetAllFormDirtyStates=a.unWatchForm=a.watchForm=void 0;var d,e=[],f=!1,g=function(a){return a.closest("form")},h=function(a){a=g(a);if(!a){return}if(q(a)){return}e.push(a)};a.watchForm=h;var i=function(a){e=e.filter(function(b){return!!b.contains(a)})};a.unWatchForm=i;var j=function(){e.forEach(function(a){a.dataset.formSubmitted="false";a.dataset.formDirty="false"})};a.resetAllFormDirtyStates=j;var k=function(a){a=g(a);if(!a){return}a.dataset.formSubmitted="false";a.dataset.formDirty="false"};a.resetFormDirtyState=k;var l=function(){e.forEach(function(a){a.dataset.formDirty="true"})};a.markAllFormsAsDirty=l;var m=function(a){a=g(a);if(!a){return}a.dataset.formDirty="true"};a.markFormAsDirty=m;var n=function(){f=!0};a.disableAllChecks=n;var o=function(){if(f){return!1}var a=e.some(function(a){return"true"===a.dataset.formSubmitted});if(a){return!1}var b=e.some(function(a){if(!a.isConnected){return!1}if("true"===a.dataset.formDirty){return!0}if(document.activeElement&&document.activeElement.dataset.propertyIsEnumerable("initialValue")){var b=q(document.activeElement),c=document.activeElement.dataset.initialValue!==document.activeElement.value;if(b&&c){return!0}}return!1});if(b){return!0}if("undefined"!=typeof window.tinyMCE){if(window.tinyMCE.editors.some(function(a){return a.isDirty()})){return!0}}return!1},p=function(a){return e.find(function(b){return b.contains(a)})},q=function(a){return e.some(function(b){return b.contains(a)})},r=function(a){return!!a.closest(".ignoredirty")},s=function(a){if(a.dataset.formChangeCheckerOverride){n();return}if(!q(a)){return}if(r(a)){return}var b=p(a);b.dataset.formDirty="true"};a.markFormChangedFromNode=s;var t=function(a){a=g(a);if(!a){return}a.dataset.formSubmitted="true"};a.markFormSubmitted=t;var u=function(){e.forEach(function(a){return t(a)})};a.markAllFormsSubmitted=u;var v=function(a){var b=o()&&!M.cfg.behatsiterunning;if(b){a.preventDefault();a.returnValue=d;return a.returnValue}window.removeEventListener("beforeunload",v);return null},w=function(){x();document.addEventListener("change",function(a){if(!q(a.target)){return}s(a.target)});document.addEventListener("click",function(a){var b=a.target.closest("[data-formchangechecker-ignore-submit]");if(!b){return}var c=g(a.target);if(c){c.dataset.ignoreSubmission="true"}});document.addEventListener("focusin",function(a){if(a.target.matches("input, textarea, select")){if(a.target.dataset.propertyIsEnumerable("initialValue")){return}a.target.dataset.initialValue=a.target.value}});document.addEventListener("submit",function(a){var b=g(a.target);if(!b){return}if(b.dataset.ignoreSubmission){b.dataset.ignoreSubmission="false";return}t(b)});document.addEventListener(b.eventTypes.editorContentRestored,function(a){if(a.target!=document){k(a.target)}else{j()}});(0,c.get_string)("changesmadereallygoaway","moodle").then(function(a){d=a}).catch();window.addEventListener("beforeunload",v)};a.startWatching=w;var x=function(){var a=function(a,b,c){return function(){window.console.warn("The moodle-core-formchangechecker has been deprecated and replaced with core_form/changechecker. "+"The ".concat(a," function has been replaced with ").concat(b,"."));c.apply(void 0,arguments)}};window.M.core_formchangechecker={init:a("init","watchFormById",y),reset_form_dirty_state:a("reset_form_dirty_state","resetFormDirtyState",j),set_form_changed:a("set_form_changed","markFormAsDirty",l),set_form_submitted:a("set_form_submitted","markFormSubmitted",markAllFormsAsSubmitted)}},y=function(a){h(document.getElementById(a))};a.watchFormById=y;a.resetFormDirtyStateById=function resetFormDirtyStateById(a){k(document.getElementById(a))};a.markFormAsDirtyById=function markFormAsDirtyById(a){m(document.getElementById(a))};w()});
|
||||
//# sourceMappingURL=changechecker.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,546 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* This module provides change detection to forms, allowing a browser to warn the user before navigating away if changes
|
||||
* have been made.
|
||||
*
|
||||
* Two flags are stored for each form:
|
||||
* * a 'dirty' flag; and
|
||||
* * a 'submitted' flag.
|
||||
*
|
||||
* When the page is unloaded each watched form is checked. If the 'dirty' flag is set for any form, and the 'submitted'
|
||||
* flag is not set for any form, then a warning is shown.
|
||||
*
|
||||
* The 'dirty' flag is set when any form element is modified within a watched form.
|
||||
* The flag can also be set programatically. This may be required for custom form elements.
|
||||
*
|
||||
* It is not possible to customise the warning message in any modern browser.
|
||||
*
|
||||
* Please note that some browsers have controls on when these alerts may or may not be shown.
|
||||
* See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload} for browser-specific
|
||||
* notes and references.
|
||||
*
|
||||
* @module core_form/changechecker
|
||||
* @copyright 2021 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @example <caption>Usage where the FormElement is already held</caption>
|
||||
*
|
||||
* import {watchForm} from 'core_form/changechecker';
|
||||
*
|
||||
* // Fetch the form element somehow.
|
||||
* watchForm(formElement);
|
||||
*
|
||||
* @example <caption>Usage from the child of a form - i.e. an input, button, div, etc.</caption>
|
||||
*
|
||||
* import {watchForm} from 'core_form/changechecker';
|
||||
*
|
||||
* // Watch the form by using a child of it.
|
||||
* watchForm(document.querySelector('input[data-foo="bar"]'););
|
||||
*
|
||||
* @example <caption>Usage from within a template</caption>
|
||||
* <form id="mod_example-entry-{{uniqid}}" ...>
|
||||
* <!--
|
||||
*
|
||||
* -->
|
||||
* </form>
|
||||
* {{#js}}
|
||||
* require(['core_form/changechecker'], function(changeChecker) {
|
||||
* watchFormById('mod_example-entry-{{uniqid}}');
|
||||
* });
|
||||
* {{/js}}
|
||||
*/
|
||||
|
||||
import {eventTypes} from 'core_editor/events';
|
||||
import {get_string as getString} from 'core/str';
|
||||
|
||||
/**
|
||||
* @property {Bool} initialised Whether the change checker has been initialised
|
||||
* @private
|
||||
*/
|
||||
let initialised = false;
|
||||
|
||||
/**
|
||||
* @property {String} warningString The warning string to show on form change failure
|
||||
* @private
|
||||
*/
|
||||
let warningString;
|
||||
|
||||
/**
|
||||
* @property {Array} watchedForms The list of watched forms
|
||||
* @private
|
||||
*/
|
||||
let watchedForms = [];
|
||||
|
||||
/**
|
||||
* @property {Bool} formChangeCheckerDisabled Whether the form change checker has been actively disabled
|
||||
* @private
|
||||
*/
|
||||
let formChangeCheckerDisabled = false;
|
||||
|
||||
/**
|
||||
* Get the nearest form element from a child element.
|
||||
*
|
||||
* @param {HTMLElement} formChild
|
||||
* @returns {HTMLFormElement|null}
|
||||
* @private
|
||||
*/
|
||||
const getFormFromChild = formChild => formChild.closest('form');
|
||||
|
||||
/**
|
||||
* Watch the specified form for changes.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} formNode
|
||||
*/
|
||||
export const watchForm = formNode => {
|
||||
// Normalise the formNode.
|
||||
formNode = getFormFromChild(formNode);
|
||||
|
||||
if (!formNode) {
|
||||
// No form found.
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWatchingForm(formNode)) {
|
||||
// This form is already watched.
|
||||
return;
|
||||
}
|
||||
|
||||
watchedForms.push(formNode);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop watching the specified form for changes.
|
||||
*
|
||||
* If the form was not watched, then no change is made.
|
||||
*
|
||||
* A child of the form may be passed instead.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} formNode
|
||||
* @example <caption>Stop watching a form for changes</caption>
|
||||
* import {unWatchForm} from 'core_form/changechecker';
|
||||
*
|
||||
* // ...
|
||||
* document.addEventListener('click', e => {
|
||||
* if (e.target.closest('[data-action="changePage"]')) {
|
||||
* unWatchForm(e.target);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
export const unWatchForm = formNode => {
|
||||
watchedForms = watchedForms.filter(watchedForm => !!watchedForm.contains(formNode));
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the 'dirty' flag for all watched forms.
|
||||
*
|
||||
* If a form was previously marked as 'dirty', then this flag will be cleared and when the page is unloaded no warning
|
||||
* will be shown.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
export const resetAllFormDirtyStates = () => {
|
||||
watchedForms.forEach(watchedForm => {
|
||||
watchedForm.dataset.formSubmitted = "false";
|
||||
watchedForm.dataset.formDirty = "false";
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the 'dirty' flag of the specified form.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} formNode
|
||||
*/
|
||||
export const resetFormDirtyState = formNode => {
|
||||
formNode = getFormFromChild(formNode);
|
||||
|
||||
if (!formNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
formNode.dataset.formSubmitted = "false";
|
||||
formNode.dataset.formDirty = "false";
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all forms as dirty.
|
||||
*
|
||||
* This function is only for backwards-compliance with the old YUI module and should not be used in any other situation.
|
||||
* It will be removed in Moodle 4.4.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
export const markAllFormsAsDirty = () => {
|
||||
watchedForms.forEach(watchedForm => {
|
||||
watchedForm.dataset.formDirty = "true";
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark a specific form as dirty.
|
||||
*
|
||||
* This behaviour may be required for custom form elements which are not caught by the standard change listeners.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} formNode
|
||||
*/
|
||||
export const markFormAsDirty = formNode => {
|
||||
formNode = getFormFromChild(formNode);
|
||||
|
||||
if (!formNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark it as dirty.
|
||||
formNode.dataset.formDirty = "true";
|
||||
};
|
||||
|
||||
/**
|
||||
* Actively disable the form change checker.
|
||||
*
|
||||
* Please note that it cannot be re-enabled once disabled.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
export const disableAllChecks = () => {
|
||||
formChangeCheckerDisabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether any watched from is dirty.
|
||||
*
|
||||
* @method
|
||||
* @returns {Bool}
|
||||
* @private
|
||||
*/
|
||||
const isAnyWatchedFormDirty = () => {
|
||||
if (formChangeCheckerDisabled) {
|
||||
// The form change checker is disabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasSubmittedForm = watchedForms.some(watchedForm => watchedForm.dataset.formSubmitted === "true");
|
||||
if (hasSubmittedForm) {
|
||||
// Do not warn about submitted forms, ever.
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasDirtyForm = watchedForms.some(watchedForm => {
|
||||
if (!watchedForm.isConnected) {
|
||||
// The watched form is not connected to the DOM.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (watchedForm.dataset.formDirty === "true") {
|
||||
// The form has been marked as dirty.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Elements currently holding focus will not have triggered change detection.
|
||||
// Check whether the value matches the original value upon form load.
|
||||
if (document.activeElement && document.activeElement.dataset.propertyIsEnumerable('initialValue')) {
|
||||
const isActiveElementWatched = isWatchingForm(document.activeElement);
|
||||
const hasValueChanged = document.activeElement.dataset.initialValue !== document.activeElement.value;
|
||||
|
||||
if (isActiveElementWatched && hasValueChanged) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (hasDirtyForm) {
|
||||
// At least one form is dirty.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle TinyMCE editor instances.
|
||||
// TinyMCE forms may not have been initialised at the time that startWatching is called.
|
||||
// Check whether any tinyMCE editor is dirty.
|
||||
if (typeof window.tinyMCE !== 'undefined') {
|
||||
if (window.tinyMCE.editors.some(editor => editor.isDirty())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No dirty forms detected.
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the watched form for the specified target.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLNode} target
|
||||
* @returns {HTMLFormElement}
|
||||
* @private
|
||||
*/
|
||||
const getFormForNode = target => watchedForms.find(watchedForm => watchedForm.contains(target));
|
||||
|
||||
/**
|
||||
* Whether the specified target is a watched form.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLNode} target
|
||||
* @returns {Bool}
|
||||
* @private
|
||||
*/
|
||||
const isWatchingForm = target => watchedForms.some(watchedForm => watchedForm.contains(target));
|
||||
|
||||
/**
|
||||
* Whether the specified target should ignore changes or not.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLNode} target
|
||||
* @returns {Bool}
|
||||
* @private
|
||||
*/
|
||||
const shouldIgnoreChangesForNode = target => !!target.closest('.ignoredirty');
|
||||
|
||||
/**
|
||||
* Mark a form as changed.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} changedNode An element in the form which was changed
|
||||
*/
|
||||
export const markFormChangedFromNode = changedNode => {
|
||||
if (changedNode.dataset.formChangeCheckerOverride) {
|
||||
// Changes to this form node disable the form change checker entirely.
|
||||
// This is intended for select fields which cause an immediate redirect.
|
||||
disableAllChecks();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWatchingForm(changedNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldIgnoreChangesForNode(changedNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the form as dirty.
|
||||
const formNode = getFormForNode(changedNode);
|
||||
formNode.dataset.formDirty = "true";
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark a form as submitted.
|
||||
*
|
||||
* @method
|
||||
* @param {HTMLElement} formNode An element in the form to mark as submitted
|
||||
*/
|
||||
export const markFormSubmitted = formNode => {
|
||||
formNode = getFormFromChild(formNode);
|
||||
|
||||
if (!formNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
formNode.dataset.formSubmitted = "true";
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all forms as submitted.
|
||||
*
|
||||
* This function is only for backwards-compliance with the old YUI module and should not be used in any other situation.
|
||||
* It will be removed in Moodle 4.4.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
export const markAllFormsSubmitted = () => {
|
||||
watchedForms.forEach(watchedForm => markFormSubmitted(watchedForm));
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the beforeunload event.
|
||||
*
|
||||
* @method
|
||||
* @param {Event} e
|
||||
* @returns {string|null}
|
||||
* @private
|
||||
*/
|
||||
const beforeUnloadHandler = e => {
|
||||
// Please note: The use of Promises in this function is forbidden.
|
||||
// This is an event handler and _cannot_ be asynchronous.
|
||||
let warnBeforeUnload = isAnyWatchedFormDirty() && !M.cfg.behatsiterunning;
|
||||
if (warnBeforeUnload) {
|
||||
// According to the specification, to show the confirmation dialog an event handler should call preventDefault()
|
||||
// on the event.
|
||||
e.preventDefault();
|
||||
|
||||
// However note that not all browsers support this method, and some instead require the event handler to
|
||||
// implement one of two legacy methods:
|
||||
// * assigning a string to the event's returnValue property; and
|
||||
// * returning a string from the event handler.
|
||||
|
||||
// Assigning a string to the event's returnValue property.
|
||||
e.returnValue = warningString;
|
||||
|
||||
// Returning a string from the event handler.
|
||||
return e.returnValue;
|
||||
}
|
||||
|
||||
// Attaching an event handler/listener to window or document's beforeunload event prevents browsers from using
|
||||
// in-memory page navigation caches, like Firefox's Back-Forward cache or WebKit's Page Cache.
|
||||
// Remove the handler.
|
||||
window.removeEventListener('beforeunload', beforeUnloadHandler);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start watching for form changes.
|
||||
*
|
||||
* This function is called on module load, and should not normally be called.
|
||||
*
|
||||
* @method
|
||||
* @protected
|
||||
*/
|
||||
export const startWatching = () => {
|
||||
if (initialised) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add legacy support to provide b/c for the old YUI version.
|
||||
addLegacyFunctions();
|
||||
|
||||
document.addEventListener('change', e => {
|
||||
if (!isWatchingForm(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
markFormChangedFromNode(e.target);
|
||||
});
|
||||
|
||||
document.addEventListener('click', e => {
|
||||
const ignoredButton = e.target.closest('[data-formchangechecker-ignore-submit]');
|
||||
if (!ignoredButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ownerForm = getFormFromChild(e.target);
|
||||
if (ownerForm) {
|
||||
ownerForm.dataset.ignoreSubmission = "true";
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('focusin', e => {
|
||||
if (e.target.matches('input, textarea, select')) {
|
||||
if (e.target.dataset.propertyIsEnumerable('initialValue')) {
|
||||
// The initial value has already been set.
|
||||
return;
|
||||
}
|
||||
e.target.dataset.initialValue = e.target.value;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('submit', e => {
|
||||
const formNode = getFormFromChild(e.target);
|
||||
if (!formNode) {
|
||||
// Weird, but watch for this anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
if (formNode.dataset.ignoreSubmission) {
|
||||
// This form was submitted by a button which requested that the form checked should not mark it as submitted.
|
||||
formNode.dataset.ignoreSubmission = "false";
|
||||
return;
|
||||
}
|
||||
|
||||
markFormSubmitted(formNode);
|
||||
});
|
||||
|
||||
document.addEventListener(eventTypes.editorContentRestored, e => {
|
||||
if (e.target != document) {
|
||||
resetFormDirtyState(e.target);
|
||||
} else {
|
||||
resetAllFormDirtyStates();
|
||||
}
|
||||
});
|
||||
|
||||
getString('changesmadereallygoaway', 'moodle')
|
||||
.then(changesMadeString => {
|
||||
warningString = changesMadeString;
|
||||
return;
|
||||
})
|
||||
.catch();
|
||||
|
||||
window.addEventListener('beforeunload', beforeUnloadHandler);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add legacy functions for backwards compatability.
|
||||
*
|
||||
* @method
|
||||
* @private
|
||||
*/
|
||||
const addLegacyFunctions = () => {
|
||||
// Create a curried function to log use of the old function and provide detail on its replacement.
|
||||
const getLoggedLegacyFallback = (oldFunctionName, newFunctionName, newFunction) => (...args) => {
|
||||
window.console.warn(
|
||||
`The moodle-core-formchangechecker has been deprecated ` +
|
||||
`and replaced with core_form/changechecker. ` +
|
||||
`The ${oldFunctionName} function has been replaced with ${newFunctionName}.`
|
||||
);
|
||||
newFunction(...args);
|
||||
};
|
||||
|
||||
/* eslint-disable */
|
||||
window.M.core_formchangechecker = {
|
||||
init: getLoggedLegacyFallback('init', 'watchFormById', watchFormById),
|
||||
reset_form_dirty_state: getLoggedLegacyFallback('reset_form_dirty_state', 'resetFormDirtyState', resetAllFormDirtyStates),
|
||||
set_form_changed: getLoggedLegacyFallback('set_form_changed', 'markFormAsDirty', markAllFormsAsDirty),
|
||||
set_form_submitted: getLoggedLegacyFallback('set_form_submitted', 'markFormSubmitted', markAllFormsAsSubmitted),
|
||||
};
|
||||
/* eslint-enable */
|
||||
};
|
||||
|
||||
/**
|
||||
* Watch the form matching the specified ID for changes.
|
||||
*
|
||||
* @method
|
||||
* @param {String} formId
|
||||
*/
|
||||
export const watchFormById = formId => {
|
||||
watchForm(document.getElementById(formId));
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset the dirty state of the form matching the specified ID..
|
||||
*
|
||||
* @method
|
||||
* @param {String} formId
|
||||
*/
|
||||
export const resetFormDirtyStateById = formId => {
|
||||
resetFormDirtyState(document.getElementById(formId));
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark the form matching the specified ID as dirty.
|
||||
*
|
||||
* @method
|
||||
* @param {String} formId
|
||||
*/
|
||||
export const markFormAsDirtyById = formId => {
|
||||
markFormAsDirty(document.getElementById(formId));
|
||||
};
|
||||
|
||||
// Configure all event listeners.
|
||||
startWatching();
|
||||
@@ -58,6 +58,8 @@ information provided here is intended especially for developers.
|
||||
completion_info::internal_set_data() to reaggregate completions that have been marked for instant course completion.
|
||||
* The following functions have been finally deprecated and can not be used anymore:
|
||||
- generate_uuid
|
||||
* The YUI moodle-core-formchangechecker module has been deprecated and replaced with a new AMD module
|
||||
core_form/changechecker.
|
||||
|
||||
=== 3.11.2 ===
|
||||
* For security reasons, filelib has been updated so all requests now use emulated redirects.
|
||||
|
||||
+48
-231
@@ -9,247 +9,64 @@ YUI.add('moodle-core-formchangechecker', function (Y, NAME) {
|
||||
/**
|
||||
* A utility to check for form changes before navigating away from a page.
|
||||
*
|
||||
* To initialise, call M.core_formchangechecker.init({formid: 'myform'}); or perhaps
|
||||
*
|
||||
* Y.use('moodle-core-formchangechecker', function() {
|
||||
* M.core_formchangechecker.init({formid: 'myform'});
|
||||
* });
|
||||
*
|
||||
* If you have some fields in your form that you don't want to have tracked, then add
|
||||
* a data-formchangechecker-ignore-dirty to the field, or any parent element, and it
|
||||
* will be ignored.
|
||||
*
|
||||
* If you have a submit button in your form that does not actually save the data,
|
||||
* then add a data-formchangechecker-ignore-submit attribute to it.
|
||||
* Please note that this YUI module has been deprecated in favour of the core_form/changechecker AMD module.
|
||||
*
|
||||
* @class M.core.formchangechecker
|
||||
* @constructor
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
var FORMCHANGECHECKERNAME = 'core-formchangechecker',
|
||||
|
||||
FORMCHANGECHECKER = function() {
|
||||
FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(FORMCHANGECHECKER, Y.Base, {
|
||||
|
||||
// The delegated listeners we need to detach after the initial value has been stored once
|
||||
initialvaluelisteners: [],
|
||||
|
||||
/**
|
||||
* Initialize the module
|
||||
*
|
||||
* @method initializer
|
||||
*/
|
||||
initializer: function() {
|
||||
var formid = 'form#' + this.get('formid'),
|
||||
currentform = Y.one(formid);
|
||||
|
||||
if (!currentform) {
|
||||
// If the form was not found, then we can't check for changes.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!M.core_formchangechecker.stateinformation.formchanged) {
|
||||
M.core_formchangechecker.stateinformation.formchanged = this.get('initialdirtystate');
|
||||
}
|
||||
|
||||
// Add a listener here for an editor restore event.
|
||||
Y.on(M.core.event.EDITOR_CONTENT_RESTORED, M.core_formchangechecker.reset_form_dirty_state, this);
|
||||
|
||||
// Add change events to the form elements
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'input', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'textarea', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'select', this);
|
||||
|
||||
// Add a focus event to check for changes which are made without triggering a change event
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'input', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'textarea', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'select', this));
|
||||
|
||||
currentform.delegate('click', function() {
|
||||
currentform.setData('ignoreSubmission', true);
|
||||
}, '[data-formchangechecker-ignore-submit]');
|
||||
|
||||
// We need any submit buttons on the form to set the submitted flag.
|
||||
Y.one(formid).on('submit', function() {
|
||||
if (currentform.getData('ignoreSubmission')) {
|
||||
// But not if we have been told to ignore this button.
|
||||
currentform.clearData('ignoreSubmission');
|
||||
return;
|
||||
}
|
||||
M.core_formchangechecker.set_form_submitted();
|
||||
}, this);
|
||||
|
||||
// YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
|
||||
// a result, the has_changed must stay in the DOM too
|
||||
window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the initial value of the currently focussed element
|
||||
*
|
||||
* If an element has been focussed and changed but not yet blurred, the on change
|
||||
* event won't be fired. We need to store it's initial value to compare it in the
|
||||
* get_form_dirty_state function later.
|
||||
*
|
||||
* @method store_initial_value
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
store_initial_value: function(e) {
|
||||
var thisevent;
|
||||
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// Detach all listen events to prevent duplicate initial value setting
|
||||
while (this.initialvaluelisteners.length) {
|
||||
thisevent = this.initialvaluelisteners.shift();
|
||||
thisevent.detach();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a note of the current element so that it can be interrogated and
|
||||
// compared in the get_form_dirty_state function
|
||||
M.core_formchangechecker.stateinformation.focused_element = {
|
||||
element: e.target,
|
||||
initial_value: e.target.get('value')
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: FORMCHANGECHECKERNAME,
|
||||
ATTRS: {
|
||||
formid: {
|
||||
'value': ''
|
||||
},
|
||||
initialdirtystate: {
|
||||
'value': false
|
||||
}
|
||||
}
|
||||
}
|
||||
window.console.warn(
|
||||
'The moodle-core-formchangechecker has been deprecated ' +
|
||||
'and replaced with core_form/changechecker. ' +
|
||||
'Please update your code to make use of the new module.'
|
||||
);
|
||||
|
||||
M.core_formchangechecker = M.core_formchangechecker || {};
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.startWatching();
|
||||
});
|
||||
|
||||
// We might have multiple instances of the form change protector
|
||||
M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
|
||||
M.core_formchangechecker.init = function(config) {
|
||||
var formchangechecker = new FORMCHANGECHECKER(config);
|
||||
M.core_formchangechecker.instances.push(formchangechecker);
|
||||
return formchangechecker;
|
||||
};
|
||||
// The following are provided to prevent race conditions.
|
||||
// Because the AMD module is loaded asynchronously after the YUI module is loaded, there is a possibility that the
|
||||
// calling code may call the YUI function calls before the AMD module has finished loading.
|
||||
// These will be removed in future and are automatically overwritten by the legacy helper provided as part of the new
|
||||
// changechecker AMD module.
|
||||
// eslint-disable-next-line camelcase
|
||||
M.core_formchangechecker = M.core_formchangechecker || {
|
||||
init: function(config) {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.watchFormById(config.formid);
|
||||
});
|
||||
},
|
||||
|
||||
// Store state information
|
||||
M.core_formchangechecker.stateinformation = [];
|
||||
/**
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_changed: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsAsDirty();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_changed = function(e) {
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e && e.target && (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true))) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_submitted: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsSubmitted();
|
||||
});
|
||||
},
|
||||
|
||||
M.core_formchangechecker.stateinformation.formchanged = 1;
|
||||
|
||||
// Once the form has been marked as dirty, we no longer need to keep track of form elements
|
||||
// which haven't yet blurred
|
||||
delete M.core_formchangechecker.stateinformation.focused_element;
|
||||
};
|
||||
|
||||
/*
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_submitted = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = 1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Attempt to determine whether the form has been modified in any way and
|
||||
* is thus 'dirty'
|
||||
*
|
||||
* @return Integer 1 is the form is dirty; 0 if not
|
||||
*/
|
||||
M.core_formchangechecker.get_form_dirty_state = function() {
|
||||
var state = M.core_formchangechecker.stateinformation,
|
||||
editor;
|
||||
|
||||
// If the form was submitted, then return a non-dirty state
|
||||
if (state.formsubmitted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If any fields have been marked dirty, return a dirty state
|
||||
if (state.formchanged) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If a field has been focused and changed, but still has focus then the browser won't fire the
|
||||
// onChange event. We check for this eventuality here
|
||||
if (state.focused_element) {
|
||||
if (state.focused_element.element.get('value') !== state.focused_element.initial_value) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TinyMCE editor instances
|
||||
// We can't add a listener in the initializer as the editors may not have been created by that point
|
||||
// so we do so here instead
|
||||
if (typeof window.tinyMCE !== 'undefined') {
|
||||
for (editor in window.tinyMCE.editors) {
|
||||
if (window.tinyMCE.editors[editor].isDirty()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we reached here, then the form hasn't met any of the dirty conditions
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Reset the form state
|
||||
*/
|
||||
M.core_formchangechecker.reset_form_dirty_state = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = false;
|
||||
M.core_formchangechecker.stateinformation.formchanged = false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Return a suitable message if changes have been made to a form
|
||||
*/
|
||||
M.core_formchangechecker.report_form_dirty_state = function(e) {
|
||||
if (!M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// the form is not dirty, so don't display any message
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the error message that we'll show to browsers which support it
|
||||
var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
|
||||
|
||||
if (M.cfg.behatsiterunning) {
|
||||
// If the behat site is running we don't want browser alerts.
|
||||
return;
|
||||
}
|
||||
|
||||
// Most browsers are happy with the returnValue being set on the event
|
||||
// But some browsers do not consistently pass the event
|
||||
if (e) {
|
||||
e.returnValue = warningmessage;
|
||||
}
|
||||
|
||||
// But some require it to be returned instead
|
||||
return warningmessage;
|
||||
/**
|
||||
* Reset the form state
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
reset_form_dirty_state: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.resetAllFormDirtyStates();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
YUI.add("moodle-core-formchangechecker",function(r,e){var c=function(){c.superclass.constructor.apply(this,arguments)};r.extend(c,r.Base,{initialvaluelisteners:[],initializer:function(){var e="form#"+this.get("formid"),t=r.one(e);t&&(M.core_formchangechecker.stateinformation.formchanged||(M.core_formchangechecker.stateinformation.formchanged=this.get("initialdirtystate")),r.on(M.core.event.EDITOR_CONTENT_RESTORED,M.core_formchangechecker.reset_form_dirty_state,this),t.delegate("change",M.core_formchangechecker.set_form_changed,"input",this),t.delegate("change",M.core_formchangechecker.set_form_changed,"textarea",this),t.delegate("change",M.core_formchangechecker.set_form_changed,"select",this),this.initialvaluelisteners.push(t.delegate("focus",this.store_initial_value,"input",this)),this.initialvaluelisteners.push(t.delegate("focus",this.store_initial_value,"textarea",this)),this.initialvaluelisteners.push(t.delegate("focus",this.store_initial_value,"select",this)),t.delegate("click",function(){t.setData("ignoreSubmission",!0)},"[data-formchangechecker-ignore-submit]"),r.one(e).on("submit",function(){t.getData("ignoreSubmission")?t.clearData("ignoreSubmission"):M.core_formchangechecker.set_form_submitted()},this),window.onbeforeunload=M.core_formchangechecker.report_form_dirty_state)},store_initial_value:function(e){if(!e.target.ancestor(".ignoredirty",!0)&&!e.target.ancestor("[data-formchangechecker-ignore-dirty]",!0))if(M.core_formchangechecker.get_form_dirty_state())for(;this.initialvaluelisteners.length;)this.initialvaluelisteners.shift().detach();else M.core_formchangechecker.stateinformation.focused_element={element:e.target,initial_value:e.target.get("value")}}},{NAME:"core-formchangechecker",ATTRS:{formid:{value:""},initialdirtystate:{value:!1}}}),M.core_formchangechecker=M.core_formchangechecker||{},M.core_formchangechecker.instances=M.core_formchangechecker.instances||[],M.core_formchangechecker.init=function(e){var t=new c(e);return M.core_formchangechecker.instances.push(t),t},M.core_formchangechecker.stateinformation=[],M.core_formchangechecker.set_form_changed=function(e){e&&e.target&&(e.target.ancestor(".ignoredirty",!0)||e.target.ancestor("[data-formchangechecker-ignore-dirty]",!0))||(M.core_formchangechecker.stateinformation.formchanged=1,delete M.core_formchangechecker.stateinformation.focused_element)},M.core_formchangechecker.set_form_submitted=function(){M.core_formchangechecker.stateinformation.formsubmitted=1},M.core_formchangechecker.get_form_dirty_state=function(){var e,t=M.core_formchangechecker.stateinformation;if(t.formsubmitted)return 0;if(t.formchanged)return 1;if(t.focused_element&&t.focused_element.element.get("value")!==t.focused_element.initial_value)return 1;if("undefined"!=typeof window.tinyMCE)for(e in window.tinyMCE.editors)if(window.tinyMCE.editors[e].isDirty())return 1;return 0},M.core_formchangechecker.reset_form_dirty_state=function(){M.core_formchangechecker.stateinformation.formsubmitted=!1,M.core_formchangechecker.stateinformation.formchanged=!1},M.core_formchangechecker.report_form_dirty_state=function(e){if(M.core_formchangechecker.get_form_dirty_state()){var t=M.util.get_string("changesmadereallygoaway","moodle");if(!M.cfg.behatsiterunning)return e&&(e.returnValue=t),t}}},"@VERSION@",{requires:["base","event-focus","moodle-core-event"]});
|
||||
YUI.add("moodle-core-formchangechecker",function(e,r){window.console.warn("The moodle-core-formchangechecker has been deprecated and replaced with core_form/changechecker. Please update your code to make use of the new module."),require(["core_form/changechecker"],function(e){e.startWatching()}),M.core_formchangechecker=M.core_formchangechecker||{init:function(r){require(["core_form/changechecker"],function(e){e.watchFormById(r.formid)})},set_form_changed:function(){require(["core_form/changechecker"],function(e){e.markAllFormsAsDirty()})},set_form_submitted:function(){require(["core_form/changechecker"],function(e){e.markAllFormsSubmitted()})},reset_form_dirty_state:function(){require(["core_form/changechecker"],function(e){e.resetAllFormDirtyStates()})}}},"@VERSION@",{requires:["base","event-focus","moodle-core-event"]});
|
||||
+48
-231
@@ -9,247 +9,64 @@ YUI.add('moodle-core-formchangechecker', function (Y, NAME) {
|
||||
/**
|
||||
* A utility to check for form changes before navigating away from a page.
|
||||
*
|
||||
* To initialise, call M.core_formchangechecker.init({formid: 'myform'}); or perhaps
|
||||
*
|
||||
* Y.use('moodle-core-formchangechecker', function() {
|
||||
* M.core_formchangechecker.init({formid: 'myform'});
|
||||
* });
|
||||
*
|
||||
* If you have some fields in your form that you don't want to have tracked, then add
|
||||
* a data-formchangechecker-ignore-dirty to the field, or any parent element, and it
|
||||
* will be ignored.
|
||||
*
|
||||
* If you have a submit button in your form that does not actually save the data,
|
||||
* then add a data-formchangechecker-ignore-submit attribute to it.
|
||||
* Please note that this YUI module has been deprecated in favour of the core_form/changechecker AMD module.
|
||||
*
|
||||
* @class M.core.formchangechecker
|
||||
* @constructor
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
var FORMCHANGECHECKERNAME = 'core-formchangechecker',
|
||||
|
||||
FORMCHANGECHECKER = function() {
|
||||
FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(FORMCHANGECHECKER, Y.Base, {
|
||||
|
||||
// The delegated listeners we need to detach after the initial value has been stored once
|
||||
initialvaluelisteners: [],
|
||||
|
||||
/**
|
||||
* Initialize the module
|
||||
*
|
||||
* @method initializer
|
||||
*/
|
||||
initializer: function() {
|
||||
var formid = 'form#' + this.get('formid'),
|
||||
currentform = Y.one(formid);
|
||||
|
||||
if (!currentform) {
|
||||
// If the form was not found, then we can't check for changes.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!M.core_formchangechecker.stateinformation.formchanged) {
|
||||
M.core_formchangechecker.stateinformation.formchanged = this.get('initialdirtystate');
|
||||
}
|
||||
|
||||
// Add a listener here for an editor restore event.
|
||||
Y.on(M.core.event.EDITOR_CONTENT_RESTORED, M.core_formchangechecker.reset_form_dirty_state, this);
|
||||
|
||||
// Add change events to the form elements
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'input', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'textarea', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'select', this);
|
||||
|
||||
// Add a focus event to check for changes which are made without triggering a change event
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'input', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'textarea', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'select', this));
|
||||
|
||||
currentform.delegate('click', function() {
|
||||
currentform.setData('ignoreSubmission', true);
|
||||
}, '[data-formchangechecker-ignore-submit]');
|
||||
|
||||
// We need any submit buttons on the form to set the submitted flag.
|
||||
Y.one(formid).on('submit', function() {
|
||||
if (currentform.getData('ignoreSubmission')) {
|
||||
// But not if we have been told to ignore this button.
|
||||
currentform.clearData('ignoreSubmission');
|
||||
return;
|
||||
}
|
||||
M.core_formchangechecker.set_form_submitted();
|
||||
}, this);
|
||||
|
||||
// YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
|
||||
// a result, the has_changed must stay in the DOM too
|
||||
window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the initial value of the currently focussed element
|
||||
*
|
||||
* If an element has been focussed and changed but not yet blurred, the on change
|
||||
* event won't be fired. We need to store it's initial value to compare it in the
|
||||
* get_form_dirty_state function later.
|
||||
*
|
||||
* @method store_initial_value
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
store_initial_value: function(e) {
|
||||
var thisevent;
|
||||
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// Detach all listen events to prevent duplicate initial value setting
|
||||
while (this.initialvaluelisteners.length) {
|
||||
thisevent = this.initialvaluelisteners.shift();
|
||||
thisevent.detach();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a note of the current element so that it can be interrogated and
|
||||
// compared in the get_form_dirty_state function
|
||||
M.core_formchangechecker.stateinformation.focused_element = {
|
||||
element: e.target,
|
||||
initial_value: e.target.get('value')
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: FORMCHANGECHECKERNAME,
|
||||
ATTRS: {
|
||||
formid: {
|
||||
'value': ''
|
||||
},
|
||||
initialdirtystate: {
|
||||
'value': false
|
||||
}
|
||||
}
|
||||
}
|
||||
window.console.warn(
|
||||
'The moodle-core-formchangechecker has been deprecated ' +
|
||||
'and replaced with core_form/changechecker. ' +
|
||||
'Please update your code to make use of the new module.'
|
||||
);
|
||||
|
||||
M.core_formchangechecker = M.core_formchangechecker || {};
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.startWatching();
|
||||
});
|
||||
|
||||
// We might have multiple instances of the form change protector
|
||||
M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
|
||||
M.core_formchangechecker.init = function(config) {
|
||||
var formchangechecker = new FORMCHANGECHECKER(config);
|
||||
M.core_formchangechecker.instances.push(formchangechecker);
|
||||
return formchangechecker;
|
||||
};
|
||||
// The following are provided to prevent race conditions.
|
||||
// Because the AMD module is loaded asynchronously after the YUI module is loaded, there is a possibility that the
|
||||
// calling code may call the YUI function calls before the AMD module has finished loading.
|
||||
// These will be removed in future and are automatically overwritten by the legacy helper provided as part of the new
|
||||
// changechecker AMD module.
|
||||
// eslint-disable-next-line camelcase
|
||||
M.core_formchangechecker = M.core_formchangechecker || {
|
||||
init: function(config) {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.watchFormById(config.formid);
|
||||
});
|
||||
},
|
||||
|
||||
// Store state information
|
||||
M.core_formchangechecker.stateinformation = [];
|
||||
/**
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_changed: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsAsDirty();
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_changed = function(e) {
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e && e.target && (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true))) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_submitted: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsSubmitted();
|
||||
});
|
||||
},
|
||||
|
||||
M.core_formchangechecker.stateinformation.formchanged = 1;
|
||||
|
||||
// Once the form has been marked as dirty, we no longer need to keep track of form elements
|
||||
// which haven't yet blurred
|
||||
delete M.core_formchangechecker.stateinformation.focused_element;
|
||||
};
|
||||
|
||||
/*
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_submitted = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = 1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Attempt to determine whether the form has been modified in any way and
|
||||
* is thus 'dirty'
|
||||
*
|
||||
* @return Integer 1 is the form is dirty; 0 if not
|
||||
*/
|
||||
M.core_formchangechecker.get_form_dirty_state = function() {
|
||||
var state = M.core_formchangechecker.stateinformation,
|
||||
editor;
|
||||
|
||||
// If the form was submitted, then return a non-dirty state
|
||||
if (state.formsubmitted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If any fields have been marked dirty, return a dirty state
|
||||
if (state.formchanged) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If a field has been focused and changed, but still has focus then the browser won't fire the
|
||||
// onChange event. We check for this eventuality here
|
||||
if (state.focused_element) {
|
||||
if (state.focused_element.element.get('value') !== state.focused_element.initial_value) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TinyMCE editor instances
|
||||
// We can't add a listener in the initializer as the editors may not have been created by that point
|
||||
// so we do so here instead
|
||||
if (typeof window.tinyMCE !== 'undefined') {
|
||||
for (editor in window.tinyMCE.editors) {
|
||||
if (window.tinyMCE.editors[editor].isDirty()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we reached here, then the form hasn't met any of the dirty conditions
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Reset the form state
|
||||
*/
|
||||
M.core_formchangechecker.reset_form_dirty_state = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = false;
|
||||
M.core_formchangechecker.stateinformation.formchanged = false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Return a suitable message if changes have been made to a form
|
||||
*/
|
||||
M.core_formchangechecker.report_form_dirty_state = function(e) {
|
||||
if (!M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// the form is not dirty, so don't display any message
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the error message that we'll show to browsers which support it
|
||||
var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
|
||||
|
||||
if (M.cfg.behatsiterunning) {
|
||||
// If the behat site is running we don't want browser alerts.
|
||||
return;
|
||||
}
|
||||
|
||||
// Most browsers are happy with the returnValue being set on the event
|
||||
// But some browsers do not consistently pass the event
|
||||
if (e) {
|
||||
e.returnValue = warningmessage;
|
||||
}
|
||||
|
||||
// But some require it to be returned instead
|
||||
return warningmessage;
|
||||
/**
|
||||
* Reset the form state
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
reset_form_dirty_state: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.resetAllFormDirtyStates();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
+51
-234
@@ -7,245 +7,62 @@
|
||||
/**
|
||||
* A utility to check for form changes before navigating away from a page.
|
||||
*
|
||||
* To initialise, call M.core_formchangechecker.init({formid: 'myform'}); or perhaps
|
||||
*
|
||||
* Y.use('moodle-core-formchangechecker', function() {
|
||||
* M.core_formchangechecker.init({formid: 'myform'});
|
||||
* });
|
||||
*
|
||||
* If you have some fields in your form that you don't want to have tracked, then add
|
||||
* a data-formchangechecker-ignore-dirty to the field, or any parent element, and it
|
||||
* will be ignored.
|
||||
*
|
||||
* If you have a submit button in your form that does not actually save the data,
|
||||
* then add a data-formchangechecker-ignore-submit attribute to it.
|
||||
* Please note that this YUI module has been deprecated in favour of the core_form/changechecker AMD module.
|
||||
*
|
||||
* @class M.core.formchangechecker
|
||||
* @constructor
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
var FORMCHANGECHECKERNAME = 'core-formchangechecker',
|
||||
|
||||
FORMCHANGECHECKER = function() {
|
||||
FORMCHANGECHECKER.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(FORMCHANGECHECKER, Y.Base, {
|
||||
|
||||
// The delegated listeners we need to detach after the initial value has been stored once
|
||||
initialvaluelisteners: [],
|
||||
|
||||
/**
|
||||
* Initialize the module
|
||||
*
|
||||
* @method initializer
|
||||
*/
|
||||
initializer: function() {
|
||||
var formid = 'form#' + this.get('formid'),
|
||||
currentform = Y.one(formid);
|
||||
|
||||
if (!currentform) {
|
||||
// If the form was not found, then we can't check for changes.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!M.core_formchangechecker.stateinformation.formchanged) {
|
||||
M.core_formchangechecker.stateinformation.formchanged = this.get('initialdirtystate');
|
||||
}
|
||||
|
||||
// Add a listener here for an editor restore event.
|
||||
Y.on(M.core.event.EDITOR_CONTENT_RESTORED, M.core_formchangechecker.reset_form_dirty_state, this);
|
||||
|
||||
// Add change events to the form elements
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'input', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'textarea', this);
|
||||
currentform.delegate('change', M.core_formchangechecker.set_form_changed, 'select', this);
|
||||
|
||||
// Add a focus event to check for changes which are made without triggering a change event
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'input', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'textarea', this));
|
||||
this.initialvaluelisteners.push(currentform.delegate('focus', this.store_initial_value, 'select', this));
|
||||
|
||||
currentform.delegate('click', function() {
|
||||
currentform.setData('ignoreSubmission', true);
|
||||
}, '[data-formchangechecker-ignore-submit]');
|
||||
|
||||
// We need any submit buttons on the form to set the submitted flag.
|
||||
Y.one(formid).on('submit', function() {
|
||||
if (currentform.getData('ignoreSubmission')) {
|
||||
// But not if we have been told to ignore this button.
|
||||
currentform.clearData('ignoreSubmission');
|
||||
return;
|
||||
}
|
||||
M.core_formchangechecker.set_form_submitted();
|
||||
}, this);
|
||||
|
||||
// YUI doesn't support onbeforeunload properly so we must use the DOM to set the onbeforeunload. As
|
||||
// a result, the has_changed must stay in the DOM too
|
||||
window.onbeforeunload = M.core_formchangechecker.report_form_dirty_state;
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the initial value of the currently focussed element
|
||||
*
|
||||
* If an element has been focussed and changed but not yet blurred, the on change
|
||||
* event won't be fired. We need to store it's initial value to compare it in the
|
||||
* get_form_dirty_state function later.
|
||||
*
|
||||
* @method store_initial_value
|
||||
* @param {EventFacade} e
|
||||
*/
|
||||
store_initial_value: function(e) {
|
||||
var thisevent;
|
||||
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// Detach all listen events to prevent duplicate initial value setting
|
||||
while (this.initialvaluelisteners.length) {
|
||||
thisevent = this.initialvaluelisteners.shift();
|
||||
thisevent.detach();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a note of the current element so that it can be interrogated and
|
||||
// compared in the get_form_dirty_state function
|
||||
M.core_formchangechecker.stateinformation.focused_element = {
|
||||
element: e.target,
|
||||
initial_value: e.target.get('value')
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
NAME: FORMCHANGECHECKERNAME,
|
||||
ATTRS: {
|
||||
formid: {
|
||||
'value': ''
|
||||
},
|
||||
initialdirtystate: {
|
||||
'value': false
|
||||
}
|
||||
}
|
||||
}
|
||||
window.console.warn(
|
||||
'The moodle-core-formchangechecker has been deprecated ' +
|
||||
'and replaced with core_form/changechecker. ' +
|
||||
'Please update your code to make use of the new module.'
|
||||
);
|
||||
|
||||
M.core_formchangechecker = M.core_formchangechecker || {};
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.startWatching();
|
||||
});
|
||||
|
||||
// We might have multiple instances of the form change protector
|
||||
M.core_formchangechecker.instances = M.core_formchangechecker.instances || [];
|
||||
M.core_formchangechecker.init = function(config) {
|
||||
var formchangechecker = new FORMCHANGECHECKER(config);
|
||||
M.core_formchangechecker.instances.push(formchangechecker);
|
||||
return formchangechecker;
|
||||
};
|
||||
|
||||
// Store state information
|
||||
M.core_formchangechecker.stateinformation = [];
|
||||
|
||||
/*
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_changed = function(e) {
|
||||
// Don't warn on elements we have been told to ignore.
|
||||
if (e && e.target && (e.target.ancestor('.ignoredirty', true) ||
|
||||
e.target.ancestor('[data-formchangechecker-ignore-dirty]', true))) {
|
||||
return;
|
||||
}
|
||||
|
||||
M.core_formchangechecker.stateinformation.formchanged = 1;
|
||||
|
||||
// Once the form has been marked as dirty, we no longer need to keep track of form elements
|
||||
// which haven't yet blurred
|
||||
delete M.core_formchangechecker.stateinformation.focused_element;
|
||||
};
|
||||
|
||||
/*
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
M.core_formchangechecker.set_form_submitted = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = 1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Attempt to determine whether the form has been modified in any way and
|
||||
* is thus 'dirty'
|
||||
*
|
||||
* @return Integer 1 is the form is dirty; 0 if not
|
||||
*/
|
||||
M.core_formchangechecker.get_form_dirty_state = function() {
|
||||
var state = M.core_formchangechecker.stateinformation,
|
||||
editor;
|
||||
|
||||
// If the form was submitted, then return a non-dirty state
|
||||
if (state.formsubmitted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If any fields have been marked dirty, return a dirty state
|
||||
if (state.formchanged) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If a field has been focused and changed, but still has focus then the browser won't fire the
|
||||
// onChange event. We check for this eventuality here
|
||||
if (state.focused_element) {
|
||||
if (state.focused_element.element.get('value') !== state.focused_element.initial_value) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle TinyMCE editor instances
|
||||
// We can't add a listener in the initializer as the editors may not have been created by that point
|
||||
// so we do so here instead
|
||||
if (typeof window.tinyMCE !== 'undefined') {
|
||||
for (editor in window.tinyMCE.editors) {
|
||||
if (window.tinyMCE.editors[editor].isDirty()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we reached here, then the form hasn't met any of the dirty conditions
|
||||
return 0;
|
||||
};
|
||||
|
||||
/*
|
||||
* Reset the form state
|
||||
*/
|
||||
M.core_formchangechecker.reset_form_dirty_state = function() {
|
||||
M.core_formchangechecker.stateinformation.formsubmitted = false;
|
||||
M.core_formchangechecker.stateinformation.formchanged = false;
|
||||
};
|
||||
|
||||
/*
|
||||
* Return a suitable message if changes have been made to a form
|
||||
*/
|
||||
M.core_formchangechecker.report_form_dirty_state = function(e) {
|
||||
if (!M.core_formchangechecker.get_form_dirty_state()) {
|
||||
// the form is not dirty, so don't display any message
|
||||
return;
|
||||
}
|
||||
|
||||
// This is the error message that we'll show to browsers which support it
|
||||
var warningmessage = M.util.get_string('changesmadereallygoaway', 'moodle');
|
||||
|
||||
if (M.cfg.behatsiterunning) {
|
||||
// If the behat site is running we don't want browser alerts.
|
||||
return;
|
||||
}
|
||||
|
||||
// Most browsers are happy with the returnValue being set on the event
|
||||
// But some browsers do not consistently pass the event
|
||||
if (e) {
|
||||
e.returnValue = warningmessage;
|
||||
}
|
||||
|
||||
// But some require it to be returned instead
|
||||
return warningmessage;
|
||||
// The following are provided to prevent race conditions.
|
||||
// Because the AMD module is loaded asynchronously after the YUI module is loaded, there is a possibility that the
|
||||
// calling code may call the YUI function calls before the AMD module has finished loading.
|
||||
// These will be removed in future and are automatically overwritten by the legacy helper provided as part of the new
|
||||
// changechecker AMD module.
|
||||
// eslint-disable-next-line camelcase
|
||||
M.core_formchangechecker = M.core_formchangechecker || {
|
||||
init: function(config) {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.watchFormById(config.formid);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the form changed state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_changed: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsAsDirty();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the form submitted state to true
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
set_form_submitted: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.markAllFormsSubmitted();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset the form state
|
||||
*/
|
||||
// eslint-disable-next-line camelcase
|
||||
reset_form_dirty_state: function() {
|
||||
require(['core_form/changechecker'], function(ChangeChecker) {
|
||||
ChangeChecker.resetAllFormDirtyStates();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user