48d4dcf79c503b4a650e04eeb8d626a706d8a399
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / jsfunc.tbe_editor.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 JavaScript for TYPO3 Core Form generator - AKA "TCEforms"
16 */
17
18 var TBE_EDITOR = {
19 /* Example:
20 elements: {
21 'data-parentPid-table-uid': {
22 'field': {
23 'range': [0, 100],
24 'rangeImg': '',
25 'required': true,
26 'requiredImg': ''
27 }
28 }
29 },
30 */
31
32 elements: {},
33 nested: {'field':{}, 'level':{}},
34 ignoreElements: [],
35 actionChecks: { submit: [] },
36
37 formname: '',
38 formnameUENC: '',
39 isChanged: 0,
40
41 isPalettedoc: null,
42 doSaveFieldName: 0,
43
44 labels: {},
45 images: {
46 req: new Image(),
47 cm: new Image(),
48 sel: new Image(),
49 clear: new Image()
50 },
51
52 clearBeforeSettingFormValueFromBrowseWin: [],
53
54 // Handling of data structures:
55 removeElement: function(record) {
56 if (TBE_EDITOR.elements && TBE_EDITOR.elements[record]) {
57 delete(TBE_EDITOR.elements[record]);
58 }
59 },
60 removeElementArray: function(removeStack) {
61 if (removeStack && removeStack.length) {
62 TBE_EDITOR.ignoreElements = removeStack;
63 for (var i=removeStack.length; i>=0; i--) {
64 TBE_EDITOR.removeElement(removeStack[i]);
65 }
66 TBE_EDITOR.ignoreElements = [];
67 }
68 },
69 getElement: function(record, field, type) {
70 var result = null;
71 var element;
72
73 if (TBE_EDITOR.elements && TBE_EDITOR.elements[record] && TBE_EDITOR.elements[record][field]) {
74 element = TBE_EDITOR.elements[record][field];
75 if (type) {
76 if (element[type]) result = element;
77 } else {
78 result = element;
79 }
80 }
81
82 return result;
83 },
84 checkElements: function(type, recentUpdated, record, field) {
85 return (document.getElementsByClassName('has-error').length == 0);
86 },
87 // Check all the input fields on a given level of nesting - if only on is unfilled, the whole level is marked as required:
88 checkNested: function(nestedLevelIdent) {
89 var nestedLevel, isClean;
90 if (nestedLevelIdent && TBE_EDITOR.nested.level && TBE_EDITOR.nested.level[nestedLevelIdent]) {
91 nestedLevel = TBE_EDITOR.nested.level[nestedLevelIdent];
92 if (!nestedLevel.clean) {
93 if (typeof nestedLevel.item === 'object') {
94 TYPO3.jQuery.each(nestedLevel.item, function(key, value) {
95 if (isClean || typeof isClean === 'undefined') {
96 isClean = (
97 TBE_EDITOR.checkElements('required', false, value[0], value[1]) &&
98 TBE_EDITOR.checkElements('range', false, value[0], value[1])
99 );
100 }
101 }
102 );
103 if (typeof isClean !== 'undefined' && !isClean) {
104 return false;
105 }
106 }
107 if (typeof nestedLevel.sub === 'object') {
108 TYPO3.jQuery.each(nestedLevel.sub, function(key, value) {
109 if (isClean || typeof isClean === 'undefined') {
110 isClean = TBE_EDITOR.checkNested(key);
111 }
112 }
113 );
114 if (typeof isClean !== 'undefined' && !isClean) {
115 return false;
116 }
117 }
118 // Store the result, that this level (the fields on this and the sub levels) are clean:
119 nestedLevel.clean = true;
120 }
121 }
122 return true;
123 },
124 addActionChecks: function(type, checks) {
125 TBE_EDITOR.actionChecks[type].push(checks);
126 },
127
128 fieldChanged_fName: function(fName,el) {
129 var idx=2;
130 var table = TBE_EDITOR.split(fName, "[", idx);
131 var uid = TBE_EDITOR.split(fName, "[", idx+1);
132 var field = TBE_EDITOR.split(fName, "[", idx+2);
133
134 table = table.substr(0,table.length-1);
135 uid = uid.substr(0,uid.length-1);
136 field = field.substr(0,field.length-1);
137 TBE_EDITOR.fieldChanged(table,uid,field,el);
138 },
139 fieldChanged: function(table,uid,field,el) {
140 var theField = 'data['+table+']['+uid+']['+field+']';
141 var theRecord = 'data['+table+']['+uid+']';
142 TBE_EDITOR.isChanged = 1;
143
144 // modify the "field has changed" info by adding a class to the container element (based on palette or main field)
145 var $formField = TYPO3.jQuery('[name="' + el + '"]');
146 var $humanReadableField = TYPO3.jQuery('[data-formengine-input-name="' + el + '"]');
147 if (!$formField.is($humanReadableField)) {
148 $humanReadableField.triggerHandler('change');
149 }
150 var $paletteField = $formField.closest('.t3js-formengine-palette-field');
151 $paletteField.addClass('has-change');
152
153 // Set required flag:
154 var imgReqObjName = "req_"+table+"_"+uid+"_"+field;
155 if (TBE_EDITOR.getElement(theRecord,field,'required') && document[TBE_EDITOR.formname][theField]) {
156 if (TBE_EDITOR.checkElements('required', false, theRecord, field)) {
157 TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.clear);
158 } else {
159 TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.req);
160 }
161 }
162 if (TBE_EDITOR.getElement(theRecord,field,'range') && document[TBE_EDITOR.formname][theField]) {
163 if (TBE_EDITOR.checkElements('range', false, theRecord, field)) {
164 TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.clear);
165 } else {
166 TBE_EDITOR.setImage(imgReqObjName,TBE_EDITOR.images.req);
167 }
168 }
169 if (TBE_EDITOR.isPalettedoc) { TBE_EDITOR.setOriginalFormFieldValue(theField) }
170 if (TYPO3.FormEngine && TYPO3.FormEngine.Validation) {
171 TYPO3.FormEngine.Validation.updateInputField(theField);
172 TYPO3.FormEngine.Validation.validate();
173 }
174 },
175 setOriginalFormFieldValue: function(theField) {
176 if (TBE_EDITOR.isPalettedoc && (TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname] && (TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname][theField]) {
177 (TBE_EDITOR.isPalettedoc).document[TBE_EDITOR.formname][theField].value = document[TBE_EDITOR.formname][theField].value;
178 }
179 },
180 isFormChanged: function(noAlert) {
181 if (TBE_EDITOR.isChanged && !noAlert && confirm(TBE_EDITOR.labels.fieldsChanged)) {
182 return 0;
183 }
184 return TBE_EDITOR.isChanged;
185 },
186 checkAndDoSubmit: function(sendAlert) {
187 if (TBE_EDITOR.checkSubmit(sendAlert)) { TBE_EDITOR.submitForm(); }
188 },
189 /**
190 * Checks if the form can be submitted according to any possible restrains like required values, item numbers etc.
191 * Returns true if the form can be submitted, otherwise false (and might issue an alert message, if "sendAlert" is 1)
192 * If "sendAlert" is false, no error message will be shown upon false return value (if "1" then it will).
193 * If "sendAlert" is "-1" then the function will ALWAYS return true regardless of constraints (except if login has expired) - this is used in the case where a form field change requests a form update and where it is accepted that constraints are not observed (form layout might change so other fields are shown...)
194 */
195 checkSubmit: function(sendAlert) {
196 var funcIndex, funcMax, funcRes;
197 var OK = 1;
198 var STOP = 0;
199
200 // $this->additionalJS_submit:
201 if (TBE_EDITOR.actionChecks && TBE_EDITOR.actionChecks.submit) {
202 for (funcIndex=0, funcMax=TBE_EDITOR.actionChecks.submit.length; funcIndex<funcMax; funcIndex++) {
203 try {
204 eval(TBE_EDITOR.actionChecks.submit[funcIndex]);
205 } catch(error) {}
206 }
207 }
208
209 if (STOP) {
210 // return false immediately, if the code in additionalJS_submit set STOP variable.
211 return false;
212 }
213
214 if (!OK) {
215 if (!confirm(unescape("SYSTEM ERROR: One or more Rich Text Editors on the page could not be contacted. This IS an error, although it should not be regular.\nYou can save the form now by pressing OK, but you will loose the Rich Text Editor content if you do.\n\nPlease report the error to your administrator if it persists."))) {
216 return false;
217 } else {
218 OK = 1;
219 }
220 }
221 // $reqLinesCheck
222 if (!TBE_EDITOR.checkElements('required', false)) { OK = 0; }
223 // $reqRangeCheck
224 if (!TBE_EDITOR.checkElements('range', false)) { OK = 0; }
225
226 if (OK || sendAlert==-1) {
227 return true;
228 } else {
229 if (sendAlert) {
230 var t = (opener != null && typeof opener.top.TYPO3 !== 'undefined' ? opener.top : top);
231 t.TYPO3.Modal.confirm(
232 t.TYPO3.lang['alert'] || 'Alert',
233 TBE_EDITOR.labels.fieldsMissing,
234 t.TYPO3.Severity.error,
235 [
236 {
237 text: t.TYPO3.lang['button.ok'] || 'OK',
238 active: true,
239 btnClass: 'btn-default',
240 name: 'ok'
241 }
242 ]
243 ).on('button.clicked', function(e) {
244 t.TYPO3.Modal.dismiss();
245 });
246 }
247 return false;
248 }
249 },
250 checkRange: function(numberOfElements, lower, upper) {
251 // for backwards compatibility, check if we're dealing with an element as first parameter
252 if(typeof numberOfElements == 'object') {
253 numberOfElements = numberOfElements.length;
254 }
255
256 if (numberOfElements >= lower && numberOfElements <= upper) {
257 return true;
258 } else {
259 return false;
260 }
261 },
262 setImage: function(name,image) {
263 var object;
264 if (document[name]) {
265 object = document[name];
266 } else if (document.getElementById(name)) {
267 object = document.getElementById(name);
268 }
269 if (object) {
270 if (typeof image == 'object') {
271 document[name].src = image.src;
272 } else {
273 document[name].src = eval(image+'.src');
274 }
275 }
276 },
277 submitForm: function() {
278 if (TBE_EDITOR.doSaveFieldName) {
279 document[TBE_EDITOR.formname][TBE_EDITOR.doSaveFieldName].value=1;
280 }
281 // Set a short timeout to allow other JS processes to complete, in particular those from
282 // EXT:backend/Resources/Public/JavaScript/FormEngine.js (reference: http://forge.typo3.org/issues/58755).
283 // TODO: This should be solved in a better way when this script is refactored.
284 window.setTimeout(function() {
285 document.getElementsByName(TBE_EDITOR.formname).item(0).submit();
286 }, 10);
287 },
288 split: function(theStr1, delim, index) {
289 var theStr = ""+theStr1;
290 var lengthOfDelim = delim.length;
291 sPos = -lengthOfDelim;
292 if (index<1) {index=1;}
293 for (var a=1; a<index; a++) {
294 sPos = theStr.indexOf(delim, sPos+lengthOfDelim);
295 if (sPos==-1) { return null; }
296 }
297 ePos = theStr.indexOf(delim, sPos+lengthOfDelim);
298 if(ePos == -1) { ePos = theStr.length; }
299 return (theStr.substring(sPos+lengthOfDelim,ePos));
300 },
301 curSelected: function(theField) {
302 var fObjSel = TYPO3.jQuery('[data-formengine-input-name="' + theField + '"]').get(1);
303 var retVal="";
304 if (fObjSel) {
305 if (fObjSel.type=='select-multiple' || fObjSel.type=='select-one') {
306 var l=fObjSel.length;
307 for (a=0;a<l;a++) {
308 if (fObjSel.options[a].selected==1) {
309 retVal+=fObjSel.options[a].value+",";
310 }
311 }
312 }
313 }
314 return retVal;
315 },
316 rawurlencode: function(str,maxlen) {
317 var output = str;
318 if (maxlen) output = output.substr(0,200);
319 output = encodeURIComponent(output);
320 return output;
321 },
322 str_replace: function(match,replace,string) {
323 var input = ''+string;
324 var matchStr = ''+match;
325 if (!matchStr) { return string; }
326 var output = '';
327 var pointer=0;
328 var pos = input.indexOf(matchStr);
329 while (pos!=-1) {
330 output+=''+input.substr(pointer, pos-pointer)+replace;
331 pointer=pos+matchStr.length;
332 pos = input.indexOf(match,pos+1);
333 }
334 output+=''+input.substr(pointer);
335 return output;
336 },
337 toggle_display_states: function(id, state_1, state_2) {
338 var node = document.getElementById(id);
339 if (node) {
340 switch (node.style.display) {
341 case state_1:
342 node.style.display = state_2;
343 break;
344 case state_2:
345 node.style.display = state_1;
346 break;
347 }
348 }
349 return false;
350 },
351
352 };
353
354 function typoSetup () {
355 this.passwordDummy = '********';
356 }
357 // @todo: maybe obsolete, need a deeper check
358 var TS = new typoSetup();
359
360 // backwards compatibility for extensions
361 var TBE_EDITOR_setHiddenContent = TBE_EDITOR.setHiddenContent;
362 var TBE_EDITOR_isChanged = TBE_EDITOR.isChanged;
363 var TBE_EDITOR_fieldChanged_fName = TBE_EDITOR.fieldChanged_fName;
364 var TBE_EDITOR_fieldChanged = TBE_EDITOR.fieldChanged;
365 var TBE_EDITOR_setOriginalFormFieldValue = TBE_EDITOR.setOriginalFormFieldValue;
366 var TBE_EDITOR_isFormChanged = TBE_EDITOR.isFormChanged;
367 var TBE_EDITOR_checkAndDoSubmit = TBE_EDITOR.checkAndDoSubmit;
368 var TBE_EDITOR_checkSubmit = TBE_EDITOR.checkSubmit;
369 var TBE_EDITOR_checkRange = TBE_EDITOR.checkRange;
370 var TBE_EDITOR_setImage = TBE_EDITOR.setImage;
371 var TBE_EDITOR_submitForm = TBE_EDITOR.submitForm;
372 var TBE_EDITOR_split = TBE_EDITOR.split;
373 var TBE_EDITOR_curSelected = TBE_EDITOR.curSelected;
374 var TBE_EDITOR_rawurlencode = TBE_EDITOR.rawurlencode;
375 var TBE_EDITOR_str_replace = TBE_EDITOR.str_replace;
376
377
378 var typo3form = {
379 fieldSetNull: function(fieldName, isNull) {
380 if (document[TBE_EDITOR.formname][fieldName]) {
381 TYPO3.jQuery(document[TBE_EDITOR.formname][fieldName]).closest('.t3js-formengine-field-item').toggleClass('disabled', isNull);
382 }
383 },
384 fieldTogglePlaceholder: function(fieldName, showPlaceholder) {
385 if (!document[TBE_EDITOR.formname][fieldName]) {
386 return;
387 }
388
389 var $formFieldItemWrapper = TYPO3.jQuery(document[TBE_EDITOR.formname][fieldName]).closest('.t3js-formengine-field-item');
390 $formFieldItemWrapper.find('.t3js-formengine-placeholder-placeholder').toggle(showPlaceholder);
391 $formFieldItemWrapper.find('.t3js-formengine-placeholder-formfield').toggle(!showPlaceholder);
392 },
393 fieldSet: function(theField, evallist, is_in, checkbox, checkboxValue) {
394 if (document[TBE_EDITOR.formname][theField]) {
395 var theFObj = new evalFunc_dummy (evallist,is_in, checkbox, checkboxValue);
396 var theValue = document[TBE_EDITOR.formname][theField].value;
397 if (checkbox && theValue==checkboxValue) {
398 document.querySelector('form[name="' + TBE_EDITOR.formname + '"] [data-formengine-input-name="' + theField + '"]').value = "";
399 if (document[TBE_EDITOR.formname][theField+"_cb"]) document[TBE_EDITOR.formname][theField+"_cb"].checked = "";
400 } else {
401 document.querySelector('form[name="' + TBE_EDITOR.formname + '"] [data-formengine-input-name="' + theField + '"]').value = evalFunc.outputObjValue(theFObj, theValue);
402 if (document[TBE_EDITOR.formname][theField+"_cb"]) document[TBE_EDITOR.formname][theField+"_cb"].checked = "on";
403 }
404 }
405 },
406 fieldGet: function(theField, evallist, is_in, checkbox, checkboxValue, checkbox_off, checkSetValue) {
407 if (document[TBE_EDITOR.formname][theField]) {
408 var theFObj = new evalFunc_dummy (evallist,is_in, checkbox, checkboxValue);
409 if (checkbox_off) {
410 if (document[TBE_EDITOR.formname][theField+"_cb"].checked) {
411 var split = evallist.split(',');
412 for (var i = 0; split.length > i; i++) {
413 var el = split[i].replace(/ /g, '');
414 if (el == 'datetime' || el == 'date') {
415 var now = new Date();
416 checkSetValue = Date.parse(now)/1000 - now.getTimezoneOffset()*60;
417 break;
418 } else if (el == 'time' || el == 'timesec') {
419 checkSetValue = evalFunc_getTimeSecs(new Date());
420 break;
421 }
422 }
423 document[TBE_EDITOR.formname][theField].value=checkSetValue;
424 } else {
425 document[TBE_EDITOR.formname][theField].value=checkboxValue;
426 }
427 }else{
428 document[TBE_EDITOR.formname][theField].value = evalFunc.evalObjValue(theFObj, document.querySelector('form[name="' + TBE_EDITOR.formname + '"] [data-formengine-input-name="' + theField + '"]').value);
429 }
430 typo3form.fieldSet(theField, evallist, is_in, checkbox, checkboxValue);
431 }
432 }
433 };
434
435 // @TODO: This function is a copy from jsfunc.evalfield.js
436 // @TODO: Remove it later, after TBE_EDITOR is not used anymore.
437 function evalFunc_dummy (evallist,is_in,checkbox,checkboxValue) {
438 this.evallist = evallist;
439 this.is_in = is_in;
440 this.checkboxValue = checkboxValue;
441 this.checkbox = checkbox;
442 }
443
444 // backwards compatibility for extensions
445 var typo3FormFieldSet = typo3form.fieldSet;
446 var typo3FormFieldGet = typo3form.fieldGet;