2dfbe36dfdd68973a073a2f7697d6eeb1d5df8fe
[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 FormEngineValidation is a native TypeScript class, we can use require() instead
17 // and drop amd-dependency and declare
18 /// <amd-dependency path="TYPO3/CMS/Backend/FormEngineValidation" name="FormEngineValidation">
19 declare let FormEngineValidation: 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 .' + FormEngineValidation.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 $popover.options.content = $list.wrapAll('<div>').parent().html();
137 $popover.setContent();
138 $popover.$tip.addClass($popover.options.placement);
139 } else {
140 $toggleButton.addClass('hidden').popover('hide');
141 }
142 };
143
144 /**
145 * Finds the field in the form and focuses it
146 *
147 * @param {Event} e
148 */
149 public switchToField = (e: Event): void => {
150 e.preventDefault();
151
152 let $listItem: any = $(e.currentTarget);
153 let referenceFieldId: string = $listItem.data('fieldId');
154 let $referenceField: any = $('#' + referenceFieldId);
155
156 // Iterate possibly nested tab panels
157 $referenceField.parents('[id][role="tabpanel"]').each(function(): void {
158 $('[aria-controls="' + $(this).attr('id') + '"]').tab('show');
159 });
160
161 $referenceField.focus();
162 };
163 }
164
165 // Create an instance and return it
166 export = new FormEngineReview();