2d8fc9a06cc8cf15d4792b53094951851915c72f
[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 ], function ($, FormEngineValidation, Modal, Severity) {
38
39 /**
40 *
41 * @type {{Validation: object, formName: *, backPath: *, openedPopupWindow: window, legacyFieldChangedCb: Function, browserUrl: string}}
42 * @exports TYPO3/CMS/Backend/FormEngine
43 */
44 var FormEngine = {
45 Validation: FormEngineValidation,
46 formName: TYPO3.settings.FormEngine.formName,
47 backPath: TYPO3.settings.FormEngine.backPath,
48 openedPopupWindow: null,
49 legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); },
50 browserUrl: ''
51 };
52
53 // functions to connect the db/file browser with this document and the formfields on it!
54
55 /**
56 * opens a popup window with the element browser (browser.php)
57 *
58 * @param {String} mode can be "db" or "file"
59 * @param {String} params additional params for the browser window
60 * @param {Number} width width of the window
61 * @param {Number} height height of the window
62 */
63 FormEngine.openPopupWindow = setFormValueOpenBrowser = function(mode, params, width, height) {
64 var url = FormEngine.backPath + FormEngine.browserUrl + '&mode=' + mode + '&bparams=' + params;
65 width = width ? width : top.TYPO3.configuration.PopupWindow.width;
66 height = height ? height : top.TYPO3.configuration.PopupWindow.height;
67 FormEngine.openedPopupWindow = window.open(url, 'Typo3WinBrowser', 'height=' + height + ',width=' + width + ',status=0,menubar=0,resizable=1,scrollbars=1');
68 FormEngine.openedPopupWindow.focus();
69 };
70
71
72 /**
73 * properly fills the select field from the popup window (element browser, link browser)
74 * or from a multi-select (two selects side-by-side)
75 * previously known as "setFormValueFromBrowseWin"
76 *
77 * @param {String} fieldName Formerly known as "fName" name of the field, like [tt_content][2387][header]
78 * @param {(String|Number)} value The value to fill in (could be an integer)
79 * @param {String} label The visible name in the selector
80 * @param {String} title The title when hovering over it
81 * @param {String} exclusiveValues If the select field has exclusive options that are not combine-able
82 * @param {$} $optionEl The jQuery object of the selected <option> tag
83 */
84 FormEngine.setSelectOptionFromExternalSource = setFormValueFromBrowseWin = function(fieldName, value, label, title, exclusiveValues, $optionEl) {
85 exclusiveValues = String(exclusiveValues);
86
87 var $fieldEl,
88 $originalFieldEl,
89 isMultiple = false,
90 isList = false;
91
92 $originalFieldEl = $fieldEl = FormEngine.getFieldElement(fieldName);
93
94 if ($originalFieldEl.length === 0 || value === '--div--') {
95 return;
96 }
97
98 // Check if the form object has a "_list" element
99 // The "_list" element exists for multiple selection select types
100 var $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
101 if ($listFieldEl.length > 0) {
102 $fieldEl = $listFieldEl;
103 isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
104 isList = true;
105 }
106
107 // clear field before adding value, if configured so (maxitems==1)
108 // @todo: clean this code
109 if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName] !== 'undefined') {
110 var clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName];
111 $fieldEl.empty();
112
113 // Clear the upload field
114 // @todo: Investigate whether we either need to fix this code or we can drop it.
115 var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
116 if (filesContainer) {
117 filesContainer.innerHTML = filesContainer.innerHTML;
118 }
119 }
120
121 if (isMultiple || isList) {
122 // If multiple values are not allowed, clear anything that is in the control already
123 if (!isMultiple) {
124 $fieldEl.empty();
125 }
126
127 // Clear elements if exclusive values are found
128 if (exclusiveValues) {
129 var reenableOptions = false;
130
131 var m = new RegExp('(^|,)' + value + '($|,)');
132 // the new value is exclusive => remove all existing values
133 if (exclusiveValues.match(m)) {
134 $fieldEl.empty();
135 reenableOptions = true;
136 } else if ($fieldEl.find('option').length == 1) {
137 // there is an old value and it was exclusive => it has to be removed
138 m = new RegExp("(^|,)" + $fieldEl.find('option').prop('value') + "($|,)");
139 if (exclusiveValues.match(m)) {
140 $fieldEl.empty();
141 reenableOptions = true;
142 }
143 }
144
145 if (reenableOptions && typeof $optionEl !== 'undefined') {
146 $optionEl.closest('select').find('[disabled]').removeClass('hidden').prop('disabled', false)
147 }
148 }
149
150 // Inserting the new element
151 var addNewValue = true;
152
153 // check if there is a "_mul" field (a field on the right) and if the field was already added
154 var $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
155 if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
156 $fieldEl.find('option').each(function(k, optionEl) {
157 if ($(optionEl).prop('value') == value) {
158 addNewValue = false;
159 return false;
160 }
161 });
162
163 if (addNewValue && typeof $optionEl !== 'undefined') {
164 $optionEl.addClass('hidden').prop('disabled', true);
165 }
166 }
167
168 // element can be added
169 if (addNewValue) {
170 // finally add the option
171 var $option = $('<option></option>');
172 $option.attr({value: value, title: title}).text(label);
173 $option.appendTo($fieldEl);
174
175 // set the hidden field
176 FormEngine.updateHiddenFieldValueFromSelect($fieldEl, $originalFieldEl);
177
178 // execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
179 FormEngine.legacyFieldChangedCb();
180 }
181
182 } else {
183
184 // The incoming value consists of the table name, an underscore and the uid
185 // or just the uid
186 // For a single selection field we need only the uid, so we extract it
187 var pattern = /_(\\d+)$/
188 ,result = value.toString().match(pattern);
189
190 if (result != null) {
191 value = result[1];
192 }
193
194 // Change the selected value
195 $fieldEl.val(value);
196 }
197 if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
198 FormEngine.Validation.validate();
199 }
200 };
201
202 /**
203 * sets the value of the hidden field, from the select list, always executed after the select field was updated
204 * previously known as global function setHiddenFromList()
205 *
206 * @param {HTMLElement} selectFieldEl the select field
207 * @param {HTMLElement} originalFieldEl the hidden form field
208 */
209 FormEngine.updateHiddenFieldValueFromSelect = setHiddenFromList = function(selectFieldEl, originalFieldEl) {
210 var selectedValues = [];
211 $(selectFieldEl).find('option').each(function() {
212 selectedValues.push($(this).prop('value'));
213 });
214
215 // make a comma separated list, if it is a multi-select
216 // set the values to the final hidden field
217 $(originalFieldEl).val(selectedValues.join(','));
218 };
219
220 /**
221 * legacy function, can be removed once this function is not in use anymore
222 *
223 * @param {String} fName
224 * @param {String} type
225 * @param {Number} maxLength
226 */
227 setFormValueManipulate = function(fName, type, maxLength) {
228 var $formEl = FormEngine.getFormElement(fName);
229 if ($formEl.length > 0) {
230 var formObj = $formEl.get(0);
231 var localArray_V = [];
232 var localArray_L = [];
233 var localArray_S = [];
234 var localArray_T = [];
235 var fObjSel = formObj[fName + '_list'];
236 var l = fObjSel.length;
237 var c = 0;
238 var a;
239
240 if (type === 'RemoveFirstIfFull') {
241 if (maxLength == 1) {
242 for (a = 1; a < l; a++) {
243 if (fObjSel.options[a].selected != 1) {
244 localArray_V[c] = fObjSel.options[a].value;
245 localArray_L[c] = fObjSel.options[a].text;
246 localArray_S[c] = 0;
247 localArray_T[c] = fObjSel.options[a].title;
248 c++;
249 }
250 }
251 } else {
252 return;
253 }
254 }
255
256 if ((type === "Remove" && fObjSel.size > 1) || type === "Top" || type === "Bottom") {
257 if (type === "Top") {
258 for (a=0;a<l;a++) {
259 if (fObjSel.options[a].selected==1) {
260 localArray_V[c]=fObjSel.options[a].value;
261 localArray_L[c]=fObjSel.options[a].text;
262 localArray_S[c]=1;
263 localArray_T[c] = fObjSel.options[a].title;
264 c++;
265 }
266 }
267 }
268 for (a=0;a<l;a++) {
269 if (fObjSel.options[a].selected!=1) {
270 localArray_V[c]=fObjSel.options[a].value;
271 localArray_L[c]=fObjSel.options[a].text;
272 localArray_S[c]=0;
273 localArray_T[c] = fObjSel.options[a].title;
274 c++;
275 }
276 }
277 if (type === "Bottom") {
278 for (a=0;a<l;a++) {
279 if (fObjSel.options[a].selected==1) {
280 localArray_V[c]=fObjSel.options[a].value;
281 localArray_L[c]=fObjSel.options[a].text;
282 localArray_S[c]=1;
283 localArray_T[c] = fObjSel.options[a].title;
284 c++;
285 }
286 }
287 }
288 }
289 if (type === "Down") {
290 var tC = 0;
291 var tA = [];
292 var aa = 0;
293
294 for (a=0;a<l;a++) {
295 if (fObjSel.options[a].selected!=1) {
296 // Add non-selected element:
297 localArray_V[c]=fObjSel.options[a].value;
298 localArray_L[c]=fObjSel.options[a].text;
299 localArray_S[c]=0;
300 localArray_T[c] = fObjSel.options[a].title;
301 c++;
302
303 // Transfer any accumulated and reset:
304 if (tA.length > 0) {
305 for (aa=0;aa<tA.length;aa++) {
306 localArray_V[c]=fObjSel.options[tA[aa]].value;
307 localArray_L[c]=fObjSel.options[tA[aa]].text;
308 localArray_S[c]=1;
309 localArray_T[c] = fObjSel.options[tA[aa]].title;
310 c++;
311 }
312
313 tC = 0;
314 tA = [];
315 }
316 } else {
317 tA[tC] = a;
318 tC++;
319 }
320 }
321 // Transfer any remaining:
322 if (tA.length > 0) {
323 for (aa=0;aa<tA.length;aa++) {
324 localArray_V[c]=fObjSel.options[tA[aa]].value;
325 localArray_L[c]=fObjSel.options[tA[aa]].text;
326 localArray_S[c]=1;
327 localArray_T[c] = fObjSel.options[tA[aa]].title;
328 c++;
329 }
330 }
331 }
332 if (type === "Up") {
333 var tC = 0;
334 var tA = [];
335 var aa = 0;
336 c = l-1;
337
338 for (a=l-1;a>=0;a--) {
339 if (fObjSel.options[a].selected!=1) {
340
341 // Add non-selected element:
342 localArray_V[c]=fObjSel.options[a].value;
343 localArray_L[c]=fObjSel.options[a].text;
344 localArray_S[c]=0;
345 localArray_T[c] = fObjSel.options[a].title;
346 c--;
347
348 // Transfer any accumulated and reset:
349 if (tA.length > 0) {
350 for (aa=0;aa<tA.length;aa++) {
351 localArray_V[c]=fObjSel.options[tA[aa]].value;
352 localArray_L[c]=fObjSel.options[tA[aa]].text;
353 localArray_S[c]=1;
354 localArray_T[c] = fObjSel.options[tA[aa]].title;
355 c--;
356 }
357
358 tC = 0;
359 tA = [];
360 }
361 } else {
362 tA[tC] = a;
363 tC++;
364 }
365 }
366 // Transfer any remaining:
367 if (tA.length > 0) {
368 for (aa=0;aa<tA.length;aa++) {
369 localArray_V[c]=fObjSel.options[tA[aa]].value;
370 localArray_L[c]=fObjSel.options[tA[aa]].text;
371 localArray_S[c]=1;
372 localArray_T[c] = fObjSel.options[tA[aa]].title;
373 c--;
374 }
375 }
376 c=l; // Restore length value in "c"
377 }
378
379 // Transfer items in temporary storage to list object:
380 fObjSel.length = c;
381 for (a = 0; a < c; a++) {
382 fObjSel.options[a].value = localArray_V[a];
383 fObjSel.options[a].text = localArray_L[a];
384 fObjSel.options[a].selected = localArray_S[a];
385 fObjSel.options[a].title = localArray_T[a];
386 }
387 FormEngine.updateHiddenFieldValueFromSelect(fObjSel, formObj[fName]);
388
389 FormEngine.legacyFieldChangedCb();
390 }
391 };
392
393
394 /**
395 * Legacy function
396 * returns the DOM object for the given form name of the current form,
397 * but only if the given field name is valid, legacy function, use "getFormElement" instead
398 *
399 * @param {String} fieldName the name of the field name
400 * @returns {*|HTMLElement}
401 */
402 setFormValue_getFObj = function(fieldName) {
403 var $formEl = FormEngine.getFormElement(fieldName);
404 if ($formEl.length > 0) {
405 // return the DOM element of the form object
406 return $formEl.get(0);
407 }
408 return null;
409 };
410
411 /**
412 * returns a jQuery object for the given form name of the current form,
413 * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
414 * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
415 *
416 * @param {String} fieldName the field name to check for, optional
417 * @returns {*|HTMLElement}
418 */
419 FormEngine.getFormElement = function(fieldName) {
420 var $formEl = $('form[name="' + FormEngine.formName + '"]:first');
421 if (fieldName) {
422 var $fieldEl = FormEngine.getFieldElement(fieldName)
423 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
424
425 // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
426 if ($fieldEl.length > 0 &&
427 (
428 ($fieldEl.prop('type') === 'select-one') ||
429 ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
430 )
431 ) {
432 return $formEl;
433 } else {
434 console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
435 alert('Form field is invalid');
436 }
437 } else {
438 return $formEl;
439 }
440 };
441
442
443 /**
444 * Returns a jQuery object of the field DOM element of the current form, can also be used to
445 * request an alternative field like "_hr", "_list" or "_mul"
446 *
447 * @param {String} fieldName the name of the field (<input name="fieldName">)
448 * @param {String} appendix optional
449 * @param {Boolean} noFallback if set, then the appendix value is returned no matter if it exists or not
450 * @returns {*|HTMLElement}
451 */
452 FormEngine.getFieldElement = function(fieldName, appendix, noFallback) {
453 var $formEl = FormEngine.getFormElement();
454
455 // if an appendix is set, return the field with the appendix (like _mul or _list)
456 if (appendix) {
457 var $fieldEl;
458 switch (appendix) {
459 case '_list':
460 $fieldEl = $(':input.tceforms-multiselect[data-formengine-input-name="' + fieldName + '"]', $formEl);
461 break;
462 case '_avail':
463 $fieldEl = $(':input[data-relatedfieldname="' + fieldName + '"]', $formEl);
464 break;
465 case '_mul':
466 case '_hr':
467 $fieldEl = $(':input[type=hidden][data-formengine-input-name="' + fieldName + '"]', $formEl);
468 break;
469 }
470 if (($fieldEl && $fieldEl.length > 0) || noFallback === true) {
471 return $fieldEl;
472 }
473 }
474
475 return $(':input[name="' + fieldName + '"]', $formEl);
476 };
477
478
479
480 /**************************************************
481 * manipulate existing options in a select field
482 **************************************************/
483
484 /**
485 * Moves currently selected options from a select field to the very top,
486 * can be multiple entries as well
487 *
488 * @param {Object} $fieldEl a jQuery object, containing the select field
489 */
490 FormEngine.moveOptionToTop = function($fieldEl) {
491 // remove the selected options
492 var selectedOptions = $fieldEl.find(':selected').detach();
493 // and add them on first position again
494 $fieldEl.prepend(selectedOptions);
495 };
496
497
498 /**
499 * moves currently selected options from a select field up by one position,
500 * can be multiple entries as well
501 *
502 * @param {Object} $fieldEl a jQuery object, containing the select field
503 */
504 FormEngine.moveOptionUp = function($fieldEl) {
505 // remove the selected options and add it before the previous sibling
506 $.each($fieldEl.find(':selected'), function(k, optionEl) {
507 var $optionEl = $(optionEl)
508 ,$optionBefore = $optionEl.prev();
509
510 // stop if first option to move is already the first one
511 if (k == 0 && $optionBefore.length === 0) {
512 return false;
513 }
514
515 $optionBefore.before($optionEl.detach());
516 });
517 };
518
519
520 /**
521 * moves currently selected options from a select field down one position,
522 * can be multiple entries as well
523 *
524 * @param {Object} $fieldEl a jQuery object, containing the select field
525 */
526 FormEngine.moveOptionDown = function($fieldEl) {
527 // remove the selected options and add it after the next sibling
528 // however, this time, we need to go from the last to the first
529 var selectedOptions = $fieldEl.find(':selected');
530 selectedOptions = $.makeArray(selectedOptions);
531 selectedOptions.reverse();
532 $.each(selectedOptions, function(k, optionEl) {
533 var $optionEl = $(optionEl)
534 ,$optionAfter = $optionEl.next();
535
536 // stop if first option to move is already the last one
537 if (k == 0 && $optionAfter.length === 0) {
538 return false;
539 }
540
541 $optionAfter.after($optionEl.detach());
542 });
543 };
544
545
546 /**
547 * moves currently selected options from a select field as the very last entries
548 *
549 * @param {Object} $fieldEl a jQuery object, containing the select field
550 */
551 FormEngine.moveOptionToBottom = function($fieldEl) {
552 // remove the selected options
553 var selectedOptions = $fieldEl.find(':selected').detach();
554 // and add them on last position again
555 $fieldEl.append(selectedOptions);
556 };
557
558 /**
559 * removes currently selected options from a select field
560 *
561 * @param {Object} $fieldEl a jQuery object, containing the select field
562 * @param {Object} $availableFieldEl a jQuery object, containing all available value
563 */
564 FormEngine.removeOption = function($fieldEl, $availableFieldEl) {
565 var $selected = $fieldEl.find(':selected');
566
567 $selected.each(function() {
568 $availableFieldEl
569 .find('option[value="' + $(this).attr('value') + '"]')
570 .removeClass('hidden')
571 .prop('disabled', false);
572 });
573
574 // remove the selected options
575 $selected.remove();
576 };
577
578
579 /**
580 * initialize events for all form engine relevant tasks
581 * this function only needs to be called once on page load,
582 * as it using deferrer methods only
583 */
584 FormEngine.initializeEvents = function() {
585 // track the arrows "Up", "Down", "Clear" etc in multi-select boxes
586 $(document).on('click', '.t3js-btn-moveoption-top, .t3js-btn-moveoption-up, .t3js-btn-moveoption-down, .t3js-btn-moveoption-bottom, .t3js-btn-removeoption', function(evt) {
587 evt.preventDefault();
588
589 var $el = $(this)
590 ,fieldName = $el.data('fieldname')
591 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
592
593 if ($listFieldEl.length > 0) {
594
595 if ($el.hasClass('t3js-btn-moveoption-top')) {
596 FormEngine.moveOptionToTop($listFieldEl);
597 } else if ($el.hasClass('t3js-btn-moveoption-up')) {
598 FormEngine.moveOptionUp($listFieldEl);
599 } else if ($el.hasClass('t3js-btn-moveoption-down')) {
600 FormEngine.moveOptionDown($listFieldEl);
601 } else if ($el.hasClass('t3js-btn-moveoption-bottom')) {
602 FormEngine.moveOptionToBottom($listFieldEl);
603 } else if ($el.hasClass('t3js-btn-removeoption')) {
604 var $availableFieldEl = FormEngine.getFieldElement(fieldName, '_avail');
605 FormEngine.removeOption($listFieldEl, $availableFieldEl);
606 }
607
608 // make sure to update the hidden field value when modifying the select value
609 FormEngine.updateHiddenFieldValueFromSelect($listFieldEl, FormEngine.getFieldElement(fieldName));
610 FormEngine.legacyFieldChangedCb();
611 if (typeof FormEngine.Validation !== 'undefined' && typeof FormEngine.Validation.validate === 'function') {
612 FormEngine.Validation.validate();
613 }
614 }
615 }).on('click', '.t3js-formengine-select-itemstoselect', function(evt) {
616 // in multi-select environments with two (e.g. "Access"), on click the item from the right should go to the left
617 var $el = $(this)
618 ,fieldName = $el.data('relatedfieldname')
619 ,exclusiveValues = $el.data('exclusivevalues');
620
621 if (fieldName) {
622 // try to add each selected field to the "left" select field
623 $el.find(':selected').each(function() {
624 var $optionEl = $(this);
625 FormEngine.setSelectOptionFromExternalSource(fieldName, $optionEl.prop('value'), $optionEl.text(), $optionEl.prop('title'), exclusiveValues, $optionEl);
626 });
627 }
628 }).on('click', '.t3js-editform-close', function(e) {
629 e.preventDefault();
630 FormEngine.preventExitIfNotSaved();
631 }).on('click', '.t3js-editform-delete-record', function(e) {
632 e.preventDefault();
633 var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
634 var content = TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete this record?';
635 var $anchorElement = $(this);
636 var $modal = Modal.confirm(title, content, Severity.warning, [
637 {
638 text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
639 active: true,
640 btnClass: 'btn-default',
641 name: 'no'
642 },
643 {
644 text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
645 btnClass: 'btn-warning',
646 name: 'yes'
647 }
648 ]);
649 $modal.on('button.clicked', function(e) {
650 if (e.target.name === 'no') {
651 Modal.dismiss();
652 } else if (e.target.name === 'yes') {
653 deleteRecord($anchorElement.data('table'), $anchorElement.data('uid'), $anchorElement.data('return-url'));
654 Modal.dismiss();
655 }
656 });
657 }).on('click', '.t3js-editform-delete-inline-record', function(e) {
658 e.preventDefault();
659 var title = TYPO3.lang['label.confirm.delete_record.title'] || 'Delete this record?';
660 var content = TYPO3.lang['label.confirm.delete_record.content'] || 'Are you sure you want to delete this record?';
661 var $anchorElement = $(this);
662 var $modal = Modal.confirm(title, content, Severity.warning, [
663 {
664 text: TYPO3.lang['buttons.confirm.delete_record.no'] || 'Cancel',
665 active: true,
666 btnClass: 'btn-default',
667 name: 'no'
668 },
669 {
670 text: TYPO3.lang['buttons.confirm.delete_record.yes'] || 'Yes, delete this record',
671 btnClass: 'btn-warning',
672 name: 'yes'
673 }
674 ]);
675 $modal.on('button.clicked', function(e) {
676 if (e.target.name === 'no') {
677 Modal.dismiss();
678 } else if (e.target.name === 'yes') {
679 var objectId = $anchorElement.data('objectid');
680 inline.deleteRecord(objectId);
681 Modal.dismiss();
682 }
683 });
684 }).on('click', '.t3js-editform-submitButton', function(event) {
685 // remember the clicked submit button. we need to know that in TBE_EDITOR.submitForm();
686 var $me = $(this),
687 name = $me.data('name') || this.name,
688 $elem = $('<input />').attr('type', 'hidden').attr('name', name).attr('value', '1');
689
690 $me.parents('form').append($elem);
691 });
692 };
693
694 /**
695 * Initializes the remaining character views based on the fields' maxlength attribute
696 */
697 FormEngine.initializeRemainingCharacterViews = function() {
698 // all fields with a "maxlength" attribute
699 var $maxlengthElements = $('[maxlength]').not('.t3js-datetimepicker');
700 $maxlengthElements.on('focus', function(e) {
701 var $field = $(this),
702 $parent = $field.parents('.t3js-formengine-field-item:first'),
703 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
704
705 // append the counter only at focus to avoid cluttering the DOM
706 $parent.append($('<div />', {'class': 't3js-charcounter'}).append(
707 $('<span />', {'class': maxlengthProperties.labelClass}).text(TBE_EDITOR.labels.remainingCharacters.replace('{0}', maxlengthProperties.remainingCharacters))
708 ));
709 }).on('blur', function() {
710 var $field = $(this),
711 $parent = $field.parents('.t3js-formengine-field-item:first');
712 $parent.find('.t3js-charcounter').remove();
713 }).on('keyup', function() {
714 var $field = $(this),
715 $parent = $field.parents('.t3js-formengine-field-item:first'),
716 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
717
718 // change class and value
719 $parent.find('.t3js-charcounter span').removeClass().addClass(maxlengthProperties.labelClass).text(TBE_EDITOR.labels.remainingCharacters.replace('{0}', maxlengthProperties.remainingCharacters))
720 });
721 $(':password').on('focus', function() {
722 $(this).attr('type', 'text').select();
723 }).on('blur', function() {
724 $(this).attr('type', 'password');
725 });
726 };
727
728 /**
729 * Initialize select checkbox element checkboxes
730 */
731 FormEngine.initializeSelectCheckboxes = function() {
732 $('.t3js-toggle-checkboxes').each(function() {
733 var $checkbox = $(this);
734 var $table = $checkbox.closest('table');
735 var $checkboxes = $table.find('.t3js-checkbox');
736 var checkIt = $checkboxes.length === $table.find('.t3js-checkbox:checked').length;
737 $checkbox.prop('checked', checkIt);
738 });
739 $(document).on('change', '.t3js-toggle-checkboxes', function(e) {
740 e.preventDefault();
741 var $checkbox = $(this);
742 var $table = $checkbox.closest('table');
743 var $checkboxes = $table.find('.t3js-checkbox');
744 var checkIt = $checkboxes.length !== $table.find('.t3js-checkbox:checked').length;
745 $checkboxes.prop('checked', checkIt);
746 $checkbox.prop('checked', checkIt);
747 });
748 $(document).on('change', '.t3js-checkbox', function(e) {
749 FormEngine.updateCheckboxState(this);
750 });
751 };
752
753 /**
754 *
755 * @param {HTMLElement} source
756 */
757 FormEngine.updateCheckboxState = function(source) {
758 var $sourceElement = $(source);
759 var $table = $sourceElement.closest('table');
760 var $checkboxes = $table.find('.t3js-checkbox');
761 var checkIt = $checkboxes.length === $table.find('.t3js-checkbox:checked').length;
762 $table.find('.t3js-toggle-checkboxes').prop('checked', checkIt);
763 };
764
765 /**
766 * Get the properties required for proper rendering of the character counter
767 *
768 * @param {Object} $field
769 * @returns {{remainingCharacters: number, labelClass: string}}
770 */
771 FormEngine.getCharacterCounterProperties = function($field) {
772 var fieldText = $field.val(),
773 maxlength = $field.attr('maxlength'),
774 currentFieldLength = fieldText.length,
775 numberOfLineBreaks = (fieldText.match(/\n/g)||[]).length, // count line breaks
776 remainingCharacters = maxlength - currentFieldLength - numberOfLineBreaks,
777 threshold = 15, // hard limit of remaining characters when the label class changes
778 labelClass = '';
779
780 if (remainingCharacters < threshold) {
781 labelClass = 'label-danger';
782 } else if(remainingCharacters < threshold * 2) {
783 labelClass = 'label-warning';
784 } else {
785 labelClass = 'label-info';
786 }
787
788 return {
789 remainingCharacters: remainingCharacters,
790 labelClass: 'label ' + labelClass
791 };
792 };
793
794 /**
795 * Select field filter functions, see TCA option "enableMultiSelectFilterTextfield"
796 * and "multiSelectFilterItems"
797 */
798 FormEngine.SelectBoxFilter = {
799 options: {
800 fieldContainerSelector: '.t3js-formengine-field-group',
801 filterContainerSelector: '.t3js-formengine-multiselect-filter-container',
802 filterTextFieldSelector: '.t3js-formengine-multiselect-filter-textfield',
803 filterSelectFieldSelector: '.t3js-formengine-multiselect-filter-dropdown',
804 itemsToSelectElementSelector: '.t3js-formengine-select-itemstoselect'
805 }
806 };
807
808 /**
809 * Make sure that all selectors and input filters are recognized
810 * note: this also works on elements that are loaded asynchronously via AJAX, no need to call this method
811 * after an AJAX load.
812 */
813 FormEngine.SelectBoxFilter.initializeEvents = function() {
814 $(document).on('keyup', FormEngine.SelectBoxFilter.options.filterTextFieldSelector, function() {
815 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
816 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
817 }).on('change', FormEngine.SelectBoxFilter.options.filterSelectFieldSelector, function() {
818 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
819 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
820 });
821 };
822
823 /**
824 * Fetch the "itemstoselect" select element where a filter item is attached to
825 *
826 * @param {Object} $relativeElement
827 * @returns {*}
828 */
829 FormEngine.SelectBoxFilter.getSelectElement = function($relativeElement) {
830 var $containerElement = $relativeElement.closest(FormEngine.SelectBoxFilter.options.fieldContainerSelector);
831 return $containerElement.find(FormEngine.SelectBoxFilter.options.itemsToSelectElementSelector);
832 };
833
834 /**
835 * Filter the actual items
836 *
837 * @param {Object} $selectElement
838 * @param {String} filterText
839 */
840 FormEngine.SelectBoxFilter.filter = function($selectElement, filterText) {
841 var $allOptionElements;
842 if (!$selectElement.data('alloptions')) {
843 $allOptionElements = $selectElement.find('option').clone();
844 $selectElement.data('alloptions', $allOptionElements);
845 } else {
846 $allOptionElements = $selectElement.data('alloptions');
847 }
848
849 if (filterText.length > 0) {
850 var matchFilter = new RegExp(filterText, 'i');
851 $selectElement.html('');
852 $allOptionElements.each(function() {
853 var $item = $(this);
854 if ($item.text().match(matchFilter)) {
855 $selectElement.append($item.clone());
856 }
857 });
858 } else {
859 $selectElement.html($allOptionElements);
860 }
861 };
862
863 /**
864 * convert all textareas so they grow when it is typed in.
865 */
866 FormEngine.convertTextareasResizable = function() {
867 var $elements = $('.t3js-formengine-textarea');
868 if (TYPO3.settings.Textarea && TYPO3.settings.Textarea.autosize && $elements.length) {
869 require(['autosize'], function(autosize) {
870 autosize($elements);
871 });
872 }
873 };
874
875 /**
876 * convert all textareas to enable tab
877 */
878 FormEngine.convertTextareasEnableTab = function() {
879 var $elements = $('.t3js-enable-tab');
880 if ($elements.length) {
881 require(['taboverride'], function(taboverride) {
882 taboverride.set($elements);
883 });
884 }
885 };
886
887 /**
888 * this is the main function that is called on page load, but also after elements are asynchroniously
889 * called e.g. after IRRE elements are loaded again, or a new flexform section is added.
890 * use this function in your extension like this "TYPO3.FormEngine.initialize()"
891 * if you add new fields dynamically.
892 *
893 */
894 FormEngine.reinitialize = function() {
895 // apply "close" button to all input / datetime fields
896 if ($('.t3js-clearable').length) {
897 require(['TYPO3/CMS/Backend/jquery.clearable'], function() {
898 $('.t3js-clearable').clearable();
899 });
900 }
901 if ($('.t3-form-suggest').length) {
902 require(['TYPO3/CMS/Backend/FormEngineSuggest'], function(Suggest) {
903 Suggest($('.t3-form-suggest'));
904 });
905 }
906
907 // Apply DatePicker to all date time fields
908 if ($('.t3js-datetimepicker').length) {
909 require(['TYPO3/CMS/Backend/DateTimePicker'], function(DateTimePicker) {
910 DateTimePicker.initialize();
911 });
912 }
913
914 FormEngine.convertTextareasResizable();
915 FormEngine.convertTextareasEnableTab();
916 };
917
918 /**
919 * Show modal to confirm closing the document without saving
920 */
921 FormEngine.preventExitIfNotSaved = function() {
922 if ($('.has-change').length > 0) {
923 var title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to quit without saving?';
924 var content = TYPO3.lang['label.confirm.close_without_save.content'] || 'You have currently unsaved changes. Are you sure that you want to discard all changes?';
925 var $modal = Modal.confirm(title, content, Severity.warning, [
926 {
927 text: TYPO3.lang['buttons.confirm.close_without_save.no'] || 'No, I will continue editing',
928 active: true,
929 btnClass: 'btn-default',
930 name: 'no'
931 },
932 {
933 text: TYPO3.lang['buttons.confirm.close_without_save.yes'] || 'Yes, discard my changes',
934 btnClass: 'btn-warning',
935 name: 'yes'
936 }
937 ]);
938 $modal.on('button.clicked', function(e) {
939 if (e.target.name === 'no') {
940 Modal.dismiss();
941 } else if (e.target.name === 'yes') {
942 Modal.dismiss();
943 FormEngine.closeDocument();
944 }
945 });
946 } else {
947 FormEngine.closeDocument();
948 }
949 };
950
951 /**
952 * Show modal to confirm closing the document without saving
953 */
954 FormEngine.preventSaveIfHasErrors = function() {
955 if ($('.has-error').length > 0) {
956 var title = TYPO3.lang['label.alert.save_with_error.title'] || 'You have errors in your form!';
957 var content = TYPO3.lang['label.alert.save_with_error.content'] || 'Please check the form, there is at least one error in your form.';
958 var $modal = Modal.confirm(title, content, Severity.error, [
959 {
960 text: TYPO3.lang['buttons.alert.save_with_error.ok'] || 'OK',
961 btnClass: 'btn-danger',
962 name: 'ok'
963 }
964 ]);
965 $modal.on('button.clicked', function(e) {
966 if (e.target.name === 'ok') {
967 Modal.dismiss();
968 }
969 });
970 return false;
971 }
972 return true;
973 };
974
975 /**
976 * Close current open document
977 */
978 FormEngine.closeDocument = function() {
979 document.editform.closeDoc.value=1;
980 document.editform.submit();
981 };
982
983 /**
984 * Main init function called from outside
985 *
986 * Sets some options and registers the DOMready handler to initialize further things
987 *
988 * @param {String} browserUrl
989 * @param {Number} mode
990 */
991 FormEngine.initialize = function(browserUrl, mode) {
992 FormEngine.browserUrl = browserUrl;
993 FormEngine.Validation.setUsMode(mode);
994
995 $(function() {
996 FormEngine.initializeEvents();
997 FormEngine.SelectBoxFilter.initializeEvents();
998 FormEngine.initializeRemainingCharacterViews();
999 FormEngine.initializeSelectCheckboxes();
1000 FormEngine.Validation.initialize();
1001 FormEngine.reinitialize();
1002 $('#t3js-ui-block').remove();
1003 });
1004 };
1005
1006 // load required modules to hook in the post initialize function
1007 if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
1008 $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine'], function(pos, moduleName) {
1009 require([moduleName]);
1010 });
1011 }
1012
1013 // make the form engine object publically visible for other objects in the TYPO3 namespace
1014 TYPO3.FormEngine = FormEngine;
1015
1016 // return the object in the global space
1017 return FormEngine;
1018 });