From a5b512fd8870a15c173c5c25280e5c79ebc9da5b Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Sat, 18 Apr 2020 22:34:36 +0200 Subject: [PATCH] [FEATURE] Introduce DocumentService as JQuery.ready substitute Module TYPO3/CMS/Core/DocumentService provides native JavaScript functions to detect DOM ready-state returning a Promise<Document>. `$(document).ready(() => {...});` can be replaced by `documentService.ready().then(() => {...});` Resolves: #91122 Releases: master Change-Id: Id812f786430f1ced6265493dd0bae472b8144588 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/64241 Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de> Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de> Reviewed-by: Oliver Hader <oliver.hader@typo3.org> --- .../Public/TypeScript/GlobalEventHandler.ts | 20 +------ .../Public/TypeScript/DocumentService.ts | 59 +++++++++++++++++++ .../Public/JavaScript/GlobalEventHandler.js | 2 +- ...DocumentServiceAsJQueryreadySubstitute.rst | 40 +++++++++++++ .../Public/JavaScript/DocumentService.js | 13 ++++ 5 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 Build/Sources/TypeScript/core/Resources/Public/TypeScript/DocumentService.ts create mode 100644 typo3/sysext/core/Documentation/Changelog/10.4/Feature-91122-IntroduceDocumentServiceAsJQueryreadySubstitute.rst create mode 100644 typo3/sysext/core/Resources/Public/JavaScript/DocumentService.js diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/GlobalEventHandler.ts index 86d12100ea82..aae1d5e39f93 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 000000000000..694c794fac82 --- /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 2bf7e9f1e76a..8a14ce2355d8 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 000000000000..cb22c298dfd8 --- /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 000000000000..1a5be706fbe1 --- /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 -- GitLab