62940ed6a8c3e533b6c5c39cfe305415a3c44b34
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Public / JavaScript / jsfunc.inline.js
1 /*<![CDATA[*/
2
3 /**
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 /**
17 * Inline-Relational-Record Editing
18 */
19
20 var inline = {
21 classVisible: 't3-form-field-container-inline-visible',
22 classCollapsed: 't3-form-field-container-inline-collapsed',
23 structureSeparator: '-',
24 flexFormSeparator: '---',
25 flexFormSubstitute: ':',
26 prependFormFieldNames: 'data',
27 noTitleString: '[No title]',
28 lockedAjaxMethod: {},
29 sourcesLoaded: {},
30 data: {},
31 isLoading: false,
32
33 addToDataArray: function (object) {
34 TYPO3.jQuery.each(object, function (key, value) {
35 if (!inline.data[key]) {
36 inline.data[key] = {};
37 }
38 TYPO3.jQuery.extend(inline.data[key], value);
39 });
40 },
41 setPrependFormFieldNames: function (value) {
42 this.prependFormFieldNames = value;
43 },
44 setNoTitleString: function (value) {
45 this.noTitleString = value;
46 },
47 toggleEvent: function (event) {
48 var $triggerElement = TYPO3.jQuery(event.target);
49 if ($triggerElement.parents('.t3-form-field-header-inline-ctrl').length == 1) {
50 return;
51 }
52
53 var $recordHeader = TYPO3.jQuery(this);
54 inline.expandCollapseRecord(
55 $recordHeader.attr('id').replace('_header', ''),
56 $recordHeader.attr('data-expandSingle'),
57 $recordHeader.attr('data-returnURL')
58 );
59 },
60 expandCollapseRecord: function (objectId, expandSingle, returnURL) {
61 var currentUid = this.parseObjectId('none', objectId, 1);
62 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
63 var escapedObjectId = this.escapeObjectId(objectId);
64
65 var $currentObject = TYPO3.jQuery('#' + escapedObjectId + '_div');
66 // if content is not loaded yet, get it now from server
67 if (inline.isLoading || (TYPO3.jQuery('#' + escapedObjectId + '_fields').length > 0 && TYPO3.jQuery("#irre-loading-indicator" + escapedObjectId).length > 0)) {
68 return false;
69 } else if (TYPO3.jQuery('#' + escapedObjectId + '_fields').length > 0 && TYPO3.jQuery('#' + escapedObjectId + '_fields').html().substr(0, 16) == '<!--notloaded-->') {
70 inline.isLoading = true;
71 // add loading-indicator
72 if (TYPO3.jQuery('#' + escapedObjectId + '_loadingbar').length === 0) {
73 var loadingBar = '<div id="' + objectId + '_loadingbar" class="t3-form-header-inline-loadingbar loadingbar"><div class="expand"></div></div>';
74 TYPO3.jQuery('#' + escapedObjectId + '_header').after(loadingBar);
75 }
76 return this.getRecordDetails(objectId, returnURL);
77 }
78
79 var isCollapsed = $currentObject.hasClass(this.classCollapsed);
80 var collapse = [];
81 var expand = [];
82
83 // if only a single record should be visibly for that set of records
84 // and the record clicked itself is no visible, collapse all others
85 if (expandSingle && $currentObject.hasClass(this.classCollapsed)) {
86 collapse = this.collapseAllRecords(objectId, objectPrefix, currentUid);
87 }
88
89 inline.toggleElement(objectId);
90
91 if (this.isNewRecord(objectId)) {
92 this.updateExpandedCollapsedStateLocally(objectId, isCollapsed);
93 } else if (isCollapsed) {
94 expand.push(currentUid);
95 } else if (!isCollapsed) {
96 collapse.push(currentUid);
97 }
98
99 this.setExpandedCollapsedState(objectId, expand.join(','), collapse.join(','));
100
101 return false;
102 },
103
104 toggleElement: function (objectId) {
105 var escapedObjectId = this.escapeObjectId(objectId);
106 var $jQueryObject = TYPO3.jQuery('#' + escapedObjectId + '_div');
107
108 if ($jQueryObject.hasClass(this.classCollapsed)) {
109 $jQueryObject.removeClass(this.classCollapsed).addClass(this.classVisible);
110 $jQueryObject.find('#' + escapedObjectId + '_header .t3-icon-irre-collapsed').removeClass('t3-icon-irre-collapsed').addClass('t3-icon-irre-expanded');
111 } else {
112 $jQueryObject.removeClass(this.classVisible).addClass(this.classCollapsed);
113 $jQueryObject.find('#' + escapedObjectId + '_header .t3-icon-irre-expanded').addClass('t3-icon-irre-collapsed').removeClass('t3-icon-irre-expanded');
114 }
115 },
116 collapseAllRecords: function (objectId, objectPrefix, callingUid) {
117 // get the form field, where all records are stored
118 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 2, true);
119 var formObj = document.getElementsByName(objectName);
120 var collapse = [];
121
122 if (formObj.length) {
123 // the uid of the calling object (last part in objectId)
124 var recObjectId = '', escapedRecordObjectId;
125
126 var records = formObj[0].value.split(',');
127 for (var i = 0; i < records.length; i++) {
128 recObjectId = objectPrefix + this.structureSeparator + records[i];
129 escapedRecordObjectId = this.escapeObjectId(recObjectId);
130
131 var $recordEntry = TYPO3.jQuery('#' + escapedRecordObjectId);
132 if (records[i] != callingUid && $recordEntry.hasClass(this.classVisible)) {
133 TYPO3.jQuery('#' + escapedRecordObjectId + '_div').removeClass(this.classVisible).addClass(this.classCollapsed);
134 if (this.isNewRecord(recObjectId)) {
135 this.updateExpandedCollapsedStateLocally(recObjectId, 0);
136 } else {
137 collapse.push(records[i]);
138 }
139 }
140 }
141 }
142
143 return collapse;
144 },
145
146 updateExpandedCollapsedStateLocally: function (objectId, value) {
147 var ucName = 'uc[inlineView]' + this.parseObjectId('parts', objectId, 3, 2, true);
148 var ucFormObj = document.getElementsByName(ucName);
149 if (ucFormObj.length) {
150 ucFormObj[0].value = value;
151 }
152 },
153
154 getRecordDetails: function (objectId, returnURL) {
155 var context = this.getContext(this.parseObjectId('full', objectId, 0, 1));
156 inline.makeAjaxCall('getRecordDetails', [inline.getNumberOfRTE(), objectId, returnURL], true, context);
157 return false;
158 },
159
160 createNewRecord: function (objectId, recordUid) {
161 if (this.isBelowMax(objectId)) {
162 var context = this.getContext(objectId);
163 if (recordUid) {
164 objectId += this.structureSeparator + recordUid;
165 }
166 this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId], true, context);
167 } else {
168 alert('There are no more relations possible at this moment!');
169 }
170 return false;
171 },
172
173 synchronizeLocalizeRecords: function (objectId, type) {
174 var context = this.getContext(objectId);
175 var parameters = [this.getNumberOfRTE(), objectId, type];
176 this.makeAjaxCall('synchronizeLocalizeRecords', parameters, true, context);
177 },
178
179 setExpandedCollapsedState: function (objectId, expand, collapse) {
180 var context = this.getContext(objectId);
181 this.makeAjaxCall('setExpandedCollapsedState', [objectId, expand, collapse], false, context);
182 },
183
184 makeAjaxCall: function (method, params, lock, context) {
185 var url = '', urlParams = '', options = {};
186 if (method && params && params.length && this.lockAjaxMethod(method, lock)) {
187 url = TBE_EDITOR.getBackendPath() + TYPO3.settings.ajaxUrls['t3lib_TCEforms_inline::' + method];
188 urlParams = '';
189 for (var i = 0, max = params.length; i < max; i++) {
190 urlParams += '&ajax[' + i + ']=' + encodeURIComponent(params[i]);
191 }
192 if (context) {
193 urlParams += '&ajax[context]=' + encodeURIComponent(Object.toJSON(context));
194 }
195 options = {
196 type: 'POST',
197 data: urlParams,
198 success: function (data, message, jqXHR) {
199 inline.isLoading = false;
200 inline.processAjaxResponse(method, jqXHR);
201 },
202 error: function (jqXHR, statusText, errorThrown) {
203 inline.isLoading = false;
204 inline.showAjaxFailure(method, jqXHR);
205 }
206 };
207
208 TYPO3.jQuery.ajax(url, options);
209 }
210 },
211
212 lockAjaxMethod: function (method, lock) {
213 if (!lock || !inline.lockedAjaxMethod[method]) {
214 inline.lockedAjaxMethod[method] = true;
215 return true;
216 } else {
217 return false;
218 }
219 },
220
221 unlockAjaxMethod: function (method) {
222 inline.lockedAjaxMethod[method] = false;
223 },
224
225 processAjaxResponse: function (method, xhr, json) {
226 var addTag = null, restart = false, processedCount = 0, element = null, errorCatch = [], sourcesWaiting = [];
227 if (!json && xhr) {
228 json = xhr.responseJSON;
229 }
230 // If there are elements the should be added to the <HEAD> tag (e.g. for RTEhtmlarea):
231 if (json.headData) {
232 var head = inline.getDomHeadTag();
233 var headTags = inline.getDomHeadChildren(head);
234 TYPO3.jQuery.each(json.headData, function (index, addTag) {
235 if (!restart) {
236 if (addTag && (addTag.innerHTML || !inline.searchInDomTags(headTags, addTag))) {
237 if (addTag.name == 'SCRIPT' && addTag.innerHTML && processedCount) {
238 restart = true;
239 return false;
240 } else {
241 if (addTag.name == 'SCRIPT' && addTag.innerHTML) {
242 try {
243 eval(addTag.innerHTML);
244 } catch (e) {
245 errorCatch.push(e);
246 }
247 } else {
248 element = inline.createNewDomElement(addTag);
249 // Set onload handler for external JS scripts:
250 if (addTag.name == 'SCRIPT' && element.src) {
251 element.onload = inline.sourceLoadedHandler(element);
252 sourcesWaiting.push(element.src);
253 }
254 head.appendChild(element);
255 processedCount++;
256 }
257 json.headData.shift();
258 }
259 }
260 }
261 });
262 }
263 if (restart || processedCount) {
264 window.setTimeout(function () {
265 inline.reprocessAjaxResponse(method, json, sourcesWaiting);
266 }, 40);
267 } else {
268 if (method) {
269 inline.unlockAjaxMethod(method);
270 }
271 if (json.scriptCall && json.scriptCall.length > 0) {
272 TYPO3.jQuery.each(json.scriptCall, function (index, value) {
273 eval(value);
274 });
275 }
276 TYPO3.TCEFORMS.convertDateFieldsToDatePicker();
277 }
278 },
279
280 // Check if dynamically added scripts are loaded and restart inline.processAjaxResponse():
281 reprocessAjaxResponse: function (method, json, sourcesWaiting) {
282 var sourcesLoaded = true;
283 if (sourcesWaiting && sourcesWaiting.length) {
284 TYPO3.jQuery.each(sourcesWaiting, function (index, source) {
285 if (!inline.sourcesLoaded[source]) {
286 sourcesLoaded = false;
287 return false;
288 }
289 });
290 }
291 if (sourcesLoaded) {
292 TYPO3.jQuery.each(sourcesWaiting, function (index, source) {
293 delete(inline.sourcesLoaded[source]);
294 });
295 window.setTimeout(function () {
296 inline.processAjaxResponse(method, null, json);
297 }, 80);
298 } else {
299 window.setTimeout(function () {
300 inline.reprocessAjaxResponse(method, json, sourcesWaiting);
301 }, 40);
302 }
303 },
304
305 sourceLoadedHandler: function (element) {
306 if (element && element.src) {
307 inline.sourcesLoaded[element.src] = true;
308 }
309 },
310
311 showAjaxFailure: function (method, xhr) {
312 inline.unlockAjaxMethod(method);
313 alert('Error: ' + xhr.status + "\n" + xhr.statusText);
314 },
315
316 // foreign_selector: used by selector box (type='select')
317 importNewRecord: function (objectId) {
318 var $selector = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_selector');
319 var selectedIndex = $selector.prop('selectedIndex');
320 if (selectedIndex != -1) {
321 var context = this.getContext(objectId);
322 var selectedValue = $selector.val();
323 if (!this.data.unique || !this.data.unique[objectId]) {
324 $selector.find('option').eq(selectedIndex).prop('selected', false);
325 }
326 this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId, selectedValue], true, context);
327 }
328 return false;
329 },
330
331 // foreign_selector: used by element browser (type='group/db')
332 importElement: function (objectId, table, uid, type) {
333 var context = this.getContext(objectId);
334 inline.makeAjaxCall('createNewRecord', [inline.getNumberOfRTE(), objectId, uid], true, context);
335 },
336
337 importElementMultiple: function (objectId, table, uidArray, type) {
338 uidArray.each(function (uid) {
339 inline.delayedImportElement(objectId, table, uid, type);
340 });
341 },
342 delayedImportElement: function (objectId, table, uid, type) {
343 if (inline.lockedAjaxMethod['createNewRecord'] == true) {
344 window.setTimeout("inline.delayedImportElement('" + objectId + "','" + table + "'," + uid + ", null );",
345 300);
346 } else {
347 inline.importElement(objectId, table, uid, type);
348 }
349 },
350 // Check uniqueness for element browser:
351 checkUniqueElement: function (objectId, table, uid, type) {
352 if (this.checkUniqueUsed(objectId, uid, table)) {
353 return {passed: false, message: 'There is already a relation to the selected element!'};
354 } else {
355 return {passed: true};
356 }
357 },
358
359 // Checks if a record was used and should be unique:
360 checkUniqueUsed: function (objectId, uid, table) {
361 if (this.data.unique && this.data.unique[objectId]) {
362 var unique = this.data.unique[objectId];
363 var values = this.getValuesFromHashMap(unique.used);
364
365 // for select: only the uid is stored
366 if (unique['type'] == 'select') {
367 if (values.indexOf(uid) != -1) {
368 return true;
369 }
370
371 // for group/db: table and uid is stored in a assoc array
372 } else if (unique.type == 'groupdb') {
373 for (var i = values.length - 1; i >= 0; i--) {
374 // if the pair table:uid is already used:
375 if (values[i].table == table && values[i].uid == uid) {
376 return true;
377 }
378 }
379 }
380 }
381 return false;
382 },
383
384 setUniqueElement: function (objectId, table, uid, type, elName) {
385 var recordUid = this.parseFormElementName('none', elName, 1, 1);
386 // alert(objectId+'/'+table+'/'+uid+'/'+recordUid);
387 this.setUnique(objectId, recordUid, uid);
388 },
389
390 getKeysFromHashMap: function (unique) {
391 return TYPO3.jQuery.map(unique, function(value, key) {
392 return key;
393 });
394 },
395
396 getValuesFromHashMap: function (hashMap) {
397 return TYPO3.jQuery.map(hashMap, function(value, key) {
398 return value;
399 });
400 },
401
402 // Remove all select items already used
403 // from a newly retrieved/expanded record
404 removeUsed: function (objectId, recordUid) {
405 if (this.data.unique && this.data.unique[objectId]) {
406 var unique = this.data.unique[objectId];
407 if (unique.type == 'select') {
408 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 1, true);
409 var formObj = document.getElementsByName(formName);
410 var recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + recordUid + '][' + unique.field + ']');
411 var values = this.getValuesFromHashMap(unique.used);
412 if (recordObj.length) {
413 if (recordObj[0].hasOwnProperty('options')) {
414 var selectedValue = recordObj[0].options[recordObj[0].selectedIndex].value;
415 for (var i=0; i<values.length; i++) {
416 if (values[i] != selectedValue) {
417 this.removeSelectOption(recordObj[0], values[i]);
418 }
419 }
420 }
421 }
422 }
423 }
424 },
425 // this function is applied to a newly inserted record by AJAX
426 // it removes the used select items, that should be unique
427 setUnique: function (objectId, recordUid, selectedValue) {
428 if (this.data.unique && this.data.unique[objectId]) {
429 var $selector = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_selector');
430 var unique = this.data.unique[objectId];
431 if (unique.type == 'select') {
432 if (!(unique.selector && unique.max == -1)) {
433 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 1, true);
434 var formObj = document.getElementsByName(formName);
435 var recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + recordUid + '][' + unique.field + ']');
436 var values = this.getValuesFromHashMap(unique.used);
437 if ($selector.length) {
438 // remove all items from the new select-item which are already used in other children
439 if (recordObj.length) {
440 for (var i = 0; i < values.length; i++) {
441 this.removeSelectOption(recordObj[0], values[i]);
442 }
443 // set the selected item automatically to the first of the remaining items if no selector is used
444 if (!unique.selector) {
445 selectedValue = recordObj[0].options[0].value;
446 recordObj[0].options[0].selected = true;
447 this.updateUnique(recordObj[0], objectId, formName, recordUid);
448 this.handleChangedField(recordObj[0], objectId + '[' + recordUid + ']');
449 }
450 }
451 for (var i = 0; i < values.length; i++) {
452 this.removeSelectOption($selector, values[i]);
453 }
454 if (typeof this.data.unique[objectId].used.length != 'undefined') {
455 this.data.unique[objectId].used = {};
456 }
457 this.data.unique[objectId].used[recordUid] = selectedValue;
458 }
459 // remove the newly used item from each select-field of the child records
460 if (formObj.length && selectedValue) {
461 var records = formObj[0].value.split(',');
462 for (var i = 0; i < records.length; i++) {
463 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
464 if (recordObj.length && records[i] != recordUid) {
465 this.removeSelectOption(TYPO3.jQuery(recordObj[0]), selectedValue);
466 }
467 }
468 }
469 }
470 } else if (unique.type == 'groupdb') {
471 // add the new record to the used items:
472 this.data.unique[objectId].used[recordUid] = {'table': unique.elTable, 'uid': selectedValue};
473 }
474
475 // remove used items from a selector-box
476 if (unique.selector == 'select' && selectedValue) {
477 this.removeSelectOption($selector, selectedValue);
478 this.data.unique[objectId]['used'][recordUid] = selectedValue;
479 }
480 }
481 },
482
483 domAddNewRecord: function (method, insertObjectId, objectPrefix, htmlData) {
484 if (this.isBelowMax(objectPrefix)) {
485 var $insertObject = TYPO3.jQuery('#' + this.escapeObjectId(insertObjectId));
486 if (method == 'bottom') {
487 $insertObject.append(htmlData);
488 } else if (method == 'after') {
489 $insertObject.after(htmlData);
490 }
491 }
492 },
493 domAddRecordDetails: function (objectId, objectPrefix, expandSingle, htmlData) {
494 var hiddenValue, formObj, valueObj;
495 var escapeObjectId = this.escapeObjectId(objectId);
496 var $objectDiv = TYPO3.jQuery('#' + escapeObjectId + '_fields');
497 if ($objectDiv.length == 0 || $objectDiv.html().substr(0, 16) != '<!--notloaded-->') {
498 return;
499 }
500
501 var elName = this.parseObjectId('full', objectId, 2, 0, true);
502
503 var $formObj = TYPO3.jQuery('[name="' + elName + '[hidden]_0"]');
504 var $valueObj = TYPO3.jQuery('[name="' + elName + '[hidden]"]');
505
506 // It might be the case that a child record
507 // cannot be hidden at all (no hidden field)
508 if ($formObj.length && $valueObj.length) {
509 hiddenValue = $formObj[0].checked;
510 $formObj[0].remove();
511 $valueObj[0].remove();
512 }
513
514 // Update DOM
515 $objectDiv.html(htmlData);
516
517 formObj = document.getElementsByName(elName + '[hidden]_0');
518 valueObj = document.getElementsByName(elName + '[hidden]');
519
520 // Set the hidden value again
521 if (formObj.length && valueObj.length) {
522 valueObj[0].value = hiddenValue ? 1 : 0;
523 formObj[0].checked = hiddenValue;
524 }
525
526 // remove loading-indicator
527 TYPO3.jQuery('#' + escapeObjectId + '_loadingbar').remove();
528
529 // now that the content is loaded, set the expandState
530 this.expandCollapseRecord(objectId, expandSingle);
531 },
532
533 // Get script and link elements from head tag:
534 getDomHeadChildren: function (head) {
535 var headTags = [];
536 TYPO3.jQuery('head script, head link').each(function () {
537 headTags.push(this);
538 });
539 return headTags;
540 },
541
542 getDomHeadTag: function () {
543 if (document && document.head) {
544 return document.head;
545 } else {
546 var $head = TYPO3.jQuery('head');
547 if ($head.length) {
548 return $head[0];
549 }
550 }
551 return false;
552 },
553
554 // Search whether elements exist in a given haystack:
555 searchInDomTags: function (haystack, needle) {
556 var result = false;
557 TYPO3.jQuery.each(haystack, function (index, element) {
558 if (element.nodeName.toUpperCase() == needle.name) {
559 var attributesCount = $H(needle.attributes).keys().length;
560 var attributesFound = 0;
561 $H(needle.attributes).each(function (attribute) {
562 if (element.getAttribute && element.getAttribute(attribute.key) == attribute.value) {
563 attributesFound++;
564 }
565 });
566 if (attributesFound == attributesCount) {
567 result = true;
568 return true;
569 }
570 }
571 });
572 return result;
573 },
574
575 // Create a new DOM element:
576 createNewDomElement: function (addTag) {
577 var element = document.createElement(addTag.name);
578 if (addTag.attributes) {
579 TYPO3.jQuery.map(addTag.attributes, function (value, key) {
580 element[key] = value;
581 });
582 }
583 return element;
584 },
585
586 changeSorting: function (objectId, direction) {
587 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 2, true);
588 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
589 var formObj = document.getElementsByName(objectName);
590
591 if (formObj.length) {
592 // the uid of the calling object (last part in objectId)
593 var callingUid = this.parseObjectId('none', objectId, 1);
594 var records = formObj[0].value.split(',');
595 var current = records.indexOf(callingUid);
596 var changed = false;
597
598 // move up
599 if (direction > 0 && current > 0) {
600 records[current] = records[current - 1];
601 records[current - 1] = callingUid;
602 changed = true;
603
604 // move down
605 } else if (direction < 0 && current < records.length - 1) {
606 records[current] = records[current + 1];
607 records[current + 1] = callingUid;
608 changed = true;
609 }
610
611 if (changed) {
612 formObj[0].value = records.join(',');
613 var cAdj = direction > 0 ? 1 : 0; // adjustment
614 var objectIdPrefix = '#' + this.escapeObjectId(objectPrefix) + this.structureSeparator;
615 TYPO3.jQuery(objectIdPrefix + records[current - cAdj] + '_div').insertBefore(
616 TYPO3.jQuery(objectIdPrefix + records[current + 1 - cAdj] + '_div')
617 );
618 this.redrawSortingButtons(objectPrefix, records);
619 }
620 }
621
622 return false;
623 },
624
625 dragAndDropSorting: function (element) {
626 require(['jquery'], function ($) {
627 var objectId = element.getAttribute('id').replace(/_records$/, '');
628 var objectName = inline.prependFormFieldNames + inline.parseObjectId('parts', objectId, 3, 0, true);
629 var formObj = document.getElementsByName(objectName);
630 var $element = TYPO3.jQuery(element);
631
632 if (formObj.length) {
633 var checked = [];
634 var order = [];
635 $element.find('.sortableHandle').each(function (i, e) {
636 order.push($(e).data('id').toString());
637 });
638 var records = formObj[0].value.split(',');
639
640 // check if ordered uid is really part of the records
641 // virtually deleted items might still be there but ordering shouldn't saved at all on them
642 for (var i = 0; i < order.length; i++) {
643 if (records.indexOf(order[i]) != -1) {
644 checked.push(order[i]);
645 }
646 }
647
648 formObj[0].value = checked.join(',');
649
650 if (inline.data.config && inline.data.config[objectId]) {
651 var table = inline.data.config[objectId].table;
652 inline.redrawSortingButtons(objectId + inline.structureSeparator + table, checked);
653 }
654 }
655 });
656 },
657
658 createDragAndDropSorting: function (objectId) {
659 require(['jquery', 'jquery-ui/jquery-ui-1.10.4.custom.min'], function ($) {
660 var $sortingContainer = $('#' + inline.escapeObjectId(objectId));
661
662 if ($sortingContainer.hasClass('ui-sortable')) {
663 $sortingContainer.sortable('enable');
664 return;
665 }
666
667 $sortingContainer.addClass('t3-form-field-container-wrap');
668 $sortingContainer.sortable(
669 {
670 containment: 'body',
671 handle: '.sortableHandle',
672 zIndex: '4000',
673 axis: 'y',
674 tolerance: 'pointer',
675 stop: function () {
676 inline.dragAndDropSorting($sortingContainer[0]);
677 }
678 }
679 );
680 });
681 },
682
683 destroyDragAndDropSorting: function (objectId) {
684 require(['jquery', 'jquery-ui/jquery-ui-1.10.4.custom.min'], function ($) {
685 var $sortingContainer = $('#' + inline.escapeObjectId(objectId));
686 if (!$sortingContainer.hasClass('ui-sortable')) {
687 return;
688 }
689 $sortingContainer.sortable('disable');
690 });
691 },
692
693 redrawSortingButtons: function (objectPrefix, records) {
694 var i, $headerObj, sortUp, sortDown;
695
696 // if no records were passed, fetch them from form field
697 if (typeof records == 'undefined') {
698 records = [];
699 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
700 var formObj = document.getElementsByName(objectName);
701 if (formObj.length) {
702 records = formObj[0].value.split(',');
703 }
704 }
705
706 for (i = 0; i < records.length; i++) {
707 if (!records[i].length) {
708 continue;
709 }
710
711 $headerObj = TYPO3.jQuery('#' + this.escapeObjectId(objectPrefix) + this.structureSeparator + records[i] + '_header');
712 sortUp = $headerObj.find('.sortingUp');
713 sortDown = $headerObj.find('.sortingDown');
714
715 if (sortUp) {
716 sortUp.css('visibility', (i == 0 ? 'hidden' : 'visible'));
717 }
718 if (sortDown) {
719 sortDown.css('visibility', (i == records.length - 1 ? 'hidden' : 'visible'));
720 }
721 }
722 },
723
724 memorizeAddRecord: function (objectPrefix, newUid, afterUid, selectedValue) {
725 if (this.isBelowMax(objectPrefix)) {
726 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
727 var formObj = document.getElementsByName(objectName);
728
729 if (formObj.length) {
730 var records = [];
731 if (formObj[0].value.length) {
732 records = formObj[0].value.split(',');
733 }
734
735 if (afterUid) {
736 var newRecords = [];
737 for (var i = 0; i < records.length; i++) {
738 if (records[i].length) {
739 newRecords.push(records[i]);
740 }
741 if (afterUid == records[i]) {
742 newRecords.push(newUid);
743 }
744 }
745 records = newRecords;
746 } else {
747 records.push(newUid);
748 }
749 formObj[0].value = records.join(',');
750 }
751
752 this.redrawSortingButtons(objectPrefix, records);
753
754 if (this.data.unique && this.data.unique[objectPrefix]) {
755 var unique = this.data.unique[objectPrefix];
756 this.setUnique(objectPrefix, newUid, selectedValue);
757 }
758 }
759
760 // if we reached the maximum off possible records after this action, hide the new buttons
761 if (!this.isBelowMax(objectPrefix)) {
762 var objectParent = this.parseObjectId('full', objectPrefix, 0, 1);
763 var md5 = this.getObjectMD5(objectParent);
764 this.hideElementsWithClassName('.inlineNewButton' + (md5 ? '.' + md5 : ''), objectParent);
765 }
766
767 if (TBE_EDITOR) {
768 TBE_EDITOR.fieldChanged_fName(objectName, formObj);
769 }
770 },
771
772 memorizeRemoveRecord: function (objectName, removeUid) {
773 var formObj = document.getElementsByName(objectName);
774 if (formObj.length) {
775 var parts = [];
776 if (formObj[0].value.length) {
777 parts = formObj[0].value.split(',');
778 parts = parts.without(removeUid);
779 formObj[0].value = parts.join(',');
780 if (TBE_EDITOR) {
781 TBE_EDITOR.fieldChanged_fName(objectName, formObj);
782 }
783 return parts.length;
784 }
785 }
786 return false;
787 },
788
789 updateUnique: function (srcElement, objectPrefix, formName, recordUid) {
790 if (this.data.unique && this.data.unique[objectPrefix]) {
791 var unique = this.data.unique[objectPrefix];
792 var oldValue = unique.used[recordUid];
793
794 if (unique.selector == 'select') {
795 var selector = $(objectPrefix + '_selector');
796 this.removeSelectOption(selector, srcElement.value);
797 if (typeof oldValue != 'undefined') {
798 this.readdSelectOption(selector, oldValue, unique);
799 }
800 }
801
802 if (!(unique.selector && unique.max == -1)) {
803 var formObj = document.getElementsByName(formName);
804 if (unique && formObj.length) {
805 var records = formObj[0].value.split(',');
806 var recordObj;
807 for (var i = 0; i < records.length; i++) {
808 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
809 if (recordObj.length && recordObj[0] != srcElement) {
810 this.removeSelectOption(recordObj[0], srcElement.value);
811 if (typeof oldValue != 'undefined') {
812 this.readdSelectOption(recordObj[0], oldValue, unique);
813 }
814 }
815 }
816 this.data.unique[objectPrefix].used[recordUid] = srcElement.value;
817 }
818 }
819 }
820 },
821
822 revertUnique: function (objectPrefix, elName, recordUid) {
823 if (this.data.unique && this.data.unique[objectPrefix]) {
824 var unique = this.data.unique[objectPrefix];
825 var fieldObj = elName ? document.getElementsByName(elName + '[' + unique.field + ']') : null;
826
827 if (unique.type == 'select') {
828 if (fieldObj && fieldObj.length) {
829 delete(this.data.unique[objectPrefix].used[recordUid]);
830
831 if (unique.selector == 'select') {
832 if (!isNaN(fieldObj[0].value)) {
833 var $selector = TYPO3.jQuery('#' + objectPrefix + '_selector');
834 this.readdSelectOption($selector, fieldObj[0].value, unique);
835 }
836 }
837
838 if (!(unique.selector && unique.max == -1)) {
839 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
840 var formObj = document.getElementsByName(formName);
841 if (formObj.length) {
842 var records = formObj[0].value.split(',');
843 var recordObj;
844 // walk through all inline records on that level and get the select field
845 for (var i = 0; i < records.length; i++) {
846 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
847 if (recordObj.length) {
848 this.readdSelectOption(recordObj[0], fieldObj[0].value, unique);
849 }
850 }
851 }
852 }
853 }
854 } else if (unique.type == 'groupdb') {
855 // alert(objectPrefix+'/'+recordUid);
856 delete(this.data.unique[objectPrefix].used[recordUid])
857 }
858 }
859 },
860
861 enableDisableRecord: function (objectId) {
862 var elName = this.parseObjectId('full', objectId, 2, 0, true) + '[hidden]';
863 var formObj = document.getElementsByName(elName + '_0');
864 var valueObj = document.getElementsByName(elName);
865 var escapedObjectId = this.escapeObjectId(objectId);
866 var $icon = TYPO3.jQuery('#' + escapedObjectId + '_disabled');
867
868 var $container = TYPO3.jQuery('#' + escapedObjectId + '_div');
869
870 // It might be the case that there's no hidden field
871 if (formObj.length && valueObj.length) {
872 formObj[0].click();
873 valueObj[0].value = formObj[0].checked ? 1 : 0;
874 TBE_EDITOR.fieldChanged_fName(elName, elName);
875 }
876
877 if ($icon.length) {
878 if ($icon.hasClass('t3-icon-edit-hide')) {
879 $icon.removeClass('t3-icon-edit-hide');
880 $icon.addClass('t3-icon-edit-unhide');
881 $container.addClass('t3-form-field-container-inline-hidden');
882 } else {
883 $icon.removeClass('t3-icon-edit-unhide');
884 $icon.addClass('t3-icon-edit-hide');
885 $container.removeClass('t3-form-field-container-inline-hidden');
886 }
887 }
888
889 return false;
890 },
891
892 deleteRecord: function (objectId, options) {
893 var i, j, inlineRecords, records, childObjectId, childTable;
894 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
895 var elName = this.parseObjectId('full', objectId, 2, 0, true);
896 var shortName = this.parseObjectId('parts', objectId, 2, 0, true);
897 var recordUid = this.parseObjectId('none', objectId, 1);
898 var beforeDeleteIsBelowMax = this.isBelowMax(objectPrefix);
899
900 // revert the unique settings if available
901 this.revertUnique(objectPrefix, elName, recordUid);
902
903 // Remove from TBE_EDITOR (required fields, required range, etc.):
904 if (TBE_EDITOR && TBE_EDITOR.removeElement) {
905 var removeStack = [];
906 // Iterate over all child records:
907 inlineRecords = Element.select(objectId + '_div', '.inlineRecord');
908 // Remove nested child records from TBE_EDITOR required/range checks:
909 for (i = inlineRecords.length - 1; i >= 0; i--) {
910 if (inlineRecords[i].value.length) {
911 records = inlineRecords[i].value.split(',');
912 childObjectId = this.data.map[inlineRecords[i].name];
913 childTable = this.data.config[childObjectId].table;
914 for (j = records.length - 1; j >= 0; j--) {
915 removeStack.push(this.prependFormFieldNames + '[' + childTable + '][' + records[j] + ']');
916 }
917 }
918 }
919 removeStack.push(this.prependFormFieldNames + shortName);
920 TBE_EDITOR.removeElementArray(removeStack);
921 }
922
923 // Mark this container as deleted
924 var $deletedRecordContainer = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_div');
925 if ($deletedRecordContainer.length) {
926 $deletedRecordContainer.addClass('inlineIsDeletedRecord');
927 }
928
929 // If the record is new and was never saved before, just remove it from DOM:
930 if (this.isNewRecord(objectId) || options && options.forceDirectRemoval) {
931 this.fadeAndRemove(objectId + '_div');
932 // If the record already exists in storage, mark it to be deleted on clicking the save button:
933 } else {
934 document.getElementsByName('cmd' + shortName + '[delete]')[0].disabled = false;
935 new Effect.Fade(objectId + '_div');
936 }
937
938 var recordCount = this.memorizeRemoveRecord(
939 this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 2, true),
940 recordUid
941 );
942
943 if (recordCount <= 1) {
944 this.destroyDragAndDropSorting(this.parseObjectId('full', objectId, 0, 2) + '_records');
945 }
946 this.redrawSortingButtons(objectPrefix);
947
948 // if the NEW-button was hidden and now we can add again new children, show the button
949 if (!beforeDeleteIsBelowMax && this.isBelowMax(objectPrefix)) {
950 var objectParent = this.parseObjectId('full', objectPrefix, 0, 1);
951 var md5 = this.getObjectMD5(objectParent);
952 this.showElementsWithClassName('.inlineNewButton' + (md5 ? '.' + md5 : ''), objectParent);
953 }
954 return false;
955 },
956
957 parsePath: function (path) {
958 var backSlash = path.lastIndexOf('\\');
959 var normalSlash = path.lastIndexOf('/');
960
961 if (backSlash > 0) {
962 path = path.substring(0, backSlash + 1);
963 } else if (normalSlash > 0) {
964 path = path.substring(0, normalSlash + 1);
965 } else {
966 path = '';
967 }
968
969 return path;
970 },
971
972 parseFormElementName: function (wrap, formElementName, rightCount, skipRight) {
973 var idParts = this.splitFormElementName(formElementName);
974
975 if (!wrap) {
976 wrap = 'full';
977 }
978 if (!skipRight) {
979 skipRight = 0;
980 }
981
982 var elParts = [];
983 for (var i = 0; i < skipRight; i++) {
984 idParts.pop();
985 }
986
987 if (rightCount > 0) {
988 for (var i = 0; i < rightCount; i++) {
989 elParts.unshift(idParts.pop());
990 }
991 } else {
992 for (var i = 0; i < -rightCount; i++) {
993 idParts.shift();
994 }
995 elParts = idParts;
996 }
997
998 return this.constructFormElementName(wrap, elParts);
999 },
1000
1001 splitFormElementName: function (formElementName) {
1002 // remove left and right side "data[...|...]" -> '...|...'
1003 formElementName = formElementName.substr(0, formElementName.lastIndexOf(']')).substr(formElementName.indexOf('[') + 1);
1004 return formElementName.split('][');
1005 },
1006
1007 splitObjectId: function (objectId) {
1008 objectId = objectId.substr(objectId.indexOf(this.structureSeparator) + 1);
1009 objectId = objectId.split(this.flexFormSeparator).join(this.flexFormSubstitute);
1010 return objectId.split(this.structureSeparator);
1011 },
1012
1013 constructFormElementName: function (wrap, parts) {
1014 var elReturn;
1015
1016 if (wrap == 'full') {
1017 elReturn = this.prependFormFieldNames + '[' + parts.join('][') + ']';
1018 elReturn = elReturn.split(this.flexFormSubstitute).join('][');
1019 } else if (wrap == 'parts') {
1020 elReturn = '[' + parts.join('][') + ']';
1021 elReturn = elReturn.split(this.flexFormSubstitute).join('][');
1022 } else if (wrap == 'none') {
1023 elReturn = parts.length > 1 ? parts : parts.join('');
1024 }
1025
1026 return elReturn;
1027 },
1028
1029 constructObjectId: function (wrap, parts) {
1030 var elReturn;
1031
1032 if (wrap == 'full') {
1033 elReturn = this.prependFormFieldNames + this.structureSeparator + parts.join(this.structureSeparator);
1034 elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
1035 } else if (wrap == 'parts') {
1036 elReturn = this.structureSeparator + parts.join(this.structureSeparator);
1037 elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
1038 } else if (wrap == 'none') {
1039 elReturn = parts.length > 1 ? parts : parts.join('');
1040 }
1041
1042 return elReturn;
1043 },
1044
1045 parseObjectId: function (wrap, objectId, rightCount, skipRight, returnAsFormElementName) {
1046 var idParts = this.splitObjectId(objectId);
1047
1048 if (!wrap) {
1049 wrap = 'full';
1050 }
1051 if (!skipRight) {
1052 skipRight = 0;
1053 }
1054
1055 var elParts = [];
1056 for (var i = 0; i < skipRight; i++) {
1057 idParts.pop();
1058 }
1059
1060 if (rightCount > 0) {
1061 for (var i = 0; i < rightCount; i++) {
1062 elParts.unshift(idParts.pop());
1063 }
1064 } else {
1065 for (var i = 0; i < -rightCount; i++) {
1066 idParts.shift();
1067 }
1068 elParts = idParts;
1069 }
1070
1071 var elReturn;
1072 if (returnAsFormElementName) {
1073 elReturn = this.constructFormElementName(wrap, elParts);
1074 } else {
1075 elReturn = this.constructObjectId(wrap, elParts);
1076 }
1077
1078 return elReturn;
1079 },
1080
1081 handleChangedField: function (formField, objectId) {
1082 var formObj;
1083 if (typeof formField == 'object') {
1084 formObj = formField;
1085 } else {
1086 formObj = document.getElementsByName(formField);
1087 if (formObj.length) {
1088 formObj = formObj[0];
1089 }
1090 }
1091
1092 if (formObj != undefined) {
1093 var value;
1094 if (formObj.nodeName == 'SELECT') {
1095 value = formObj.options[formObj.selectedIndex].text;
1096 } else {
1097 value = formObj.value;
1098 }
1099 TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_label').html(value.length ? value : this.noTitleString);
1100 }
1101 return true;
1102 },
1103
1104 arrayAssocCount: function (object) {
1105 var count = 0;
1106 if (typeof object.length != 'undefined') {
1107 count = object.length;
1108 } else {
1109 for (var i in object) {
1110 count++;
1111 }
1112 }
1113 return count;
1114 },
1115
1116 isBelowMax: function (objectPrefix) {
1117 var isBelowMax = true;
1118 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
1119 var formObj = document.getElementsByName(objectName);
1120
1121 if (this.data.config && this.data.config[objectPrefix] && formObj.length) {
1122 var recordCount = formObj[0].value ? formObj[0].value.split(',').length : 0;
1123 if (recordCount >= this.data.config[objectPrefix].max) {
1124 isBelowMax = false;
1125 }
1126 }
1127 if (isBelowMax && this.data.unique && this.data.unique[objectPrefix]) {
1128 var unique = this.data.unique[objectPrefix];
1129 if (this.arrayAssocCount(unique.used) >= unique.max && unique.max >= 0) {
1130 isBelowMax = false;
1131 }
1132 }
1133 return isBelowMax;
1134 },
1135
1136 getOptionsHash: function ($selectObj) {
1137 var optionsHash = {};
1138 $selectObj.find('option').each(function(i, option) {
1139 optionsHash[option.value] = i;
1140 });
1141 return optionsHash;
1142 },
1143
1144 removeSelectOption: function ($selectObj, value) {
1145 var optionsHash = this.getOptionsHash($selectObj);
1146 if (optionsHash[value] != undefined) {
1147 $selectObj.find('option').eq(optionsHash[value]).remove();
1148 }
1149 },
1150
1151 readdSelectOption: function ($selectObj, value, unique) {
1152 var index = null;
1153 var optionsHash = this.getOptionsHash($selectObj);
1154 var possibleValues = this.getKeysFromHashMap(unique.possible);
1155
1156 for (var possibleValue in unique.possible) {
1157 if (possibleValue == value) {
1158 break;
1159 }
1160 if (optionsHash[possibleValue] != undefined) {
1161 index = optionsHash[possibleValue];
1162 }
1163 }
1164
1165 if (index == null) {
1166 index = 0;
1167 } else if (index < $selectObj.find('option').length) {
1168 index++;
1169 }
1170 // recreate the <option> tag
1171 var readdOption = document.createElement('option');
1172 readdOption.text = unique.possible[value];
1173 readdOption.value = value;
1174 // add the <option> at the right position
1175 // I didn't find a possibility to add an option to a predefined position
1176 // with help of an index in jQuery. So we realized it the "old" style
1177 var selectObj = $selectObj.get(0);
1178 selectObj.add(readdOption, document.all ? index : selectObj.options[index]);
1179 },
1180
1181 hideElementsWithClassName: function (selector, parentElement) {
1182 this.setVisibilityOfElementsWithClassName('hide', selector, parentElement);
1183 },
1184
1185 showElementsWithClassName: function (selector, parentElement) {
1186 this.setVisibilityOfElementsWithClassName('show', selector, parentElement);
1187 },
1188
1189 setVisibilityOfElementsWithClassName: function (action, selector, parentElement) {
1190 var domObjects = Selector.findChildElements($(parentElement), [selector]);
1191 if (action == 'hide') {
1192 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1193 new Effect.Fade(domObject);
1194 });
1195 } else if (action == 'show') {
1196 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1197 new Effect.Appear(domObject);
1198 });
1199 }
1200 },
1201
1202 fadeOutFadeIn: function (objectId) {
1203 var optIn = { duration: 0.5, transition: Effect.Transitions.linear, from: 0.50, to: 1.00 };
1204 var optOut = { duration: 0.5, transition: Effect.Transitions.linear, from: 1.00, to: 0.50 };
1205 optOut.afterFinish = function () {
1206 new Effect.Opacity(objectId, optIn);
1207 };
1208 new Effect.Opacity(objectId, optOut);
1209 },
1210
1211 isNewRecord: function (objectId) {
1212 var $selector = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_div');
1213 return $selector.length && $selector.hasClass('inlineIsNewRecord')
1214 ? true
1215 : false;
1216 },
1217
1218 // Find and fix nested of inline and tab levels if a new element was created dynamically (it doesn't know about its nesting):
1219 findContinuedNestedLevel: function (nested, objectId) {
1220 if (this.data.nested && this.data.nested[objectId]) {
1221 // Remove the first element from the new nested stack, it's just a hint:
1222 nested.shift();
1223 nested = this.data.nested[objectId].concat(nested);
1224 return nested;
1225 } else {
1226 return nested;
1227 }
1228 },
1229
1230 getNumberOfRTE: function () {
1231 var number = 0;
1232 if (typeof RTEarea != 'undefined' && RTEarea.length > 0) {
1233 number = RTEarea.length - 1;
1234 }
1235 return number;
1236 },
1237
1238 getObjectMD5: function (objectPrefix) {
1239 var md5 = false;
1240 if (this.data.config && this.data.config[objectPrefix] && this.data.config[objectPrefix].md5) {
1241 md5 = this.data.config[objectPrefix].md5;
1242 }
1243 return md5
1244 },
1245
1246 fadeAndRemove: function (element) {
1247 if (TYPO3.jQuery('#' + this.escapeObjectId(element)).length) {
1248 new Effect.Fade(element, { afterFinish: function () {
1249 Element.remove(element);
1250 } });
1251 }
1252 },
1253
1254 getContext: function (objectId) {
1255 var result = null;
1256
1257 if (objectId !== '' && typeof this.data.config[objectId] !== 'undefined' && typeof this.data.config[objectId].context !== 'undefined') {
1258 result = this.data.config[objectId].context;
1259 }
1260
1261 return result;
1262 },
1263
1264 /**
1265 * Escapes object identifiers to be used in jQuery.
1266 *
1267 * @param string objectId
1268 * @return string
1269 */
1270 escapeObjectId: function (objectId) {
1271 var escapedObjectId;
1272 escapedObjectId = objectId.replace(/:/g, '\\:');
1273 escapedObjectId = escapedObjectId.replace(/\./g, '\\.');
1274 return escapedObjectId;
1275 },
1276
1277 /**
1278 * Escapes object identifiers to be used as jQuery selector.
1279 *
1280 * @param string objectId
1281 * @return string
1282 */
1283 escapeSelectorObjectId: function (objectId) {
1284 var escapedSelectorObjectId;
1285 var escapedObjectId = this.escapeObjectId(objectId);
1286 escapedSelectorObjectId = escapedObjectId.replace(/\\:/g, '\\\\\\:');
1287 escapedSelectorObjectId = escapedSelectorObjectId.replace(/\\\./g, '\\\\\\.');
1288 return escapedSelectorObjectId;
1289 }
1290 };
1291
1292 Object.extend(Array.prototype, {
1293 diff: function (current) {
1294 var diff = [];
1295 if (this.length == current.length) {
1296 for (var i = 0; i < this.length; i++) {
1297 if (this[i] !== current[i]) {
1298 diff.push(i);
1299 }
1300 }
1301 }
1302 return diff;
1303 }
1304 });
1305
1306 /*]]>*/
1307 (function ($) {
1308 $(function () {
1309 $(document).delegate('div.t3-form-field-header-inline', 'click', inline.toggleEvent);
1310 });
1311 })(TYPO3.jQuery);