diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts b/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts new file mode 100644 index 0000000000000000000000000000000000000000..384e9d0b14aa26d0cd565874be63c6bf18de6f15 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/BackendException.ts @@ -0,0 +1,22 @@ +/* + * 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! + */ + +export class BackendException { + public readonly message: string; + public readonly code: number; + + constructor(message = '', code = 0) { + this.message = message; + this.code = code; + } +} diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..2aa583e544f4161659c5a8371fdbcbe38d5b166b --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ClientRequest.ts @@ -0,0 +1,25 @@ +/* + * 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! + */ + +import InteractionRequest = require('./InteractionRequest'); + +class ClientRequest extends InteractionRequest { + public readonly clientEvent: any; + + constructor(type: string, clientEvent: Event = null) { + super(type); + this.clientEvent = clientEvent; + } +} + +export = ClientRequest; diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts new file mode 100644 index 0000000000000000000000000000000000000000..845a00c7759648aa05cf892ddcd40480cb334dc3 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/Consumable.ts @@ -0,0 +1,20 @@ +/* + * 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! + */ + +import InteractionRequest = require('./InteractionRequest'); + +interface Consumable { + consume(interactionRequest: InteractionRequest): any; +} + +export = Consumable; diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts new file mode 100644 index 0000000000000000000000000000000000000000..3adfe53dd279826c1afbf05eab4b683269a18320 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/ConsumerScope.ts @@ -0,0 +1,55 @@ +/* + * 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! + */ + +import $ = require('jquery'); +import Consumable = require('./Consumable'); +import InteractionRequest = require('./InteractionRequest'); + +class ConsumerScope { + private consumers: Consumable[] = []; + + public getConsumers(): Consumable[] { + return this.consumers; + } + + public hasConsumer(consumer: Consumable): boolean { + return this.consumers.indexOf(consumer) !== -1; + } + + public attach(consumer: Consumable) { + if (!this.hasConsumer(consumer)) { + this.consumers.push(consumer); + } + } + + public detach(consumer: Consumable) { + this.consumers = this.consumers.filter( + (currentConsumer: Consumable) => currentConsumer !== consumer, + ); + } + + public invoke(request: InteractionRequest): any { + const deferreds: any[] = []; + this.consumers.forEach( + (consumer: Consumable) => { + const deferred: any = consumer.consume.call(consumer, request); + if (deferred) { + deferreds.push(deferred); + } + }, + ); + return ($ as any).when.apply($, deferreds); + } +} + +export = new ConsumerScope(); diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4b91971cf5ea1fe7e20b7b06291fc7234161dbb --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequest.ts @@ -0,0 +1,47 @@ +/* + * 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! + */ + +class InteractionRequest { + public readonly type: string; + public readonly parentRequest: InteractionRequest; + protected processed = false; + protected processedData: any = null; + + public get outerMostRequest(): InteractionRequest { + let request: InteractionRequest = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + } + return request; + } + + constructor(type: string, parentRequest: InteractionRequest = null) { + this.type = type; + this.parentRequest = parentRequest; + } + + public isProcessed(): boolean { + return this.processed; + } + + public getProcessedData(): any { + return this.processedData; + } + + public setProcessedData(processedData: any = null) { + this.processed = true; + this.processedData = processedData; + } +} + +export = InteractionRequest; diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2a1ca8ccb267b156877b716b9bd97d4b4aa7828 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestAssignment.ts @@ -0,0 +1,22 @@ +/* + * 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! + */ + +import InteractionRequest = require('./InteractionRequest'); + +interface InteractionRequestAssignment { + request: InteractionRequest; + // @todo Add type for jQuery.Deferred[] + deferreds: any[]; +} + +export = InteractionRequestAssignment; diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts new file mode 100644 index 0000000000000000000000000000000000000000..2334eb167c6f07b21f8c271dd1949362227a9e17 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/InteractionRequestMap.ts @@ -0,0 +1,76 @@ +/* + * 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! + */ + +import $ = require('jquery'); +import InteractionRequest = require('./InteractionRequest'); +import InteractionRequestAssignment = require('./InteractionRequestAssignment'); + +class InteractionRequestMap { + private assignments: InteractionRequestAssignment[] = []; + + public attachFor(request: InteractionRequest, deferred: any) { + let targetAssignment = this.getFor(request); + if (targetAssignment === null) { + targetAssignment = {request, deferreds: []} as InteractionRequestAssignment; + this.assignments.push(targetAssignment); + } + targetAssignment.deferreds.push(deferred); + } + + public detachFor(request: InteractionRequest) { + const targetAssignment = this.getFor(request); + this.assignments = this.assignments.filter( + (assignment: InteractionRequestAssignment) => assignment === targetAssignment, + ); + } + + public getFor(triggerEvent: InteractionRequest): InteractionRequestAssignment { + let targetAssignment: InteractionRequestAssignment = null; + this.assignments.some( + (assignment: InteractionRequestAssignment) => { + if (assignment.request === triggerEvent) { + targetAssignment = assignment; + return true; + } + return false; + }, + ); + return targetAssignment; + } + + public resolveFor(triggerEvent: InteractionRequest) { + const targetAssignment = this.getFor(triggerEvent); + if (targetAssignment === null) { + return false; + } + targetAssignment.deferreds.forEach( + (deferred: any) => deferred.resolve(), + ); + this.detachFor(triggerEvent); + return true; + } + + public rejectFor(triggerEvent: InteractionRequest) { + const targetAssignment = this.getFor(triggerEvent); + if (targetAssignment === null) { + return false; + } + targetAssignment.deferreds.forEach( + (deferred: any) => deferred.reject(), + ); + this.detachFor(triggerEvent); + return true; + } +} + +export = new InteractionRequestMap(); diff --git a/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts b/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts new file mode 100644 index 0000000000000000000000000000000000000000..433c6fd12886e42c2d4469cc086ac6cbadb87476 --- /dev/null +++ b/typo3/sysext/backend/Resources/Private/TypeScript/Event/TriggerRequest.ts @@ -0,0 +1,50 @@ +/* + * 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! + */ + +import InteractionRequest = require('./InteractionRequest'); + +class TriggerRequest extends InteractionRequest { + constructor(type: string, parentRequest: InteractionRequest = null) { + super(type, parentRequest); + } + + public concerns(ancestorRequest: InteractionRequest): boolean { + if (this === ancestorRequest) { + return true; + } + let request: InteractionRequest = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + if (request === ancestorRequest) { + return true; + } + } + return false; + } + + public concernsTypes(types: string[]): boolean { + if (types.indexOf(this.type) !== -1) { + return true; + } + let request: InteractionRequest = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + if (types.indexOf(request.type) !== -1) { + return true; + } + } + return false; + } +} + +export = TriggerRequest; diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js b/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js new file mode 100644 index 0000000000000000000000000000000000000000..3f752b124560a4568a8bcc7346145b21f0cd96ed --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/BackendException.js @@ -0,0 +1,26 @@ +/* + * 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 (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + var BackendException = (function () { + function BackendException(message, code) { + if (message === void 0) { message = ''; } + if (code === void 0) { code = 0; } + this.message = message; + this.code = code; + } + return BackendException; + }()); + exports.BackendException = BackendException; +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js index 94201460216bcb358cdf64c6e064895d81750660..999d9653813c62eebbc37486fe1e79f4a3490a2a 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/ContextMenuActions.js @@ -145,7 +145,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=1'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; @@ -153,7 +153,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][' + table + '%7C' + uid + ']=0'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; @@ -161,7 +161,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][' + table + '%7C' + uid + ']=1'+ '&CB[setCopyMode]=0'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js new file mode 100644 index 0000000000000000000000000000000000000000..1052a892163512e61bcc2947845daa35170f5bb9 --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ClientRequest.js @@ -0,0 +1,36 @@ +/* + * 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! + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +define(["require", "exports", "./InteractionRequest"], function (require, exports, InteractionRequest) { + "use strict"; + var ClientRequest = (function (_super) { + __extends(ClientRequest, _super); + function ClientRequest(type, clientEvent) { + if (clientEvent === void 0) { clientEvent = null; } + var _this = _super.call(this, type) || this; + _this.clientEvent = clientEvent; + return _this; + } + return ClientRequest; + }(InteractionRequest)); + return ClientRequest; +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js new file mode 100644 index 0000000000000000000000000000000000000000..fcabab445aeb0e5dd421a2916443631a558bd7b4 --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/Consumable.js @@ -0,0 +1,16 @@ +/* + * 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 (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js new file mode 100644 index 0000000000000000000000000000000000000000..440cab88b6b00aca273387b905b9503c6176960f --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/ConsumerScope.js @@ -0,0 +1,46 @@ +/* + * 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", "jquery"], function (require, exports, $) { + "use strict"; + var ConsumerScope = (function () { + function ConsumerScope() { + this.consumers = []; + } + ConsumerScope.prototype.getConsumers = function () { + return this.consumers; + }; + ConsumerScope.prototype.hasConsumer = function (consumer) { + return this.consumers.indexOf(consumer) !== -1; + }; + ConsumerScope.prototype.attach = function (consumer) { + if (!this.hasConsumer(consumer)) { + this.consumers.push(consumer); + } + }; + ConsumerScope.prototype.detach = function (consumer) { + this.consumers = this.consumers.filter(function (currentConsumer) { return currentConsumer !== consumer; }); + }; + ConsumerScope.prototype.invoke = function (request) { + var deferreds = []; + this.consumers.forEach(function (consumer) { + var deferred = consumer.consume.call(consumer, request); + if (deferred) { + deferreds.push(deferred); + } + }); + return $.when.apply($, deferreds); + }; + return ConsumerScope; + }()); + return new ConsumerScope(); +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js new file mode 100644 index 0000000000000000000000000000000000000000..1b726f5353ea7d2f07fe950476f3107c2a063dde --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequest.js @@ -0,0 +1,48 @@ +/* + * 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 (require, exports) { + "use strict"; + var InteractionRequest = (function () { + function InteractionRequest(type, parentRequest) { + if (parentRequest === void 0) { parentRequest = null; } + this.processed = false; + this.processedData = null; + this.type = type; + this.parentRequest = parentRequest; + } + Object.defineProperty(InteractionRequest.prototype, "outerMostRequest", { + get: function () { + var request = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + } + return request; + }, + enumerable: true, + configurable: true + }); + InteractionRequest.prototype.isProcessed = function () { + return this.processed; + }; + InteractionRequest.prototype.getProcessedData = function () { + return this.processedData; + }; + InteractionRequest.prototype.setProcessedData = function (processedData) { + if (processedData === void 0) { processedData = null; } + this.processed = true; + this.processedData = processedData; + }; + return InteractionRequest; + }()); + return InteractionRequest; +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js new file mode 100644 index 0000000000000000000000000000000000000000..fcabab445aeb0e5dd421a2916443631a558bd7b4 --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestAssignment.js @@ -0,0 +1,16 @@ +/* + * 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 (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js new file mode 100644 index 0000000000000000000000000000000000000000..9983ef9423a64aeca35915b25195bfdc6506f7f3 --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/InteractionRequestMap.js @@ -0,0 +1,63 @@ +/* + * 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 (require, exports) { + "use strict"; + var InteractionRequestMap = (function () { + function InteractionRequestMap() { + this.assignments = []; + } + InteractionRequestMap.prototype.attachFor = function (request, deferred) { + var targetAssignment = this.getFor(request); + if (targetAssignment === null) { + targetAssignment = { request: request, deferreds: [] }; + this.assignments.push(targetAssignment); + } + targetAssignment.deferreds.push(deferred); + }; + InteractionRequestMap.prototype.detachFor = function (request) { + var targetAssignment = this.getFor(request); + this.assignments = this.assignments.filter(function (assignment) { return assignment === targetAssignment; }); + }; + InteractionRequestMap.prototype.getFor = function (triggerEvent) { + var targetAssignment = null; + this.assignments.some(function (assignment) { + if (assignment.request === triggerEvent) { + targetAssignment = assignment; + return true; + } + return false; + }); + return targetAssignment; + }; + InteractionRequestMap.prototype.resolveFor = function (triggerEvent) { + var targetAssignment = this.getFor(triggerEvent); + if (targetAssignment === null) { + return false; + } + targetAssignment.deferreds.forEach(function (deferred) { return deferred.resolve(); }); + this.detachFor(triggerEvent); + return true; + }; + InteractionRequestMap.prototype.rejectFor = function (triggerEvent) { + var targetAssignment = this.getFor(triggerEvent); + if (targetAssignment === null) { + return false; + } + targetAssignment.deferreds.forEach(function (deferred) { return deferred.reject(); }); + this.detachFor(triggerEvent); + return true; + }; + return InteractionRequestMap; + }()); + return new InteractionRequestMap(); +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js b/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js new file mode 100644 index 0000000000000000000000000000000000000000..28d91dfb16c680d52b8403ec555779beb047af33 --- /dev/null +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Event/TriggerRequest.js @@ -0,0 +1,60 @@ +/* + * 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! + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +define(["require", "exports", "./InteractionRequest"], function (require, exports, InteractionRequest) { + "use strict"; + var TriggerRequest = (function (_super) { + __extends(TriggerRequest, _super); + function TriggerRequest(type, parentRequest) { + if (parentRequest === void 0) { parentRequest = null; } + return _super.call(this, type, parentRequest) || this; + } + TriggerRequest.prototype.concerns = function (ancestorRequest) { + if (this === ancestorRequest) { + return true; + } + var request = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + if (request === ancestorRequest) { + return true; + } + } + return false; + }; + TriggerRequest.prototype.concernsTypes = function (types) { + if (types.indexOf(this.type) !== -1) { + return true; + } + var request = this; + while (request.parentRequest instanceof InteractionRequest) { + request = request.parentRequest; + if (types.indexOf(request.type) !== -1) { + return true; + } + } + return false; + }; + return TriggerRequest; + }(InteractionRequest)); + return TriggerRequest; +}); diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js index edf73ce6aa0cb2d783e3ea97344ef4993534d2c8..a88a42f6b6c569300c70162cfad1265bf89fd9c3 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js @@ -33,16 +33,30 @@ var setFormValueOpenBrowser, define(['jquery', 'TYPO3/CMS/Backend/FormEngineValidation', 'TYPO3/CMS/Backend/Modal', - 'TYPO3/CMS/Backend/Severity' - ], function ($, FormEngineValidation, Modal, Severity) { + 'TYPO3/CMS/Backend/Severity', + 'TYPO3/CMS/Backend/BackendException', + 'TYPO3/CMS/Backend/Event/InteractionRequestMap' + ], function ($, FormEngineValidation, Modal, Severity, BackendException, InteractionRequestMap) { + + /** + * @param {InteractionRequest} interactionRequest + * @param {boolean} response + */ + function handleConsumeResponse(interactionRequest, response) { + if (response) { + FormEngine.interactionRequestMap.resolveFor(interactionRequest); + } else { + FormEngine.interactionRequestMap.rejectFor(interactionRequest); + } + } /** - * - * @type {{Validation: object, formName: *, openedPopupWindow: window, legacyFieldChangedCb: Function, browserUrl: string}} * @exports TYPO3/CMS/Backend/FormEngine */ var FormEngine = { + consumeTypes: ['typo3.setUrl', 'typo3.beforeSetUrl', 'typo3.refresh'], Validation: FormEngineValidation, + interactionRequestMap: InteractionRequestMap, formName: TYPO3.settings.FormEngine.formName, openedPopupWindow: null, legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); }, @@ -67,7 +81,6 @@ define(['jquery', FormEngine.openedPopupWindow.focus(); }; - /** * properly fills the select field from the popup window (element browser, link browser) * or from a multi-select (two selects side-by-side) @@ -581,6 +594,12 @@ define(['jquery', * as it using deferrer methods only */ FormEngine.initializeEvents = function() { + if (top.TYPO3 && typeof top.TYPO3.Backend !== 'undefined') { + top.TYPO3.Backend.consumerScope.attach(FormEngine); + $(window).on('unload', function() { + top.TYPO3.Backend.consumerScope.detach(FormEngine); + }); + } $(document).on('click', '.t3js-btn-moveoption-top, .t3js-btn-moveoption-up, .t3js-btn-moveoption-down, .t3js-btn-moveoption-bottom, .t3js-btn-removeoption', function(evt) { evt.preventDefault(); @@ -626,7 +645,9 @@ define(['jquery', } }).on('click', '.t3js-editform-close', function(e) { e.preventDefault(); - FormEngine.preventExitIfNotSaved(); + FormEngine.preventExitIfNotSaved( + FormEngine.preventExitIfNotSavedCallback + ); }).on('click', '.t3js-editform-delete-record', function(e) { e.preventDefault(); var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?'; @@ -727,6 +748,45 @@ define(['jquery', }); }; + /** + * @param {InteractionRequest} interactionRequest + * @return {jQuery.Deferred} + */ + FormEngine.consume = function(interactionRequest) { + if (!interactionRequest) { + throw new BackendException('No interaction request given', 1496589980); + } + if (interactionRequest.concernsTypes(FormEngine.consumeTypes)) { + var outerMostRequest = interactionRequest.outerMostRequest; + var deferred = $.Deferred(); + + FormEngine.interactionRequestMap.attachFor( + outerMostRequest, + deferred + ); + // resolve or reject deferreds with previous user choice + if (outerMostRequest.isProcessed()) { + handleConsumeResponse( + outerMostRequest, + outerMostRequest.getProcessedData().response + ); + // show confirmation dialog + } else if (FormEngine.hasChange()) { + FormEngine.preventExitIfNotSaved(function(response) { + outerMostRequest.setProcessedData( + {response: response} + ); + handleConsumeResponse(outerMostRequest, response); + }); + // resolve directly + } else { + FormEngine.interactionRequestMap.resolveFor(outerMostRequest); + } + + return deferred; + } + }; + /** * Initializes the remaining character views based on the fields' maxlength attribute */ @@ -1052,10 +1112,30 @@ define(['jquery', }; /** - * Show modal to confirm closing the document without saving + * @return {boolean} */ - FormEngine.preventExitIfNotSaved = function() { - if ($('form[name="' + FormEngine.formName + '"] .has-change').length > 0) { + FormEngine.hasChange = function() { + return $('form[name="' + FormEngine.formName + '"] .has-change').length > 0; + }; + + /** + * @param {boolean} response + */ + FormEngine.preventExitIfNotSavedCallback = function(response) { + if (response) { + FormEngine.closeDocument(); + } + }; + + /** + * Show modal to confirm closing the document without saving. + * + * @param {Function} callback + */ + FormEngine.preventExitIfNotSaved = function(callback) { + callback = callback || FormEngine.preventExitIfNotSavedCallback; + + if (FormEngine.hasChange()) { var title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to quit without saving?'; var content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You have currently unsaved changes. Are you sure that you want to discard all changes?'; var $modal = Modal.confirm(title, content, Severity.warning, [ @@ -1074,13 +1154,14 @@ define(['jquery', $modal.on('button.clicked', function(e) { if (e.target.name === 'no') { Modal.dismiss(); + callback.call(null, false); } else if (e.target.name === 'yes') { Modal.dismiss(); - FormEngine.closeDocument(); + callback.call(null, true); } }); } else { - FormEngine.closeDocument(); + callback.call(null, true); } }; diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js index 5b226158bfd05045e2fe1fdd81f8d59862a8c520..480585f44aa2cf3e42d6ec7978127984de893dca 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/ModuleMenu.js @@ -20,12 +20,15 @@ require( 'jquery', 'TYPO3/CMS/Backend/Storage', 'TYPO3/CMS/Backend/Icons', - 'TYPO3/CMS/Backend/Viewport' + 'TYPO3/CMS/Backend/Viewport', + 'TYPO3/CMS/Backend/Event/ClientRequest', + 'TYPO3/CMS/Backend/Event/TriggerRequest' ], - function ($, Storage, Icons) { + function ($, Storage, Icons, Viewport, ClientRequest, TriggerRequest) { if (typeof TYPO3.ModuleMenu !== 'undefined') { return TYPO3.ModuleMenu.App; } + TYPO3.ModuleMenu = {}; TYPO3.ModuleMenu.App = { loadedModule: null, @@ -35,42 +38,52 @@ require( initialize: function () { var me = this; + var deferred = $.Deferred(); + deferred.resolve(); + // load the start module if (top.startInModule && top.startInModule[0] && $('#' + top.startInModule[0]).length > 0) { - me.showModule(top.startInModule[0], top.startInModule[1]); + deferred = me.showModule( + top.startInModule[0], + top.startInModule[1] + ); } else { // fetch first module if ($('.t3js-mainmodule:first').attr('id')) { - me.showModule($('.t3js-mainmodule:first').attr('id')); + deferred = me.showModule( + $('.t3js-mainmodule:first').attr('id') + ); } // else case: the main module has no entries, this is probably a backend // user with very little access rights, maybe only the logout button and // a user settings module in topbar. } - // check if module menu should be collapsed or not - var state = Storage.Persistent.get('BackendComponents.States.typo3-module-menu'); - if (state && state.collapsed) { - TYPO3.ModuleMenu.App.toggleMenu(state.collapsed === 'true'); - } - - // check if there are collapsed items in the users' configuration - var collapsedMainMenuItems = me.getCollapsedMainMenuItems(); - $.each(collapsedMainMenuItems, function (key, itm) { - if (itm !== true) { - return; - } - var $group = $('#' + key); - if ($group.length > 0) { - var $groupContainer = $group.find('.modulemenu-group-container'); - $group.addClass('collapsed').removeClass('expanded'); - TYPO3.Backend.NavigationContainer.cleanup(); - $groupContainer.hide().promise().done(function () { - TYPO3.Backend.doLayout(); - }); + deferred.then(function() { + // check if module menu should be collapsed or not + var state = Storage.Persistent.get('BackendComponents.States.typo3-module-menu'); + if (state && state.collapsed) { + TYPO3.ModuleMenu.App.toggleMenu(state.collapsed === 'true'); } + + // check if there are collapsed items in the users' configuration + var collapsedMainMenuItems = me.getCollapsedMainMenuItems(); + $.each(collapsedMainMenuItems, function (key, itm) { + if (itm !== true) { + return; + } + var $group = $('#' + key); + if ($group.length > 0) { + var $groupContainer = $group.find('.modulemenu-group-container'); + $group.addClass('collapsed').removeClass('expanded'); + TYPO3.Backend.NavigationContainer.cleanup(); + $groupContainer.hide().promise().done(function () { + TYPO3.Backend.doLayout(); + }); + } + }); + me.initializeEvents(); }); - me.initializeEvents(); }, initializeEvents: function () { @@ -98,8 +111,11 @@ require( // register clicking on sub modules $(document).on('click', '.modulemenu-item,.t3-menuitem-submodule', function (evt) { evt.preventDefault(); - me.showModule($(this).attr('id')); - TYPO3.Backend.doLayout(); + me.showModule( + $(this).attr('id'), + null, + evt + ); }); $(document).on('click', '.t3js-topbar-button-modulemenu', function (evt) { @@ -163,33 +179,71 @@ require( }; }, - showModule: function (mod, params) { + /** + * @param {string} mod + * @param {string} params + * @param {Event} [event] + * @return {jQuery.Deferred} + */ + showModule: function (mod, params, event) { params = params || ''; params = this.includeId(mod, params); var record = this.getRecordFromName(mod); - this.loadModuleComponents(record, params); + return this.loadModuleComponents( + record, + params, + new ClientRequest('typo3.showModule', event) + ); }, - loadModuleComponents: function (record, params) { + /** + * @param {object} record + * @param {string} params + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + loadModuleComponents: function (record, params, interactionRequest) { var mod = record.name; - if (record.navigationComponentId) { - this.loadNavigationComponent(record.navigationComponentId); - } else if (record.navigationFrameScript) { - TYPO3.Backend.NavigationContainer.show('typo3-navigationIframe'); - this.openInNavFrame(record.navigationFrameScript, record.navigationFrameScriptParam); - } else { - TYPO3.Backend.NavigationContainer.hide(); - } - - this.highlightModuleMenuItem(mod); - this.loadedModule = mod; - this.openInContentFrame(record.link, params); - - // compatibility - top.currentSubScript = record.link; - top.currentModuleLoaded = mod; - TYPO3.Backend.doLayout(); + var deferred = TYPO3.Backend.ContentContainer.beforeSetUrl(interactionRequest); + deferred.then( + $.proxy(function() { + if (record.navigationComponentId) { + this.loadNavigationComponent(record.navigationComponentId); + } else if (record.navigationFrameScript) { + TYPO3.Backend.NavigationContainer.show('typo3-navigationIframe'); + this.openInNavFrame( + record.navigationFrameScript, + record.navigationFrameScriptParam, + new TriggerRequest( + 'typo3.loadModuleComponents', + interactionRequest + ) + ); + } else { + TYPO3.Backend.NavigationContainer.hide(); + } + + this.highlightModuleMenuItem(mod); + this.loadedModule = mod; + this.openInContentFrame( + record.link, + params, + new TriggerRequest( + 'typo3.loadModuleComponents', + interactionRequest + ) + ); + + // compatibility + top.currentSubScript = record.link; + top.currentModuleLoaded = mod; + + TYPO3.Backend.doLayout(); + }, this + )); + + return deferred; }, includeId: function (mod, params) { @@ -231,23 +285,55 @@ require( this.availableNavigationComponents[componentId] = initCallback; }, - openInNavFrame: function (url, params) { + /** + * @param {string} url + * @param {string} params + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + openInNavFrame: function (url, params, interactionRequest) { var navUrl = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : ''); var currentUrl = TYPO3.Backend.NavigationContainer.getUrl(); + var deferred = TYPO3.Backend.NavigationContainer.setUrl( + url, + new TriggerRequest('typo3.openInNavFrame', interactionRequest) + ); if (currentUrl !== navUrl) { - TYPO3.Backend.NavigationContainer.refresh(); + // if deferred is already resolved, execute directly + if (deferred.state() === 'resolved') { + TYPO3.Backend.NavigationContainer.refresh(); + // otherwise hand in future callback + } else { + deferred.then(TYPO3.Backend.NavigationContainer.refresh); + } } - TYPO3.Backend.NavigationContainer.setUrl(url); + return deferred; }, - openInContentFrame: function (url, params) { + /** + * @param {string} url + * @param {string} params + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + openInContentFrame: function (url, params, interactionRequest) { + var deferred; + if (top.nextLoadModuleUrl) { - TYPO3.Backend.ContentContainer.setUrl(top.nextLoadModuleUrl); + deferred = TYPO3.Backend.ContentContainer.setUrl( + top.nextLoadModuleUrl, + new TriggerRequest('typo3.openInContentFrame', interactionRequest) + ); top.nextLoadModuleUrl = ''; } else { var urlToLoad = url + (params ? (url.indexOf('?') !== -1 ? '&' : '?') + params : ''); - TYPO3.Backend.ContentContainer.setUrl(urlToLoad); + deferred = TYPO3.Backend.ContentContainer.setUrl( + urlToLoad, + new TriggerRequest('typo3.openInContentFrame', interactionRequest) + ); } + + return deferred; }, highlightModuleMenuItem: function (module, mainModule) { diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js index afb2f50d634ab7da03938a1981fe6a91090cbb46..7b6aecc00fd2d877b0cea1f2a421a7a5df602574 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport.js @@ -21,11 +21,26 @@ define( [ 'jquery', 'TYPO3/CMS/Backend/Icons', + 'TYPO3/CMS/Backend/Event/ConsumerScope', + 'TYPO3/CMS/Backend/Event/TriggerRequest' ], - function ($, Icons) { + function ($, Icons, ConsumerScope, TriggerRequest) { 'use strict'; + function resolveIFrameElement() { + var $iFrame = $('.t3js-scaffold-content-module-iframe:first'); + if ($iFrame.length === 0) { + return null; + } + return $iFrame.get(0); + } + TYPO3.Backend = { + /** + * @type {ConsumerScope} + */ + consumerScope: ConsumerScope, + initialize: function() { TYPO3.Backend.doLayout(); $(window).on('resize', TYPO3.Backend.doLayout); @@ -88,15 +103,29 @@ define( $('.t3js-scaffold-content-navigation [data-component]').hide(); $('.t3js-scaffold-content-navigation [data-component=' + component + ']').show(); }, - setUrl: function(urlToLoad) { - $('.t3js-scaffold').addClass('scaffold-content-navigation-expanded'); - $('.t3js-scaffold-content-navigation-iframe').attr('src', urlToLoad); + /** + * @param {string} urlToLoad + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + setUrl: function(urlToLoad, interactionRequest) { + var deferred = TYPO3.Backend.consumerScope.invoke( + new TriggerRequest('typo3.setUrl', interactionRequest) + ); + deferred.then(function() { + $('.t3js-scaffold').addClass('scaffold-content-navigation-expanded'); + $('.t3js-scaffold-content-navigation-iframe').attr('src', urlToLoad); + }); + return deferred; }, getUrl: function() { return $('.t3js-scaffold-content-navigation-iframe').attr('src'); }, - refresh: function() { - $('.t3js-scaffold-content-navigation-iframe')[0].contentWindow.location.reload(); + /** + * @param {boolean} forceGet + */ + refresh: function(forceGet) { + $('.t3js-scaffold-content-navigation-iframe')[0].contentWindow.location.reload(forceGet); }, calculateScrollbar: function (){ TYPO3.Backend.NavigationContainer.cleanup(); @@ -125,19 +154,66 @@ define( get: function() { return $('.t3js-scaffold-content-module-iframe')[0].contentWindow; }, - setUrl: function (urlToLoad) { - TYPO3.Backend.Loader.start(); - $('.t3js-scaffold-content-module-iframe') - .attr('src', urlToLoad) - .one('load', function() { - TYPO3.Backend.Loader.finish(); - }); + /** + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + beforeSetUrl: function(interactionRequest) { + return TYPO3.Backend.consumerScope.invoke( + new TriggerRequest('typo3.beforeSetUrl', interactionRequest) + ); + }, + /** + * @param {String} urlToLoad + * @param {InteractionRequest} [interactionRequest] + * @return {jQuery.Deferred} + */ + setUrl: function (urlToLoad, interactionRequest) { + var deferred; + var iFrame = resolveIFrameElement(); + // abort, if no IFRAME can be found + if (iFrame === null) { + deferred = $.Deferred(); + deferred.reject(); + return deferred; + } + deferred = TYPO3.Backend.consumerScope.invoke( + new TriggerRequest('typo3.setUrl', interactionRequest) + ); + deferred.then(function() { + TYPO3.Backend.Loader.start(); + $('.t3js-scaffold-content-module-iframe') + .attr('src', urlToLoad) + .one('load', function() { + TYPO3.Backend.Loader.finish(); + }); + }); + return deferred; }, getUrl: function() { return $('.t3js-scaffold-content-module-iframe').attr('src'); }, - refresh: function() { - $('.t3js-scaffold-content-module-iframe')[0].contentWindow.location.reload(); + /** + * @param {boolean} forceGet + * @param {InteractionRequest} interactionRequest + * @return {jQuery.Deferred} + */ + refresh: function(forceGet, interactionRequest) { + var deferred; + var iFrame = resolveIFrameElement(); + // abort, if no IFRAME can be found + if (iFrame === null) { + deferred = $.Deferred(); + deferred.reject(); + return deferred; + } + deferred = TYPO3.Backend.consumerScope.invoke( + new TriggerRequest('typo3.refresh', interactionRequest) + ); + deferred.then(function() { + iFrame.contentWindow.location.reload(forceGet); + }); + return deferred; }, getIdFromUrl: function() { if(this.getUrl) { diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js b/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js index fbba6a3adcd4e8b0b0f663588866e82c3fb670e9..3e640de39205efd2a13b186774f6d7060d4b0f61 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/extjs/components/pagetree/javascript/actions.js @@ -313,7 +313,6 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ editPageProperties: function(node) { - node.select(); var returnUrl = TYPO3.Backend.ContentContainer.getUrl(); if (returnUrl.indexOf('returnUrl') !== -1) { returnUrl = TYPO3.Utility.getParameterFromUrl(returnUrl, 'returnUrl'); @@ -329,6 +328,8 @@ TYPO3.Components.PageTree.Actions = { TYPO3.Backend.ContentContainer.setUrl( TYPO3.settings.FormEngine.moduleUrl + '&edit[pages][' + node.attributes.nodeData.id + ']=edit&returnUrl=' + returnUrl + ).then( + node.select ); }, @@ -339,9 +340,10 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ newPageWizard: function(node) { - node.select(); TYPO3.Backend.ContentContainer.setUrl( TYPO3.settings.NewRecord.moduleUrl + '&id=' + node.attributes.nodeData.id + '&pagesOnly=1' + ).then( + node.select ); }, @@ -362,9 +364,10 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ openHistoryPopUp: function(node) { - node.select(); TYPO3.Backend.ContentContainer.setUrl( TYPO3.settings.RecordHistory.moduleUrl + '&element=pages:' + node.attributes.nodeData.id + ).then( + node.select ); }, @@ -375,13 +378,14 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ exportT3d: function(node) { - node.select(); TYPO3.Backend.ContentContainer.setUrl( TYPO3.settings.ImportExport.moduleUrl + '&tx_impexp[action]=export&' + 'id=0&tx_impexp[pagetree][id]=' + node.attributes.nodeData.id + '&tx_impexp[pagetree][levels]=0' + '&tx_impexp[pagetree][tables][]=_ALL' + ).then( + node.select ); }, @@ -392,11 +396,12 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ importT3d: function(node) { - node.select(); TYPO3.Backend.ContentContainer.setUrl( TYPO3.settings.ImportExport.moduleUrl + '&id=' + node.attributes.nodeData.id + '&table=pages&tx_impexp[action]=import' + ).then( + node.select ); }, @@ -709,24 +714,22 @@ TYPO3.Components.PageTree.Actions = { * @return {void} */ singleClick: function(node, tree) { - tree.currentSelectedNode = node; - var separator = '?'; if (currentSubScript.indexOf('?') !== -1) { separator = '&'; } - node.select(); - if (tree.stateHash) { - tree.stateHash.lastSelectedNode = node.id; - } - - fsMod.recentIds['web'] = node.attributes.nodeData.id; - fsMod.recentIds['system'] = node.attributes.nodeData.id; - TYPO3.Backend.ContentContainer.setUrl( currentSubScript + separator + 'id=' + node.attributes.nodeData.id - ); + ).then(function() { + node.select(); + tree.currentSelectedNode = node; + if (tree.stateHash) { + tree.stateHash.lastSelectedNode = node.id; + } + fsMod.recentIds['web'] = node.attributes.nodeData.id; + fsMod.recentIds['system'] = node.attributes.nodeData.id; + }); }, /** @@ -742,13 +745,14 @@ TYPO3.Components.PageTree.Actions = { return; } - node.select(); var nodeId = node.attributes.nodeData.id, idPattern = '###ID###'; TYPO3.Backend.ContentContainer.setUrl( contextItem.customAttributes.contentUrl .replace(idPattern, nodeId) .replace(encodeURIComponent(idPattern), nodeId) + ).then( + node.select ); }, diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst new file mode 100644 index 0000000000000000000000000000000000000000..3ba07876e1269a634305fd858f6f51e63dd7a977 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-77268-IntroduceJavaScriptTriggerRequestAPI.rst @@ -0,0 +1,97 @@ +.. include:: ../../Includes.txt + +========================================================== +Feature: #77268 - Introduce JavaScript trigger request API +========================================================== + +See :issue:`77268` + +Description +=========== + +JavaScript event handling the backend of the TYPO3 core is based on the optimistic +assumption, that most executions can be executed sequentially and are processed +just in time. This concept does not consider the fact that other nested components +can defer the execution based on additional user input e.g. as used in confirmation +dialogs. + +That's why a trigger request API is introduced to first inform dependent components +about a planned action which will defer the regular execution based on specific +application state logic of registered components. In the current implementation, +FormEngine's edit forms register themselves to be notified, thus accidentally +closing modified forms by clicking e.g. the module menu any other page in the +page tree can be handled. + +Registering component +~~~~~~~~~~~~~~~~~~~~~ + +The following code attaches or detaches a particular component (a **consumer**) +to be notified. + +.. code-block:: javascript + + // FormEngine must implement the Consumable interface, + // thus having a function named consume(interactionRequest) + top.TYPO3.Backend.consumerScope.attach(FormEngine); + top.TYPO3.Backend.consumerScope.detach(FormEngine); + +Invoking consumers +~~~~~~~~~~~~~~~~~~ + +Registered consumers are invoked with a specific interaction request that has a +defined action type and optionally additional information about the parent call +(e.g. some client event issued by users). Invocations return a jQuery.Deferred() +object that resolves when no consumers are registered or every consumer sends a +resolve command as well - if only one consumer rejects, the collective invocation +promise is rejected as well. + +.. code-block:: javascript + + var deferred = TYPO3.Backend.consumerScope.invoke( + new TriggerRequest('typo3.setUrl', interactionRequest) + ); + deferred + .then(function() { console.log('consumers are resolved'); }) + .fail(function() { console.log('some consumer was rejected'); }); + +Creating interaction requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently there are two types of reuqests, `ClientRequest` that is based on some +client event (e.g. `click` event) and `TriggerRequest` which may be based on some +parent request of type `InteractionRequest` - this is used to cascade actions. + +.. code-block:: javascript + + var clickRequest = new ClientRequest('typo3.showModule', event); + var triggerRequestA = new TriggerRequest('typo3.a', clickRequest); + var triggerRequestB = new TriggerRequest('typo3.b', triggerRequestA); + +In the example `triggerRequestB` has all information from the initial click +event down to the specific `typo3.b` action type. The first request can be +resolved from the most specific request by `triggerRequestB.outerMostRequest` +and will return `clickRequest` in this case. + +Working with interaction requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ++ `triggerRequestB.concerns(clickRequest)` checks whether `clickRequest` is an + ancestor request in the cascade of `triggerRequestB` (which is true, based on + the previous example) ++ `triggerRequestB.concernsType('typo3.showModule')` checks whether `typo3.showModule` + is the type of some ancestor request in the cascade of `triggerRequestB` (which + is true, based on the previous example) ++ `triggerRequestB.outerMostRequest.setProcessedData({response: true})` sets the + property evaluated by `clickRequest.isProcessed()` to `true` and stores any + custom user response (e.g. from some confirmation dialog) at the outer-most + interaction request + +Impact +====== + +Using interaction requests requires some modifications in the JavaScript processing +logic which changes from sequential processing to possibly deferred asynchronous +processing. This is required since e.g. user input is required first to be able +to continue the processing. The created promises are based on `jQuery.Deferred`. + +.. index:: Backend, JavaScript diff --git a/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js b/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js index 1cf258657ba84cf4c6f1b769407a390479e8cacb..799bb6615a4f668873db9165d48fc49e4a87cd4d 100644 --- a/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js +++ b/typo3/sysext/filelist/Resources/Public/JavaScript/ContextMenuActions.js @@ -109,7 +109,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][_FILE%7C' + shortMD5 + ']=' + top.rawurlencode(uid) + '&CB[setCopyMode]=1'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; @@ -118,7 +118,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][_FILE%7C' + shortMD5 + ']=0&CB[setCopyMode]=1'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; @@ -127,7 +127,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][_FILE%7C' + shortMD5 + ']=' + top.rawurlencode(uid); $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); }; @@ -136,7 +136,7 @@ define(['jquery', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Severity'], func var url = TYPO3.settings.ajaxUrls['contextmenu_clipboard']; url += '&CB[el][_FILE%7C' + shortMD5 + ']=0'; $.ajax(url).always(function () { - top.list_frame.location.reload(true); + top.TYPO3.Backend.ContentContainer.refresh(true); }); };