61dc6ecb904636e396be07537809bb2a2c47568b
[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 define('TYPO3/CMS/Backend/FormEngine', ['jquery'], function ($) {
32
33 // main options
34 var FormEngine = {
35 formName: TYPO3.settings.FormEngine.formName
36 ,backPath: TYPO3.settings.FormEngine.backPath
37 ,openedPopupWindow: null
38 ,legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); }
39 ,browserUrl: ''
40 };
41
42 FormEngine.setBrowserUrl = function(browserUrl) {
43 FormEngine.browserUrl = browserUrl;
44 };
45
46 // functions to connect the db/file browser with this document and the formfields on it!
47
48 /**
49 * opens a popup window with the element browser (browser.php)
50 *
51 * @param mode can be "db" or "file"
52 * @param params additional params for the browser window
53 * @param width width of the window
54 * @param height height of the window
55 */
56 FormEngine.openPopupWindow = setFormValueOpenBrowser = function(mode, params, width, height) {
57 var url = FormEngine.backPath + FormEngine.browserUrl + '&mode=' + mode + '&bparams=' + params;
58 width = width ? width : top.TYPO3.configuration.PopupWindow.width;
59 height = height ? height : top.TYPO3.configuration.PopupWindow.height;
60 FormEngine.openedPopupWindow = window.open(url, 'Typo3WinBrowser', 'height=' + height + ',width=' + width + ',status=0,menubar=0,resizable=1,scrollbars=1');
61 FormEngine.openedPopupWindow.focus();
62 };
63
64
65 /**
66 * properly fills the select field from the popup window (element browser, link browser)
67 * or from a multi-select (two selects side-by-side)
68 * previously known as "setFormValueFromBrowseWin"
69 *
70 * @param fieldName formerly known as "fName" name of the field, like [tt_content][2387][header]
71 * @param value the value to fill in (could be an integer)
72 * @param label the visible name in the selector
73 * @param title the title when hovering over it
74 * @param exclusiveValues if the select field has exclusive options that are not combine-able
75 */
76 FormEngine.setSelectOptionFromExternalSource = setFormValueFromBrowseWin = function(fieldName, value, label, title, exclusiveValues) {
77 exclusiveValues = String(exclusiveValues);
78
79 var $originalFieldEl = $fieldEl = FormEngine.getFieldElement(fieldName)
80 ,isMultiple = false
81 ,isList = false;
82
83 if ($originalFieldEl.length == 0 || value === '--div--') {
84 return;
85 }
86
87 // Check if the form object has a "_list" element
88 // The "_list" element exists for multiple selection select types
89 var $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
90 if ($listFieldEl.length > 0) {
91 $fieldEl = $listFieldEl;
92 isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
93 isList = true;
94 }
95
96 // clear field before adding value, if configured so (maxitems==1)
97 // @todo: clean this code
98 if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName] != 'undefined') {
99 clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName];
100 $fieldEl.empty();
101
102 // Clear the upload field
103 var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
104 if (filesContainer) {
105 filesContainer.innerHTML = filesContainer.innerHTML;
106 }
107 }
108
109 if (isMultiple || isList) {
110
111 // If multiple values are not allowed, clear anything that is in the control already
112 if (!isMultiple) {
113 $fieldEl.empty();
114 }
115
116 // Clear elements if exclusive values are found
117 if (exclusiveValues) {
118 var m = new RegExp('(^|,)' + value + '($|,)');
119 // the new value is exclusive => remove all existing values
120 if (exclusiveValues.match(m)) {
121 $fieldEl.empty();
122
123 // there is an old value and it was exclusive => it has to be removed
124 } else if ($fieldEl.children('option').length == 1) {
125 m = new RegExp("(^|,)" + $fieldEl.children('option').prop('value') + "($|,)");
126 if (exclusiveValues.match(m)) {
127 $fieldEl.empty();
128 }
129 }
130 }
131
132 // Inserting the new element
133 var addNewValue = true;
134
135 // check if there is a "_mul" field (a field on the right) and if the field was already added
136 var $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
137 if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
138 $fieldEl.children('option').each(function(k, optionEl) {
139 if ($(optionEl).prop('value') == value) {
140 addNewValue = false;
141 return false;
142 }
143 });
144 }
145
146 // element can be added
147 if (addNewValue) {
148 // finally add the option
149 var $option = $('<option value="' + value + '" title="' + title + '"></option>');
150 $option.attr({value: value, title: title}).text(label);
151 $option.appendTo($fieldEl);
152
153 // set the hidden field
154 FormEngine.updateHiddenFieldValueFromSelect($fieldEl, $originalFieldEl);
155
156 // execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
157 FormEngine.legacyFieldChangedCb();
158 }
159
160 } else {
161
162 // The incoming value consists of the table name, an underscore and the uid
163 // For a single selection field we need only the uid, so we extract it
164 var pattern = /_(\\d+)$/
165 ,result = value.match(pattern);
166
167 if (result != null) {
168 value = result[1];
169 }
170
171 // Change the selected value
172 $fieldEl.val(value);
173 }
174 };
175
176 /**
177 * sets the value of the hidden field, from the select list, always executed after the select field was updated
178 * previously known as global function setHiddenFromList()
179 *
180 * @param selectFieldEl the select field
181 * @param originalFieldEl the hidden form field
182 */
183 FormEngine.updateHiddenFieldValueFromSelect = setHiddenFromList = function(selectFieldEl, originalFieldEl) {
184 var selectedValues = [];
185 $(selectFieldEl).children('option').each(function() {
186 selectedValues.push($(this).prop('value'));
187 });
188
189 // make a comma separated list, if it is a multi-select
190 // set the values to the final hidden field
191 $(originalFieldEl).val(selectedValues.join(','));
192 };
193
194 // legacy function, can be removed once this function is not in use anymore
195 setFormValueManipulate = function(fName, type, maxLength) {
196 var $formEl = FormEngine.getFormElement(fName);
197 if ($formEl.length > 0) {
198 var formObj = $formEl.get(0);
199 var localArray_V = new Array();
200 var localArray_L = new Array();
201 var localArray_S = new Array();
202 var localArray_T = new Array();
203 var fObjSel = formObj[fName + '_list'];
204 var l = fObjSel.length;
205 var c = 0;
206
207 if (type == 'RemoveFirstIfFull') {
208 if (maxLength == 1) {
209 for (a = 1; a < l; a++) {
210 if (fObjSel.options[a].selected != 1) {
211 localArray_V[c] = fObjSel.options[a].value;
212 localArray_L[c] = fObjSel.options[a].text;
213 localArray_S[c] = 0;
214 localArray_T[c] = fObjSel.options[a].title;
215 c++;
216 }
217 }
218 } else {
219 return;
220 }
221 }
222
223 if ((type=="Remove" && fObjSel.size > 1) || type=="Top" || type=="Bottom") {
224 if (type=="Top") {
225 for (a=0;a<l;a++) {
226 if (fObjSel.options[a].selected==1) {
227 localArray_V[c]=fObjSel.options[a].value;
228 localArray_L[c]=fObjSel.options[a].text;
229 localArray_S[c]=1;
230 localArray_T[c] = fObjSel.options[a].title;
231 c++;
232 }
233 }
234 }
235 for (a=0;a<l;a++) {
236 if (fObjSel.options[a].selected!=1) {
237 localArray_V[c]=fObjSel.options[a].value;
238 localArray_L[c]=fObjSel.options[a].text;
239 localArray_S[c]=0;
240 localArray_T[c] = fObjSel.options[a].title;
241 c++;
242 }
243 }
244 if (type=="Bottom") {
245 for (a=0;a<l;a++) {
246 if (fObjSel.options[a].selected==1) {
247 localArray_V[c]=fObjSel.options[a].value;
248 localArray_L[c]=fObjSel.options[a].text;
249 localArray_S[c]=1;
250 localArray_T[c] = fObjSel.options[a].title;
251 c++;
252 }
253 }
254 }
255 }
256 if (type=="Down") {
257 var tC = 0;
258 var tA = new Array();
259
260 for (a=0;a<l;a++) {
261 if (fObjSel.options[a].selected!=1) {
262 // Add non-selected element:
263 localArray_V[c]=fObjSel.options[a].value;
264 localArray_L[c]=fObjSel.options[a].text;
265 localArray_S[c]=0;
266 localArray_T[c] = fObjSel.options[a].title;
267 c++;
268
269 // Transfer any accumulated and reset:
270 if (tA.length > 0) {
271 for (aa=0;aa<tA.length;aa++) {
272 localArray_V[c]=fObjSel.options[tA[aa]].value;
273 localArray_L[c]=fObjSel.options[tA[aa]].text;
274 localArray_S[c]=1;
275 localArray_T[c] = fObjSel.options[tA[aa]].title;
276 c++;
277 }
278
279 var tC = 0;
280 var tA = new Array();
281 }
282 } else {
283 tA[tC] = a;
284 tC++;
285 }
286 }
287 // Transfer any remaining:
288 if (tA.length > 0) {
289 for (aa=0;aa<tA.length;aa++) {
290 localArray_V[c]=fObjSel.options[tA[aa]].value;
291 localArray_L[c]=fObjSel.options[tA[aa]].text;
292 localArray_S[c]=1;
293 localArray_T[c] = fObjSel.options[tA[aa]].title;
294 c++;
295 }
296 }
297 }
298 if (type=="Up") {
299 var tC = 0;
300 var tA = new Array();
301 var c = l-1;
302
303 for (a=l-1;a>=0;a--) {
304 if (fObjSel.options[a].selected!=1) {
305
306 // Add non-selected element:
307 localArray_V[c]=fObjSel.options[a].value;
308 localArray_L[c]=fObjSel.options[a].text;
309 localArray_S[c]=0;
310 localArray_T[c] = fObjSel.options[a].title;
311 c--;
312
313 // Transfer any accumulated and reset:
314 if (tA.length > 0) {
315 for (aa=0;aa<tA.length;aa++) {
316 localArray_V[c]=fObjSel.options[tA[aa]].value;
317 localArray_L[c]=fObjSel.options[tA[aa]].text;
318 localArray_S[c]=1;
319 localArray_T[c] = fObjSel.options[tA[aa]].title;
320 c--;
321 }
322
323 var tC = 0;
324 var tA = new Array();
325 }
326 } else {
327 tA[tC] = a;
328 tC++;
329 }
330 }
331 // Transfer any remaining:
332 if (tA.length > 0) {
333 for (aa=0;aa<tA.length;aa++) {
334 localArray_V[c]=fObjSel.options[tA[aa]].value;
335 localArray_L[c]=fObjSel.options[tA[aa]].text;
336 localArray_S[c]=1;
337 localArray_T[c] = fObjSel.options[tA[aa]].title;
338 c--;
339 }
340 }
341 c=l; // Restore length value in "c"
342 }
343
344 // Transfer items in temporary storage to list object:
345 fObjSel.length = c;
346 for (a = 0; a < c; a++) {
347 fObjSel.options[a].value = localArray_V[a];
348 fObjSel.options[a].text = localArray_L[a];
349 fObjSel.options[a].selected = localArray_S[a];
350 fObjSel.options[a].title = localArray_T[a];
351 }
352 FormEngine.updateHiddenFieldValueFromSelect(fObjSel, formObj[fName]);
353
354 FormEngine.legacyFieldChangedCb();
355 }
356 };
357
358
359 /**
360 * legacy function
361 * returns the DOM object for the given form name of the current form,
362 * but only if the given field name is valid, legacy function, use "getFormElement" instead
363 *
364 * @param fieldName the name of the field name
365 * @returns {*|DOMElement}
366 */
367 setFormValue_getFObj = function(fieldName) {
368 var $formEl = FormEngine.getFormElement(fieldName);
369 if ($formEl.length > 0) {
370 // return the DOM element of the form object
371 return $formEl.get(0);
372 } else {
373 return null;
374 }
375 };
376
377 /**
378 * returns a jQuery object for the given form name of the current form,
379 * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
380 * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
381 *
382 * @param fieldName the field name to check for, optional
383 * @returns {*|HTMLElement}
384 */
385 FormEngine.getFormElement = function(fieldName) {
386 var $formEl = $('form[name="' + FormEngine.formName + '"]:first');
387 if (fieldName) {
388 var $fieldEl = FormEngine.getFieldElement(fieldName)
389 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
390
391 // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
392 if ($fieldEl.length > 0 &&
393 (
394 ($fieldEl.prop('type') == 'select-one') ||
395 ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
396 )
397 ) {
398 return $formEl;
399 } else {
400 console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
401 alert('Form field is invalid');
402 }
403 } else {
404 return $formEl;
405 }
406 };
407
408
409 /**
410 * returns a jQuery object of the field DOM element of the current form, can also be used to
411 * request an alternative field like "_hr", "_list" or "_mul"
412 *
413 * @param fieldName the name of the field (<input name="fieldName">)
414 * @param appendix optional
415 * @param noFallback if set, then the appendix value is returned no matter if it exists or not
416 * @returns {*|HTMLElement}
417 */
418 FormEngine.getFieldElement = function(fieldName, appendix, noFallback) {
419 var $formEl = FormEngine.getFormElement();
420
421 // if an appendix is set, return the field with the appendix (like _mul or _list)
422 if (appendix) {
423 var $fieldEl = $(':input[name="' + fieldName + appendix + '"]', $formEl);
424 if ($fieldEl.length > 0 || noFallback === true) {
425 return $fieldEl;
426 }
427 }
428
429 return $(':input[name="' + fieldName + '"]', $formEl);
430 };
431
432
433
434 /**************************************************
435 * manipulate existing options in a select field
436 **************************************************/
437
438 /**
439 * moves currently selected options from a select field to the very top,
440 * can be multiple entries as well
441 *
442 * @param $fieldEl a jQuery object, containing the select field
443 */
444 FormEngine.moveOptionToTop = function($fieldEl) {
445 // remove the selected options
446 var selectedOptions = $fieldEl.find(':selected').detach();
447 // and add them on first position again
448 $fieldEl.prepend(selectedOptions);
449 };
450
451
452 /**
453 * moves currently selected options from a select field up by one position,
454 * can be multiple entries as well
455 *
456 * @param $fieldEl a jQuery object, containing the select field
457 */
458 FormEngine.moveOptionUp = function($fieldEl) {
459 // remove the selected options and add it before the previous sibling
460 $.each($fieldEl.find(':selected'), function(k, optionEl) {
461 var $optionEl = $(optionEl)
462 ,$optionBefore = $optionEl.prev();
463
464 // stop if first option to move is already the first one
465 if (k == 0 && $optionBefore.length === 0) {
466 return false;
467 }
468
469 $optionBefore.before($optionEl.detach());
470 });
471 };
472
473
474 /**
475 * moves currently selected options from a select field down one position,
476 * can be multiple entries as well
477 *
478 * @param $fieldEl a jQuery object, containing the select field
479 */
480 FormEngine.moveOptionDown = function($fieldEl) {
481 // remove the selected options and add it after the next sibling
482 // however, this time, we need to go from the last to the first
483 var selectedOptions = $fieldEl.find(':selected');
484 selectedOptions = $.makeArray(selectedOptions);
485 selectedOptions.reverse();
486 $.each(selectedOptions, function(k, optionEl) {
487 var $optionEl = $(optionEl)
488 ,$optionAfter = $optionEl.next();
489
490 // stop if first option to move is already the last one
491 if (k == 0 && $optionAfter.length === 0) {
492 return false;
493 }
494
495 $optionAfter.after($optionEl.detach());
496 });
497 };
498
499
500 /**
501 * moves currently selected options from a select field as the very last entries
502 *
503 * @param $fieldEl a jQuery object, containing the select field
504 */
505 FormEngine.moveOptionToBottom = function($fieldEl) {
506 // remove the selected options
507 var selectedOptions = $fieldEl.find(':selected').detach();
508 // and add them on last position again
509 $fieldEl.append(selectedOptions);
510 };
511
512 /**
513 * removes currently selected options from a select field
514 *
515 * @param $fieldEl a jQuery object, containing the select field
516 */
517 FormEngine.removeOption = function($fieldEl) {
518 // remove the selected options
519 $fieldEl.find(':selected').remove();
520 };
521
522
523 /**
524 * initialize events for all form engine relevant tasks
525 * this function only needs to be called once on page load,
526 * as it using deferrer methods only
527 */
528 FormEngine.initializeEvents = function() {
529
530 // track the arrows "Up", "Down", "Clear" etc in multi-select boxes
531 $(document).on('click', '.t3-btn-moveoption-top, .t3-btn-moveoption-up, .t3-btn-moveoption-down, .t3-btn-moveoption-bottom, .t3-btn-removeoption', function(evt) {
532 var $el = $(this)
533 ,fieldName = $el.data('fieldname')
534 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
535
536 if ($listFieldEl.length > 0) {
537
538 if ($el.hasClass('t3-btn-moveoption-top')) {
539 FormEngine.moveOptionToTop($listFieldEl);
540 } else if ($el.hasClass('t3-btn-moveoption-up')) {
541 FormEngine.moveOptionUp($listFieldEl);
542 } else if ($el.hasClass('t3-btn-moveoption-down')) {
543 FormEngine.moveOptionDown($listFieldEl);
544 } else if ($el.hasClass('t3-btn-moveoption-bottom')) {
545 FormEngine.moveOptionToBottom($listFieldEl);
546 } else if ($el.hasClass('t3-btn-removeoption')) {
547 FormEngine.removeOption($listFieldEl);
548 }
549
550 // make sure to update the hidden field value when modifying the select value
551 FormEngine.updateHiddenFieldValueFromSelect($listFieldEl, FormEngine.getFieldElement(fieldName));
552 FormEngine.legacyFieldChangedCb();
553 }
554 });
555
556 FormEngine.initializeRemainingCharacterViews();
557
558 // in multi-select environments with two (e.g. "Access"), on click the item from the right should go to the left
559 $(document).on('click', '.t3js-formengine-select-itemstoselect', function(evt) {
560 var $el = $(this)
561 ,fieldName = $el.data('relatedfieldname')
562 ,exclusiveValues = $el.data('exclusivevalues');
563
564 if (fieldName) {
565 // try to add each selected field to the "left" select field
566 $el.find(':selected').each(function() {
567 var $optionEl = $(this);
568 FormEngine.setSelectOptionFromExternalSource(fieldName, $optionEl.prop('value'), $optionEl.text(), $optionEl.prop('title'), exclusiveValues);
569 });
570 }
571 });
572 };
573
574 /**
575 * Initializes the remaining character views based on the fields' maxlength attribute
576 */
577 FormEngine.initializeRemainingCharacterViews = function() {
578 // all fields with a "maxlength" attribute
579 var $maxlengthElements = $('[maxlength]');
580 $maxlengthElements.on('focus', function(e) {
581 var $field = $(this),
582 $parent = $field.parents('.t3js-formengine-field-item'),
583 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
584
585 // append the counter only at focus to avoid cluttering the DOM
586 $parent.append($('<div />', {'class': 't3js-charcounter'}).append(
587 $('<span />', {'class': maxlengthProperties.labelClass}).text(TBE_EDITOR.labels.remainingCharacters.replace('{0}', maxlengthProperties.remainingCharacters))
588 ));
589 }).on('blur', function() {
590 var $field = $(this),
591 $parent = $field.parents('.t3js-formengine-field-item');
592 $parent.find('.t3js-charcounter').remove();
593 }).on('keyup', function() {
594 var $field = $(this),
595 $parent = $field.parents('.t3js-formengine-field-item'),
596 maxlengthProperties = FormEngine.getCharacterCounterProperties($field);
597
598 // change class and value
599 $parent.find('.t3js-charcounter span').removeClass().addClass(maxlengthProperties.labelClass).text(TBE_EDITOR.labels.remainingCharacters.replace('{0}', maxlengthProperties.remainingCharacters))
600 });
601 };
602
603 /**
604 * Get the properties required for proper rendering of the character counter
605 */
606 FormEngine.getCharacterCounterProperties = function($field) {
607 var fieldText = $field.val(),
608 maxlength = $field.attr('maxlength'),
609 currentFieldLength = fieldText.length,
610 numberOfLineBreaks = (fieldText.match(/\n/g)||[]).length, // count line breaks
611 remainingCharacters = maxlength - currentFieldLength - numberOfLineBreaks,
612 threshold = 15, // hard limit of remaining characters when the label class changes
613 labelClass = '';
614
615 if (remainingCharacters < threshold) {
616 labelClass = 'label-danger';
617 } else if(remainingCharacters < threshold * 2) {
618 labelClass = 'label-warning';
619 } else {
620 labelClass = 'label-info';
621 }
622
623 return {
624 remainingCharacters: remainingCharacters,
625 labelClass: 'label ' + labelClass
626 };
627 };
628
629 /**
630 * select field filter functions, see TCA option "enableMultiSelectFilterTextfield"
631 * and "multiSelectFilterItems"
632 */
633 FormEngine.SelectBoxFilter = {
634 options: {
635 fieldContainerSelector: '.t3js-formengine-field-group',
636 filterContainerSelector: '.t3js-formengine-multiselect-filter-container',
637 filterTextFieldSelector: '.t3js-formengine-multiselect-filter-textfield',
638 filterSelectFieldSelector: '.t3js-formengine-multiselect-filter-dropdown',
639 itemsToSelectElementSelector: '.t3js-formengine-select-itemstoselect'
640 }
641 };
642
643 /**
644 * make sure that all selectors and input filters are recognized
645 * note: this also works on elements that are loaded asynchronously via AJAX, no need to call this method
646 * after an AJAX load.
647 */
648 FormEngine.SelectBoxFilter.initializeEvents = function() {
649 $(document).on('keyup', FormEngine.SelectBoxFilter.options.filterTextFieldSelector, function() {
650 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
651 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
652 }).on('change', FormEngine.SelectBoxFilter.options.filterSelectFieldSelector, function() {
653 var $selectElement = FormEngine.SelectBoxFilter.getSelectElement($(this));
654 FormEngine.SelectBoxFilter.filter($selectElement, $(this).val());
655 });
656 };
657
658 /**
659 * fetch the "itemstoselect" select element where a filter item is attached to
660 */
661 FormEngine.SelectBoxFilter.getSelectElement = function($relativeElement) {
662 var $containerElement = $relativeElement.closest(FormEngine.SelectBoxFilter.options.fieldContainerSelector);
663 return $containerElement.find(FormEngine.SelectBoxFilter.options.itemsToSelectElementSelector);
664 };
665
666 /**
667 * filter the actual items
668 */
669 FormEngine.SelectBoxFilter.filter = function($selectElement, filterText) {
670 var $allOptionElements;
671 if (!$selectElement.data('alloptions')) {
672 $allOptionElements = $selectElement.find('option').clone();
673 $selectElement.data('alloptions', $allOptionElements);
674 } else {
675 $allOptionElements = $selectElement.data('alloptions');
676 }
677
678 if (filterText.length > 0) {
679 var matchFilter = new RegExp(filterText, 'i');
680 $selectElement.html('');
681 $allOptionElements.each(function() {
682 if ($(this).text().match(matchFilter)) {
683 $selectElement.append($(this).clone());
684 }
685 });
686 } else {
687 $selectElement.html($allOptionElements);
688 }
689 };
690
691 /**
692 * convert all textareas so they grow when it is typed in.
693 */
694 FormEngine.convertTextareasResizable = function() {
695 var $elements = $('.t3js-formengine-textarea');
696 if (TYPO3.settings.Textarea && TYPO3.settings.Textarea.autosize && $elements.length) {
697 require(['jquery/jquery.autosize.min'], function() {
698 $elements.autosize();
699 });
700 }
701 };
702
703 /**
704 * this is the main function that is called on page load, but also after elements are asynchroniously
705 * called e.g. after IRRE elements are loaded again, or a new flexform section is added.
706 * use this function in your extension like this "TYPO3.FormEngine.initialize()"
707 * if you add new fields dynamically.
708 *
709 */
710 FormEngine.reinitialize = function() {
711 // apply "close" button to all input / datetime fields
712 if ($('.t3js-clearable').length) {
713 require(['jquery/jquery.clearable'], function() {
714 $('.t3js-clearable').clearable();
715 });
716 }
717 // apply DatePicker to all date time fields
718 require(['TYPO3/CMS/Backend/DateTimePicker'], function(DateTimePicker) {
719 DateTimePicker.initialize();
720 });
721 FormEngine.convertTextareasResizable();
722 $(document).on('click', '.t3js-editform-close', function(e) {
723 e.preventDefault();
724 FormEngine.preventExitIfNotSaved();
725 });
726 };
727
728 /**
729 * Show modal to confirm closing the document without saving
730 */
731 FormEngine.preventExitIfNotSaved = function() {
732 if ($('.has-change').length > 0) {
733 var title = TYPO3.lang['label.confirm.close_without_save.title'] || 'Do you want to quit without saving?';
734 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?';
735 top.TYPO3.Modal.confirm(title, content, top.TYPO3.Severity.warning, [
736 {
737 text: TYPO3.lang['buttons.confirm.close_without_save.no'] || 'No, I will continue editing',
738 active: true,
739 trigger: function() {
740 top.TYPO3.Modal.dismiss();
741 }
742 },
743 {
744 text: TYPO3.lang['buttons.confirm.close_without_save.yes'] || 'Yes, discard my changes',
745 btnClass: 'btn-warning',
746 trigger: function() {
747 top.TYPO3.Modal.dismiss();
748 FormEngine.closeDocument();
749 }
750 }
751 ]);
752 } else {
753 FormEngine.closeDocument()
754 }
755 };
756
757 /**
758 * Close current open document
759 */
760 FormEngine.closeDocument = function() {
761 document.editform.closeDoc.value=1;
762 document.editform.submit();
763 };
764
765 /**
766 * initialize function, always require possible post-render hooks return the main object
767 */
768 return function() {
769 // the functions are both using delegates, thus no need to be called again
770 FormEngine.initializeEvents();
771 FormEngine.SelectBoxFilter.initializeEvents();
772 FormEngine.reinitialize();
773
774 // load required modules to hook in the post initialize function
775 if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
776 $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine'], function(pos, moduleName) {
777 require([moduleName]);
778 });
779 }
780
781 // make the form engine object publically visible for other objects in the TYPO3 namespace
782 TYPO3.FormEngine = FormEngine;
783
784 // return the object in the global space
785 return FormEngine;
786 }();
787 });