f4e8b02d891a8bd3bfd02ba0d32d373966067bf1
[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" + objectId).length > 0)) {
68 return false;
69 } else if (TYPO3.jQuery('#' + objectId + '_fields').length > 0 && TYPO3.jQuery('#' + objectId + '_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="' + escapedObjectId + '_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 = new Array();
81 var expand = new Array();
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 max, 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('#' + 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 unique = this.data.unique[objectId];
429 if (unique.type == 'select') {
430 if (!(unique.selector && unique.max == -1)) {
431 var formName = this.prependFormFieldNames + this.parseObjectId('parts', objectId, 3, 1, true);
432 var formObj = document.getElementsByName(formName);
433 var recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + recordUid + '][' + unique.field + ']');
434 var values = this.getValuesFromHashMap(unique.used);
435 var selector = TYPO3.jQuery('#' + objectId + '_selector');
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 var selector = TYPO3.jQuery('#' + objectId + '_selector');
477 this.removeSelectOption(selector, selectedValue);
478 this.data.unique[objectId]['used'][recordUid] = selectedValue;
479 }
480 }
481 },
482
483 domAddNewRecord: function (method, insertObject, objectPrefix, htmlData) {
484 if (this.isBelowMax(objectPrefix)) {
485 if (method == 'bottom') {
486 TYPO3.jQuery('#' + insertObject).append(htmlData);
487 } else if (method == 'after') {
488 TYPO3.jQuery('#' + insertObject).after(htmlData);
489 }
490 }
491 },
492 domAddRecordDetails: function (objectId, objectPrefix, expandSingle, htmlData) {
493 var hiddenValue, formObj, valueObj;
494 var objectDiv = TYPO3.jQuery('#' + objectId + '_fields');
495 if (!objectDiv || objectDiv.html().substr(0, 16) != '<!--notloaded-->') {
496 return;
497 }
498
499 var elName = this.parseObjectId('full', objectId, 2, 0, true);
500
501 var escapeSelectorObjectId = this.escapeSelectorObjectId(objectId);
502
503 formObj = TYPO3.jQuery('[name="' + elName + '[hidden]_0"]');
504 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('#' + escapeSelectorObjectId + '_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 = '#' + 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 = new Array();
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 = $('#' + 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 = $('#' + objectId);
686 if (!$sortingContainer.hasClass('ui-sortable')) {
687 return;
688 }
689 $('#' + objectId).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 = new Array();
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('#' + 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 = new Array();
731 if (formObj[0].value.length) {
732 records = formObj[0].value.split(',');
733 }
734
735 if (afterUid) {
736 var newRecords = new Array();
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 = new Array();
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 = $(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,
840 true);
841 var formObj = document.getElementsByName(formName);
842 if (formObj.length) {
843 var records = formObj[0].value.split(',');
844 var recordObj;
845 // walk through all inline records on that level and get the select field
846 for (var i = 0; i < records.length; i++) {
847 recordObj = document.getElementsByName(this.prependFormFieldNames + '[' + unique.table + '][' + records[i] + '][' + unique.field + ']');
848 if (recordObj.length) {
849 this.readdSelectOption(recordObj[0], fieldObj[0].value, unique);
850 }
851 }
852 }
853 }
854 }
855 } else if (unique.type == 'groupdb') {
856 // alert(objectPrefix+'/'+recordUid);
857 delete(this.data.unique[objectPrefix].used[recordUid])
858 }
859 }
860 },
861
862 enableDisableRecord: function (objectId) {
863 var elName = this.parseObjectId('full', objectId, 2, 0, true) + '[hidden]';
864 var formObj = document.getElementsByName(elName + '_0');
865 var valueObj = document.getElementsByName(elName);
866 var icon = $(objectId + '_disabled');
867
868 var $container = TYPO3.jQuery('#' + objectId + '_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) {
878 if (icon.hasClassName('t3-icon-edit-hide')) {
879 icon.removeClassName('t3-icon-edit-hide');
880 icon.addClassName('t3-icon-edit-unhide');
881 $container.addClass('t3-form-field-container-inline-hidden');
882 } else {
883 icon.removeClassName('t3-icon-edit-unhide');
884 icon.addClassName('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('#' + objectId + '_div');
925 if (deletedRecordContainer) {
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 = new Array();
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 var elReturn = this.constructFormElementName(wrap, elParts);
999
1000 return elReturn;
1001 },
1002
1003 splitFormElementName: function (formElementName) {
1004 // remove left and right side "data[...|...]" -> '...|...'
1005 formElementName = formElementName.substr(0,
1006 formElementName.lastIndexOf(']')).substr(formElementName.indexOf('[') + 1);
1007 var parts = objectId.split('][');
1008
1009 return parts;
1010 },
1011
1012 splitObjectId: function (objectId) {
1013 objectId = objectId.substr(objectId.indexOf(this.structureSeparator) + 1);
1014 objectId = objectId.split(this.flexFormSeparator).join(this.flexFormSubstitute);
1015 var parts = objectId.split(this.structureSeparator);
1016
1017 return parts;
1018 },
1019
1020 constructFormElementName: function (wrap, parts) {
1021 var elReturn;
1022
1023 if (wrap == 'full') {
1024 elReturn = this.prependFormFieldNames + '[' + parts.join('][') + ']';
1025 elReturn = elReturn.split(this.flexFormSubstitute).join('][');
1026 } else if (wrap == 'parts') {
1027 elReturn = '[' + parts.join('][') + ']';
1028 elReturn = elReturn.split(this.flexFormSubstitute).join('][');
1029 } else if (wrap == 'none') {
1030 elReturn = parts.length > 1 ? parts : parts.join('');
1031 }
1032
1033 return elReturn;
1034 },
1035
1036 constructObjectId: function (wrap, parts) {
1037 var elReturn;
1038
1039 if (wrap == 'full') {
1040 elReturn = this.prependFormFieldNames + this.structureSeparator + parts.join(this.structureSeparator);
1041 elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
1042 } else if (wrap == 'parts') {
1043 elReturn = this.structureSeparator + parts.join(this.structureSeparator);
1044 elReturn = elReturn.split(this.flexFormSubstitute).join(this.flexFormSeparator);
1045 } else if (wrap == 'none') {
1046 elReturn = parts.length > 1 ? parts : parts.join('');
1047 }
1048
1049 return elReturn;
1050 },
1051
1052 parseObjectId: function (wrap, objectId, rightCount, skipRight, returnAsFormElementName) {
1053 var idParts = this.splitObjectId(objectId);
1054
1055 if (!wrap) {
1056 wrap = 'full';
1057 }
1058 if (!skipRight) {
1059 skipRight = 0;
1060 }
1061
1062 var elParts = new Array();
1063 for (var i = 0; i < skipRight; i++) {
1064 idParts.pop();
1065 }
1066
1067 if (rightCount > 0) {
1068 for (var i = 0; i < rightCount; i++) {
1069 elParts.unshift(idParts.pop());
1070 }
1071 } else {
1072 for (var i = 0; i < -rightCount; i++) {
1073 idParts.shift();
1074 }
1075 elParts = idParts;
1076 }
1077
1078 if (returnAsFormElementName) {
1079 var elReturn = this.constructFormElementName(wrap, elParts);
1080 } else {
1081 var elReturn = this.constructObjectId(wrap, elParts);
1082 }
1083
1084 return elReturn;
1085 },
1086
1087 handleChangedField: function (formField, objectId) {
1088 var formObj;
1089 if (typeof formField == 'object') {
1090 formObj = formField;
1091 } else {
1092 formObj = document.getElementsByName(formField);
1093 if (formObj.length) {
1094 formObj = formObj[0];
1095 }
1096 }
1097
1098 if (formObj != undefined) {
1099 var value;
1100 if (formObj.nodeName == 'SELECT') {
1101 value = formObj.options[formObj.selectedIndex].text;
1102 } else {
1103 value = formObj.value;
1104 }
1105 TYPO3.jQuery('#' + objectId + '_label').html(value.length ? value : this.noTitleString);
1106 }
1107 return true;
1108 },
1109
1110 arrayAssocCount: function (object) {
1111 var count = 0;
1112 if (typeof object.length != 'undefined') {
1113 count = object.length;
1114 } else {
1115 for (var i in object) {
1116 count++;
1117 }
1118 }
1119 return count;
1120 },
1121
1122 isBelowMax: function (objectPrefix) {
1123 var isBelowMax = true;
1124 var objectName = this.prependFormFieldNames + this.parseObjectId('parts', objectPrefix, 3, 1, true);
1125 var formObj = document.getElementsByName(objectName);
1126
1127 if (this.data.config && this.data.config[objectPrefix] && formObj.length) {
1128 var recordCount = formObj[0].value ? formObj[0].value.split(',').length : 0;
1129 if (recordCount >= this.data.config[objectPrefix].max) {
1130 isBelowMax = false;
1131 }
1132 }
1133 if (isBelowMax && this.data.unique && this.data.unique[objectPrefix]) {
1134 var unique = this.data.unique[objectPrefix];
1135 if (this.arrayAssocCount(unique.used) >= unique.max && unique.max >= 0) {
1136 isBelowMax = false;
1137 }
1138 }
1139 return isBelowMax;
1140 },
1141
1142 getOptionsHash: function (selectObj) {
1143 var optionsHash = {};
1144 for (var i = 0; i < selectObj.options.length; i++) {
1145 optionsHash[selectObj.options[i].value] = i;
1146 }
1147 return optionsHash;
1148 },
1149
1150 removeSelectOption: function (selectObj, value) {
1151 var optionsHash = this.getOptionsHash(selectObj);
1152 if (optionsHash[value] != undefined) {
1153 selectObj.options[optionsHash[value]] = null;
1154 }
1155 },
1156
1157 readdSelectOption: function (selectObj, value, unique) {
1158 var index = null;
1159 var optionsHash = this.getOptionsHash(selectObj);
1160 var possibleValues = this.getKeysFromHashMap(unique.possible);
1161
1162 for (var possibleValue in unique.possible) {
1163 if (possibleValue == value) {
1164 break;
1165 }
1166 if (optionsHash[possibleValue] != undefined) {
1167 index = optionsHash[possibleValue];
1168 }
1169 }
1170
1171 if (index == null) {
1172 index = 0;
1173 } else if (index < selectObj.options.length) {
1174 index++;
1175 }
1176 // recreate the <option> tag
1177 var readdOption = document.createElement('option');
1178 readdOption.text = unique.possible[value];
1179 readdOption.value = value;
1180 // add the <option> at the right position
1181 selectObj.add(readdOption, document.all ? index : selectObj.options[index]);
1182 },
1183
1184 hideElementsWithClassName: function (selector, parentElement) {
1185 this.setVisibilityOfElementsWithClassName('hide', selector, parentElement);
1186 },
1187
1188 showElementsWithClassName: function (selector, parentElement) {
1189 this.setVisibilityOfElementsWithClassName('show', selector, parentElement);
1190 },
1191
1192 setVisibilityOfElementsWithClassName: function (action, selector, parentElement) {
1193 var domObjects = Selector.findChildElements($(parentElement), [selector]);
1194 if (action == 'hide') {
1195 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1196 new Effect.Fade(domObject);
1197 });
1198 } else if (action == 'show') {
1199 TYPO3.jQuery.each(domObjects, function (index, domObject) {
1200 new Effect.Appear(domObject);
1201 });
1202 }
1203 },
1204
1205 fadeOutFadeIn: function (objectId) {
1206 var optIn = { duration: 0.5, transition: Effect.Transitions.linear, from: 0.50, to: 1.00 };
1207 var optOut = { duration: 0.5, transition: Effect.Transitions.linear, from: 1.00, to: 0.50 };
1208 optOut.afterFinish = function () {
1209 new Effect.Opacity(objectId, optIn);
1210 };
1211 new Effect.Opacity(objectId, optOut);
1212 },
1213
1214 isNewRecord: function (objectId) {
1215 return TYPO3.jQuery('#' + objectId + '_div') && TYPO3.jQuery('#' + objectId + '_div').hasClass('inlineIsNewRecord')
1216 ? true
1217 : false;
1218 },
1219
1220 // Find and fix nested of inline and tab levels if a new element was created dynamically (it doesn't know about its nesting):
1221 findContinuedNestedLevel: function (nested, objectId) {
1222 if (this.data.nested && this.data.nested[objectId]) {
1223 // Remove the first element from the new nested stack, it's just a hint:
1224 nested.shift();
1225 nested = this.data.nested[objectId].concat(nested);
1226 return nested;
1227 } else {
1228 return nested;
1229 }
1230 },
1231
1232 getNumberOfRTE: function () {
1233 var number = 0;
1234 if (typeof RTEarea != 'undefined' && RTEarea.length > 0) {
1235 number = RTEarea.length - 1;
1236 }
1237 return number;
1238 },
1239
1240 getObjectMD5: function (objectPrefix) {
1241 var md5 = false;
1242 if (this.data.config && this.data.config[objectPrefix] && this.data.config[objectPrefix].md5) {
1243 md5 = this.data.config[objectPrefix].md5;
1244 }
1245 return md5
1246 },
1247
1248 fadeAndRemove: function (element) {
1249 if (TYPO3.jQuery('#' + element)) {
1250 new Effect.Fade(element, { afterFinish: function () {
1251 Element.remove(element);
1252 } });
1253 }
1254 },
1255
1256 getContext: function (objectId) {
1257 var result = null;
1258
1259 if (objectId !== '' && typeof this.data.config[objectId] !== 'undefined' && typeof this.data.config[objectId].context !== 'undefined') {
1260 result = this.data.config[objectId].context;
1261 }
1262
1263 return result;
1264 },
1265
1266 /**
1267 * Escapes object identifiers to be used in jQuery.
1268 *
1269 * @param string objectId
1270 * @return string
1271 */
1272 escapeObjectId: function (objectId) {
1273 var escapedObjectId;
1274 escapedObjectId = objectId.replace(/:/g, '\\:');
1275 escapedObjectId = escapedObjectId.replace(/\./g, '\\.');
1276 return escapedObjectId;
1277 },
1278
1279 /**
1280 * Escapes object identifiers to be used as jQuery selector.
1281 *
1282 * @param string objectId
1283 * @return string
1284 */
1285 escapeSelectorObjectId: function (objectId) {
1286 var escapedSelectorObjectId;
1287 var escapedObjectId = this.escapeObjectId(objectId);
1288 escapedSelectorObjectId = escapedObjectId.replace(/\\:/g, '\\\\\\:');
1289 escapedSelectorObjectId = escapedSelectorObjectId.replace(/\\\./g, '\\\\\\.');
1290 return escapedSelectorObjectId;
1291 }
1292 };
1293
1294 Object.extend(Array.prototype, {
1295 diff: function (current) {
1296 var diff = new Array();
1297 if (this.length == current.length) {
1298 for (var i = 0; i < this.length; i++) {
1299 if (this[i] !== current[i]) {
1300 diff.push(i);
1301 }
1302 }
1303 }
1304 return diff;
1305 }
1306 });
1307
1308 /*]]>*/
1309 (function ($) {
1310 $(function () {
1311 $(document).delegate('div.t3-form-field-header-inline', 'click', inline.toggleEvent);
1312 });
1313 })(TYPO3.jQuery);