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