[BUGFIX] Add missing translation value for save_and_close in form engine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / FormEngine.js
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 /**
15 * contains all JS functions related to TYPO3 TCEforms/FormEngine
16 *
17 * there are separate issues in this main object
18 * - functions, related to Element Browser ("Popup Window") and select fields
19 * - filling select fields (by wizard etc) from outside, formerly known via "setFormValueFromBrowseWin"
20 * - select fields: move selected items up and down via buttons, remove items etc
21 */
22
23 // add legacy functions to be accessible in the global scope
24 var setFormValueOpenBrowser,
25 setFormValueFromBrowseWin,
26 setHiddenFromList,
27 setFormValueManipulate,
28 setFormValue_getFObj;
29
30 /**
31 * Module: TYPO3/CMS/Backend/FormEngine
32 */
33 define(['jquery',
34 'TYPO3/CMS/Backend/FormEngineValidation',
35 'TYPO3/CMS/Backend/Modal',
36 'TYPO3/CMS/Backend/Severity',
37 'TYPO3/CMS/Backend/BackendException',
38 'TYPO3/CMS/Backend/Event/InteractionRequestMap'
39 ], function($, FormEngineValidation, Modal, Severity, BackendException, InteractionRequestMap) {
40
41 /**
42 * @param {InteractionRequest} interactionRequest
43 * @param {boolean} response
44 */
45 function handleConsumeResponse(interactionRequest, response) {
46 if (response) {
47 FormEngine.interactionRequestMap.resolveFor(interactionRequest);
48 } else {
49 FormEngine.interactionRequestMap.rejectFor(interactionRequest);
50 }
51 }
52
53 /**
54 * @exports TYPO3/CMS/Backend/FormEngine
55 */
56 var FormEngine = {
57 consumeTypes: ['typo3.setUrl', 'typo3.beforeSetUrl', 'typo3.refresh'],
58 Validation: FormEngineValidation,
59 interactionRequestMap: InteractionRequestMap,
60 formName: TYPO3.settings.FormEngine.formName,
61 openedPopupWindow: null,
62 legacyFieldChangedCb: function() {
63 !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb();
64 },
65 browserUrl: ''
66 };
67
68 // functions to connect the db/file browser with this document and the formfields on it!
69
70 /**
71 * Opens a popup window with the element browser (browser.php)
72 *
73 * @param {String} mode can be "db" or "file"
74 * @param {String} params additional params for the browser window
75 */
76 FormEngine.openPopupWindow = setFormValueOpenBrowser = function(mode, params) {
77 Modal.advanced({
78 type: Modal.types.iframe,
79 content: FormEngine.browserUrl + '&mode=' + mode + '&bparams=' + params,
80 size: Modal.sizes.large
81 });
82 };
83
84 /**
85 * properly fills the select field from the popup window (element browser, link browser)
86 * or from a multi-select (two selects side-by-side)
87 * previously known as "setFormValueFromBrowseWin"
88 *
89 * @param {String} fieldName Formerly known as "fName" name of the field, like [tt_content][2387][header]
90 * @param {(String|Number)} value The value to fill in (could be an integer)
91 * @param {String} label The visible name in the selector
92 * @param {String} title The title when hovering over it
93 * @param {String} exclusiveValues If the select field has exclusive options that are not combine-able
94 * @param {$} $optionEl The jQuery object of the selected <option> tag
95 */
96 FormEngine.setSelectOptionFromExternalSource = setFormValueFromBrowseWin = function(fieldName, value, label, title, exclusiveValues, $optionEl) {
97 exclusiveValues = String(exclusiveValues);
98
99 var $fieldEl,
100 $originalFieldEl,
101 isMultiple = false,
102 isList = false;
103
104 $originalFieldEl = $fieldEl = FormEngine.getFieldElement(fieldName);
105
106 if ($originalFieldEl.length === 0 || value === '--div--') {
107 return;
108 }
109
110 // Check if the form object has a "_list" element
111 // The "_list" element exists for multiple selection select types
112 var $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
113 if ($listFieldEl.length > 0) {
114 $fieldEl = $listFieldEl;
115 isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
116 isList = true;
117 }
118
119 // clear field before adding value, if configured so (maxitems==1)
120 // @todo: clean this code
121 if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName] !== 'undefined') {
122 var clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName];
123 $fieldEl.empty();
124
125 // Clear the upload field
126 // @todo: Investigate whether we either need to fix this code or we can drop it.
127 var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
128 if (filesContainer) {
129 filesContainer.innerHTML = filesContainer.innerHTML;
130 }
131 }
132
133 if (isMultiple || isList) {
134 // If multiple values are not allowed, clear anything that is in the control already
135 if (!isMultiple) {
136 var $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
137 $fieldEl.find('option').each(function() {
138 $availableFieldEl
139 .find('option[value="' + $.escapeSelector($(this).attr('value')) + '"]')
140 .removeClass('hidden')
141 .prop('disabled', false);
142 });
143 $fieldEl.empty();
144 }
145
146 // Clear elements if exclusive values are found
147 if (exclusiveValues) {
148 var reenableOptions = false;
149
150 var m = new RegExp('(^|,)' + value + '($|,)');
151 // the new value is exclusive => remove all existing values
152 if (exclusiveValues.match(m)) {
153 $fieldEl.empty();
154 reenableOptions = true;
155 } else if ($fieldEl.find('option').length == 1) {
156 // there is an old value and it was exclusive => it has to be removed
157 m = new RegExp("(^|,)" + $fieldEl.find('option').prop('value') + "($|,)");
158 if (exclusiveValues.match(m)) {
159 $fieldEl.empty();
160 reenableOptions = true;
161 }
162 }
163
164 if (reenableOptions && typeof $optionEl !== 'undefined') {
165 $optionEl.closest('select').find('[disabled]').removeClass('hidden').prop('disabled', false)
166 }
167 }
168
169 // Inserting the new element
170 var addNewValue = true;
171
172 // check if there is a "_mul" field (a field on the right) and if the field was already added
173 var $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
174 if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
175 $fieldEl.find('option').each(function(k, optionEl) {
176 if ($(optionEl).prop('value') == value) {
177 addNewValue = false;
178 return false;
179 }
180 });
181
182 if (addNewValue && typeof $optionEl !== 'undefined') {
183 $optionEl.addClass('hidden').prop('disabled', true);
184 }
185 }
186
187 // element can be added
188 if (addNewValue) {
189 // finally add the option
190 var $option = $('<option></option>');
191 $option.attr({value: value, title: title}).text(label);
192 $option.appendTo($fieldEl);
193
194 // set the hidden field
195 FormEngine.updateHiddenFieldValueFromSelect($fieldEl, $originalFieldEl);
196
197 // execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
198 FormEngine.legacyFieldChangedCb();
199 }
200
201 } else {
202
203 // The incoming value consists of the table name, an underscore and the uid
204 // or just the uid
205 // For a single selection field we need only the uid, so we extract it
206 var pattern = /_(\\d+)$/
207 , result = value.toString().match(pattern);
208
209 if (result != null) {
210 value = result[1];
211 }
212
213 // Change the selected value
214 $fieldEl.val(value);
215 }
216 if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
217 FormEngine.Validation.validate();
218 }
219 };
220
221 /**
222 * sets the value of the hidden field, from the select list, always executed after the select field was updated
223 * previously known as global function setHiddenFromList()
224 *
225 * @param {HTMLElement} selectFieldEl the select field
226 * @param {HTMLElement} originalFieldEl the hidden form field
227 */
228 FormEngine.updateHiddenFieldValueFromSelect = setHiddenFromList = function(selectFieldEl, originalFieldEl) {
229 var selectedValues = [];
230 $(selectFieldEl).find('option').each(function() {
231 selectedValues.push($(this).prop('value'));
232 });
233
234 // make a comma separated list, if it is a multi-select
235 // set the values to the final hidden field
236 $(originalFieldEl).val(selectedValues.join(','));
237 };
238
239 /**
240 * legacy function, can be removed once this function is not in use anymore
241 *
242 * @param {String} fName
243 * @param {String} type
244 * @param {Number} maxLength
245 */
246 setFormValueManipulate = function(fName, type, maxLength) {
247 var $formEl = FormEngine.getFormElement(fName);
248 if ($formEl.length > 0) {
249 var formObj = $formEl.get(0);
250 var localArray_V = [];
251 var localArray_L = [];
252 var localArray_S = [];
253 var localArray_T = [];
254 var fObjSel = formObj[fName + '_list'];
255 var l = fObjSel.length;
256 var c = 0;
257 var a;
258
259 if (type === 'RemoveFirstIfFull') {
260 if (maxLength == 1) {
261 for (a = 1; a < l; a++) {
262 if (fObjSel.options[a].selected != 1) {
263 localArray_V[c] = fObjSel.options[a].value;
264 localArray_L[c] = fObjSel.options[a].text;
265 localArray_S[c] = 0;
266 localArray_T[c] = fObjSel.options[a].title;
267 c++;
268 }
269 }
270 } else {
271 return;
272 }
273 }
274
275 if ((type === "Remove" && fObjSel.size > 1) || type === "Top" || type === "Bottom") {
276 if (type === "Top") {
277 for (a = 0; a < l; a++) {
278 if (fObjSel.options[a].selected == 1) {
279 localArray_V[c] = fObjSel.options[a].value;
280 localArray_L[c] = fObjSel.options[a].text;
281 localArray_S[c] = 1;
282 localArray_T[c] = fObjSel.options[a].title;
283 c++;
284 }
285 }
286 }
287 for (a = 0; a < l; a++) {
288 if (fObjSel.options[a].selected != 1) {
289 localArray_V[c] = fObjSel.options[a].value;
290 localArray_L[c] = fObjSel.options[a].text;
291 localArray_S[c] = 0;
292 localArray_T[c] = fObjSel.options[a].title;
293 c++;
294 }
295 }
296 if (type === "Bottom") {
297 for (a = 0; a < l; a++) {
298 if (fObjSel.options[a].selected == 1) {
299 localArray_V[c] = fObjSel.options[a].value;
300 localArray_L[c] = fObjSel.options[a].text;
301 localArray_S[c] = 1;
302 localArray_T[c] = fObjSel.options[a].title;
303 c++;
304 }
305 }
306 }
307 }
308 if (type === "Down") {
309 var tC = 0;
310 var tA = [];
311 var aa = 0;
312
313 for (a = 0; a < l; a++) {
314 if (fObjSel.options[a].selected != 1) {
315 // Add non-selected element:
316 localArray_V[c] = fObjSel.options[a].value;
317 localArray_L[c] = fObjSel.options[a].text;
318 localArray_S[c] = 0;
319 localArray_T[c] = fObjSel.options[a].title;
320 c++;
321
322 // Transfer any accumulated and reset:
323 if (tA.length > 0) {
324 for (aa = 0; aa < tA.length; aa++) {
325 localArray_V[c] = fObjSel.options[tA[aa]].value;
326 localArray_L[c] = fObjSel.options[tA[aa]].text;
327 localArray_S[c] = 1;
328 localArray_T[c] = fObjSel.options[tA[aa]].title;
329 c++;
330 }
331
332 tC = 0;
333 tA = [];
334 }
335 } else {
336 tA[tC] = a;
337 tC++;
338 }
339 }
340 // Transfer any remaining:
341 if (tA.length > 0) {
342 for (aa = 0; aa < tA.length; aa++) {
343 localArray_V[c] = fObjSel.options[tA[aa]].value;
344 localArray_L[c] = fObjSel.options[tA[aa]].text;
345 localArray_S[c] = 1;
346 localArray_T[c] = fObjSel.options[tA[aa]].title;
347 c++;
348 }
349 }
350 }
351 if (type === "Up") {
352 var tC = 0;
353 var tA = [];
354 var aa = 0;
355 c = l - 1;
356
357 for (a = l - 1; a >= 0; a--) {
358 if (fObjSel.options[a].selected != 1) {
359
360 // Add non-selected element:
361 localArray_V[c] = fObjSel.options[a].value;
362 localArray_L[c] = fObjSel.options[a].text;
363 localArray_S[c] = 0;
364 localArray_T[c] = fObjSel.options[a].title;
365 c--;
366
367 // Transfer any accumulated and reset:
368 if (tA.length > 0) {
369 for (aa = 0; aa < tA.length; aa++) {
370 localArray_V[c] = fObjSel.options[tA[aa]].value;
371 localArray_L[c] = fObjSel.options[tA[aa]].text;
372 localArray_S[c] = 1;
373 localArray_T[c] = fObjSel.options[tA[aa]].title;
374 c--;
375 }
376
377 tC = 0;
378 tA = [];
379 }
380 } else {
381 tA[tC] = a;
382 tC++;
383 }
384 }
385 // Transfer any remaining:
386 if (tA.length > 0) {
387 for (aa = 0; aa < tA.length; aa++) {
388 localArray_V[c] = fObjSel.options[tA[aa]].value;
389 localArray_L[c] = fObjSel.options[tA[aa]].text;
390 localArray_S[c] = 1;
391 localArray_T[c] = fObjSel.options[tA[aa]].title;
392 c--;
393 }
394 }
395 c = l; // Restore length value in "c"
396 }
397
398 // Transfer items in temporary storage to list object:
399 fObjSel.length = c;
400 for (a = 0; a < c; a++) {
401 fObjSel.options[a].value = localArray_V[a];
402 fObjSel.options[a].text = localArray_L[a];
403 fObjSel.options[a].selected = localArray_S[a];
404 fObjSel.options[a].title = localArray_T[a];
405 }
406 FormEngine.updateHiddenFieldValueFromSelect(fObjSel, formObj[fName]);
407
408 FormEngine.legacyFieldChangedCb();
409 }
410 };
411
412
413 /**
414 * Legacy function
415 * returns the DOM object for the given form name of the current form,
416 * but only if the given field name is valid, legacy function, use "getFormElement" instead
417 *
418 * @param {String} fieldName the name of the field name
419 * @returns {*|HTMLElement}
420 */
421 setFormValue_getFObj = function(fieldName) {
422 var $formEl = FormEngine.getFormElement(fieldName);
423 if ($formEl.length > 0) {
424 // return the DOM element of the form object
425 return $formEl.get(0);
426 }
427 return null;
428 };
429
430 /**
431 * returns a jQuery object for the given form name of the current form,
432 * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
433 * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
434 *
435 * @param {String} fieldName the field name to check for, optional
436 * @returns {*|HTMLElement}
437 */
438 FormEngine.getFormElement = function(fieldName) {
439 var $formEl = $('form[name="' + FormEngine.formName + '"]:first');
440 if (fieldName) {
441 var $fieldEl = FormEngine.getFieldElement(fieldName)
442 , $listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
443
444 // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
445 if ($fieldEl.length > 0 &&
446 (
447 ($fieldEl.prop('type') === 'select-one') ||
448 ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
449 )
450 ) {
451 return $formEl;
452 } else {
453 console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
454 alert('Form field is invalid');
455 }
456 } else {
457 return $formEl;
458 }
459 };
460
461
462 /**
463 * Returns a jQuery object of the field DOM element of the current form, can also be used to
464 * request an alternative field like "_hr", "_list" or "_mul"
465 *
466 * @param {String} fieldName the name of the field (<input name="fieldName">)
467 * @param {String} appendix optional
468 * @param {Boolean} noFallback if set, then the appendix value is returned no matter if it exists or not
469 * @returns {*|HTMLElement}
470 */
471 FormEngine.getFieldElement = function(fieldName, appendix, noFallback) {
472 var $formEl = FormEngine.getFormElement();
473
474 // if an appendix is set, return the field with the appendix (like _mul or _list)
475 if (appendix) {
476 var $fieldEl;
477 switch (appendix) {
478 case '_list':
479 $fieldEl = $(':input.tceforms-multiselect[data-formengine-input-name="' + fieldName + '"]', $formEl);
480 break;
481 case '_avail':
482 $fieldEl = $(':input[data-relatedfieldname="' + fieldName + '"]', $formEl);
483 break;
484 case '_mul':
485 case '_hr':
486 $fieldEl = $(':input[type=hidden][data-formengine-input-name="' + fieldName + '"]', $formEl);
487 break;
488 }
489 if (($fieldEl && $fieldEl.length > 0) || noFallback === true) {
490 return $fieldEl;
491 }
492 }
493
494 return $(':input[name="' + fieldName + '"]', $formEl);
495 };
496
497
498 /**************************************************
499 * manipulate existing options in a select field
500 **************************************************/
501
502 /**
503 * Moves currently selected options from a select field to the very top,
504 * can be multiple entries as well
505 *
506 * @param {Object} $fieldEl a jQuery object, containing the select field
507 */
508 FormEngine.moveOptionToTop = function($fieldEl) {
509 // remove the selected options
510 var selectedOptions = $fieldEl.find(':selected').detach();
511 // and add them on first position again
512 $fieldEl.prepend(selectedOptions);
513 };
514
515
516 /**
517 * moves currently selected options from a select field up by one position,
518 * can be multiple entries as well
519 *
520 * @param {Object} $fieldEl a jQuery object, containing the select field
521 */
522 FormEngine.moveOptionUp = function($fieldEl) {
523 // remove the selected options and add it before the previous sibling
524 $.each($fieldEl.find(':selected'), function(k, optionEl) {
525 var $optionEl = $(optionEl)
526 , $optionBefore = $optionEl.prev();
527
528 // stop if first option to move is already the first one
529 if (k == 0 && $optionBefore.length === 0) {
530 return false;
531 }
532
533 $optionBefore.before($optionEl.detach());
534 });
535 };
536
537
538 /**
539 * moves currently selected options from a select field down one position,
540 * can be multiple entries as well
541 *
542 * @param {Object} $fieldEl a jQuery object, containing the select field
543 */
544 FormEngine.moveOptionDown = function($fieldEl) {
545 // remove the selected options and add it after the next sibling
546 // however, this time, we need to go from the last to the first
547 var selectedOptions = $fieldEl.find(':selected');
548 selectedOptions = $.makeArray(selectedOptions);
549 selectedOptions.reverse();
550 $.each(selectedOptions, function(k, optionEl) {
551 var $optionEl = $(optionEl)
552 , $optionAfter = $optionEl.next();
553
554 // stop if first option to move is already the last one
555 if (k == 0 && $optionAfter.length === 0) {
556 return false;
557 }
558
559 $optionAfter.after($optionEl.detach());
560 });
561 };
562
563
564 /**
565 * moves currently selected options from a select field as the very last entries
566 *
567 * @param {Object} $fieldEl a jQuery object, containing the select field
568 */
569 FormEngine.moveOptionToBottom = function($fieldEl) {
570 // remove the selected options
571 var selectedOptions = $fieldEl.find(':selected').detach();
572 // and add them on last position again
573 $fieldEl.append(selectedOptions);
574 };
575
576 /**
577 * removes currently selected options from a select field
578 *
579 * @param {Object} $fieldEl a jQuery object, containing the select field
580 * @param {Object} $availableFieldEl a jQuery object, containing all available value
581 */
582 FormEngine.removeOption = function($fieldEl, $availableFieldEl) {
583 var $selected = $fieldEl.find(':selected');
584
585 $selected.each(function() {
586 $availableFieldEl
587 .find('option[value="' + $.escapeSelector($(this).attr('value')) + '"]')
588 .removeClass('hidden')
589 .prop('disabled', false);
590 });
591
592 // remove the selected options
593 $selected.remove();
594 };
595
596
597 /**
598 * Initialize events for all form engine relevant tasks.
599 * This function only needs to be called once on page load,
600 * as it using deferrer methods only
601 */
602 FormEngine.initializeEvents = function() {
603 if (top.TYPO3 && typeof top.TYPO3.Backend !== 'undefined') {
604 top.TYPO3.Backend.consumerScope.attach(FormEngine);
605 $(window).on('unload', function() {
606 top.TYPO3.Backend.consumerScope.detach(FormEngine);
607 });
608 }
609 $(document).on('click', '.t3js-btn-moveoption-top, .t3js-btn-moveoption-up, .t3js-btn-moveoption-down, .t3js-btn-moveoption-bottom, .t3js-btn-removeoption', function(evt) {
610 evt.preventDefault();
611
612 // track the arrows "Up", "Down", "Clear" etc in multi-select boxes
613 var $el = $(this)
614 , fieldName = $el.data('fieldname')
615 , $listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
616
617 if ($listFieldEl.length > 0) {
618
619 if ($el.hasClass('t3js-btn-moveoption-top')) {
620 FormEngine.moveOptionToTop($listFieldEl);
621 } else if ($el.hasClass('t3js-btn-moveoption-up')) {
622 FormEngine.moveOptionUp($listFieldEl);
623 } else if ($el.hasClass('t3js-btn-moveoption-down')) {
624 FormEngine.moveOptionDown($listFieldEl);
625 } else if ($el.hasClass('t3js-btn-moveoption-bottom')) {
626 FormEngine.moveOptionToBottom($listFieldEl);
627 } else if ($el.hasClass('t3js-btn-removeoption')) {
628 var $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
629 FormEngine.removeOption($listFieldEl, $availableFieldEl);
630 }
631
632 // make sure to update the hidden field value when modifying the select value
633 FormEngine.updateHiddenFieldValueFromSelect($listFieldEl, FormEngine.getFieldElement(fieldName));
634 FormEngine.legacyFieldChangedCb();
635 if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
636 FormEngine.Validation.validate();
637 }
638 }
639 }).on('click', '.t3js-formengine-select-itemstoselect', function(evt) {
640 // in multi-select environments with two (e.g. "Access"), on click the item from the right should go to the left
641 var $el = $(this)
642 , fieldName = $el.data('relatedfieldname')
643 , exclusiveValues = $el.data('exclusivevalues');
644
645 if (fieldName) {
646 // try to add each selected field to the "left" select field
647 $el.find(':selected').each(function() {
648 var $optionEl = $(this);
649 FormEngine.setSelectOptionFromExternalSource(fieldName, $optionEl.prop('value'), $optionEl.text(), $optionEl.prop('title'), exclusiveValues, $optionEl);
650 });
651 }
652 }).on('click', '.t3js-editform-close', function(e) {
653 e.preventDefault();
654 FormEngine.preventExitIfNotSaved(
655 FormEngine.preventExitIfNotSavedCallback
656 );
657 }).on('click', '.t3js-editform-view', function(e) {
658 e.preventDefault();
659 FormEngine.previewAction(e, FormEngine.previewActionCallback);
660 }).on('click', '.t3js-editform-new', function(e) {
661 e.preventDefault();
662 FormEngine.newAction(e, FormEngine.newActionCallback);
663 }).on('click', '.t3js-editform-duplicate', function(e) {
664 e.preventDefault();
665 FormEngine.duplicateAction(e, FormEngine.duplicateActionCallback);
666 }).on('click', '.t3js-editform-delete-record', function(e) {
667 e.preventDefault();
668 FormEngine.deleteAction(e, FormEngine.deleteActionCallback);
669 }).on('click', '.t3js-editform-delete-inline-record', function(e) {
670 e.preventDefault();
671 var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
672 var content = TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete this record?';
673 var $anchorElement = $(this);
674 var $modal = Modal.confirm(title, content, Severity.warning, [
675 {
676 text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
677 active: true,
678 btnClass: 'btn-default',
679 name: 'no'
680 },
681 {
682 text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
683 btnClass: 'btn-warning',
684 name: 'yes'
685 }
686 ]);
687 $modal.on('button.clicked', function(e) {
688 if (e.target.name === 'no') {
689 Modal.dismiss();
690 } else if (e.target.name === 'yes') {
691 var objectId = $anchorElement.data('objectid');
692 inline.deleteRecord(objectId);
693 Modal.dismiss();
694 }
695 });
696 }).on('click', '.t3js-editform-submitButton', function(event) {
697 // remember the clicked submit button. we need to know that in TBE_EDITOR.submitForm();
698 var $me = $(this),
699 name = $me.data('name') || this.name,
700 $elem = $('<input />').attr('type', 'hidden').attr('name', name).attr('value', '1');
701
702 $me.parents('form').append($elem);
703 }).on('change', '.t3-form-field-eval-null-checkbox input[type="checkbox"]', function(e) {
704 // Null checkboxes without placeholder click event handler
705 $(this).closest('.t3js-formengine-field-item').toggleClass('disabled');
706 }).on('change', '.t3js-form-field-eval-null-placeholder-checkbox input[type="checkbox"]', function(e) {
707 FormEngine.toggleCheckboxField($(this));
708 }).on('change', '.t3js-l10n-state-container input[type=radio]', function(event) {
709 // Change handler for "l10n_state" field changes
710 var $me = $(this);
711 var $input = $me.closest('.t3js-formengine-field-item').find('[data-formengine-input-name]');
712
713 if ($input.length > 0) {
714 var lastState = $input.data('last-l10n-state') || false,
715 currentState = $(this).val();
716
717 if (lastState && currentState === lastState) {
718 return;
719 }
720
721 if (currentState === 'custom') {
722 if (lastState) {
723 $(this).attr('data-original-language-value', $input.val());
724 }
725 $input.attr('disabled', false);
726 } else {
727 if (lastState === 'custom') {
728 $(this).closest('.t3js-l10n-state-container').find('.t3js-l10n-state-custom').attr('data-original-language-value', $input.val());
729 }
730 $input.attr('disabled', 'disabled');
731 }
732
733 $input.val($(this).attr('data-original-language-value')).trigger('change');
734 $input.data('last-l10n-state', $(this).val());
735 }
736 }).on('formengine.dp.change', function(event, $field) {
737 FormEngine.Validation.validate();
738 FormEngine.Validation.markFieldAsChanged($field);
739 $('.module-docheader-bar .btn').removeClass('disabled').prop('disabled', false);
740 }).on('change', function(event) {
741 $('.module-docheader-bar .btn').removeClass('disabled').prop('disabled', false);
742 });
743 };
744
745 /**
746 * @param {InteractionRequest} interactionRequest
747 * @return {jQuery.Deferred}
748 */
749 FormEngine.consume = function(interactionRequest) {
750 if (!interactionRequest) {
751 throw new BackendException('No interaction request given', 1496589980);
752 }
753 if (interactionRequest.concernsTypes(FormEngine.consumeTypes)) {
754 var outerMostRequest = interactionRequest.outerMostRequest;
755 var deferred = $.Deferred();
756
757 FormEngine.interactionRequestMap.attachFor(
758 outerMostRequest,
759 deferred
760 );
761 // resolve or reject deferreds with previous user choice
762 if (outerMostRequest.isProcessed()) {
763 handleConsumeResponse(
764 outerMostRequest,
765 outerMostRequest.getProcessedData().response
766 );
767 // show confirmation dialog
768 } else if (FormEngine.hasChange()) {
769 FormEngine.preventExitIfNotSaved(function(response) {
770 outerMostRequest.setProcessedData(
771 {response: response}
772 );
773 handleConsumeResponse(outerMostRequest, response);
774 });
775 // resolve directly
776 } else {
777 FormEngine.interactionRequestMap.resolveFor(outerMostRequest);
778 }
779
780 return deferred;
781 }
782 };
783
784 /**
785 * Initializes the remaining character views based on the fields' maxlength attribute
786 */
787 FormEngine.initializeRemainingCharacterViews = function() {
788 // all fields with a "maxlength" attribute
789 var $maxlengthElements = $('[maxlength]').not('.t3js-datetimepicker').not('.t3js-charcounter-initialized');
790 $maxlengthElements.on('focus', function(e) {
791 var $field = $(this),
792 $parent = $field.parents('.t3js-formengine-field-item:first'),
793 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
794
795 // append the counter only at focus to avoid cluttering the DOM
796 $parent.append($('<div />', {'class': 't3js-charcounter'}).append(
797 $('<span />', {'class': maxlengthProperties.labelClass}).text(TYPO3.lang['FormEngine.remainingCharacters'].replace('{0}', maxlengthProperties.remainingCharacters))
798 ));
799 }).on('blur', function() {
800 var $field = $(this),
801 $parent = $field.parents('.t3js-formengine-field-item:first');
802 $parent.find('.t3js-charcounter').remove();
803 }).on('keyup', function() {
804 var $field = $(this),
805 $parent = $field.parents('.t3js-formengine-field-item:first'),
806 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
807
808 // change class and value
809 $parent.find('.t3js-charcounter span').removeClass().addClass(maxlengthProperties.labelClass).text(TYPO3.lang['FormEngine.remainingCharacters'].replace('{0}', maxlengthProperties.remainingCharacters))
810 });
811 $maxlengthElements.addClass('t3js-charcounter-initialized');
812 $(':password').on('focus', function() {
813 $(this).attr('type', 'text').select();
814 }).on('blur', function() {
815 $(this).attr('type', 'password');
816 });
817 };
818
819 /**
820 * Initialize select checkbox element checkboxes
821 */
822 FormEngine.initializeSelectCheckboxes = function() {
823 $('.t3js-toggle-checkboxes').each(function() {
824 var $checkbox = $(this);
825 var $table = $checkbox.closest('table');
826 var $checkboxes = $table.find('.t3js-checkbox');
827 var checkIt = $checkboxes.length === $table.find('.t3js-checkbox:checked').length;
828 $checkbox.prop('checked', checkIt);
829 });
830 $(document).on('change', '.t3js-toggle-checkboxes', function(e) {
831 e.preventDefault();
832 var $checkbox = $(this);
833 var $table = $checkbox.closest('table');
834 var $checkboxes = $table.find('.t3js-checkbox');
835 var checkIt = $checkboxes.length !== $table.find('.t3js-checkbox:checked').length;
836 $checkboxes.prop('checked', checkIt);
837 $checkbox.prop('checked', checkIt);
838 FormEngine.Validation.markFieldAsChanged($checkbox);
839 });
840 $(document).on('change', '.t3js-checkbox', function(e) {
841 FormEngine.updateCheckboxState(this);
842 });
843 };
844
845 /**
846 *
847 * @param {HTMLElement} source
848 */
849 FormEngine.updateCheckboxState = function(source) {
850 var $sourceElement = $(source);
851 var $table = $sourceElement.closest('table');
852 var $checkboxes = $table.find('.t3js-checkbox');
853 var checkIt = $checkboxes.length === $table.find('.t3js-checkbox:checked').length;
854 $table.find('.t3js-toggle-checkboxes').prop('checked', checkIt);
855 };
856
857 /**
858 * Get the properties required for proper rendering of the character counter
859 *
860 * @param {Object} $field
861 * @returns {{remainingCharacters: number, labelClass: string}}
862 */
863 FormEngine.getCharacterCounterProperties = function($field) {
864 var fieldText = $field.val(),
865 maxlength = $field.attr('maxlength'),
866 currentFieldLength = fieldText.length,
867 numberOfLineBreaks = (fieldText.match(/\n/g) || []).length, // count line breaks
868 remainingCharacters = maxlength - currentFieldLength - numberOfLineBreaks,
869 threshold = 15, // hard limit of remaining characters when the label class changes
870 labelClass = '';
871
872 if (remainingCharacters < threshold) {
873 labelClass = 'label-danger';
874 } else if (remainingCharacters < threshold * 2) {
875 labelClass = 'label-warning';
876 } else {
877 labelClass = 'label-info';
878 }
879
880 return {
881 remainingCharacters: remainingCharacters,
882 labelClass: 'label ' + labelClass
883 };
884 };
885
886 /**
887 * Select field filter functions, see TCA option "enableMultiSelectFilterTextfield"
888 * and "multiSelectFilterItems"
889 */
890 FormEngine.SelectBoxFilter = {
891 options: {
892 fieldContainerSelector: '.t3js-formengine-field-group',
893 filterContainerSelector: '.t3js-formengine-multiselect-filter-container',
894 filterTextFieldSelector: '.t3js-formengine-multiselect-filter-textfield',
895 filterSelectFieldSelector: '.t3js-formengine-multiselect-filter-dropdown',
896 itemsToSelectElementSelector: '.t3js-formengine-select-itemstoselect'
897 }
898 };
899
900 /**
901 * Make sure that all selectors and input filters are recognized
902 * note: this also works on elements that are loaded asynchronously via AJAX, no need to call this method
903 * after an AJAX load.
904 */
905 FormEngine.SelectBoxFilter.initializeEvents = function() {
906 $(document).on('keyup', FormEngine.SelectBoxFilter.options.filterTextFieldSelector, function() {
907 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
908 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
909 }).on('change', FormEngine.SelectBoxFilter.options.filterSelectFieldSelector, function() {
910 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
911 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
912 });
913 };
914
915 /**
916 * Fetch the "itemstoselect" select element where a filter item is attached to
917 *
918 * @param {Object} $relativeElement
919 * @returns {*}
920 */
921 FormEngine.SelectBoxFilter.getSelectElement = function($relativeElement) {
922 var $containerElement = $relativeElement.closest(FormEngine.SelectBoxFilter.options.fieldContainerSelector);
923 return $containerElement.find(FormEngine.SelectBoxFilter.options.itemsToSelectElementSelector);
924 };
925
926 /**
927 * Filter the actual items
928 *
929 * @param {Object} $selectElement
930 * @param {String} filterText
931 */
932 FormEngine.SelectBoxFilter.filter = function($selectElement, filterText) {
933 var $allOptionElements;
934 if (!$selectElement.data('alloptions')) {
935 $allOptionElements = $selectElement.find('option').clone();
936 $selectElement.data('alloptions', $allOptionElements);
937 } else {
938 $allOptionElements = $selectElement.data('alloptions');
939 }
940
941 if (filterText.length > 0) {
942 var matchFilter = new RegExp(filterText, 'i');
943 $selectElement.html('');
944 $allOptionElements.each(function() {
945 var $item = $(this);
946 if ($item.text().match(matchFilter)) {
947 $selectElement.append($item.clone());
948 }
949 });
950 } else {
951 $selectElement.html($allOptionElements);
952 }
953 };
954
955 /**
956 * convert all textareas so they grow when it is typed in.
957 */
958 FormEngine.convertTextareasResizable = function() {
959 var $elements = $('.t3js-formengine-textarea');
960 if (TYPO3.settings.Textarea && TYPO3.settings.Textarea.autosize && $elements.length) {
961 require(['autosize'], function(autosize) {
962 autosize($elements);
963 });
964 }
965 };
966
967 /**
968 * convert all textareas to enable tab
969 */
970 FormEngine.convertTextareasEnableTab = function() {
971 var $elements = $('.t3js-enable-tab');
972 if ($elements.length) {
973 require(['taboverride'], function(taboverride) {
974 taboverride.set($elements);
975 });
976 }
977 };
978
979 /**
980 * Initialize input / text field "null" checkbox CSS overlay if no placeholder is set.
981 */
982 FormEngine.initializeNullNoPlaceholderCheckboxes = function() {
983 $('.t3-form-field-eval-null-checkbox').each(function() {
984 // Add disabled class to "t3js-formengine-field-item" if the null checkbox is NOT set,
985 // This activates a CSS overlay "disabling" the input field and everything around.
986 var $checkbox = $(this).find('input[type="checkbox"]');
987 var $fieldItem = $(this).closest('.t3js-formengine-field-item');
988 if (!$checkbox.attr('checked')) {
989 $fieldItem.addClass('disabled');
990 }
991 });
992 };
993
994 /**
995 * Initialize input / text field "null" checkbox placeholder / real field if placeholder is set.
996 */
997 FormEngine.initializeNullWithPlaceholderCheckboxes = function() {
998 $('.t3js-form-field-eval-null-placeholder-checkbox').each(function() {
999 FormEngine.toggleCheckboxField($(this).find('input[type="checkbox"]'));
1000 });
1001 };
1002
1003 /**
1004 * Set initial state of both div's (one containing actual field, other containing placeholder field)
1005 * depending on whether checkbox is checked or not
1006 * @param $checkbox
1007 */
1008 FormEngine.toggleCheckboxField = function($checkbox) {
1009 var $item = $checkbox.closest('.t3js-formengine-field-item');
1010 if ($checkbox.prop('checked')) {
1011 $item.find('.t3js-formengine-placeholder-placeholder').hide();
1012 $item.find('.t3js-formengine-placeholder-formfield').show();
1013 } else {
1014 $item.find('.t3js-formengine-placeholder-placeholder').show();
1015 $item.find('.t3js-formengine-placeholder-formfield').hide();
1016 }
1017 };
1018
1019 /**
1020 * This is the main function that is called on page load, but also after elements are asynchronously
1021 * called e.g. after inline elements are loaded, or a new flexform section is added.
1022 * Use this function in your extension like this "TYPO3.FormEngine.initialize()"
1023 * if you add new fields dynamically.
1024 */
1025 FormEngine.reinitialize = function() {
1026 // Apply "close" button to all input / datetime fields
1027 if ($('.t3js-clearable').length) {
1028 require(['TYPO3/CMS/Backend/jquery.clearable'], function() {
1029 $('.t3js-clearable').clearable();
1030 });
1031 }
1032 if ($('.t3-form-suggest').length) {
1033 require(['TYPO3/CMS/Backend/FormEngineSuggest'], function(Suggest) {
1034 $('.t3-form-suggest').each(function(index, suggestElement) {
1035 new Suggest(suggestElement);
1036 });
1037 });
1038 }
1039 // Apply DatePicker to all date time fields
1040 if ($('.t3js-datetimepicker').length) {
1041 require(['TYPO3/CMS/Backend/DateTimePicker'], function(DateTimePicker) {
1042 DateTimePicker.initialize();
1043 });
1044 }
1045
1046 FormEngine.convertTextareasResizable();
1047 FormEngine.convertTextareasEnableTab();
1048 FormEngine.initializeNullNoPlaceholderCheckboxes();
1049 FormEngine.initializeNullWithPlaceholderCheckboxes();
1050 FormEngine.initializeInputLinkToggle();
1051 FormEngine.initializeLocalizationStateSelector();
1052 FormEngine.initializeRemainingCharacterViews();
1053 };
1054
1055 /**
1056 * Disable the input field on load if localization state selector is set to "parent" or "source"
1057 */
1058 FormEngine.initializeLocalizationStateSelector = function() {
1059 $('.t3js-l10n-state-container').each(function() {
1060 var $input = $(this).closest('.t3js-formengine-field-item').find('[data-formengine-input-name]');
1061 var currentState = $(this).find('input[type="radio"]:checked').val();
1062 if (currentState === 'parent' || currentState === 'source') {
1063 $input.attr('disabled', 'disabled');
1064 }
1065 });
1066 };
1067
1068 /**
1069 * Toggle for input link explanation
1070 */
1071 FormEngine.initializeInputLinkToggle = function() {
1072 var toggleClass = '.t3js-form-field-inputlink-explanation-toggle',
1073 inputFieldClass = '.t3js-form-field-inputlink-input',
1074 explanationClass = '.t3js-form-field-inputlink-explanation';
1075
1076 // if empty, show input field
1077 $(explanationClass).filter(function() {
1078 return !$.trim($(this).val());
1079 }).each(function() {
1080 var $group = $(this).closest('.t3js-form-field-inputlink'),
1081 $inputField = $group.find(inputFieldClass),
1082 $explanationField = $group.find(explanationClass);
1083 $explanationField.toggleClass('hidden', true);
1084 $inputField.toggleClass('hidden', false);
1085 $group.find('.form-control-clearable button.close').toggleClass('hidden', false)
1086 });
1087
1088 $(document).on('click', toggleClass, function(e) {
1089 e.preventDefault();
1090
1091 var $group = $(this).closest('.t3js-form-field-inputlink'),
1092 $inputField = $group.find(inputFieldClass),
1093 $explanationField = $group.find(explanationClass),
1094 explanationShown;
1095
1096 explanationShown = !$explanationField.hasClass('hidden');
1097 $explanationField.toggleClass('hidden', explanationShown);
1098 $inputField.toggleClass('hidden', !explanationShown);
1099 $group.find('.form-control-clearable button.close').toggleClass('hidden', !explanationShown)
1100 });
1101
1102 $(inputFieldClass).on('change', function() {
1103 var $group = $(this).closest('.t3js-form-field-inputlink'),
1104 $inputField = $group.find(inputFieldClass),
1105 $explanationField = $group.find(explanationClass),
1106 explanationShown;
1107
1108 if (!$explanationField.hasClass('hidden')) {
1109
1110 explanationShown = !$explanationField.hasClass('hidden');
1111 $explanationField.toggleClass('hidden', explanationShown);
1112 $inputField.toggleClass('hidden', !explanationShown);
1113 $group.find('.form-control-clearable button.close').toggleClass('hidden', !explanationShown)
1114 }
1115 });
1116 };
1117
1118 /**
1119 * @return {boolean}
1120 */
1121 FormEngine.hasChange = function() {
1122 var formElementChanges = $('form[name="' + FormEngine.formName + '"] .has-change').length > 0,
1123 inlineRecordChanges = $('[name^="data["].has-change').length > 0;
1124 return formElementChanges || inlineRecordChanges;
1125 };
1126
1127 /**
1128 * @param {boolean} response
1129 */
1130 FormEngine.preventExitIfNotSavedCallback = function(response) {
1131 FormEngine.closeDocument();
1132 };
1133
1134 /**
1135 * Show modal to confirm following a clicked link to confirm leaving the document without saving
1136 *
1137 * @param {String} href
1138 * @returns {Boolean}
1139 */
1140 FormEngine.preventFollowLinkIfNotSaved = function(href) {
1141 FormEngine.preventExitIfNotSaved(
1142 function () {
1143 window.location.href = href;
1144 }
1145 );
1146 return false;
1147 };
1148
1149 /**
1150 * Show modal to confirm closing the document without saving.
1151 *
1152 * @param {Function} callback
1153 */
1154 FormEngine.preventExitIfNotSaved = function(callback) {
1155 callback = callback || FormEngine.preventExitIfNotSavedCallback;
1156
1157 if (FormEngine.hasChange()) {
1158 var title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to close without saving?';
1159 var content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You currently have unsaved changes. Are you sure you want to discard these changes?';
1160 var $elem = $('<input />').attr('type', 'hidden').attr('name', '_saveandclosedok').attr('value', '1');
1161 var buttons = [
1162 {
1163 text: TYPO3.lang['buttons.confirm.close_without_save.no'] || 'No, I will continue editing',
1164 btnClass: 'btn-default',
1165 name: 'no'
1166 },
1167 {
1168 text: TYPO3.lang['buttons.confirm.close_without_save.yes'] || 'Yes, discard my changes',
1169 btnClass: 'btn-default',
1170 name: 'yes'
1171 }
1172 ];
1173 if ($('.has-error').length === 0) {
1174 buttons.push({
1175 text: TYPO3.lang['buttons.confirm.save_and_close'] || 'Save and close',
1176 btnClass: 'btn-warning',
1177 name: 'save',
1178 active: true
1179 });
1180 }
1181
1182 var $modal = Modal.confirm(title, content, Severity.warning, buttons);
1183 $modal.on('button.clicked', function(e) {
1184 if (e.target.name === 'no') {
1185 Modal.dismiss();
1186 } else if (e.target.name === 'yes') {
1187 Modal.dismiss();
1188 callback.call(null, true);
1189 } else if (e.target.name === 'save') {
1190 $('form[name=' + FormEngine.formName + ']').append($elem);
1191 $('input[name=doSave]').val(1);
1192 Modal.dismiss();
1193 document.editform.submit();
1194 }
1195 });
1196 } else {
1197 callback.call(null, true);
1198 }
1199 };
1200
1201 /**
1202 * Show modal to confirm closing the document without saving
1203 */
1204 FormEngine.preventSaveIfHasErrors = function() {
1205 if ($('.has-error').length > 0) {
1206 var title = TYPO3.lang['label.alert.save_with_error.title'] || 'You have errors in your form!';
1207 var content = TYPO3.lang['label.alert.save_with_error.content'] || 'Please check the form, there is at least one error in your form.';
1208 var $modal = Modal.confirm(title, content, Severity.error, [
1209 {
1210 text: TYPO3.lang['buttons.alert.save_with_error.ok'] || 'OK',
1211 btnClass: 'btn-danger',
1212 name: 'ok'
1213 }
1214 ]);
1215 $modal.on('button.clicked', function(e) {
1216 if (e.target.name === 'ok') {
1217 Modal.dismiss();
1218 }
1219 });
1220 return false;
1221 }
1222 return true;
1223 };
1224
1225 /**
1226 * Preview action
1227 *
1228 * When there are changes:
1229 * Will take action based on local storage preset
1230 * If preset is not available, a modal will open
1231 *
1232 * @param {Event} event
1233 * @param {Function} callback
1234 */
1235 FormEngine.previewAction = function(event, callback) {
1236 callback = callback || FormEngine.previewActionCallback;
1237
1238 var previewUrl = event.target.href;
1239 var isNew = event.target.dataset.hasOwnProperty('isNew');
1240 var $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedokview').attr('value', '1');
1241 if (FormEngine.hasChange()) {
1242 FormEngine.showPreviewModal(previewUrl, isNew, $actionElement, callback);
1243 } else {
1244 $('form[name=' + FormEngine.formName + ']').append($actionElement);
1245 window.open('', 'newTYPO3frontendWindow');
1246 document.editform.submit();
1247 }
1248 };
1249
1250 /**
1251 * The callback for the preview action
1252 *
1253 * @param {string} modalButtonName
1254 * @param {string} previewUrl
1255 * @param {element} $actionElement
1256 */
1257 FormEngine.previewActionCallback = function(modalButtonName, previewUrl, $actionElement) {
1258 Modal.dismiss();
1259 switch(modalButtonName) {
1260 case 'discard':
1261 var previewWin = window.open(previewUrl, 'newTYPO3frontendWindow');
1262 previewWin.focus();
1263 if (previewWin.location.href === previewUrl) {
1264 previewWin.location.reload();
1265 }
1266 break;
1267 case 'save':
1268 $('form[name=' + FormEngine.formName + ']').append($actionElement);
1269 $('input[name=doSave]').val(1);
1270 window.open('', 'newTYPO3frontendWindow');
1271 document.editform.submit();
1272 break;
1273 }
1274 };
1275
1276 /**
1277 * Show the preview modal
1278 *
1279 * @param {string} previewUrl
1280 * @param {bool} isNew
1281 * @param {element} $actionElement
1282 * @param {Function} callback
1283 */
1284 FormEngine.showPreviewModal = function(previewUrl, isNew, $actionElement, callback) {
1285 var title = TYPO3.lang['label.confirm.view_record_changed.title'] || 'Do you want to save before viewing?';
1286 var modalCancelButtonConfiguration = {
1287 text: TYPO3.lang['buttons.confirm.view_record_changed.cancel'] || 'Cancel',
1288 btnClass: 'btn-default',
1289 name: 'cancel'
1290 };
1291 var modaldismissViewButtonConfiguration = {
1292 text: TYPO3.lang['buttons.confirm.view_record_changed.no-save'] || 'View without changes',
1293 btnClass: 'btn-info',
1294 name: 'discard'
1295 };
1296 var modalsaveViewButtonConfiguration = {
1297 text: TYPO3.lang['buttons.confirm.view_record_changed.save'] || 'Save changes and view',
1298 btnClass: 'btn-info',
1299 name: 'save',
1300 active: true
1301 };
1302 var modalButtons = [];
1303 var content = '';
1304 if (isNew) {
1305 modalButtons = [
1306 modalCancelButtonConfiguration,
1307 modalsaveViewButtonConfiguration
1308 ];
1309 content = (
1310 TYPO3.lang['label.confirm.view_record_changed.content.is-new-page']
1311 || 'You need to save your changes before viewing the page. Do you want to save and view them now?'
1312 );
1313 } else {
1314 modalButtons = [
1315 modalCancelButtonConfiguration,
1316 modaldismissViewButtonConfiguration,
1317 modalsaveViewButtonConfiguration
1318 ];
1319 content = (
1320 TYPO3.lang['label.confirm.view_record_changed.content']
1321 || 'You currently have unsaved changes. You can either discard these changes or save and view them.'
1322 )
1323 }
1324 var $modal = Modal.confirm(title, content, Severity.info, modalButtons);
1325 $modal.on('button.clicked', function (event) {
1326 callback(event.target.name, previewUrl, $actionElement, $modal);
1327 });
1328 };
1329
1330 /**
1331 * New action
1332 *
1333 * When there are changes:
1334 * Will take action based on local storage preset
1335 * If preset is not available, a modal will open
1336 *
1337 * @param {Event} event
1338 * @param {Function} callback
1339 */
1340 FormEngine.newAction = function(event, callback) {
1341 callback = callback || FormEngine.newActionCallback;
1342
1343 var $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_savedoknew').attr('value', '1');
1344 var isNew = event.target.dataset.hasOwnProperty('isNew');
1345 if (FormEngine.hasChange()) {
1346 FormEngine.showNewModal(isNew, $actionElement, callback);
1347 } else {
1348 $('form[name=' + FormEngine.formName + ']').append($actionElement);
1349 document.editform.submit();
1350 }
1351 };
1352
1353 /**
1354 * The callback for the preview action
1355 *
1356 * @param {string} modalButtonName
1357 * @param {element} $actionElement
1358 */
1359 FormEngine.newActionCallback = function(modalButtonName, $actionElement) {
1360 var $form = $('form[name=' + FormEngine.formName + ']');
1361 Modal.dismiss();
1362 switch(modalButtonName) {
1363 case 'no':
1364 $form.append($actionElement);
1365 document.editform.submit();
1366 break;
1367 case 'yes':
1368 $form.append($actionElement);
1369 $('input[name=doSave]').val(1);
1370 document.editform.submit();
1371 break;
1372 }
1373 };
1374
1375 /**
1376 * Show the new modal
1377 *
1378 * @param {element} $actionElement
1379 * @param {Function} callback
1380 * @param {bool} isNew
1381 */
1382 FormEngine.showNewModal = function(isNew, $actionElement, callback) {
1383 var title = TYPO3.lang['label.confirm.new_record_changed.title'] || 'Do you want to save before adding?';
1384 var content = (
1385 TYPO3.lang['label.confirm.new_record_changed.content']
1386 || 'You need to save your changes before creating a new record. Do you want to save and create now?'
1387 );
1388 var modalButtons = [];
1389 var modalCancelButtonConfiguration = {
1390 text: TYPO3.lang['buttons.confirm.new_record_changed.cancel'] || 'Cancel',
1391 btnClass: 'btn-default',
1392 name: 'cancel'
1393 };
1394 var modalNoButtonConfiguration = {
1395 text: TYPO3.lang['buttons.confirm.new_record_changed.no'] || 'No, just add',
1396 btnClass: 'btn-default',
1397 name: 'no'
1398 };
1399 var modalYesButtonConfiguration = {
1400 text: TYPO3.lang['buttons.confirm.new_record_changed.yes'] || 'Yes, save and create now',
1401 btnClass: 'btn-info',
1402 name: 'yes',
1403 active: true
1404 };
1405 if (isNew) {
1406 modalButtons = [
1407 modalCancelButtonConfiguration,
1408 modalYesButtonConfiguration
1409 ];
1410 } else {
1411 modalButtons = [
1412 modalCancelButtonConfiguration,
1413 modalNoButtonConfiguration,
1414 modalYesButtonConfiguration
1415 ];
1416 }
1417 var $modal = Modal.confirm(title, content, Severity.info, modalButtons);
1418 $modal.on('button.clicked', function (event) {
1419 callback(event.target.name, $actionElement);
1420 });
1421 };
1422
1423 /**
1424 * Duplicate action
1425 *
1426 * When there are changes:
1427 * Will take action based on local storage preset
1428 * If preset is not available, a modal will open
1429 *
1430 * @param {Event} event
1431 * @param {Function} callback
1432 */
1433 FormEngine.duplicateAction = function(event, callback) {
1434 callback = callback || FormEngine.duplicateActionCallback;
1435
1436 var $actionElement = $('<input />').attr('type', 'hidden').attr('name', '_duplicatedoc').attr('value', '1');
1437 var isNew = event.target.dataset.hasOwnProperty('isNew');
1438 if (FormEngine.hasChange()) {
1439 FormEngine.showDuplicateModal(isNew, $actionElement, callback);
1440 } else {
1441 $('form[name=' + FormEngine.formName + ']').append($actionElement);
1442 document.editform.submit();
1443 }
1444 };
1445
1446 /**
1447 * The callback for the duplicate action
1448 *
1449 * @param {string} modalButtonName
1450 * @param {element} $actionElement
1451 */
1452 FormEngine.duplicateActionCallback = function(modalButtonName, $actionElement) {
1453 var $form = $('form[name=' + FormEngine.formName + ']');
1454 Modal.dismiss();
1455 switch(modalButtonName) {
1456 case 'no':
1457 $form.append($actionElement);
1458 document.editform.submit();
1459 break;
1460 case 'yes':
1461 $form.append($actionElement);
1462 $('input[name=doSave]').val(1);
1463 document.editform.submit();
1464 break;
1465 }
1466 };
1467
1468 /**
1469 * Show the duplicate modal
1470 *
1471 * @param {bool} isNew
1472 * @param {element} $actionElement
1473 * @param {Function} callback
1474 */
1475 FormEngine.showDuplicateModal = function(isNew, $actionElement, callback) {
1476 var title = TYPO3.lang['label.confirm.duplicate_record_changed.title'] || 'Do you want to save before duplicating this record?';
1477 var content = (
1478 TYPO3.lang['label.confirm.duplicate_record_changed.content']
1479 || 'You currently have unsaved changes. Do you want to save your changes before duplicating this record?'
1480 );
1481 var modalButtons = [];
1482 var modalCancelButtonConfiguration = {
1483 text: TYPO3.lang['buttons.confirm.duplicate_record_changed.cancel'] || 'Cancel',
1484 btnClass: 'btn-default',
1485 name: 'cancel'
1486 };
1487 var modalDismissDuplicateButtonConfiguration = {
1488 text: TYPO3.lang['buttons.confirm.duplicate_record_changed.no'] || 'No, just duplicate the original',
1489 btnClass: 'btn-default',
1490 name: 'no'
1491 };
1492 var modalSaveDuplicateButtonConfiguration = {
1493 text: TYPO3.lang['buttons.confirm.duplicate_record_changed.yes'] || 'Yes, save and duplicate this record',
1494 btnClass: 'btn-info',
1495 name: 'yes',
1496 active: true
1497 };
1498 if (isNew) {
1499 modalButtons = [
1500 modalCancelButtonConfiguration,
1501 modalSaveDuplicateButtonConfiguration
1502 ];
1503 } else {
1504 modalButtons = [
1505 modalCancelButtonConfiguration,
1506 modalDismissDuplicateButtonConfiguration,
1507 modalSaveDuplicateButtonConfiguration
1508 ];
1509 }
1510 var $modal = Modal.confirm(title, content, Severity.info, modalButtons);
1511 $modal.on('button.clicked', function (event) {
1512 callback(event.target.name, $actionElement);
1513 });
1514 };
1515
1516 /**
1517 * Delete action
1518 *
1519 * When there are changes:
1520 * Will take action based on local storage preset
1521 * If preset is not available, a modal will open
1522 *
1523 * @param {Event} event
1524 * @param {Function} callback
1525 */
1526 FormEngine.deleteAction = function(event, callback) {
1527 callback = callback || FormEngine.deleteActionCallback;
1528
1529 var $anchorElement = $(event.target);
1530
1531 FormEngine.showDeleteModal($anchorElement, callback);
1532 };
1533
1534 /**
1535 * The callback for the delete action
1536 *
1537 * @param {string} modalButtonName
1538 * @param {element} $anchorElement
1539 */
1540 FormEngine.deleteActionCallback = function(modalButtonName, $anchorElement) {
1541 Modal.dismiss();
1542 switch(modalButtonName) {
1543 case 'yes':
1544 deleteRecord($anchorElement.data('table'), $anchorElement.data('uid'), $anchorElement.data('return-url'));
1545 break;
1546 }
1547 };
1548
1549 /**
1550 * Show the delete modal
1551 *
1552 * @param {element} $anchorElement
1553 * @param {Function} callback
1554 */
1555 FormEngine.showDeleteModal = function($anchorElement, callback) {
1556 var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
1557 var content = TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete this record?';
1558
1559 if ($anchorElement.data('reference-count-message')) {
1560 content += ' ' + $anchorElement.data('reference-count-message');
1561 }
1562
1563 if ($anchorElement.data('translation-count-message')) {
1564 content += ' ' + $anchorElement.data('translation-count-message');
1565 }
1566
1567 var $modal = Modal.confirm(title, content, Severity.warning, [
1568 {
1569 text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
1570 btnClass: 'btn-default',
1571 name: 'no'
1572 },
1573 {
1574 text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
1575 btnClass: 'btn-warning',
1576 name: 'yes',
1577 active: true
1578 }
1579 ]);
1580 $modal.on('button.clicked', function (event) {
1581 callback(event.target.name, $anchorElement);
1582 });
1583 };
1584
1585 /**
1586 * Close current open document
1587 */
1588 FormEngine.closeDocument = function() {
1589 document.editform.closeDoc.value = 1;
1590 document.editform.submit();
1591 };
1592
1593 /**
1594 * Main init function called from outside
1595 *
1596 * Sets some options and registers the DOMready handler to initialize further things
1597 *
1598 * @param {String} browserUrl
1599 * @param {Number} mode
1600 */
1601 FormEngine.initialize = function(browserUrl, mode) {
1602 FormEngine.browserUrl = browserUrl;
1603 FormEngine.Validation.setUsMode(mode);
1604
1605 $(function() {
1606 FormEngine.initializeEvents();
1607 FormEngine.SelectBoxFilter.initializeEvents();
1608 FormEngine.initializeSelectCheckboxes();
1609 FormEngine.Validation.initialize();
1610 FormEngine.reinitialize();
1611 $('#t3js-ui-block').remove();
1612 });
1613 };
1614
1615 // load required modules to hook in the post initialize function
1616 if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
1617 $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine'], function(pos, moduleName) {
1618 require([moduleName]);
1619 });
1620 }
1621
1622 // make the form engine object publicly visible for other objects in the TYPO3 namespace
1623 TYPO3.FormEngine = FormEngine;
1624
1625 // return the object in the global space
1626 return FormEngine;
1627 });