Commit e13e3047 authored by Andreas Fernandez's avatar Andreas Fernandez Committed by Daniel Goerz
Browse files

[TASK] Install Tool: Move action buttons into modal footer

Resolves: #88523
Releases: master
Change-Id: I26404c120702bac669b3ad3aaf9280a76eb8f0e2
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60899


Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Daniel Goerz's avatarDaniel Goerz <daniel.goerz@posteo.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Daniel Goerz's avatarDaniel Goerz <daniel.goerz@posteo.de>
parent e4cc43a4
......@@ -279,6 +279,56 @@ class Modal {
return this.generate(<Configuration>configuration);
}
/**
* Sets action buttons for the modal window or removed the footer, if no buttons are given.
*
* @param {Array<Button>} buttons
*/
public setButtons(buttons: Array<Button>): JQuery {
if (buttons.length > 0) {
this.currentModal.find(Identifiers.footer).empty();
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i];
const $button = $('<button />', {'class': 'btn'});
$button.html('<span>' + this.securityUtility.encodeHtml(button.text, false) + '</span>');
if (button.active) {
$button.addClass('t3js-active');
}
if (button.btnClass !== '') {
$button.addClass(button.btnClass);
}
if (button.name !== '') {
$button.attr('name', button.name);
}
if (button.trigger) {
$button.on('click', button.trigger);
}
if (button.dataAttributes) {
if (Object.keys(button.dataAttributes).length > 0) {
Object.keys(button.dataAttributes).map((value: string): any => {
$button.attr('data-' + value, button.dataAttributes[value]);
});
}
}
if (button.icon) {
$button.prepend('<span class="t3js-modal-icon-placeholder" data-icon="' + button.icon + '"></span>');
}
this.currentModal.find(Identifiers.footer).append($button);
}
this.currentModal.find(Identifiers.footer).show();
this.currentModal
.find(Identifiers.footer).find('button')
.on('click', (e: JQueryEventObject): void => {
$(e.currentTarget).trigger('button.clicked');
});
} else {
this.currentModal.find(Identifiers.footer).hide();
}
return this.currentModal;
}
/**
* Initialize markup with data attributes
*
......@@ -386,45 +436,6 @@ class Modal {
currentModal.find(Identifiers.body).append(configuration.content);
}
// Add buttons
if (configuration.buttons.length > 0) {
for (let i = 0; i < configuration.buttons.length; i++) {
const button = configuration.buttons[i];
const $button = $('<button />', {'class': 'btn'});
$button.html('<span>' + this.securityUtility.encodeHtml(button.text, false) + '</span>');
if (button.active) {
$button.addClass('t3js-active');
}
if (button.btnClass !== '') {
$button.addClass(button.btnClass);
}
if (button.name !== '') {
$button.attr('name', button.name);
}
if (button.trigger) {
$button.on('click', button.trigger);
}
if (button.dataAttributes) {
if (Object.keys(button.dataAttributes).length > 0) {
Object.keys(button.dataAttributes).map((value: string): any => {
$button.attr('data-' + value, button.dataAttributes[value]);
});
}
}
if (button.icon) {
$button.prepend('<span class="t3js-modal-icon-placeholder" data-icon="' + button.icon + '"></span>');
}
currentModal.find(Identifiers.footer).append($button);
}
currentModal
.find(Identifiers.footer).find('button')
.on('click', (e: JQueryEventObject): void => {
$(e.currentTarget).trigger('button.clicked');
});
} else {
currentModal.find(Identifiers.footer).remove();
}
currentModal.on('shown.bs.modal', (e: JQueryEventObject): void => {
const $me = $(e.currentTarget);
// focus the button which was configured as active button
......@@ -455,6 +466,8 @@ class Modal {
// When modal is opened/shown add it to Modal.instances and make it Modal.currentModal
currentModal.on('show.bs.modal', (e: JQueryEventObject): void => {
this.currentModal = $(e.currentTarget);
// Add buttons
this.setButtons(configuration.buttons);
this.instances.push(this.currentModal);
});
currentModal.on('modal-dismiss', (e: JQueryEventObject): void => {
......
......@@ -14,6 +14,7 @@
export abstract class AbstractInteractableModule {
private readonly selectorModalBody: string = '.t3js-modal-body';
private readonly selectorModalContent: string = '.t3js-module-content';
private readonly selectorModalFooter: string = '.t3js-modal-footer';
protected currentModal: JQuery;
abstract initialize(currentModal: JQuery): void;
......@@ -26,6 +27,10 @@ export abstract class AbstractInteractableModule {
return this.findInModal(this.selectorModalContent);
}
protected getModalFooter(): JQuery {
return this.findInModal(this.selectorModalFooter);
}
protected findInModal(selector: string): JQuery {
return this.currentModal.find(selector);
}
......
......@@ -25,7 +25,7 @@ class Cache implements InlineModuleInterface {
url: Router.getUrl('cacheClearAll', 'maintenance'),
cache: false,
beforeSend: (): void => {
$trigger.addClass('disabled');
$trigger.addClass('disabled').prop('disabled', true);
},
success: (data: any): void => {
if (data.success === true && Array.isArray(data.status)) {
......@@ -46,7 +46,7 @@ class Cache implements InlineModuleInterface {
);
},
complete: (): void => {
$trigger.removeClass('disabled');
$trigger.removeClass('disabled').prop('disabled', false);
},
});
}
......
......@@ -15,19 +15,20 @@ import {AbstractInteractableModule} from './AbstractInteractableModule';
import * as $ from 'jquery';
import Router = require('../Router');
import PasswordStrength = require('./PasswordStrength');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
* Module: TYPO3/CMS/Install/Module/ChangeInstallToolPassword
*/
class ChangeInstallToolPassword extends AbstractInteractableModule {
private selectorChangeForm: string = '#t3js-changeInstallToolPassword-form';
private selectorChangeButton: string = '.t3js-changeInstallToolPassword-change';
public initialize(currentModal: JQuery): void {
this.currentModal = currentModal;
this.getData();
currentModal.on('submit', this.selectorChangeForm, (e: JQueryEventObject): void => {
currentModal.on('click', this.selectorChangeButton, (e: JQueryEventObject): void => {
e.preventDefault();
this.change();
});
......@@ -44,6 +45,7 @@ class ChangeInstallToolPassword extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
} else {
Notification.error('Something went wrong');
}
......
......@@ -14,6 +14,7 @@
import {AbstractInteractableModule} from './AbstractInteractableModule';
import * as $ from 'jquery';
import Router = require('../Router');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -54,6 +55,7 @@ class ClearTables extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
if (Array.isArray(data.stats) && data.stats.length > 0) {
data.stats.forEach((element: any): void => {
if (element.rowCount > 0) {
......
......@@ -14,6 +14,7 @@
import {AbstractInteractableModule} from './AbstractInteractableModule';
import * as $ from 'jquery';
import Router = require('../Router');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -53,6 +54,7 @@ class ClearTypo3tempFiles extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
if (Array.isArray(data.stats) && data.stats.length > 0) {
data.stats.forEach((element: any): void => {
if (element.numberOfFiles > 0) {
......
......@@ -16,6 +16,7 @@ import * as $ from 'jquery';
import Router = require('../Router');
import FlashMessage = require('../Renderable/FlashMessage');
import Severity = require('../Renderable/Severity');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
interface ActionItem {
......@@ -68,7 +69,7 @@ class CoreUpdate extends AbstractInteractableModule {
};
private selectorOutput: string = '.t3js-coreUpdate-output';
private selectorTemplate: string = '.t3js-coreUpdate-buttonTemplate';
private updateButton: string = '.t3js-coreUpdate-button';
/**
* Clone of a DOM object acts as button template
......@@ -81,18 +82,29 @@ class CoreUpdate extends AbstractInteractableModule {
public initialize(currentModal: JQuery): void {
this.currentModal = currentModal;
this.getData().done((): void => {
const buttonTemplateSection = currentModal.find(this.selectorTemplate);
this.buttonTemplate = buttonTemplateSection.children().clone();
this.buttonTemplate = this.findInModal(this.updateButton).clone();
});
currentModal.on('click', '.t3js-coreUpdate-init', (e: JQueryEventObject): void => {
e.preventDefault();
// Don't use jQuery's data() function, as the DOM is re-rendered and any set data attribute gets lost.
// See showActionButton()
const action = $(e.target).attr('data-action');
const action = $(e.currentTarget).attr('data-action');
currentModal.find(this.selectorOutput).empty();
CoreUpdate.call(action);
this.findInModal(this.selectorOutput).empty();
switch (action) {
case 'checkForUpdate':
this.callAction('coreUpdateIsUpdateAvailable');
break;
case 'updateDevelopment':
this.update('development');
break;
case 'updateRegular':
this.update('regular');
break;
default:
throw 'Unknown update action "' + action + '"';
}
});
}
......@@ -104,6 +116,7 @@ class CoreUpdate extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
} else {
Notification.error('Something went wrong');
}
......@@ -114,27 +127,6 @@ class CoreUpdate extends AbstractInteractableModule {
});
}
/**
* Internal action called by callAction()
*/
private checkForUpdate(): void {
this.callAction('coreUpdateIsUpdateAvailable');
}
/**
* Internal action called by callAction()
*/
private updateDevelopment(): void {
this.update('development');
}
/**
* Internal action called by callAction()
*/
private updateRegular(): void {
this.update('regular');
}
/**
* Execute core update.
*
......@@ -256,7 +248,7 @@ class CoreUpdate extends AbstractInteractableModule {
if (title) {
domButton.text(title);
}
this.findInModal(this.selectorOutput).append(domButton);
this.findInModal(this.updateButton).replaceWith(domButton);
}
/**
......
......@@ -15,19 +15,20 @@ import {AbstractInteractableModule} from './AbstractInteractableModule';
import * as $ from 'jquery';
import Router = require('../Router');
import PasswordStrength = require('./PasswordStrength');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
* Module: TYPO3/CMS/Install/Module/CreateAdmin
*/
class CreateAdmin extends AbstractInteractableModule {
private selectorCreateForm: string = '#t3js-createAdmin-form';
private selectorAdminCreateButton: string = '.t3js-createAdmin-create';
public initialize(currentModal: JQuery): void {
this.currentModal = currentModal;
this.getData();
currentModal.on('submit', this.selectorCreateForm, (e: JQueryEventObject): void => {
currentModal.on('click', this.selectorAdminCreateButton, (e: JQueryEventObject): void => {
e.preventDefault();
this.create();
});
......@@ -45,6 +46,7 @@ class CreateAdmin extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
} else {
Notification.error('Something went wrong');
}
......
......@@ -17,6 +17,7 @@ import Router = require('../Router');
import ProgressBar = require('../Renderable/ProgressBar');
import InfoBox = require('../Renderable/InfoBox');
import Severity = require('../Renderable/Severity');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -57,6 +58,7 @@ class DatabaseAnalyzer extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
this.analyze();
} else {
Notification.error('Something went wrong');
......@@ -70,9 +72,10 @@ class DatabaseAnalyzer extends AbstractInteractableModule {
private analyze(): void {
const modalContent = this.getModalBody();
const modalFooter = this.getModalFooter();
const outputContainer = modalContent.find(this.selectorOutputContainer);
const executeTrigger = modalContent.find(this.selectorExecuteTrigger);
const analyzeTrigger = modalContent.find(this.selectorAnalyzeTrigger);
const executeTrigger = modalFooter.find(this.selectorExecuteTrigger);
const analyzeTrigger = modalFooter.find(this.selectorAnalyzeTrigger);
outputContainer.empty().append(ProgressBar.render(Severity.loading, 'Analyzing current database schema...', ''));
......
......@@ -25,7 +25,7 @@ class DumpAutoload implements InlineModuleInterface {
url: Router.getUrl('dumpAutoload'),
cache: false,
beforeSend: (): void => {
$trigger.addClass('disabled');
$trigger.addClass('disabled').prop('disabled', true);
},
success: (data: any): void => {
if (data.success === true && Array.isArray(data.status)) {
......@@ -44,7 +44,7 @@ class DumpAutoload implements InlineModuleInterface {
Notification.error('Dumping autoload files went wrong on the server side. Check the system for broken extensions and try again');
},
complete: (): void => {
$trigger.removeClass('disabled');
$trigger.removeClass('disabled').prop('disabled', false);
},
});
}
......
......@@ -18,6 +18,7 @@ import Router = require('../Router');
import ProgressBar = require('../Renderable/ProgressBar');
import InfoBox = require('../Renderable/InfoBox');
import Severity = require('../Renderable/Severity');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -46,11 +47,14 @@ class EnvironmentCheck extends AbstractInteractableModule {
$errorBadge.text('').hide();
const message = ProgressBar.render(Severity.loading, 'Loading...', '');
modalContent.find(this.selectorOutputContainer).empty().append(message);
this.findInModal(this.selectorExecuteTrigger).addClass('disabled').prop('disabled', true);
$.ajax({
url: Router.getUrl('environmentCheckGetStatus'),
cache: false,
success: (data: any): void => {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
let warningCount = 0;
let errorCount = 0;
if (data.success === true && typeof(data.status) === 'object') {
......
......@@ -18,6 +18,7 @@ import Router = require('../Router');
import ProgressBar = require('../Renderable/ProgressBar');
import InfoBox = require('../Renderable/InfoBox');
import Severity = require('../Renderable/Severity');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -33,8 +34,8 @@ class ExtensionCompatTester extends AbstractInteractableModule {
this.getLoadedExtensionList();
currentModal.on('click', this.selectorCheckTrigger, (e: JQueryEventObject): void => {
currentModal.find(this.selectorUninstallTrigger).hide();
currentModal.find(this.selectorOutputContainer).empty();
this.findInModal(this.selectorUninstallTrigger).addClass('hidden');
this.findInModal(this.selectorOutputContainer).empty();
this.getLoadedExtensionList();
});
currentModal.on('click', this.selectorUninstallTrigger, (e: JQueryEventObject): void => {
......@@ -43,9 +44,10 @@ class ExtensionCompatTester extends AbstractInteractableModule {
}
private getLoadedExtensionList(): void {
this.findInModal(this.selectorCheckTrigger).prop('disabled', true);
this.findInModal(this.selectorCheckTrigger).addClass('disabled').prop('disabled', true);
this.findInModal('.modal-loading').hide();
const modalContent = this.getModalBody();
const modalFooter = this.getModalFooter();
const $outputContainer = this.findInModal(this.selectorOutputContainer);
const message = ProgressBar.render(Severity.loading, 'Loading...', '');
$outputContainer.append(message);
......@@ -55,6 +57,7 @@ class ExtensionCompatTester extends AbstractInteractableModule {
cache: false,
success: (data: any): void => {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
const $innerOutputContainer: JQuery = this.findInModal(this.selectorOutputContainer);
const progressBar = ProgressBar.render(Severity.loading, 'Loading...', '');
$innerOutputContainer.append(progressBar);
......@@ -88,13 +91,13 @@ class ExtensionCompatTester extends AbstractInteractableModule {
'Loading ' + response.scope + ' of extension "' + response.extension + '" failed',
);
$innerOutputContainer.append(aMessage);
modalContent.find(this.selectorUninstallTrigger)
modalFooter.find(this.selectorUninstallTrigger)
.text('Unload extension "' + response.extension + '"')
.attr('data-extension', response.extension)
.show();
.removeClass('hidden');
}).always((): void => {
$innerOutputContainer.find('.alert-loading').remove();
this.findInModal(this.selectorCheckTrigger).prop('disabled', false);
this.findInModal(this.selectorCheckTrigger).removeClass('disabled').prop('disabled', false);
});
} else {
Notification.error('Something went wrong');
......@@ -182,7 +185,7 @@ class ExtensionCompatTester extends AbstractInteractableModule {
modalContent.find(this.selectorOutputContainer).empty().append(aMessage);
});
}
$(this.selectorUninstallTrigger).hide();
this.findInModal(this.selectorUninstallTrigger).addClass('hidden');
this.getLoadedExtensionList();
} else {
Notification.error('Something went wrong');
......
......@@ -16,6 +16,7 @@ import * as $ from 'jquery';
import 'bootstrap';
import AjaxQueue = require('../Ajax/AjaxQueue');
import Router = require('../Router');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
interface FileData {
......@@ -50,6 +51,7 @@ class ExtensionScanner extends AbstractInteractableModule {
private selectorExtensionContainer: string = '.t3js-extensionScanner-extension';
private selectorNumberOfFiles: string = '.t3js-extensionScanner-number-of-files';
private selectorScanSingleTrigger: string = '.t3js-extensionScanner-scan-single';
private selectorExtensionScanButton: string = '.t3js-extensionScanner-scan-all';
public initialize(currentModal: JQuery): void {
this.currentModal = currentModal;
......@@ -68,9 +70,10 @@ class ExtensionScanner extends AbstractInteractableModule {
e.preventDefault();
const extension = $(e.currentTarget).closest(this.selectorExtensionContainer).data('extension');
this.scanSingleExtension(extension);
}).on('click', '.t3js-extensionScanner-scan-all', (e: JQueryEventObject): void => {
}).on('click', this.selectorExtensionScanButton, (e: JQueryEventObject): void => {
// Scan all button
e.preventDefault();
$(e.currentTarget).addClass('disabled').prop('disabled', true);
const $extensions = currentModal.find(this.selectorExtensionContainer);
this.scanAll($extensions);
});
......@@ -84,6 +87,7 @@ class ExtensionScanner extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
} else {
Notification.error('Something went wrong');
}
......@@ -149,6 +153,7 @@ class ExtensionScanner extends AbstractInteractableModule {
.text(numberOfScannedExtensions + ' of ' + numberOfExtensions + ' scanned');
if (numberOfScannedExtensions === numberOfExtensions) {
this.findInModal(this.selectorExtensionScanButton).removeClass('disabled').prop('disabled', false);
Notification.success('Scan finished', 'All extensions have been scanned');
AjaxQueue.add({
url: Router.getUrl(),
......
......@@ -14,6 +14,7 @@
import {AbstractInteractableModule} from './AbstractInteractableModule';
import * as $ from 'jquery';
import Router = require('../Router');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -40,6 +41,7 @@ class Features extends AbstractInteractableModule {
success: (data: any): void => {
if (data.success === true && data.html !== 'undefined' && data.html.length > 0) {
modalContent.empty().append(data.html);
Modal.setButtons(data.buttons);
} else {
Notification.error('Something went wrong');
}
......
......@@ -18,6 +18,7 @@ import Router = require('../Router');
import ProgressBar = require('../Renderable/ProgressBar');
import InfoBox = require('../Renderable/InfoBox');
import Severity = require('../Renderable/Severity');
import Modal = require('TYPO3/CMS/Backend/Modal');
import Notification = require('TYPO3/CMS/Backend/Notification');
/**
......@@ -45,6 +46,7 @@ class FolderStructure extends AbstractInteractableModule {
currentModal.on('click', this.selectorErrorFixTrigger, (e: JQueryEventObject): void => {
e.preventDefault();
$(e.currentTarget).addClass('disabled').prop('disabled', true);
this.fix();