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