55c5dd6b47985ae943d0cfc89f9f98f8454e2960
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / FormEngine.js
1 /**
2 * (c) 2013 Benjamin Mack
3 * Released under the GPL v2+, part of TYPO3
4 *
5 * contains all JS functions related to TYPO3 TCEforms/FormEngine
6 *
7 * there are separate issues in this main object
8 * - functions, related to Element Browser ("Popup Window") and select fields
9 * - filling select fields (by wizard etc) from outside, formerly known via "setFormValueFromBrowseWin"
10 * - select fields: move selected items up and down via buttons, remove items etc
11 * -
12 *
13 */
14
15 // add legacy functions to be accessible in the global scope
16 var setFormValueOpenBrowser
17 ,setFormValueFromBrowseWin
18 ,setHiddenFromList
19 ,setFormValueManipulate
20 ,setFormValue_getFObj
21
22
23 define('TYPO3/CMS/Backend/FormEngine', ['jquery'], function ($) {
24
25 // main options
26 var FormEngine = {
27 formName: TYPO3.settings.FormEngine.formName
28 ,backPath: TYPO3.settings.FormEngine.backPath
29 ,openedPopupWindow: null
30 ,legacyFieldChangedCb: function() { !$.isFunction(TYPO3.settings.FormEngine.legacyFieldChangedCb) || TYPO3.settings.FormEngine.legacyFieldChangedCb(); }
31 };
32
33
34 // functions to connect the db/file browser with this document and the formfields on it!
35
36 /**
37 * opens a popup window with the element browser (browser.php)
38 *
39 * @param mode can be "db" or "file"
40 * @param params additional params for the browser window
41 */
42 FormEngine.openPopupWindow = setFormValueOpenBrowser = function(mode, params) {
43 var url = FormEngine.backPath + 'browser.php?mode=' + mode + '&bparams=' + params;
44 FormEngine.openedPopupWindow = window.open(url, 'Typo3WinBrowser', 'height=650,width=' + (mode == 'db' ? 650 : 600) + ',status=0,menubar=0,resizable=1,scrollbars=1');
45 FormEngine.openedPopupWindow.focus();
46 };
47
48
49 /**
50 * properly fills the select field from the popup window (element browser, link browser)
51 * or from a multi-select (two selects side-by-side)
52 * previously known as "setFormValueFromBrowseWin"
53 *
54 * @param fieldName formerly known as "fName" name of the field, like [tt_content][2387][header]
55 * @param value the value to fill in (could be an integer)
56 * @param label the visible name in the selector
57 * @param title the title when hovering over it
58 * @param exclusiveValues if the select field has exclusive options that are not combine-able
59 */
60 FormEngine.setSelectOptionFromExternalSource = setFormValueFromBrowseWin = function(fieldName, value, label, title, exclusiveValues) {
61 var $originalFieldEl = $fieldEl = FormEngine.getFieldElement(fieldName)
62 ,isMultiple = false
63 ,isList = false;
64
65 if ($originalFieldEl.length == 0 || value === '--div--') {
66 return;
67 }
68
69 // Check if the form object has a "_list" element
70 // The "_list" element exists for multiple selection select types
71 var $listFieldEl = FormEngine.getFieldElement(fieldName, '_list', true);
72 if ($listFieldEl.length > 0) {
73 $fieldEl = $listFieldEl;
74 isMultiple = ($fieldEl.prop('multiple') && $fieldEl.prop('size') != '1');
75 isList = true;
76 }
77
78 // clear field before adding value, if configured so (maxitems==1)
79 // @todo: clean this code
80 if (typeof TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName] != 'undefined') {
81 clearSettings = TBE_EDITOR.clearBeforeSettingFormValueFromBrowseWin[fieldName];
82 $fieldEl.empty();
83
84 // Clear the upload field
85 var filesContainer = document.getElementById(clearSettings.itemFormElID_file);
86 if (filesContainer) {
87 filesContainer.innerHTML = filesContainer.innerHTML;
88 }
89 }
90
91 if (isMultiple || isList) {
92
93 // If multiple values are not allowed, clear anything that is in the control already
94 if (!isMultiple) {
95 $fieldEl.empty();
96 }
97
98 // Clear elements if exclusive values are found
99 if (exclusiveValues) {
100 var m = new RegExp('(^|,)' + value + '($|,)');
101 // the new value is exclusive => remove all existing values
102 if (exclusiveValues.match(m)) {
103 $fieldEl.empty();
104
105 // there is an old value and it was exclusive => it has to be removed
106 } else if ($fieldEl.children('option').length == 1) {
107 m = new RegExp("(^|,)" + $fieldEl.children('option').prop('value') + "($|,)");
108 if (exclusiveValues.match(m)) {
109 $fieldEl.empty();
110 }
111 }
112 }
113
114 // Inserting the new element
115 var addNewValue = true;
116
117 // check if there is a "_mul" field (a field on the right) and if the field was already added
118 var $multipleFieldEl = FormEngine.getFieldElement(fieldName, '_mul', true);
119 if ($multipleFieldEl.length == 0 || $multipleFieldEl.val() == 0) {
120 $fieldEl.children('option').each(function(k, optionEl) {
121 if ($(optionEl).prop('value') == value) {
122 addNewValue = false;
123 return false;
124 }
125 });
126 }
127
128 // element can be added
129 if (addNewValue) {
130 // finally add the option
131 $fieldEl.append('<option value="' + value + '" title="' + title + '">' + decodeURI(label) + '</option>');
132
133 // set the hidden field
134 FormEngine.updateHiddenFieldValueFromSelect($fieldEl, $originalFieldEl);
135
136 // execute the phpcode from $FormEngine->TBE_EDITOR_fieldChanged_func
137 FormEngine.legacyFieldChangedCb();
138 }
139
140 } else {
141
142 // The incoming value consists of the table name, an underscore and the uid
143 // For a single selection field we need only the uid, so we extract it
144 var pattern = /_(\\d+)$/
145 ,result = value.match(pattern);
146
147 if (result != null) {
148 value = result[1];
149 }
150
151 // Change the selected value
152 $fieldEl.val(value);
153 }
154 };
155
156 /**
157 * sets the value of the hidden field, from the select list, always executed after the select field was updated
158 * previously known as global function setHiddenFromList()
159 *
160 * @param selectFieldEl the select field
161 * @param originalFieldEl the hidden form field
162 */
163 FormEngine.updateHiddenFieldValueFromSelect = setHiddenFromList = function(selectFieldEl, originalFieldEl) {
164 var selectedValues = [];
165 $(selectFieldEl).children('option').each(function() {
166 selectedValues.push($(this).prop('value'));
167 });
168
169 // make a comma separated list, if it is a multi-select
170 // set the values to the final hidden field
171 $(originalFieldEl).val(selectedValues.join(','));
172 };
173
174 // legacy function, can be removed once this function is not in use anymore
175 setFormValueManipulate = function(fName, type, maxLength) {
176 var $formEl = FormEngine.getFormElement(fName);
177 if ($formEl.length > 0) {
178 var formObj = $formEl.get(0);
179 var localArray_V = new Array();
180 var localArray_L = new Array();
181 var localArray_S = new Array();
182 var localArray_T = new Array();
183 var fObjSel = formObj[fName + '_list'];
184 var l = fObjSel.length;
185 var c = 0;
186
187 if (type == 'RemoveFirstIfFull') {
188 if (maxLength == 1) {
189 for (a = 1; a < l; a++) {
190 if (fObjSel.options[a].selected != 1) {
191 localArray_V[c] = fObjSel.options[a].value;
192 localArray_L[c] = fObjSel.options[a].text;
193 localArray_S[c] = 0;
194 localArray_T[c] = fObjSel.options[a].title;
195 c++;
196 }
197 }
198 } else {
199 return;
200 }
201 }
202
203 if ((type=="Remove" && fObjSel.size > 1) || type=="Top" || type=="Bottom") {
204 if (type=="Top") {
205 for (a=0;a<l;a++) {
206 if (fObjSel.options[a].selected==1) {
207 localArray_V[c]=fObjSel.options[a].value;
208 localArray_L[c]=fObjSel.options[a].text;
209 localArray_S[c]=1;
210 localArray_T[c] = fObjSel.options[a].title;
211 c++;
212 }
213 }
214 }
215 for (a=0;a<l;a++) {
216 if (fObjSel.options[a].selected!=1) {
217 localArray_V[c]=fObjSel.options[a].value;
218 localArray_L[c]=fObjSel.options[a].text;
219 localArray_S[c]=0;
220 localArray_T[c] = fObjSel.options[a].title;
221 c++;
222 }
223 }
224 if (type=="Bottom") {
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 }
236 if (type=="Down") {
237 var tC = 0;
238 var tA = new Array();
239
240 for (a=0;a<l;a++) {
241 if (fObjSel.options[a].selected!=1) {
242 // Add non-selected element:
243 localArray_V[c]=fObjSel.options[a].value;
244 localArray_L[c]=fObjSel.options[a].text;
245 localArray_S[c]=0;
246 localArray_T[c] = fObjSel.options[a].title;
247 c++;
248
249 // Transfer any accumulated and reset:
250 if (tA.length > 0) {
251 for (aa=0;aa<tA.length;aa++) {
252 localArray_V[c]=fObjSel.options[tA[aa]].value;
253 localArray_L[c]=fObjSel.options[tA[aa]].text;
254 localArray_S[c]=1;
255 localArray_T[c] = fObjSel.options[tA[aa]].title;
256 c++;
257 }
258
259 var tC = 0;
260 var tA = new Array();
261 }
262 } else {
263 tA[tC] = a;
264 tC++;
265 }
266 }
267 // Transfer any remaining:
268 if (tA.length > 0) {
269 for (aa=0;aa<tA.length;aa++) {
270 localArray_V[c]=fObjSel.options[tA[aa]].value;
271 localArray_L[c]=fObjSel.options[tA[aa]].text;
272 localArray_S[c]=1;
273 localArray_T[c] = fObjSel.options[tA[aa]].title;
274 c++;
275 }
276 }
277 }
278 if (type=="Up") {
279 var tC = 0;
280 var tA = new Array();
281 var c = l-1;
282
283 for (a=l-1;a>=0;a--) {
284 if (fObjSel.options[a].selected!=1) {
285
286 // Add non-selected element:
287 localArray_V[c]=fObjSel.options[a].value;
288 localArray_L[c]=fObjSel.options[a].text;
289 localArray_S[c]=0;
290 localArray_T[c] = fObjSel.options[a].title;
291 c--;
292
293 // Transfer any accumulated and reset:
294 if (tA.length > 0) {
295 for (aa=0;aa<tA.length;aa++) {
296 localArray_V[c]=fObjSel.options[tA[aa]].value;
297 localArray_L[c]=fObjSel.options[tA[aa]].text;
298 localArray_S[c]=1;
299 localArray_T[c] = fObjSel.options[tA[aa]].title;
300 c--;
301 }
302
303 var tC = 0;
304 var tA = new Array();
305 }
306 } else {
307 tA[tC] = a;
308 tC++;
309 }
310 }
311 // Transfer any remaining:
312 if (tA.length > 0) {
313 for (aa=0;aa<tA.length;aa++) {
314 localArray_V[c]=fObjSel.options[tA[aa]].value;
315 localArray_L[c]=fObjSel.options[tA[aa]].text;
316 localArray_S[c]=1;
317 localArray_T[c] = fObjSel.options[tA[aa]].title;
318 c--;
319 }
320 }
321 c=l; // Restore length value in "c"
322 }
323
324 // Transfer items in temporary storage to list object:
325 fObjSel.length = c;
326 for (a = 0; a < c; a++) {
327 fObjSel.options[a].value = localArray_V[a];
328 fObjSel.options[a].text = localArray_L[a];
329 fObjSel.options[a].selected = localArray_S[a];
330 fObjSel.options[a].title = localArray_T[a];
331 }
332 FormEngine.updateHiddenFieldValueFromSelect(fObjSel, formObj[fName]);
333
334 FormEngine.legacyFieldChangedCb();
335 }
336 };
337
338
339 /**
340 * legacy function
341 * returns the DOM object for the given form name of the current form,
342 * but only if the given field name is valid, legacy function, use "getFormElement" instead
343 *
344 * @param fieldName the name of the field name
345 * @returns {*|DOMElement}
346 */
347 setFormValue_getFObj = function(fieldName) {
348 var $formEl = FormEngine.getFormElement(fieldName);
349 if ($formEl.length > 0) {
350 // return the DOM element of the form object
351 return $formEl.get(0);
352 } else {
353 return null;
354 }
355 };
356
357 /**
358 * returns a jQuery object for the given form name of the current form,
359 * if the parameter "fieldName" is given, then the form element is only returned if the field name is available
360 * the latter behaviour mirrors the one of the function "setFormValue_getFObj"
361 *
362 * @param fieldName the field name to check for, optional
363 * @returns {*|HTMLElement}
364 */
365 FormEngine.getFormElement = function(fieldName) {
366 var $formEl = $('form[name="' + FormEngine.formName + '"]:first');
367 if (fieldName) {
368 var $fieldEl = FormEngine.getFieldElement(fieldName)
369 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
370
371 // Take the form object if it is either of type select-one or of type-multiple and it has a "_list" element
372 if ($fieldEl.length > 0 &&
373 (
374 ($fieldEl.prop('type') == 'select-one') ||
375 ($listFieldEl.length > 0 && $listFieldEl.prop('type').match(/select-(one|multiple)/))
376 )
377 ) {
378 return $formEl;
379 } else {
380 console.error('Form fields missing: form: ' + FormEngine.formName + ', field name: ' + fieldName);
381 alert('Form field is invalid');
382 }
383 } else {
384 return $formEl;
385 }
386 };
387
388
389 /**
390 * returns a jQuery object of the field DOM element of the current form, can also be used to
391 * request an alternative field like "_hr", "_list" or "_mul"
392 *
393 * @param fieldName the name of the field (<input name="fieldName">)
394 * @param appendix optional
395 * @param noFallback if set, then the appendix value is returned no matter if it exists or not
396 * @returns {*|HTMLElement}
397 */
398 FormEngine.getFieldElement = function(fieldName, appendix, noFallback) {
399 var $formEl = FormEngine.getFormElement();
400
401 // if an appendix is set, return the field with the appendix (like _mul or _list)
402 if (appendix) {
403 var $fieldEl = $(':input[name="' + fieldName + appendix + '"]', $formEl);
404 if ($fieldEl.length > 0 || noFallback === true) {
405 return $fieldEl;
406 }
407 }
408
409 return $(':input[name="' + fieldName + '"]', $formEl);
410 };
411
412
413
414 /**************************************************
415 * manipulate existing options in a select field
416 **************************************************/
417
418 /**
419 * moves currently selected options from a select field to the very top,
420 * can be multiple entries as well
421 *
422 * @param $fieldEl a jQuery object, containing the select field
423 */
424 FormEngine.moveOptionToTop = function($fieldEl) {
425 // remove the selected options
426 var selectedOptions = $fieldEl.find(':selected').detach();
427 // and add them on first position again
428 $fieldEl.prepend(selectedOptions);
429 };
430
431
432 /**
433 * moves currently selected options from a select field up by one position,
434 * can be multiple entries as well
435 *
436 * @param $fieldEl a jQuery object, containing the select field
437 */
438 FormEngine.moveOptionUp = function($fieldEl) {
439 // remove the selected options and add it before the previous sibling
440 $.each($fieldEl.find(':selected'), function(k, optionEl) {
441 var $optionEl = $(optionEl)
442 ,$optionBefore = $optionEl.prev();
443
444 // stop if first option to move is already the first one
445 if (k == 0 && $optionBefore.length === 0) {
446 return false;
447 }
448
449 $optionBefore.before($optionEl.detach());
450 });
451 };
452
453
454 /**
455 * moves currently selected options from a select field down one position,
456 * can be multiple entries as well
457 *
458 * @param $fieldEl a jQuery object, containing the select field
459 */
460 FormEngine.moveOptionDown = function($fieldEl) {
461 // remove the selected options and add it after the next sibling
462 // however, this time, we need to go from the last to the first
463 var selectedOptions = $fieldEl.find(':selected');
464 selectedOptions = $.makeArray(selectedOptions);
465 selectedOptions.reverse();
466 $.each(selectedOptions, function(k, optionEl) {
467 var $optionEl = $(optionEl)
468 ,$optionAfter = $optionEl.next();
469
470 // stop if first option to move is already the last one
471 if (k == 0 && $optionAfter.length === 0) {
472 return false;
473 }
474
475 $optionAfter.after($optionEl.detach());
476 });
477 };
478
479
480 /**
481 * moves currently selected options from a select field as the very last entries
482 *
483 * @param $fieldEl a jQuery object, containing the select field
484 */
485 FormEngine.moveOptionToBottom = function($fieldEl) {
486 // remove the selected options
487 var selectedOptions = $fieldEl.find(':selected').detach();
488 // and add them on last position again
489 $fieldEl.append(selectedOptions);
490 };
491
492 /**
493 * removes currently selected options from a select field
494 *
495 * @param $fieldEl a jQuery object, containing the select field
496 */
497 FormEngine.removeOption = function($fieldEl) {
498 // remove the selected options
499 $fieldEl.find(':selected').remove();
500 };
501
502
503 /**
504 * initialize events for all form engine relevant tasks
505 */
506 FormEngine.initializeEvents = function() {
507
508 // track the arrows "Up", "Down", "Clear" etc in multi-select boxes
509 $(document).on('click', '.t3-btn-moveoption-top, .t3-btn-moveoption-up, .t3-btn-moveoption-down, .t3-btn-moveoption-bottom, .t3-btn-removeoption', function(evt) {
510 var $el = $(this)
511 ,fieldName = $el.data('fieldname')
512 ,$listFieldEl = FormEngine.getFieldElement(fieldName, '_list');
513
514 if ($listFieldEl.length > 0) {
515
516 if ($el.hasClass('t3-btn-moveoption-top')) {
517 FormEngine.moveOptionToTop($listFieldEl);
518 } else if ($el.hasClass('t3-btn-moveoption-up')) {
519 FormEngine.moveOptionUp($listFieldEl);
520 } else if ($el.hasClass('t3-btn-moveoption-down')) {
521 FormEngine.moveOptionDown($listFieldEl);
522 } else if ($el.hasClass('t3-btn-moveoption-bottom')) {
523 FormEngine.moveOptionToBottom($listFieldEl);
524 } else if ($el.hasClass('t3-btn-removeoption')) {
525 FormEngine.removeOption($listFieldEl);
526 }
527
528 // make sure to update the hidden field value when modifying the select value
529 FormEngine.updateHiddenFieldValueFromSelect($listFieldEl, FormEngine.getFieldElement(fieldName));
530 FormEngine.legacyFieldChangedCb();
531 }
532 });
533
534 // in multi-select environments with two (e.g. "Access"), on click the item from the right should go to the left
535 $(document).on('click', '.t3-form-select-itemstoselect', function(evt) {
536 var $el = $(this)
537 ,fieldName = $el.data('relatedfieldname')
538 ,exclusiveValues = $el.data('exclusivevalues');
539
540 if (fieldName) {
541 // try to add each selected field to the "left" select field
542 $el.find(':selected').each(function() {
543 var $optionEl = $(this);
544 FormEngine.setSelectOptionFromExternalSource(fieldName, $optionEl.prop('value'), $optionEl.text(), $optionEl.prop('title'), exclusiveValues);
545 });
546 }
547 });
548 };
549
550
551
552 // initialize function, always require possible post-render hooks return the main object
553 var initializeModule = function(options) {
554
555 FormEngine.initializeEvents();
556
557 // load required modules to hook in the post initialize function
558 if (undefined !== TYPO3.settings.RequireJS && undefined !== TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine']) {
559 $.each(TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/FormEngine'], function(pos, moduleName) {
560 require([moduleName]);
561 });
562 }
563
564 // make the form engine object publically visible for other objects in the TYPO3 namespace
565 TYPO3.FormEngine = FormEngine;
566
567 // return the object in the global space
568 return FormEngine;
569 };
570
571 // call the main initialize function and execute the hooks
572 return initializeModule();
573 });