Commit 60c68ede authored by Andreas Fernandez's avatar Andreas Fernandez Committed by Benjamin Franzke
Browse files

[BUGFIX] Load dependencies of ImmediateActionElement dynamically

The module ImmediateActionElement is used to render a defined custom
HTML tag triggering certain actions once it's rendered, like reloading
the module menu or the backend's topbar. To achieve this, it needs
functionality from the modules TYPO3.ModuleMenu.App.refreshMenu and
TYPO3.Backend.Topbar.refresh.

This module is loaded at a very early stage where those dependencies
cannot do anything meaningful as there is nothing to do, namely the
backend login, which causes JavaScript warnings and increases loading
time.

To relax the situation, the modules are now loaded dynamically when
required by using async functions.

In a perfect world, there would be some kind of a hooking system and no
hardcoded list of actions, but we're not there, yet.

Resolves: #92350
Releases: master, 10.4
Change-Id: I50029789f564ce339b3204d51f61e4cc39bb3cf9
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/65795

Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Richard Haeser's avatarRichard Haeser <richard@richardhaeser.com>
Tested-by: default avatarMartin Kutschker <mkutschker-typo3@yahoo.com>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Richard Haeser's avatarRichard Haeser <richard@richardhaeser.com>
Reviewed-by: default avatarMartin Kutschker <mkutschker-typo3@yahoo.com>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent dec6f5bf
......@@ -11,9 +11,6 @@
* The TYPO3 project - inspiring people to share!
*/
import moduleMenuApp = require('TYPO3/CMS/Backend/ModuleMenu');
import viewportObject = require('TYPO3/CMS/Backend/Viewport');
import windowManager = require('TYPO3/CMS/Backend/WindowManager');
import Utility = require('TYPO3/CMS/Backend/Utility');
/**
......@@ -29,13 +26,16 @@ export class ImmediateActionElement extends HTMLElement {
private action: string;
private args: any[] = [];
private static getDelegate(action: string): Function {
private static async getDelegate(action: string): Promise<Function> {
switch (action) {
case 'TYPO3.ModuleMenu.App.refreshMenu':
const moduleMenuApp = await import('TYPO3/CMS/Backend/ModuleMenu');
return moduleMenuApp.App.refreshMenu.bind(moduleMenuApp);
case 'TYPO3.Backend.Topbar.refresh':
const viewportObject = await import('TYPO3/CMS/Backend/Viewport');
return viewportObject.Topbar.refresh.bind(viewportObject.Topbar);
case 'TYPO3.WindowManager.localOpen':
const windowManager = await import('TYPO3/CMS/Backend/WindowManager');
return windowManager.localOpen.bind(windowManager);
default:
throw Error('Unknown action "' + action + '"');
......@@ -75,7 +75,7 @@ export class ImmediateActionElement extends HTMLElement {
if (!this.action) {
throw new Error('Missing mandatory action attribute');
}
ImmediateActionElement.getDelegate(this.action).apply(null, this.args);
ImmediateActionElement.getDelegate(this.action).then((callback: Function): void => callback.apply(null, this.args));
}
}
......
......@@ -18,11 +18,13 @@ class WindowManager {
private windows: {[key: string]: Window} = {};
// alias for `localOpen`
public open = (...args: any[]): Window => this.localOpen.apply(this, args);
public open = (...args: any[]): Window => this._localOpen.apply(this, args);
// @todo Not implemented, yet
public globalOpen = (...args: any[]): Window => this.localOpen.apply(this, args);
public globalOpen = (...args: any[]): Window => this._localOpen.apply(this, args);
public localOpen(uri: string, switchFocus?: boolean, windowName: string = 'newTYPO3frontendWindow', windowFeatures: string = ''): Window | null {
public localOpen = (uri: string, switchFocus?: boolean, windowName: string = 'newTYPO3frontendWindow', windowFeatures: string = ''): Window | null => this._localOpen(uri, switchFocus, windowName, windowFeatures);
private _localOpen(uri: string, switchFocus?: boolean, windowName: string = 'newTYPO3frontendWindow', windowFeatures: string = ''): Window | null {
if (!uri) {
return null;
}
......
......@@ -28,7 +28,7 @@ describe('TYPO3/CMS/Backend/Element/ImmediateActionElement:', () => {
root = null;
});
it('dispatches action when created via constructor', () => {
it('dispatches action when created via constructor', async () => {
const backup = viewportObject.Topbar.refresh;
const observer = {
callback: (): void => {
......@@ -41,11 +41,12 @@ describe('TYPO3/CMS/Backend/Element/ImmediateActionElement:', () => {
element.setAttribute('action', 'TYPO3.Backend.Topbar.refresh');
expect(observer.callback).not.toHaveBeenCalled();
root.appendChild(element);
await import('TYPO3/CMS/Backend/Viewport');
expect(observer.callback).toHaveBeenCalled();
viewportObject.Topbar.refresh = backup;
});
it('dispatches action when created via createElement', () => {
it('dispatches action when created via createElement', async () => {
const backup = viewportObject.Topbar.refresh;
const observer = {
callback: (): void => {
......@@ -58,11 +59,12 @@ describe('TYPO3/CMS/Backend/Element/ImmediateActionElement:', () => {
element.setAttribute('action', 'TYPO3.Backend.Topbar.refresh');
expect(observer.callback).not.toHaveBeenCalled();
root.appendChild(element);
await import('TYPO3/CMS/Backend/Viewport');
expect(observer.callback).toHaveBeenCalled();
viewportObject.Topbar.refresh = backup;
});
it('dispatches action when created from string', () => {
it('dispatches action when created from string', async () => {
const backup = moduleMenuApp.App.refreshMenu;
const observer = {
callback: (): void => {
......@@ -74,11 +76,12 @@ describe('TYPO3/CMS/Backend/Element/ImmediateActionElement:', () => {
const element = document.createRange().createContextualFragment('<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>').querySelector('typo3-immediate-action');
expect(observer.callback).not.toHaveBeenCalled();
root.appendChild(element);
await import('TYPO3/CMS/Backend/ModuleMenu');
expect(observer.callback).toHaveBeenCalled();
moduleMenuApp.App.refreshMenu = backup;
});
it('dispatches action when created via innerHTML', () => {
it('dispatches action when created via innerHTML', async () => {
const backup = moduleMenuApp.App.refreshMenu;
const observer = {
callback: (): void => {
......@@ -88,6 +91,7 @@ describe('TYPO3/CMS/Backend/Element/ImmediateActionElement:', () => {
spyOn(observer, 'callback').and.callThrough();
moduleMenuApp.App.refreshMenu = observer.callback;
root.innerHTML = '<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>';
await import('TYPO3/CMS/Backend/ModuleMenu');
expect(observer.callback).toHaveBeenCalled();
moduleMenuApp.App.refreshMenu = backup;
});
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","TYPO3/CMS/Backend/ModuleMenu","TYPO3/CMS/Backend/Viewport","TYPO3/CMS/Backend/WindowManager","TYPO3/CMS/Backend/Utility"],(function(e,t,n,r,a,i){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ImmediateActionElement=void 0;class s extends HTMLElement{constructor(){super(...arguments),this.args=[]}static getDelegate(e){switch(e){case"TYPO3.ModuleMenu.App.refreshMenu":return n.App.refreshMenu.bind(n);case"TYPO3.Backend.Topbar.refresh":return r.Topbar.refresh.bind(r.Topbar);case"TYPO3.WindowManager.localOpen":return a.localOpen.bind(a);default:throw Error('Unknown action "'+e+'"')}}static get observedAttributes(){return["action","args","args-list"]}attributeChangedCallback(e,t,n){if("action"===e)this.action=n;else if("args"===e){const e=n.replace(/&quot;/g,'"'),t=JSON.parse(e);this.args=t instanceof Array?i.trimItems(t):[]}else if("args-list"===e){const e=n.split(",");this.args=i.trimItems(e)}}connectedCallback(){if(!this.action)throw new Error("Missing mandatory action attribute");s.getDelegate(this.action).apply(null,this.args)}}t.ImmediateActionElement=s,window.customElements.define("typo3-immediate-action",s)}));
\ No newline at end of file
var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),__importStar=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&__createBinding(t,e,n);return __setModuleDefault(t,e),t};define(["require","exports","TYPO3/CMS/Backend/Utility"],(function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.ImmediateActionElement=void 0;class r extends HTMLElement{constructor(){super(...arguments),this.args=[]}static async getDelegate(t){switch(t){case"TYPO3.ModuleMenu.App.refreshMenu":const n=await new Promise((t,n)=>{e(["TYPO3/CMS/Backend/ModuleMenu"],t,n)}).then(__importStar);return n.App.refreshMenu.bind(n);case"TYPO3.Backend.Topbar.refresh":const r=await new Promise((t,n)=>{e(["TYPO3/CMS/Backend/Viewport"],t,n)}).then(__importStar);return r.Topbar.refresh.bind(r.Topbar);case"TYPO3.WindowManager.localOpen":const i=await new Promise((t,n)=>{e(["TYPO3/CMS/Backend/WindowManager"],t,n)}).then(__importStar);return i.localOpen.bind(i);default:throw Error('Unknown action "'+t+'"')}}static get observedAttributes(){return["action","args","args-list"]}attributeChangedCallback(e,t,r){if("action"===e)this.action=r;else if("args"===e){const e=r.replace(/&quot;/g,'"'),t=JSON.parse(e);this.args=t instanceof Array?n.trimItems(t):[]}else if("args-list"===e){const e=r.split(",");this.args=n.trimItems(e)}}connectedCallback(){if(!this.action)throw new Error("Missing mandatory action attribute");r.getDelegate(this.action).then(e=>e.apply(null,this.args))}}t.ImmediateActionElement=r,window.customElements.define("typo3-immediate-action",r)}));
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports"],(function(n,o){"use strict";class t{constructor(){this.windows={},this.open=(...n)=>this.localOpen.apply(this,n),this.globalOpen=(...n)=>this.localOpen.apply(this,n)}localOpen(n,o,t="newTYPO3frontendWindow",e=""){if(!n)return null;null===o?o=!window.opener:void 0===o&&(o=!0);const i=this.windows[t];if((i instanceof Window&&!i.closed?i.location.href:null)===n)return i.location.reload(),i;const s=window.open(n,t,e);return this.windows[t]=s,o&&s.focus(),s}}const e=new t;return top.TYPO3.WindowManager||(top.document===window.document?top.TYPO3.WindowManager=e:top.TYPO3.WindowManager=new t),e}));
\ No newline at end of file
define(["require","exports"],(function(n,o){"use strict";class e{constructor(){this.windows={},this.open=(...n)=>this._localOpen.apply(this,n),this.globalOpen=(...n)=>this._localOpen.apply(this,n),this.localOpen=(n,o,e="newTYPO3frontendWindow",t="")=>this._localOpen(n,o,e,t)}_localOpen(n,o,e="newTYPO3frontendWindow",t=""){if(!n)return null;null===o?o=!window.opener:void 0===o&&(o=!0);const i=this.windows[e];if((i instanceof Window&&!i.closed?i.location.href:null)===n)return i.location.reload(),i;const s=window.open(n,e,t);return this.windows[e]=s,o&&s.focus(),s}}const t=new e;return top.TYPO3.WindowManager||(top.document===window.document?top.TYPO3.WindowManager=t:top.TYPO3.WindowManager=new e),t}));
\ No newline at end of file
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
define(["require","exports","TYPO3/CMS/Backend/Element/ImmediateActionElement","TYPO3/CMS/Backend/ModuleMenu","TYPO3/CMS/Backend/Viewport"],(function(e,a,t,c,n){"use strict";Object.defineProperty(a,"__esModule",{value:!0}),describe("TYPO3/CMS/Backend/Element/ImmediateActionElement:",()=>{let e;beforeEach(()=>{e=document.createElement("div"),document.body.appendChild(e)}),afterEach(()=>{e.remove(),e=null}),it("dispatches action when created via constructor",()=>{const a=n.Topbar.refresh,c={callback:()=>{}};spyOn(c,"callback").and.callThrough(),n.Topbar.refresh=c.callback;const l=new t.ImmediateActionElement;l.setAttribute("action","TYPO3.Backend.Topbar.refresh"),expect(c.callback).not.toHaveBeenCalled(),e.appendChild(l),expect(c.callback).toHaveBeenCalled(),n.Topbar.refresh=a}),it("dispatches action when created via createElement",()=>{const a=n.Topbar.refresh,t={callback:()=>{}};spyOn(t,"callback").and.callThrough(),n.Topbar.refresh=t.callback;const c=document.createElement("typo3-immediate-action");c.setAttribute("action","TYPO3.Backend.Topbar.refresh"),expect(t.callback).not.toHaveBeenCalled(),e.appendChild(c),expect(t.callback).toHaveBeenCalled(),n.Topbar.refresh=a}),it("dispatches action when created from string",()=>{const a=c.App.refreshMenu,t={callback:()=>{}};spyOn(t,"callback").and.callThrough(),c.App.refreshMenu=t.callback;const n=document.createRange().createContextualFragment('<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>').querySelector("typo3-immediate-action");expect(t.callback).not.toHaveBeenCalled(),e.appendChild(n),expect(t.callback).toHaveBeenCalled(),c.App.refreshMenu=a}),it("dispatches action when created via innerHTML",()=>{const a=c.App.refreshMenu,t={callback:()=>{}};spyOn(t,"callback").and.callThrough(),c.App.refreshMenu=t.callback,e.innerHTML='<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>',expect(t.callback).toHaveBeenCalled(),c.App.refreshMenu=a})})}));
\ No newline at end of file
var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,a,n){void 0===n&&(n=a),Object.defineProperty(e,n,{enumerable:!0,get:function(){return t[a]}})}:function(e,t,a,n){void 0===n&&(n=a),e[n]=t[a]}),__setModuleDefault=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),__importStar=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var a in e)"default"!==a&&Object.prototype.hasOwnProperty.call(e,a)&&__createBinding(t,e,a);return __setModuleDefault(t,e),t};define(["require","exports","TYPO3/CMS/Backend/Element/ImmediateActionElement","TYPO3/CMS/Backend/ModuleMenu","TYPO3/CMS/Backend/Viewport"],(function(e,t,a,n,c){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),describe("TYPO3/CMS/Backend/Element/ImmediateActionElement:",()=>{let t;beforeEach(()=>{t=document.createElement("div"),document.body.appendChild(t)}),afterEach(()=>{t.remove(),t=null}),it("dispatches action when created via constructor",async()=>{const n=c.Topbar.refresh,r={callback:()=>{}};spyOn(r,"callback").and.callThrough(),c.Topbar.refresh=r.callback;const o=new a.ImmediateActionElement;o.setAttribute("action","TYPO3.Backend.Topbar.refresh"),expect(r.callback).not.toHaveBeenCalled(),t.appendChild(o),await new Promise((t,a)=>{e(["TYPO3/CMS/Backend/Viewport"],t,a)}).then(__importStar),expect(r.callback).toHaveBeenCalled(),c.Topbar.refresh=n}),it("dispatches action when created via createElement",async()=>{const a=c.Topbar.refresh,n={callback:()=>{}};spyOn(n,"callback").and.callThrough(),c.Topbar.refresh=n.callback;const r=document.createElement("typo3-immediate-action");r.setAttribute("action","TYPO3.Backend.Topbar.refresh"),expect(n.callback).not.toHaveBeenCalled(),t.appendChild(r),await new Promise((t,a)=>{e(["TYPO3/CMS/Backend/Viewport"],t,a)}).then(__importStar),expect(n.callback).toHaveBeenCalled(),c.Topbar.refresh=a}),it("dispatches action when created from string",async()=>{const a=n.App.refreshMenu,c={callback:()=>{}};spyOn(c,"callback").and.callThrough(),n.App.refreshMenu=c.callback;const r=document.createRange().createContextualFragment('<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>').querySelector("typo3-immediate-action");expect(c.callback).not.toHaveBeenCalled(),t.appendChild(r),await new Promise((t,a)=>{e(["TYPO3/CMS/Backend/ModuleMenu"],t,a)}).then(__importStar),expect(c.callback).toHaveBeenCalled(),n.App.refreshMenu=a}),it("dispatches action when created via innerHTML",async()=>{const a=n.App.refreshMenu,c={callback:()=>{}};spyOn(c,"callback").and.callThrough(),n.App.refreshMenu=c.callback,t.innerHTML='<typo3-immediate-action action="TYPO3.ModuleMenu.App.refreshMenu"></typo3-immediate-action>',await new Promise((t,a)=>{e(["TYPO3/CMS/Backend/ModuleMenu"],t,a)}).then(__importStar),expect(c.callback).toHaveBeenCalled(),n.App.refreshMenu=a})})}));
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment