[TASK] Migrate LayoutModule/DragDrop.js 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 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();