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