1dc40dd5170504312832894259206bd0fe963492
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / AjaxDataHandler.ts
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 import {SeverityEnum} from './Enum/Severity';
15 import MessageInterface from './AjaxDataHandler/MessageInterface';
16 import ResponseInterface from './AjaxDataHandler/ResponseInterface';
17 import * as $ from 'jquery';
18 import Icons = require('./Icons');
19 import Modal = require('./Modal');
20 import Notification = require('./Notification');
21 import Viewport = require('./Viewport');
22
23 enum Identifiers {
24 hide = '.t3js-record-hide',
25 delete = '.t3js-record-delete',
26 icon = '.t3js-icon'
27 }
28
29 /**
30 * Module: TYPO3/CMS/Backend/AjaxDataHandler
31 * AjaxDataHandler - Javascript functions to work with AJAX and interacting with tce_db.php
32 */
33 class AjaxDataHandler {
34 /**
35 * Refresh the page tree
36 */
37 private static refreshPageTree(): void {
38 if (Viewport.NavigationContainer && Viewport.NavigationContainer.PageTree) {
39 Viewport.NavigationContainer.PageTree.refreshTree();
40 }
41 }
42
43 constructor() {
44 $((): void => {
45 this.initialize();
46 });
47 }
48
49 /**
50 * Generic function to call from the outside the script and validate directly showing errors
51 *
52 * @param {Object} parameters
53 * @returns {JQueryPromise<any>}
54 */
55 public process(parameters: Object): JQueryPromise<any> {
56 return this._call(parameters).done((result: ResponseInterface): void => {
57 if (result.hasErrors) {
58 this.handleErrors(result);
59 }
60 });
61 }
62
63 private initialize(): void {
64 // HIDE/UNHIDE: click events for all action icons to hide/unhide
65 $(document).on('click', Identifiers.hide, (e: JQueryEventObject): void => {
66 e.preventDefault();
67 const $anchorElement = $(e.currentTarget);
68 const $iconElement = $anchorElement.find(Identifiers.icon);
69 const $rowElement = $anchorElement.closest('tr[data-uid]');
70 const params = $anchorElement.data('params');
71
72 // add a spinner
73 this._showSpinnerIcon($iconElement);
74
75 // make the AJAX call to toggle the visibility
76 this._call(params).done((result: ResponseInterface): void => {
77 // print messages on errors
78 if (result.hasErrors) {
79 this.handleErrors(result);
80 } else {
81 // adjust overlay icon
82 this.toggleRow($rowElement);
83 }
84 });
85 });
86
87 // DELETE: click events for all action icons to delete
88 $(document).on('click', Identifiers.delete, (evt: JQueryEventObject): void => {
89 evt.preventDefault();
90 const $anchorElement = $(evt.currentTarget);
91 const $modal = Modal.confirm($anchorElement.data('title'), $anchorElement.data('message'), SeverityEnum.warning, [
92 {
93 text: $anchorElement.data('button-close-text') || TYPO3.lang['button.cancel'] || 'Cancel',
94 active: true,
95 btnClass: 'btn-default',
96 name: 'cancel'
97 },
98 {
99 text: $anchorElement.data('button-ok-text') || TYPO3.lang['button.delete'] || 'Delete',
100 btnClass: 'btn-warning',
101 name: 'delete'
102 }
103 ]);
104 $modal.on('button.clicked', (e: JQueryEventObject): void => {
105 if (e.target.getAttribute('name') === 'cancel') {
106 Modal.dismiss();
107 } else if (e.target.getAttribute('name') === 'delete') {
108 Modal.dismiss();
109 this.deleteRecord($anchorElement);
110 }
111 });
112 });
113 }
114
115 /**
116 * Toggle row visibility after record has been changed
117 *
118 * @param {JQuery} $rowElement
119 */
120 private toggleRow($rowElement: JQuery): void {
121 const $anchorElement = $rowElement.find(Identifiers.hide);
122 const table = $anchorElement.closest('table[data-table]').data('table');
123 const params = $anchorElement.data('params');
124 let nextParams;
125 let nextState;
126 let iconName;
127
128 if ($anchorElement.data('state') === 'hidden') {
129 nextState = 'visible';
130 nextParams = params.replace('=0', '=1');
131 iconName = 'actions-edit-hide';
132 } else {
133 nextState = 'hidden';
134 nextParams = params.replace('=1', '=0');
135 iconName = 'actions-edit-unhide';
136 }
137 $anchorElement.data('state', nextState).data('params', nextParams);
138
139 // Update tooltip title
140 $anchorElement.tooltip('hide').one('hidden.bs.tooltip', (): void => {
141 const nextTitle = $anchorElement.data('toggleTitle');
142 // Bootstrap Tooltip internally uses only .attr('data-original-title')
143 $anchorElement
144 .data('toggleTitle', $anchorElement.attr('data-original-title'))
145 .attr('data-original-title', nextTitle)
146 .tooltip('show');
147 });
148
149 const $iconElement = $anchorElement.find(Identifiers.icon);
150 Icons.getIcon(iconName, Icons.sizes.small).done((icon: string): void => {
151 $iconElement.replaceWith(icon);
152 });
153
154 // Set overlay for the record icon
155 const $recordIcon = $rowElement.find('.col-icon ' + Identifiers.icon);
156 if (nextState === 'hidden') {
157 Icons.getIcon('miscellaneous-placeholder', Icons.sizes.small, 'overlay-hidden').done((icon: string): void => {
158 $recordIcon.append($(icon).find('.icon-overlay'));
159 });
160 } else {
161 $recordIcon.find('.icon-overlay').remove();
162 }
163
164 $rowElement.fadeTo('fast', 0.4, (): void => {
165 $rowElement.fadeTo('fast', 1);
166 });
167 if (table === 'pages') {
168 AjaxDataHandler.refreshPageTree();
169 }
170 }
171
172 /**
173 * Delete record by given element (icon in table)
174 * don't call it directly!
175 *
176 * @param {JQuery} $anchorElement
177 */
178 private deleteRecord($anchorElement: JQuery): void {
179 const params = $anchorElement.data('params');
180 let $iconElement = $anchorElement.find(Identifiers.icon);
181
182 // add a spinner
183 this._showSpinnerIcon($iconElement);
184
185 // make the AJAX call to toggle the visibility
186 this._call(params).done((result: ResponseInterface): void => {
187 // revert to the old class
188 Icons.getIcon('actions-edit-delete', Icons.sizes.small).done((icon: string): void => {
189 $iconElement = $anchorElement.find(Identifiers.icon);
190 $iconElement.replaceWith(icon);
191 });
192 // print messages on errors
193 if (result.hasErrors) {
194 this.handleErrors(result);
195 } else {
196 const $table = $anchorElement.closest('table[data-table]');
197 const $panel = $anchorElement.closest('.panel');
198 const $panelHeading = $panel.find('.panel-heading');
199 const table = $table.data('table');
200 let $rowElements = $anchorElement.closest('tr[data-uid]');
201 const uid = $rowElements.data('uid');
202 const $translatedRowElements = $table.find('[data-l10nparent=' + uid + ']').closest('tr[data-uid]');
203 $rowElements = $rowElements.add($translatedRowElements);
204
205 $rowElements.fadeTo('slow', 0.4, (): void => {
206 $rowElements.slideUp('slow', (): void => {
207 $rowElements.remove();
208 if ($table.find('tbody tr').length === 0) {
209 $panel.slideUp('slow');
210 }
211 });
212 });
213 if ($anchorElement.data('l10parent') === '0' || $anchorElement.data('l10parent') === '') {
214 const count = Number($panelHeading.find('.t3js-table-total-items').html());
215 $panelHeading.find('.t3js-table-total-items').text(count - 1);
216 }
217
218 if (table === 'pages') {
219 AjaxDataHandler.refreshPageTree();
220 }
221 }
222 });
223 }
224
225 /**
226 * Handle the errors from result object
227 *
228 * @param {Object} result
229 */
230 private handleErrors(result: ResponseInterface): void {
231 $.each(result.messages, (position: number, message: MessageInterface): void => {
232 Notification.error(message.title, message.message);
233 });
234 }
235
236 /**
237 * AJAX call to tce_db.php
238 * returns a jQuery Promise to work with
239 *
240 * @param {Object} params
241 * @returns {JQueryXHR}
242 */
243 private _call(params: Object): JQueryXHR {
244 return $.getJSON(TYPO3.settings.ajaxUrls.record_process, params);
245 }
246
247 /**
248 * Replace the given icon with a spinner icon
249 *
250 * @param {Object} $iconElement
251 * @private
252 */
253 private _showSpinnerIcon($iconElement: JQuery): void {
254 Icons.getIcon('spinner-circle-dark', Icons.sizes.small).done((icon: string): void => {
255 $iconElement.replaceWith(icon);
256 });
257 }
258 }
259
260 export default new AjaxDataHandler();