8f7176b323ac022e99d1fbed84b5bf3d1aa84892
[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 if (selector.selectedIndex != -1) {
320 var context = this.getContext(objectId);
321 var selectedValue = selector.options[selector.selectedIndex].value;
322 if (!this.data.unique || !this.data.unique[objectId]) {
323 selector.options[selector.selectedIndex].selected = false;
324 }
325 this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId, selectedValue], true, context);
326 }
327 return false;
328 },
329
330 // foreign_selector: used by element browser (type='group/db')
331 importElement: function (objectId, table, uid, type) {
332 var context = this.getContext(objectId);
333 inline.makeAjaxCall('createNewRecord', [inline.getNumberOfRTE(), objectId, uid], true, context);
334 },
335
336 importElementMultiple: function (objectId, table, uidArray, type) {
337 uidArray.each(function (uid) {
338 inline.delayedImportElement(objectId, table, uid, type);
339 });
340 },
341 delayedImportElement: function (objectId, table, uid, type) {
342 if (inline.lockedAjaxMethod['createNewRecord'] == true) {
343 window.setTimeout("inline.delayedImportElement('" + objectId + "','" + table + "'," + uid + ", null );",
344 300);
345 } else {
346 inline.importElement(objectId, table, uid, type);
347 }
348 },
349 // Check uniqueness for element browser:
350 checkUniqueElement: function (objectId, table, uid, type) {
351 if (this.checkUniqueUsed(objectId, uid, table)) {
352 return {passed: false, message: 'There is already a relation to the selected element!'};
353 } else {
354 return {passed: true};
355 }
356 },
357
358 // Checks if a record was used and should be unique:
359 checkUniqueUsed: function (objectId, uid, table) {
360 if (this.data.unique && this.data.unique[objectId]) {
361 var unique = this.data.unique[objectId];
362 var values = this.getValuesFromHashMap(unique.used);
363
364 // for select: only the uid is stored
365 if (unique['type'] == 'select') {
366 if (values.indexOf(uid) != -1) {
367 return true;
368 }
369
370 // for group/db: table and uid is stored in a assoc array
371 } else if (unique.type == 'groupdb') {
372 for (var i = values.length - 1; i >= 0; i--) {
373 // if the pair table:uid is already used:
374 if (values[i].table == table && values[i].uid == uid) {
375 return true;
376 }
377 }
378 }
379 }
380 return false;
381 },
382
383 setUniqueElement: function (objectId, table, uid, type, elName) {
384 var recordUid = this.parseFormElementName('none', elName, 1, 1);
385 // alert(objectId+'/'+table+'/'+uid+'/'+recordUid);
386 this.setUnique(objectId, recordUid, uid);
387 },
388
389 getKeysFromHashMap: function (unique) {
390 return TYPO3.jQuery.map(unique, function(value, key) {
391 return key;
392 });
393 },
394
395 getValuesFromHashMap: function (hashMap) {
396 return TYPO3.jQuery.map(hashMap, function(value, key) {
397 return value;
398 });
399 },
400
401 // Remove all select items already used
402 // from a newly retrieved/expanded record
403 removeUsed: function (objectId, recordUid) {
404 if (this.data.unique && this.data.unique[objectId]) {
405 var unique = this.data.unique[objectId];
406 if (unique.type == 'select') {
407 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 1, true);
408 var formObj = document.getElementsByName(formName);
409 var recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + recordUid + '][' + unique.field + ']');
410 var values = this.getValuesFromHashMap(unique.used);
411 if (recordObj.length) {
412 if (recordObj[0].hasOwnProperty('options')) {
413 var selectedValue = recordObj[0].options[recordObj[0].selectedIndex].value;
414 for (var i=0; i<values.length; i++) {
415 if (values[i] != selectedValue) {
416 this.removeSelectOption(recordObj[0], values[i]);
417 }
418 }
419 }
420 }
421 }
422 }
423 },
424 // this function is applied to a newly inserted record by AJAX
425 // it removes the used select items, that should be unique
426 setUnique: function (objectId, recordUid, selectedValue) {
427 if (this.data.unique && this.data.unique[objectId]) {
428 var selector = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_selector');
429 var unique = this.data.unique[objectId];
430 if (unique.type == 'select') {
431 if (!(unique.selector && unique.max == -1)) {
432 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 1, true);
433 var formObj = document.getElementsByName(formName);
434 var recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + recordUid + '][' + unique.field + ']');
435 var values = this.getValuesFromHashMap(unique.used);
436 if (selector.length) {
437 // remove all items from the new select-item which are already used in other children
438 if (recordObj.length) {
439 for (var i = 0; i < values.length; i++) {
440 this.removeSelectOption(recordObj[0], values[i]);
441 }
442 // set the selected item automatically to the first of the remaining items if no selector is used
443 if (!unique.selector) {
444 selectedValue = recordObj[0].options[0].value;
445 recordObj[0].options[0].selected = true;
446 this.updateUnique(recordObj[0], objectId, formName, recordUid);
447 this.handleChangedField(recordObj[0], objectId + '[' + recordUid + ']');
448 }
449 }
450 for (var i = 0; i < values.length; i++) {
451 this.removeSelectOption(selector, values[i]);
452 }
453 if (typeof this.data.unique[objectId].used.length != 'undefined') {
454 this.data.unique[objectId].used = {};
455 }
456 this.data.unique[objectId].used[recordUid] = selectedValue;
457 }
458 // remove the newly used item from each select-field of the child records
459 if (formObj.length && selectedValue) {
460 var records = formObj[0].value.split(',');
461 for (var i = 0; i < records.length; i++) {
462 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
463 if (recordObj.length && records[i] != recordUid) {
464 this.removeSelectOption(recordObj[0], selectedValue);
465 }
466 }
467 }
468 }
469 } else if (unique.type == 'groupdb') {
470 // add the new record to the used items:
471 this.data.unique[objectId].used[recordUid] = {'table': unique.elTable, 'uid': selectedValue};
472 }
473
474 // remove used items from a selector-box
475 if (unique.selector == 'select' && selectedValue) {
476 this.removeSelectOption(selector, selectedValue);
477 this.data.unique[objectId]['used'][recordUid] = selectedValue;
478 }
479 }
480 },
481
482 domAddNewRecord: function (method, insertObjectId, objectPrefix, htmlData) {
483 if (this.isBelowMax(objectPrefix)) {
484 var insertObject = TYPO3.jQuery('#' + this.escapeObjectId(insertObjectId));
485 if (method == 'bottom') {
486 insertObject.append(htmlData);
487 } else if (method == 'after') {
488 insertObject.after(htmlData);
489 }
490 }
491 },
492 domAddRecordDetails: function (objectId, objectPrefix, expandSingle, htmlData) {
493 var hiddenValue, formObj, valueObj;
494 var escapeObjectId = this.escapeObjectId(objectId);
495 var objectDiv = TYPO3.jQuery('#' + escapeObjectId + '_fields');
496 if (objectDiv.length == 0 || objectDiv.html().substr(0, 16) != '<!--notloaded-->') {
497 return;
498 }
499
500 var elName = this.parseObjectId('full', objectId, 2, 0, true);
501
502 formObj = TYPO3.jQuery('[name="' + elName + '[hidden]_0"]');
503 valueObj = TYPO3.jQuery('[name="' + elName + '[hidden]"]');
504
505 // It might be the case that a child record
506 // cannot be hidden at all (no hidden field)
507 if (formObj.length && valueObj.length) {
508 hiddenValue = formObj[0].checked;
509 formObj[0].remove();
510 valueObj[0].remove();
511 }
512
513 // Update DOM
514 objectDiv.html(htmlData);
515
516 formObj = document.getElementsByName(elName + '[hidden]_0');
517 valueObj = document.getElementsByName(elName + '[hidden]');
518
519 // Set the hidden value again
520 if (formObj.length && valueObj.length) {
521 valueObj[0].value = hiddenValue ? 1 : 0;
522 formObj[0].checked = hiddenValue;
523 }
524
525 // remove loading-indicator
526 TYPO3.jQuery('#' + escapeObjectId + '_loadingbar').remove();
527
528 // now that the content is loaded, set the expandState
529 this.expandCollapseRecord(objectId, expandSingle);
530 },
531
532 // Get script and link elements from head tag:
533 getDomHeadChildren: function (head) {
534 var headTags = [];
535 TYPO3.jQuery('head script, head link').each(function () {
536 headTags.push(this);
537 });
538 return headTags;
539 },
540
541 getDomHeadTag: function () {
542 if (document && document.head) {
543 return document.head;
544 } else {
545 var head = TYPO3.jQuery('head');
546 if (head.length) {
547 return head[0];
548 }
549 }
550 return false;
551 },
552
553 // Search whether elements exist in a given haystack:
554 searchInDomTags: function (haystack, needle) {
555 var result = false;
556 TYPO3.jQuery.each(haystack, function (index, element) {
557 if (element.nodeName.toUpperCase() == needle.name) {
558 var attributesCount = $H(needle.attributes).keys().length;
559 var attributesFound = 0;
560 $H(needle.attributes).each(function (attribute) {
561 if (element.getAttribute && element.getAttribute(attribute.key) == attribute.value) {
562 attributesFound++;
563 }
564 });
565 if (attributesFound == attributesCount) {
566 result = true;
567 return true;
568 }
569 }
570 });
571 return result;
572 },
573
574 // Create a new DOM element:
575 createNewDomElement: function (addTag) {
576 var element = document.createElement(addTag.name);
577 if (addTag.attributes) {
578 TYPO3.jQuery.map(addTag.attributes, function (value, key) {
579 element[key] = value;
580 });
581 }
582 return element;
583 },
584
585 changeSorting: function (objectId, direction) {
586 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 2, true);
587 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
588 var formObj = document.getElementsByName(objectName);
589
590 if (formObj.length) {
591 // the uid of the calling object (last part in objectId)
592 var callingUid = this.parseObjectId('none', objectId, 1);
593 var records = formObj[0].value.split(',');
594 var current = records.indexOf(callingUid);
595 var changed = false;
596
597 // move up
598 if (direction > 0 && current > 0) {
599 records[current] = records[current - 1];
600 records[current - 1] = callingUid;
601 changed = true;
602
603 // move down
604 } else if (direction < 0 && current < records.length - 1) {
605 records[current] = records[current + 1];
606 records[current + 1] = callingUid;
607 changed = true;
608 }
609
610 if (changed) {
611 formObj[0].value = records.join(',');
612 var cAdj = direction > 0 ? 1 : 0; // adjustment
613 var objectIdPrefix = '#' + this.escapeObjectId(objectPrefix) + this.structureSeparator;
614 TYPO3.jQuery(objectIdPrefix + records[current - cAdj] + '_div').insertBefore(
615 TYPO3.jQuery(objectIdPrefix + records[current + 1 - cAdj] + '_div')
616 );
617 this.redrawSortingButtons(objectPrefix, records);
618 }
619 }
620
621 return false;
622 },
623
624 dragAndDropSorting: function (element) {
625 require(['jquery'], function ($) {
626 var objectId = element.getAttribute('id').replace(/_records$/, '');
627 var objectName = inline.prependFormFieldNames + inline.parseObjectId('parts', objectId, 3, 0, true);
628 var formObj = document.getElementsByName(objectName);
629 var $element = TYPO3.jQuery(element);
630
631 if (formObj.length) {
632 var checked = [];
633 var order = [];
634 $element.find('.sortableHandle').each(function (i, e) {
635 order.push($(e).data('id').toString());
636 });
637 var records = formObj[0].value.split(',');
638
639 // check if ordered uid is really part of the records
640 // virtually deleted items might still be there but ordering shouldn't saved at all on them
641 for (var i = 0; i < order.length; i++) {
642 if (records.indexOf(order[i]) != -1) {
643 checked.push(order[i]);
644 }
645 }
646
647 formObj[0].value = checked.join(',');
648
649 if (inline.data.config && inline.data.config[objectId]) {
650 var table = inline.data.config[objectId].table;
651 inline.redrawSortingButtons(objectId + inline.structureSeparator + table, checked);
652 }
653 }
654 });
655 },
656
657 createDragAndDropSorting: function (objectId) {
658 require(['jquery', 'jquery-ui/jquery-ui-1.10.4.custom.min'], function ($) {
659 var $sortingContainer = $('#' + inline.escapeObjectId(objectId));
660
661 if ($sortingContainer.hasClass('ui-sortable')) {
662 $sortingContainer.sortable('enable');
663 return;
664 }
665
666 $sortingContainer.addClass('t3-form-field-container-wrap');
667 $sortingContainer.sortable(
668 {
669 containment: 'body',
670 handle: '.sortableHandle',
671 zIndex: '4000',
672 axis: 'y',
673 tolerance: 'pointer',
674 stop: function () {
675 inline.dragAndDropSorting($sortingContainer[0]);
676 }
677 }
678 );
679 });
680 },
681
682 destroyDragAndDropSorting: function (objectId) {
683 require(['jquery', 'jquery-ui/jquery-ui-1.10.4.custom.min'], function ($) {
684 var $sortingContainer = $('#' + inline.escapeObjectId(objectId));
685 if (!$sortingContainer.hasClass('ui-sortable')) {
686 return;
687 }
688 $sortingContainer.sortable('disable');
689 });
690 },
691
692 redrawSortingButtons: function (objectPrefix, records) {
693 var i, headerObj, sortUp, sortDown;
694
695 // if no records were passed, fetch them from form field
696 if (typeof records == 'undefined') {
697 records = [];
698 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
699 var formObj = document.getElementsByName(objectName);
700 if (formObj.length) {
701 records = formObj[0].value.split(',');
702 }
703 }
704
705 for (i = 0; i < records.length; i++) {
706 if (!records[i].length) {
707 continue;
708 }
709
710 headerObj = TYPO3.jQuery('#' + this.escapeObjectId(objectPrefix) + this.structureSeparator + records[i] + '_header');
711 sortUp = headerObj.find('.sortingUp');
712 sortDown = headerObj.find('.sortingDown');
713
714 if (sortUp) {
715 sortUp.css('visibility', (i == 0 ? 'hidden' : 'visible'));
716 }
717 if (sortDown) {
718 sortDown.css('visibility', (i == records.length - 1 ? 'hidden' : 'visible'));
719 }
720 }
721 },
722
723 memorizeAddRecord: function (objectPrefix, newUid, afterUid, selectedValue) {
724 if (this.isBelowMax(objectPrefix)) {
725 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
726 var formObj = document.getElementsByName(objectName);
727
728 if (formObj.length) {
729 var records = [];
730 if (formObj[0].value.length) {
731 records = formObj[0].value.split(',');
732 }
733
734 if (afterUid) {
735 var newRecords = [];
736 for (var i = 0; i < records.length; i++) {
737 if (records[i].length) {
738 newRecords.push(records[i]);
739 }
740 if (afterUid == records[i]) {
741 newRecords.push(newUid);
742 }
743 }
744 records = newRecords;
745 } else {
746 records.push(newUid);
747 }
748 formObj[0].value = records.join(',');
749 }
750
751 this.redrawSortingButtons(objectPrefix, records);
752
753 if (this.data.unique && this.data.unique[objectPrefix]) {
754 var unique = this.data.unique[objectPrefix];
755 this.setUnique(objectPrefix, newUid, selectedValue);
756 }
757 }
758
759 // if we reached the maximum off possible records after this action, hide the new buttons
760 if (!this.isBelowMax(objectPrefix)) {
761 var objectParent = this.parseObjectId('full', objectPrefix, 0, 1);
762 var md5 = this.getObjectMD5(objectParent);
763 this.hideElementsWithClassName('.inlineNewButton' + (md5 ? '.' + md5 : ''), objectParent);
764 }
765
766 if (TBE_EDITOR) {
767 TBE_EDITOR.fieldChanged_fName(objectName, formObj);
768 }
769 },
770
771 memorizeRemoveRecord: function (objectName, removeUid) {
772 var formObj = document.getElementsByName(objectName);
773 if (formObj.length) {
774 var parts = [];
775 if (formObj[0].value.length) {
776 parts = formObj[0].value.split(',');
777 parts = parts.without(removeUid);
778 formObj[0].value = parts.join(',');
779 if (TBE_EDITOR) {
780 TBE_EDITOR.fieldChanged_fName(objectName, formObj);
781 }
782 return parts.length;
783 }
784 }
785 return false;
786 },
787
788 updateUnique: function (srcElement, objectPrefix, formName, recordUid) {
789 if (this.data.unique && this.data.unique[objectPrefix]) {
790 var unique = this.data.unique[objectPrefix];
791 var oldValue = unique.used[recordUid];
792
793 if (unique.selector == 'select') {
794 var selector = $(objectPrefix + '_selector');
795 this.removeSelectOption(selector, srcElement.value);
796 if (typeof oldValue != 'undefined') {
797 this.readdSelectOption(selector, oldValue, unique);
798 }
799 }
800
801 if (!(unique.selector && unique.max == -1)) {
802 var formObj = document.getElementsByName(formName);
803 if (unique && formObj.length) {
804 var records = formObj[0].value.split(',');
805 var recordObj;
806 for (var i = 0; i < records.length; i++) {
807 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
808 if (recordObj.length && recordObj[0] != srcElement) {
809 this.removeSelectOption(recordObj[0], srcElement.value);
810 if (typeof oldValue != 'undefined') {
811 this.readdSelectOption(recordObj[0], oldValue, unique);
812 }
813 }
814 }
815 this.data.unique[objectPrefix].used[recordUid] = srcElement.value;
816 }
817 }
818 }
819 },
820
821 revertUnique: function (objectPrefix, elName, recordUid) {
822 if (this.data.unique && this.data.unique[objectPrefix]) {
823 var unique = this.data.unique[objectPrefix];
824 var fieldObj = elName ? document.getElementsByName(elName + '[' + unique.field + ']') : null;
825
826 if (unique.type == 'select') {
827 if (fieldObj && fieldObj.length) {
828 delete(this.data.unique[objectPrefix].used[recordUid]);
829
830 if (unique.selector == 'select') {
831 if (!isNaN(fieldObj[0].value)) {
832 var selector = $(objectPrefix + '_selector');
833 this.readdSelectOption(selector, fieldObj[0].value, unique);
834 }
835 }
836
837 if (!(unique.selector && unique.max == -1)) {
838 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1,
839 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 for (var i = 0; i < selectObj.options.length; i++) {
1139 optionsHash[selectObj.options[i].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.options[optionsHash[value]] = null;
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.options.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 selectObj.add(readdOption, document.all ? index : selectObj.options[index]);
1176 },
1177
1178 hideElementsWithClassName: function (selector, parentElement) {
1179 this.setVisibilityOfElementsWithClassName('hide', selector, parentElement);
1180 },
1181
1182 showElementsWithClassName: function (selector, parentElement) {
1183 this.setVisibilityOfElementsWithClassName('show', selector, parentElement);
1184 },
1185
1186 setVisibilityOfElementsWithClassName: function (action, selector, parentElement) {
1187 var domObjects = Selector.findChildElements($(parentElement), [selector]);
1188 if (action == 'hide') {
1189 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1190 new Effect.Fade(domObject);
1191 });
1192 } else if (action == 'show') {
1193 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1194 new Effect.Appear(domObject);
1195 });
1196 }
1197 },
1198
1199 fadeOutFadeIn: function (objectId) {
1200 var optIn = { duration: 0.5, transition: Effect.Transitions.linear, from: 0.50, to: 1.00 };
1201 var optOut = { duration: 0.5, transition: Effect.Transitions.linear, from: 1.00, to: 0.50 };
1202 optOut.afterFinish = function () {
1203 new Effect.Opacity(objectId, optIn);
1204 };
1205 new Effect.Opacity(objectId, optOut);
1206 },
1207
1208 isNewRecord: function (objectId) {
1209 var selector = TYPO3.jQuery('#' + this.escapeObjectId(objectId) + '_div');
1210 return selector.length && selector.hasClass('inlineIsNewRecord')
1211 ? true
1212 : false;
1213 },
1214
1215 // Find and fix nested of inline and tab levels if a new element was created dynamically (it doesn't know about its nesting):
1216 findContinuedNestedLevel: function (nested, objectId) {
1217 if (this.data.nested && this.data.nested[objectId]) {
1218 // Remove the first element from the new nested stack, it's just a hint:
1219 nested.shift();
1220 nested = this.data.nested[objectId].concat(nested);
1221 return nested;
1222 } else {
1223 return nested;
1224 }
1225 },
1226
1227 getNumberOfRTE: function () {
1228 var number = 0;
1229 if (typeof RTEarea != 'undefined' && RTEarea.length > 0) {
1230 number = RTEarea.length - 1;
1231 }
1232 return number;
1233 },
1234
1235 getObjectMD5: function (objectPrefix) {
1236 var md5 = false;
1237 if (this.data.config && this.data.config[objectPrefix] && this.data.config[objectPrefix].md5) {
1238 md5 = this.data.config[objectPrefix].md5;
1239 }
1240 return md5
1241 },
1242
1243 fadeAndRemove: function (element) {
1244 if (TYPO3.jQuery('#' + this.escapeObjectId(element)).length) {
1245 new Effect.Fade(element, { afterFinish: function () {
1246 Element.remove(element);
1247 } });
1248 }
1249 },
1250
1251 getContext: function (objectId) {
1252 var result = null;
1253
1254 if (objectId !== '' && typeof this.data.config[objectId] !== 'undefined' && typeof this.data.config[objectId].context !== 'undefined') {
1255 result = this.data.config[objectId].context;
1256 }
1257
1258 return result;
1259 },
1260
1261 /**
1262 * Escapes object identifiers to be used in jQuery.
1263 *
1264 * @param string objectId
1265 * @return string
1266 */
1267 escapeObjectId: function (objectId) {
1268 var escapedObjectId;
1269 escapedObjectId = objectId.replace(/:/g, '\\:');
1270 escapedObjectId = escapedObjectId.replace(/\./g, '\\.');
1271 return escapedObjectId;
1272 },
1273
1274 /**
1275 * Escapes object identifiers to be used as jQuery selector.
1276 *
1277 * @param string objectId
1278 * @return string
1279 */
1280 escapeSelectorObjectId: function (objectId) {
1281 var escapedSelectorObjectId;
1282 var escapedObjectId = this.escapeObjectId(objectId);
1283 escapedSelectorObjectId = escapedObjectId.replace(/\\:/g, '\\\\\\:');
1284 escapedSelectorObjectId = escapedSelectorObjectId.replace(/\\\./g, '\\\\\\.');
1285 return escapedSelectorObjectId;
1286 }
1287 };
1288
1289 Object.extend(Array.prototype, {
1290 diff: function (current) {
1291 var diff = [];
1292 if (this.length == current.length) {
1293 for (var i = 0; i < this.length; i++) {
1294 if (this[i] !== current[i]) {
1295 diff.push(i);
1296 }
1297 }
1298 }
1299 return diff;
1300 }
1301 });
1302
1303 /*]]>*/
1304 (function ($) {
1305 $(function () {
1306 $(document).delegate('div.t3-form-field-header-inline', 'click', inline.toggleEvent);
1307 });
1308 })(TYPO3.jQuery);