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