diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts index 86d12100ea8260d0646769f32bec681c913d056c..aae1d5e39f933209cc2f1d1a250b63340ee21ead 100644 --- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts +++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts @@ -11,6 +11,8 @@ * The TYPO3 project - inspiring people to share! */ +import documentService = require('TYPO3/CMS/Core/DocumentService'); + type HTMLFormChildElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement; /** @@ -37,25 +39,9 @@ class GlobalEventHandler { }; constructor() { - this.onReady(() => { - this.registerEvents(); - }); + documentService.ready().then((): void => this.registerEvents()); }; - private onReady(callback: Function): void { - if (document.readyState === 'complete') { - callback.call(this); - } else { - const delegate = () => { - window.removeEventListener('load', delegate); - document.removeEventListener('DOMContentLoaded', delegate); - callback.call(this); - }; - window.addEventListener('load', delegate); - document.addEventListener('DOMContentLoaded', delegate); - } - } - private registerEvents(): void { document.querySelectorAll(this.options.onChangeSelector).forEach((element: HTMLElement) => { document.addEventListener('change', this.handleChangeEvent.bind(this)); diff --git a/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts new file mode 100644 index 0000000000000000000000000000000000000000..694c794fac82c7f5a4152d8342caaa193e31910c --- /dev/null +++ b/Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts @@ -0,0 +1,59 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Module: TYPO3/CMS/Core/DocumentService + * @exports TYPO3/CMS/Core/DocumentService + */ +class DocumentService { + private readonly windowRef: Window; + private readonly documentRef: Document; + + /** + * @param {Window} windowRef + * @param {Document} documentRef + */ + constructor(windowRef: Window = window, documentRef: Document = document) { + this.windowRef = windowRef; + this.documentRef = documentRef; + } + + ready(): Promise<Document> { + return new Promise<Document>((resolve: Function, reject: Function) => { + if (this.documentRef.readyState === 'complete') { + resolve(this.documentRef); + } else { + // timeout & reject after 30 seconds + const timer = setTimeout((): void => { + clearListeners(); + reject(this.documentRef); + }, 30000); + const clearListeners = (): void => { + this.windowRef.removeEventListener('load', delegate); + this.documentRef.removeEventListener('DOMContentLoaded', delegate); + }; + const delegate = (): void => { + clearListeners(); + clearTimeout(timer); + resolve(this.documentRef); + }; + this.windowRef.addEventListener('load', delegate); + this.documentRef.addEventListener('DOMContentLoaded', delegate); + } + + }); + } +} + +const documentService = new DocumentService(); +export = documentService; diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js b/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js index 2bf7e9f1e76af98de3426633ddde6109b37fcf32..8a14ce2355d84610b98bee5657faaa01549f8c24 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/GlobalEventHandler.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -define(["require","exports"],(function(e,t){"use strict";return new class{constructor(){this.options={onChangeSelector:'[data-global-event="change"]',onClickSelector:'[data-global-event="click"]'},this.onReady(()=>{this.registerEvents()})}onReady(e){if("complete"===document.readyState)e.call(this);else{const t=()=>{window.removeEventListener("load",t),document.removeEventListener("DOMContentLoaded",t),e.call(this)};window.addEventListener("load",t),document.addEventListener("DOMContentLoaded",t)}}registerEvents(){document.querySelectorAll(this.options.onChangeSelector).forEach(e=>{document.addEventListener("change",this.handleChangeEvent.bind(this))}),document.querySelectorAll(this.options.onClickSelector).forEach(e=>{document.addEventListener("click",this.handleClickEvent.bind(this))})}handleChangeEvent(e){const t=e.target;this.handleSubmitAction(e,t)||this.handleNavigateAction(e,t)}handleClickEvent(e){e.currentTarget}handleSubmitAction(e,t){const n=t.dataset.actionSubmit;if(!n)return!1;if("$form"===n&&this.isHTMLFormChildElement(t))return t.form.submit(),!0;const o=document.querySelector(n);return o instanceof HTMLFormElement&&(o.submit(),!0)}handleNavigateAction(e,t){const n=t.dataset.actionNavigate;if(!n)return!1;const o=this.resolveHTMLFormChildElementValue(t);return!("$value"!==n||!o)&&(window.location.href=o,!0)}isHTMLFormChildElement(e){return e instanceof HTMLSelectElement||e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}resolveHTMLFormChildElementValue(e){return e instanceof HTMLSelectElement?e.options[e.selectedIndex].value:e instanceof HTMLInputElement?e.value:null}}})); \ No newline at end of file +define(["require","exports","TYPO3/CMS/Core/DocumentService"],(function(e,t,n){"use strict";return new class{constructor(){this.options={onChangeSelector:'[data-global-event="change"]',onClickSelector:'[data-global-event="click"]'},n.ready().then(()=>this.registerEvents())}registerEvents(){document.querySelectorAll(this.options.onChangeSelector).forEach(e=>{document.addEventListener("change",this.handleChangeEvent.bind(this))}),document.querySelectorAll(this.options.onClickSelector).forEach(e=>{document.addEventListener("click",this.handleClickEvent.bind(this))})}handleChangeEvent(e){const t=e.target;this.handleSubmitAction(e,t)||this.handleNavigateAction(e,t)}handleClickEvent(e){e.currentTarget}handleSubmitAction(e,t){const n=t.dataset.actionSubmit;if(!n)return!1;if("$form"===n&&this.isHTMLFormChildElement(t))return t.form.submit(),!0;const i=document.querySelector(n);return i instanceof HTMLFormElement&&(i.submit(),!0)}handleNavigateAction(e,t){const n=t.dataset.actionNavigate;if(!n)return!1;const i=this.resolveHTMLFormChildElementValue(t);return!("$value"!==n||!i)&&(window.location.href=i,!0)}isHTMLFormChildElement(e){return e instanceof HTMLSelectElement||e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement}resolveHTMLFormChildElementValue(e){return e instanceof HTMLSelectElement?e.options[e.selectedIndex].value:e instanceof HTMLInputElement?e.value:null}}})); \ No newline at end of file diff --git a/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst b/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst new file mode 100644 index 0000000000000000000000000000000000000000..cb22c298dfd8013ad1de496df8fb40c7bb0de2a1 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst @@ -0,0 +1,40 @@ +.. include:: ../../Includes.txt + +====================================================================== +Feature: #91122 - Introduce DocumentService as JQuery.ready substitute +====================================================================== + +See :issue:`91122` + +Description +=========== + +The module :js:`TYPO3/CMS/Core/DocumentService` provides native JavaScript +functions to detect DOM ready-state returning a :js:`Promise<Document>`. + +Internally the Promise is resolved when native :js:`DOMContentLoaded` event has +been emitted or when :js:`document.readyState` is defined already. It means +that initial HTML document has been completely loaded and parsed, without +waiting for stylesheets, images, and subframes to finish loading. + + +Impact +====== + +.. code-block:: javascript + + $(document).ready(() => { + // your application code + }); + +Above JQuery code can be transformed into the following using :js:`DocumentService`: + +.. code-block:: javascript + + require(['TYPO3/CMS/Core/DocumentService'], function (DocumentService) { + DocumentService.ready().then(() => { + // your application code + }); + }); + +.. index:: Backend, JavaScript, ext:core diff --git a/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js b/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js new file mode 100644 index 0000000000000000000000000000000000000000..1a5be706fbe1737b446c6e6729ee48e05f8a25a1 --- /dev/null +++ b/typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js @@ -0,0 +1,13 @@ +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +define(["require","exports"],(function(e,t){"use strict";return new class{constructor(e=window,t=document){this.windowRef=e,this.documentRef=t}ready(){return new Promise((e,t)=>{if("complete"===this.documentRef.readyState)e(this.documentRef);else{const n=setTimeout(()=>{o(),t(this.documentRef)},3e4),o=()=>{this.windowRef.removeEventListener("load",i),this.documentRef.removeEventListener("DOMContentLoaded",i)},i=()=>{o(),clearTimeout(n),e(this.documentRef)};this.windowRef.addEventListener("load",i),this.documentRef.addEventListener("DOMContentLoaded",i)}})}}})); \ No newline at end of file