[BUGFIX] JS: Fix FormEngine initialization
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / FormEngineReview.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 /// <amd-dependency path="bootstrap">
15
16 // todo: once FormEngine is a native TypeScript class, we can use require() instead
17 // and drop amd-dependency and declare
18 /// <amd-dependency path="TYPO3/CMS/Backend/FormEngine" name="FormEngine">
19 declare let FormEngine: any;
20 declare let TYPO3: any;
21
22 import $ = require('jquery');
23
24 /**
25  * Module: TYPO3/CMS/Backend/FormEngineReview
26  * Enables interaction with record fields that need review
27  * @exports TYPO3/CMS/Backend/FormEngineReview
28  */
29 class FormEngineReview {
30     /**
31      * Fetches all fields that have a failed validation
32      *
33      * @return {$}
34      */
35     public static findInvalidField(): any {
36         return $(document).find('.tab-content .' + FormEngine.Validation.errorClass);
37     }
38
39     /**
40      * Renders an invisible button to toggle the review panel into the least possible toolbar
41      *
42      * @param {Object} context
43      */
44     public static attachButtonToModuleHeader(context: any): void {
45         let $leastButtonBar: any = $('.t3js-module-docheader-bar-buttons').children().last().find('[role="toolbar"]');
46         let $button: any = $('<a />', {
47             'class': 'btn btn-danger btn-sm hidden ' + context.toggleButtonClass,
48             href: '#',
49             title: TYPO3.lang['buttons.reviewFailedValidationFields'],
50         }).append(
51             $('<span />', {'class': 'fa fa-fw fa-info'})
52         );
53
54         $button.popover({
55             container: 'body',
56             html: true,
57             placement: 'bottom',
58         });
59
60         $leastButtonBar.prepend($button);
61     }
62
63     /**
64      * Class for the toggle button
65      */
66     private toggleButtonClass: string;
67
68     /**
69      * Class for field list items
70      */
71     private fieldListItemClass: string;
72
73     /**
74      * Class of FormEngine labels
75      */
76     private labelSelector: string;
77
78     /**
79      * The constructor, set the class properties default values
80      */
81     constructor() {
82         this.toggleButtonClass = 't3js-toggle-review-panel';
83         this.fieldListItemClass = 't3js-field-item';
84         this.labelSelector = '.t3js-formengine-label';
85
86         this.initialize();
87     }
88
89     /**
90      * Initialize the events
91      */
92     public initialize(): void {
93         let me: any = this;
94         let $document: any = $(document);
95
96         $(function(): void {
97             FormEngineReview.attachButtonToModuleHeader(me);
98         });
99         $document.on('click', '.' + this.fieldListItemClass, this.switchToField);
100         $document.on('t3-formengine-postfieldvalidation', this.checkForReviewableField);
101     }
102
103     /**
104      * Checks if fields have failed validation. In such case, the markup is rendered and the toggle button is unlocked.
105      */
106     public checkForReviewableField = (): void => {
107         let me: any = this;
108         let $invalidFields: any = FormEngineReview.findInvalidField();
109         let $toggleButton: any = $('.' + this.toggleButtonClass);
110
111         if ($invalidFields.length > 0) {
112             let $list: any = $('<div />', {'class': 'list-group'});
113
114             $invalidFields.each(function(): void {
115                 let $field: any = $(this);
116                 let $input: any = $field.find('[data-formengine-validation-rules]');
117                 let inputId: any = $input.attr('id');
118
119                 if (typeof inputId === 'undefined') {
120                     inputId = $input.parent().children('[id]').first().attr('id');
121                 }
122
123                 $list.append(
124                     $('<a />', {
125                         href: '#',
126                         'class': 'list-group-item ' + me.fieldListItemClass,
127                         'data-field-id': inputId,
128                     }).text($field.find(me.labelSelector).text())
129                 );
130             });
131
132             $toggleButton.removeClass('hidden');
133
134             // Bootstrap has no official API to update the content of a popover w/o destroying it
135             let $popover: any = $toggleButton.data('bs.popover');
136             if ($popover) {
137               $popover.options.content = $list.wrapAll('<div>').parent().html();
138               $popover.setContent();
139               $popover.$tip.addClass($popover.options.placement);
140             }
141         } else {
142             $toggleButton.addClass('hidden').popover('hide');
143         }
144     };
145
146     /**
147      * Finds the field in the form and focuses it
148      *
149      * @param {Event} e
150      */
151     public switchToField = (e: Event): void => {
152         e.preventDefault();
153
154         let $listItem: any = $(e.currentTarget);
155         let referenceFieldId: string = $listItem.data('fieldId');
156         let $referenceField: any = $('#' + referenceFieldId);
157
158         // Iterate possibly nested tab panels
159         $referenceField.parents('[id][role="tabpanel"]').each(function(): void {
160             $('[aria-controls="' + $(this).attr('id') + '"]').tab('show');
161         });
162
163         $referenceField.focus();
164     };
165 }
166
167 // Create an instance and return it
168 export = new FormEngineReview();