1132ce12c3e7dc09eb2418fb0391c7afd4539522
[Packages/TYPO3.CMS.git] / t3lib / jsfunc.inline.js
1 /*<![CDATA[*/
2
3 /***************************************************************
4 * Inline-Relational-Record Editing
5 *
6 * $Id$
7 *
8 *
9 *
10 * Copyright notice
11 *
12 * (c) 2006-2011 Oliver Hader <oh@inpublica.de>
13 * All rights reserved
14 *
15 * This script is part of the TYPO3 project. The TYPO3 project is
16 * free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * The GNU General Public License can be found at
22 * http://www.gnu.org/copyleft/gpl.html.
23 *
24 * This script is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31
32 var inline = {
33 structureSeparator: '-',
34 prependFormFieldNames: 'data',
35 noTitleString: '[No title]',
36 lockedAjaxMethod: {},
37 sourcesLoaded: {},
38 data: {},
39 isLoading: false,
40
41 addToDataArray: function(object) {
42 $H(object).each(function(pair) {
43 inline.data[pair.key] = $H(inline.data[pair.key]).merge(pair.value).toObject();
44 });
45 },
46 setPrependFormFieldNames: function(value) { this.prependFormFieldNames = value; },
47 setNoTitleString: function(value) { this.noTitleString = value; },
48
49 expandCollapseRecord: function(objectId, expandSingle, returnURL) {
50 var currentUid = this.parseObjectId('none', objectId, 1);
51 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
52
53 // if content is not loaded yet, get it now from server
54 if(($(objectId+'_fields') && $("irre-loading-indicator"+objectId)) || inline.isLoading) {
55 return false;
56 } else if ($(objectId+'_fields') && $(objectId+'_fields').innerHTML.substr(0,16) == '<!--notloaded-->') {
57 inline.isLoading = true;
58 // add loading-indicator
59 if ($(objectId + '_icon')) {
60 $(objectId + '_icon').hide();
61 $(objectId + '_iconcontainer').addClassName('loading-indicator');
62 }
63 return this.getRecordDetails(objectId, returnURL);
64 }
65
66 var currentState = '';
67 var collapse = new Array();
68 var expand = new Array();
69
70 // if only a single record should be visibly for that set of records
71 // and the record clicked itself is no visible, collapse all others
72 if (expandSingle && !Element.visible(objectId+'_fields'))
73 collapse = this.collapseAllRecords(objectId, objectPrefix, currentUid);
74
75 Element.toggle(objectId+'_fields');
76 currentState = Element.visible(objectId+'_fields') ? 1 : 0
77
78 if (this.isNewRecord(objectId))
79 this.updateExpandedCollapsedStateLocally(objectId, currentState);
80 else if (currentState)
81 expand.push(currentUid);
82 else if (!currentState)
83 collapse.push(currentUid);
84
85 this.setExpandedCollapsedState(objectId, expand.join(','), collapse.join(','));
86
87 return false;
88 },
89
90 collapseAllRecords: function(objectId, objectPrefix, callingUid) {
91 // get the form field, where all records are stored
92 var objectName = this.prependFormFieldNames+this.parseObjectId('parts', objectId, 3, 2, true);
93 var formObj = document.getElementsByName(objectName);
94 var collapse = [];
95
96 if (formObj.length) {
97 // the uid of the calling object (last part in objectId)
98 var recObjectId = '';
99
100 var records = formObj[0].value.split(',');
101 for (var i=0; i<records.length; i++) {
102 recObjectId = objectPrefix + this.structureSeparator + records[i];
103 if (records[i] != callingUid && Element.visible(recObjectId+'_fields')) {
104 Element.hide(recObjectId+'_fields');
105 if (this.isNewRecord(recObjectId)) this.updateExpandedCollapsedStateLocally(recObjectId, 0);
106 else collapse.push(records[i]);
107 }
108 }
109 }
110
111 return collapse;
112 },
113
114 updateExpandedCollapsedStateLocally: function(objectId, value) {
115 var ucName = 'uc[inlineView]'+this.parseObjectId('parts', objectId, 3, 2, true);
116 var ucFormObj = document.getElementsByName(ucName);
117 if (ucFormObj.length) {
118 ucFormObj[0].value = value;
119 }
120 },
121
122 getRecordDetails: function(objectId, returnURL) {
123 inline.makeAjaxCall('getRecordDetails', [inline.getNumberOfRTE(), objectId, returnURL], true);
124 return false;
125 },
126
127 createNewRecord: function(objectId, recordUid) {
128 if (this.isBelowMax(objectId)) {
129 if (recordUid) {
130 objectId += this.structureSeparator + recordUid;
131 }
132 this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId], true);
133 } else {
134 alert('There are no more relations possible at this moment!');
135 }
136 return false;
137 },
138
139 synchronizeLocalizeRecords: function(objectId, type) {
140 var parameters = [this.getNumberOfRTE(), objectId, type];
141 this.makeAjaxCall('synchronizeLocalizeRecords', parameters, true);
142 },
143
144 setExpandedCollapsedState: function(objectId, expand, collapse) {
145 this.makeAjaxCall('setExpandedCollapsedState', [objectId, expand, collapse]);
146 },
147
148 makeAjaxCall: function(method, params, lock) {
149 var max, url='', urlParams='', options={};
150 if (method && params && params.length && this.lockAjaxMethod(method, lock)) {
151 url = TBE_EDITOR.getBackendPath() + 'ajax.php';
152 urlParams = '&ajaxID=t3lib_TCEforms_inline::'+method;
153 for (var i=0, max=params.length; i<max; i++) {
154 urlParams += '&ajax['+i+']='+params[i];
155 }
156 options = {
157 method: 'post',
158 parameters: urlParams,
159 onSuccess: function(xhr) { inline.isLoading = false; inline.processAjaxResponse(method, xhr); },
160 onFailure: function(xhr) { inline.isLoading = false; inline.showAjaxFailure(method, xhr); }
161 };
162
163 new Ajax.Request(url, options);
164 }
165 },
166
167 lockAjaxMethod: function(method, lock) {
168 if (!lock || !inline.lockedAjaxMethod[method]) {
169 inline.lockedAjaxMethod[method] = true;
170 return true;
171 } else {
172 return false;
173 }
174 },
175
176 unlockAjaxMethod: function(method) {
177 inline.lockedAjaxMethod[method] = false;
178 },
179
180 processAjaxResponse: function(method, xhr, json) {
181 var addTag=null, restart=false, processedCount=0, element=null, errorCatch=[], sourcesWaiting=[];
182 if (!json && xhr) {
183 json = eval('('+xhr.responseText+')');
184 }
185 // If there are elements the should be added to the <HEAD> tag (e.g. for RTEhtmlarea):
186 if (json.headData) {
187 var head = inline.getDomHeadTag();
188 var headTags = inline.getDomHeadChildren(head);
189 $A(json.headData).each(function(addTag) {
190 if (!restart) {
191 if (addTag && (addTag.innerHTML || !inline.searchInDomTags(headTags, addTag))) {
192 if (addTag.name=='SCRIPT' && addTag.innerHTML && processedCount) {
193 restart = true;
194 return false;
195 } else {
196 if (addTag.name=='SCRIPT' && addTag.innerHTML) {
197 try {
198 eval(addTag.innerHTML);
199 } catch(e) {
200 errorCatch.push(e);
201 }
202 } else {
203 element = inline.createNewDomElement(addTag);
204 // Set onload handler for external JS scripts:
205 if (addTag.name=='SCRIPT' && element.src) {
206 element.onload = inline.sourceLoadedHandler(element);
207 sourcesWaiting.push(element.src);
208 }
209 head.appendChild(element);
210 processedCount++;
211 }
212 json.headData.shift();
213 }
214 }
215 }
216 });
217 }
218 if (restart || processedCount) {
219 window.setTimeout(function() { inline.reprocessAjaxResponse(method, json, sourcesWaiting); }, 40);
220 } else {
221 if (method) {
222 inline.unlockAjaxMethod(method);
223 }
224 if (json.scriptCall && json.scriptCall.length) {
225 $A(json.scriptCall).each(function(value) { eval(value); });
226 }
227 TYPO3.TCEFORMS.convertDateFieldsToDatePicker();
228 }
229 },
230
231 // Check if dynamically added scripts are loaded and restart inline.processAjaxResponse():
232 reprocessAjaxResponse: function (method, json, sourcesWaiting) {
233 var sourcesLoaded = true;
234 if (sourcesWaiting && sourcesWaiting.length) {
235 $A(sourcesWaiting).each(function(source) {
236 if (!inline.sourcesLoaded[source]) {
237 sourcesLoaded = false;
238 return false;
239 }
240 });
241 }
242 if (sourcesLoaded) {
243 $A(sourcesWaiting).each(function(source) {
244 delete(inline.sourcesLoaded[source]);
245 });
246 window.setTimeout(function() { inline.processAjaxResponse(method, null, json); }, 80);
247 } else {
248 window.setTimeout(function() { inline.reprocessAjaxResponse(method, json, sourcesWaiting); }, 40);
249 }
250 },
251
252 sourceLoadedHandler: function(element) {
253 if (element && element.src) {
254 inline.sourcesLoaded[element.src] = true;
255 }
256 },
257
258 showAjaxFailure: function(method, xhr) {
259 inline.unlockAjaxMethod(method);
260 alert('Error: '+xhr.status+"\n"+xhr.statusText);
261 },
262
263 // foreign_selector: used by selector box (type='select')
264 importNewRecord: function(objectId) {
265 var selector = $(objectId+'_selector');
266 if (selector.selectedIndex != -1) {
267 var selectedValue = selector.options[selector.selectedIndex].value;
268 if (!this.data.unique || !this.data.unique[objectId]) {
269 selector.options[selector.selectedIndex].selected = false;
270 }
271 this.makeAjaxCall('createNewRecord', [this.getNumberOfRTE(), objectId, selectedValue], true);
272 }
273 return false;
274 },
275
276 // foreign_selector: used by element browser (type='group/db')
277 importElement: function(objectId, table, uid, type) {
278 window.setTimeout(
279 function() {
280 inline.makeAjaxCall('createNewRecord', [inline.getNumberOfRTE(), objectId, uid], true);
281 },
282 10
283 );
284 },
285
286 // Check uniqueness for element browser:
287 checkUniqueElement: function(objectId, table, uid, type) {
288 if (this.checkUniqueUsed(objectId, uid, table)) {
289 return {passed: false,message: 'There is already a relation to the selected element!'};
290 } else {
291 return {passed: true};
292 }
293 },
294
295 // Checks if a record was used and should be unique:
296 checkUniqueUsed: function(objectId, uid, table) {
297 if (this.data.unique && this.data.unique[objectId]) {
298 var unique = this.data.unique[objectId];
299 var values = $H(unique.used).values();
300
301 // for select: only the uid is stored
302 if (unique['type'] == 'select') {
303 if (values.indexOf(uid) != -1) return true;
304
305 // for group/db: table and uid is stored in a assoc array
306 } else if (unique.type == 'groupdb') {
307 for (var i=values.length-1; i>=0; i--) {
308 // if the pair table:uid is already used:
309 if (values[i].table==table && values[i].uid==uid) return true;
310 }
311 }
312 }
313 return false;
314 },
315
316 setUniqueElement: function(objectId, table, uid, type, elName) {
317 var recordUid = this.parseFormElementName('none', elName, 1, 1);
318 // alert(objectId+'/'+table+'/'+uid+'/'+recordUid);
319 this.setUnique(objectId, recordUid, uid);
320 },
321 // Remove all select items already used
322 // from a newly retrieved/expanded record
323 removeUsed: function(objectId, recordUid) {
324 if (this.data.unique && this.data.unique[objectId]) {
325 var unique = this.data.unique[objectId];
326 if (unique.type == 'select') {
327 var formName = this.prependFormFieldNames+this.parseObjectId('parts', objectId, 3, 1, true);
328 var formObj = document.getElementsByName(formName);
329 var recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+recordUid+']['+unique.field+']');
330 var values = $H(unique.used).values();
331 if (recordObj.length) {
332 var selectedValue = recordObj[0].options[recordObj[0].selectedIndex].value;
333 for (var i=0; i<values.length; i++) {
334 if (values[i] != selectedValue) {
335 this.removeSelectOption(recordObj[0], values[i]);
336 }
337 }
338 }
339 }
340 }
341 },
342 // this function is applied to a newly inserted record by AJAX
343 // it removes the used select items, that should be unique
344 setUnique: function(objectId, recordUid, selectedValue) {
345 if (this.data.unique && this.data.unique[objectId]) {
346 var unique = this.data.unique[objectId];
347 if (unique.type == 'select') {
348 if (!(unique.selector && unique.max == -1)) {
349 var formName = this.prependFormFieldNames+this.parseObjectId('parts', objectId, 3, 1, true);
350 var formObj = document.getElementsByName(formName);
351 var recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+recordUid+']['+unique.field+']');
352 var values = $H(unique.used).values();
353 var selector = $(objectId+'_selector');
354 if (selector.length) {
355 // remove all items from the new select-item which are already used in other children
356 if (recordObj.length) {
357 for (var i=0; i<values.length; i++) {
358 this.removeSelectOption(recordObj[0], values[i]);
359 }
360 // set the selected item automatically to the first of the remaining items if no selector is used
361 if (!unique.selector) {
362 selectedValue = recordObj[0].options[0].value;
363 recordObj[0].options[0].selected = true;
364 this.updateUnique(recordObj[0], objectId, formName, recordUid);
365 this.handleChangedField(recordObj[0], objectId+'['+recordUid+']');
366 }
367 }
368 for (var i=0; i<values.length; i++) {
369 this.removeSelectOption(selector, values[i]);
370 }
371 if (typeof this.data.unique[objectId].used.length != 'undefined') {
372 this.data.unique[objectId].used = {};
373 }
374 this.data.unique[objectId].used[recordUid] = selectedValue;
375 }
376 // remove the newly used item from each select-field of the child records
377 if (formObj.length && selectedValue) {
378 var records = formObj[0].value.split(',');
379 for (var i=0; i<records.length; i++) {
380 recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+records[i]+']['+unique.field+']');
381 if (recordObj.length && records[i] != recordUid) {
382 this.removeSelectOption(recordObj[0], selectedValue);
383 }
384 }
385 }
386 }
387 } else if (unique.type == 'groupdb') {
388 // add the new record to the used items:
389 this.data.unique[objectId].used[recordUid] = {'table':unique.elTable, 'uid':selectedValue};
390 }
391
392 // remove used items from a selector-box
393 if (unique.selector == 'select' && selectedValue) {
394 var selector = $(objectId+'_selector');
395 this.removeSelectOption(selector, selectedValue);
396 this.data.unique[objectId]['used'][recordUid] = selectedValue;
397 }
398 }
399 },
400
401 domAddNewRecord: function(method, insertObject, objectPrefix, htmlData) {
402 if (this.isBelowMax(objectPrefix)) {
403 if (method == 'bottom')
404 new Insertion.Bottom(insertObject, htmlData);
405 else if (method == 'after')
406 new Insertion.After(insertObject, htmlData);
407 }
408 },
409 domAddRecordDetails: function(objectId, objectPrefix, expandSingle, htmlData) {
410 var objectDiv = $(objectId + '_fields');
411 if (!objectDiv || objectDiv.innerHTML.substr(0,16) != '<!--notloaded-->')
412 return;
413 objectDiv.update(htmlData);
414 // remove loading-indicator
415 if ($(objectId + '_icon')) {
416 $(objectId + '_iconcontainer').removeClassName('loading-indicator');
417 $(objectId + '_icon').show();
418 }
419 // now that the content is loaded, set the expandState
420 this.expandCollapseRecord(objectId, expandSingle);
421 },
422
423 // Get script and link elements from head tag:
424 getDomHeadChildren: function(head) {
425 var headTags=[];
426 $$('head script', 'head link').each(function(tag) { headTags.push(tag); });
427 return headTags;
428 },
429
430 getDomHeadTag: function() {
431 if (document && document.head) {
432 return document.head;
433 } else {
434 var head = $$('head');
435 if (head.length) {
436 return head[0];
437 }
438 }
439 return false;
440 },
441
442 // Search whether elements exist in a given haystack:
443 searchInDomTags: function(haystack, needle) {
444 var result = false;
445 $A(haystack).each(function(element) {
446 if (element.nodeName.toUpperCase()==needle.name) {
447 var attributesCount = $H(needle.attributes).keys().length;
448 var attributesFound = 0;
449 $H(needle.attributes).each(function(attribute) {
450 if (element.getAttribute && element.getAttribute(attribute.key)==attribute.value) {
451 attributesFound++;
452 }
453 });
454 if (attributesFound==attributesCount) {
455 result = true;
456 return true;
457 }
458 }
459 });
460 return result;
461 },
462
463 // Create a new DOM element:
464 createNewDomElement: function(addTag) {
465 var element = document.createElement(addTag.name);
466 if (addTag.attributes) {
467 $H(addTag.attributes).each(function(attribute) {
468 element[attribute.key] = attribute.value;
469 });
470 }
471 return element;
472 },
473
474 changeSorting: function(objectId, direction) {
475 var objectName = this.prependFormFieldNames+this.parseObjectId('parts', objectId, 3, 2, true);
476 var objectPrefix = this.parseObjectId('full', objectId, 0, 1);
477 var formObj = document.getElementsByName(objectName);
478
479 if (formObj.length) {
480 // the uid of the calling object (last part in objectId)
481 var callingUid = this.parseObjectId('none', objectId, 1);
482 var records = formObj[0].value.split(',');
483 var current = records.indexOf(callingUid);
484 var changed = false;
485
486 // move up
487 if (direction > 0 && current > 0) {
488 records[current] = records[current-1];
489 records[current-1] = callingUid;
490 changed = true;
491
492 // move down
493 } else if (direction < 0 && current < records.length-1) {
494 records[current] = records[current+1];
495 records[current+1] = callingUid;
496 changed = true;
497 }
498
499 if (changed) {
500 formObj[0].value = records.join(',');
501 var cAdj = direction > 0 ? 1 : 0; // adjustment
502 $(objectId+'_div').parentNode.insertBefore(
503 $(objectPrefix + this.structureSeparator + records[current-cAdj] + '_div'),
504 $(objectPrefix + this.structureSeparator + records[current+1-cAdj] + '_div')
505 );
506 this.redrawSortingButtons(objectPrefix, records);
507 }
508 }
509
510 return false;
511 },
512
513 dragAndDropSorting: function(element) {
514 var objectId = element.getAttribute('id').replace(/_records$/, '');
515 var objectName = inline.prependFormFieldNames+inline.parseObjectId('parts', objectId, 3, 0, true);
516 var formObj = document.getElementsByName(objectName);
517
518 if (formObj.length) {
519 var checked = new Array();
520 var order = Sortable.sequence(element);
521 var records = formObj[0].value.split(',');
522
523 // check if ordered uid is really part of the records
524 // virtually deleted items might still be there but ordering shouldn't saved at all on them
525 for (var i=0; i<order.length; i++) {
526 if (records.indexOf(order[i]) != -1) {
527 checked.push(order[i]);
528 }
529 }
530
531 formObj[0].value = checked.join(',');
532
533 if (inline.data.config && inline.data.config[objectId]) {
534 var table = inline.data.config[objectId].table;
535 inline.redrawSortingButtons(objectId + inline.structureSeparator + table, checked);
536 }
537 }
538 },
539
540 createDragAndDropSorting: function(objectId) {
541 Position.includeScrollOffsets = true;
542 Sortable.create(
543 objectId,
544 {
545 format: /^[^_\-](?:[A-Za-z0-9\-\_]*)-(.*)_div$/,
546 onUpdate: inline.dragAndDropSorting,
547 tag: 'div',
548 handle: 'sortableHandle',
549 overlap: 'vertical',
550 constraint: 'vertical'
551 }
552 );
553 },
554
555 destroyDragAndDropSorting: function(objectId) {
556 Sortable.destroy(objectId);
557 },
558
559 redrawSortingButtons: function(objectPrefix, records) {
560 var i;
561 var headerObj;
562 var sortingObj = new Array();
563
564 // if no records were passed, fetch them from form field
565 if (typeof records == 'undefined') {
566 records = new Array();
567 var objectName = this.prependFormFieldNames+this.parseObjectId('parts', objectPrefix, 3, 1, true);
568 var formObj = document.getElementsByName(objectName);
569 if (formObj.length) records = formObj[0].value.split(',');
570 }
571
572 for (i=0; i<records.length; i++) {
573 if (!records[i].length) continue;
574
575 headerObj = $(objectPrefix + this.structureSeparator + records[i] + '_header');
576 sortingObj[0] = Element.select(headerObj, '.sortingUp');
577 sortingObj[1] = Element.select(headerObj, '.sortingDown');
578
579 if (sortingObj[0].length) {
580 sortingObj[0][0].style.visibility = (i == 0 ? 'hidden' : 'visible');
581 }
582 if (sortingObj[1].length) {
583 sortingObj[1][0].style.visibility = (i == records.length-1 ? 'hidden' : 'visible');
584 }
585 }
586 },
587
588 memorizeAddRecord: function(objectPrefix, newUid, afterUid, selectedValue) {
589 if (this.isBelowMax(objectPrefix)) {
590 var objectName = this.prependFormFieldNames+this.parseObjectId('parts', objectPrefix, 3, 1, true);
591 var formObj = document.getElementsByName(objectName);
592
593 if (formObj.length) {
594 var records = new Array();
595 if (formObj[0].value.length) records = formObj[0].value.split(',');
596
597 if (afterUid) {
598 var newRecords = new Array();
599 for (var i=0; i<records.length; i++) {
600 if (records[i].length) newRecords.push(records[i]);
601 if (afterUid == records[i]) newRecords.push(newUid);
602 }
603 records = newRecords;
604 } else {
605 records.push(newUid);
606 }
607 formObj[0].value = records.join(',');
608 }
609
610 this.redrawSortingButtons(objectPrefix, records);
611
612 if (this.data.unique && this.data.unique[objectPrefix]) {
613 var unique = this.data.unique[objectPrefix];
614 this.setUnique(objectPrefix, newUid, selectedValue);
615 }
616 }
617
618 // if we reached the maximum off possible records after this action, hide the new buttons
619 if (!this.isBelowMax(objectPrefix)) {
620 var objectParent = this.parseObjectId('full', objectPrefix, 0 , 1);
621 var md5 = this.getObjectMD5(objectParent);
622 this.hideElementsWithClassName('.inlineNewButton'+(md5 ? '.'+md5 : ''), objectParent);
623 }
624
625 if (TBE_EDITOR) TBE_EDITOR.fieldChanged_fName(objectName, formObj);
626 },
627
628 memorizeRemoveRecord: function(objectName, removeUid) {
629 var formObj = document.getElementsByName(objectName);
630 if (formObj.length) {
631 var parts = new Array();
632 if (formObj[0].value.length) {
633 parts = formObj[0].value.split(',');
634 parts = parts.without(removeUid);
635 formObj[0].value = parts.join(',');
636 if (TBE_EDITOR) TBE_EDITOR.fieldChanged_fName(objectName, formObj);
637 return parts.length;
638 }
639 }
640 return false;
641 },
642
643 updateUnique: function(srcElement, objectPrefix, formName, recordUid) {
644 if (this.data.unique && this.data.unique[objectPrefix]) {
645 var unique = this.data.unique[objectPrefix];
646 var oldValue = unique.used[recordUid];
647
648 if (unique.selector == 'select') {
649 var selector = $(objectPrefix+'_selector');
650 this.removeSelectOption(selector, srcElement.value);
651 if (typeof oldValue != 'undefined') this.readdSelectOption(selector, oldValue, unique);
652 }
653
654 if (!(unique.selector && unique.max == -1)) {
655 var formObj = document.getElementsByName(formName);
656 if (unique && formObj.length) {
657 var records = formObj[0].value.split(',');
658 var recordObj;
659 for (var i=0; i<records.length; i++) {
660 recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+records[i]+']['+unique.field+']');
661 if (recordObj.length && recordObj[0] != srcElement) {
662 this.removeSelectOption(recordObj[0], srcElement.value);
663 if (typeof oldValue != 'undefined') this.readdSelectOption(recordObj[0], oldValue, unique);
664 }
665 }
666 this.data.unique[objectPrefix].used[recordUid] = srcElement.value;
667 }
668 }
669 }
670 },
671
672 revertUnique: function(objectPrefix, elName, recordUid) {
673 if (this.data.unique && this.data.unique[objectPrefix]) {
674 var unique = this.data.unique[objectPrefix];
675 var fieldObj = elName ? document.getElementsByName(elName+'['+unique.field+']') : null;
676
677 if (unique.type == 'select') {
678 if (fieldObj && fieldObj.length) {
679 delete(this.data.unique[objectPrefix].used[recordUid])
680
681 if (unique.selector == 'select') {
682 if (!isNaN(fieldObj[0].value)) {
683 var selector = $(objectPrefix+'_selector');
684 this.readdSelectOption(selector, fieldObj[0].value, unique);
685 }
686 }
687
688 if (!(unique.selector && unique.max == -1)) {
689 var formName = this.prependFormFieldNames+this.parseObjectId('parts', objectPrefix, 3, 1, true);
690 var formObj = document.getElementsByName(formName);
691 if (formObj.length) {
692 var records = formObj[0].value.split(',');
693 var recordObj;
694 // walk through all inline records on that level and get the select field
695 for (var i=0; i<records.length; i++) {
696 recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+records[i]+']['+unique.field+']');
697 if (recordObj.length) this.readdSelectOption(recordObj[0], fieldObj[0].value, unique);
698 }
699 }
700 }
701 }
702 } else if (unique.type == 'groupdb') {
703 // alert(objectPrefix+'/'+recordUid);
704 delete(this.data.unique[objectPrefix].used[recordUid])
705 }
706 }
707 },
708
709 enableDisableRecord: function(objectId) {
710 var elName = this.parseObjectId('full', objectId, 2, 0, true);
711 var imageObj = $(objectId+'_disabled');
712 var valueObj = document.getElementsByName(elName+'[hidden]');
713 var formObj = document.getElementsByName(elName+'[hidden]_0');
714 var imagePath = '';
715
716 if (valueObj && formObj) {
717 formObj[0].click();
718 imagePath = this.parsePath(imageObj.src);
719 imageObj.src = imagePath+(valueObj[0].value > 0 ? 'button_unhide.gif' : 'button_hide.gif');
720 }
721
722 return false;
723 },
724
725 deleteRecord: function(objectId, options) {
726 var i, j, inlineRecords, records, childObjectId, childTable;
727 var objectPrefix = this.parseObjectId('full', objectId, 0 , 1);
728 var elName = this.parseObjectId('full', objectId, 2, 0, true);
729 var shortName = this.parseObjectId('parts', objectId, 2, 0, true);
730 var recordUid = this.parseObjectId('none', objectId, 1);
731 var beforeDeleteIsBelowMax = this.isBelowMax(objectPrefix);
732
733 // revert the unique settings if available
734 this.revertUnique(objectPrefix, elName, recordUid);
735
736 // Remove from TBE_EDITOR (required fields, required range, etc.):
737 if (TBE_EDITOR && TBE_EDITOR.removeElement) {
738 var removeStack = [];
739 inlineRecords = Element.select(objectId+'_div', '.inlineRecord');
740 // Remove nested child records from TBE_EDITOR required/range checks:
741 for (i=inlineRecords.length-1; i>=0; i--) {
742 if (inlineRecords[i].value.length) {
743 records = inlineRecords[i].value.split(',');
744 childObjectId = this.data.map[inlineRecords[i].name];
745 childTable = this.data.config[childObjectId].table;
746 for (j=records.length-1; j>=0; j--) {
747 removeStack.push(this.prependFormFieldNames+'['+childTable+']['+records[j]+']');
748 }
749 }
750 }
751 removeStack.push(this.prependFormFieldNames+shortName);
752 TBE_EDITOR.removeElementArray(removeStack);
753 }
754
755 // Mark this container as deleted
756 var deletedRecordContainer = $(objectId + '_div');
757 if (deletedRecordContainer) {
758 deletedRecordContainer.addClassName('inlineIsDeletedRecord');
759 }
760
761 // If the record is new and was never saved before, just remove it from DOM:
762 if (this.isNewRecord(objectId) || options && options.forceDirectRemoval) {
763 this.fadeAndRemove(objectId+'_div');
764 // If the record already exists in storage, mark it to be deleted on clicking the save button:
765 } else {
766 document.getElementsByName('cmd'+shortName+'[delete]')[0].disabled = false;
767 new Effect.Fade(objectId+'_div');
768 }
769
770 var recordCount = this.memorizeRemoveRecord(
771 this.prependFormFieldNames+this.parseObjectId('parts', objectId, 3, 2, true),
772 recordUid
773 );
774
775 if (recordCount <= 1) {
776 this.destroyDragAndDropSorting(this.parseObjectId('full', objectId, 0 , 2)+'_records');
777 }
778 this.redrawSortingButtons(objectPrefix);
779
780 // if the NEW-button was hidden and now we can add again new children, show the button
781 if (!beforeDeleteIsBelowMax && this.isBelowMax(objectPrefix)) {
782 var objectParent = this.parseObjectId('full', objectPrefix, 0 , 1);
783 var md5 = this.getObjectMD5(objectParent);
784 this.showElementsWithClassName('.inlineNewButton'+(md5 ? '.'+md5 : ''), objectParent);
785 }
786 return false;
787 },
788
789 parsePath: function(path) {
790 var backSlash = path.lastIndexOf('\\');
791 var normalSlash = path.lastIndexOf('/');
792
793 if (backSlash > 0)
794 path = path.substring(0,backSlash+1);
795 else if (normalSlash > 0)
796 path = path.substring(0,normalSlash+1);
797 else
798 path = '';
799
800 return path;
801 },
802
803 parseFormElementName: function(wrap, formElementName, rightCount, skipRight) {
804 var idParts = this.splitFormElementName(formElementName);
805
806 if (!wrap) wrap = 'full';
807 if (!skipRight) skipRight = 0;
808
809 var elParts = new Array();
810 for (var i=0; i<skipRight; i++) idParts.pop();
811
812 if (rightCount > 0) {
813 for (var i=0; i<rightCount; i++) elParts.unshift(idParts.pop());
814 } else {
815 for (var i=0; i<-rightCount; i++) idParts.shift();
816 elParts = idParts;
817 }
818
819 var elReturn = this.constructFormElementName(wrap, elParts);
820
821 return elReturn;
822 },
823
824 splitFormElementName: function(formElementName) {
825 // remove left and right side "data[...|...]" -> '...|...'
826 formElementName = formElementName.substr(0, formElementName.lastIndexOf(']')).substr(formElementName.indexOf('[')+1);
827 var parts = objectId.split('][');
828
829 return parts;
830 },
831
832 splitObjectId: function(objectId) {
833 objectId = objectId.substr(objectId.indexOf(this.structureSeparator)+1);
834 var parts = objectId.split(this.structureSeparator);
835
836 return parts;
837 },
838
839 constructFormElementName: function(wrap, parts) {
840 var elReturn;
841
842 if (wrap == 'full') {
843 elReturn = this.prependFormFieldNames+'['+parts.join('][')+']';
844 } else if (wrap == 'parts') {
845 elReturn = '['+parts.join('][')+']';
846 } else if (wrap == 'none') {
847 elReturn = parts.length > 1 ? parts : parts.join('');
848 }
849
850 return elReturn;
851 },
852
853 constructObjectId: function(wrap, parts) {
854 var elReturn;
855
856 if (wrap == 'full') {
857 elReturn = this.prependFormFieldNames+this.structureSeparator+parts.join(this.structureSeparator);
858 } else if (wrap == 'parts') {
859 elReturn = this.structureSeparator+parts.join(this.structureSeparator);
860 } else if (wrap == 'none') {
861 elReturn = parts.length > 1 ? parts : parts.join('');
862 }
863
864 return elReturn;
865 },
866
867 parseObjectId: function(wrap, objectId, rightCount, skipRight, returnAsFormElementName) {
868 var idParts = this.splitObjectId(objectId);
869
870 if (!wrap) wrap = 'full';
871 if (!skipRight) skipRight = 0;
872
873 var elParts = new Array();
874 for (var i=0; i<skipRight; i++) idParts.pop();
875
876 if (rightCount > 0) {
877 for (var i=0; i<rightCount; i++) elParts.unshift(idParts.pop());
878 } else {
879 for (var i=0; i<-rightCount; i++) idParts.shift();
880 elParts = idParts;
881 }
882
883 if (returnAsFormElementName) {
884 var elReturn = this.constructFormElementName(wrap, elParts);
885 } else {
886 var elReturn = this.constructObjectId(wrap, elParts);
887 }
888
889 return elReturn;
890 },
891
892 handleChangedField: function(formField, objectId) {
893 var formObj;
894 if (typeof formField == 'object') {
895 formObj = formField;
896 } else {
897 formObj = document.getElementsByName(formField);
898 if (formObj.length) formObj = formObj[0];
899 }
900
901 if (formObj != undefined) {
902 var value;
903 if (formObj.nodeName == 'SELECT') value = formObj.options[formObj.selectedIndex].text;
904 else value = formObj.value;
905 $(objectId+'_label').innerHTML = value.length ? value : this.noTitleString;
906 }
907 return true;
908 },
909
910 arrayAssocCount: function(object) {
911 var count = 0;
912 if (typeof object.length != 'undefined') {
913 count = object.length;
914 } else {
915 for (var i in object) count++;
916 }
917 return count;
918 },
919
920 isBelowMax: function(objectPrefix) {
921 var isBelowMax = true;
922 var objectName = this.prependFormFieldNames+this.parseObjectId('parts', objectPrefix, 3, 1, true);
923 var formObj = document.getElementsByName(objectName);
924
925 if (this.data.config && this.data.config[objectPrefix] && formObj.length) {
926 var recordCount = formObj[0].value ? formObj[0].value.split(',').length : 0;
927 if (recordCount >= this.data.config[objectPrefix].max) isBelowMax = false;
928 }
929 if (isBelowMax && this.data.unique && this.data.unique[objectPrefix]) {
930 var unique = this.data.unique[objectPrefix];
931 if (this.arrayAssocCount(unique.used) >= unique.max && unique.max >= 0) isBelowMax = false;
932 }
933 return isBelowMax;
934 },
935
936 getOptionsHash: function(selectObj) {
937 var optionsHash = {};
938 for (var i=0; i<selectObj.options.length; i++) optionsHash[selectObj.options[i].value] = i;
939 return optionsHash;
940 },
941
942 removeSelectOption: function(selectObj, value) {
943 var optionsHash = this.getOptionsHash(selectObj);
944 if (optionsHash[value] != undefined) selectObj.options[optionsHash[value]] = null;
945 },
946
947 readdSelectOption: function(selectObj, value, unique) {
948 var index = null;
949 var optionsHash = this.getOptionsHash(selectObj);
950 var possibleValues = $H(unique.possible).keys();
951
952 for (var possibleValue in unique.possible) {
953 if (possibleValue == value) break;
954 if (optionsHash[possibleValue] != undefined) index = optionsHash[possibleValue];
955 }
956
957 if (index == null) index = 0;
958 else if (index < selectObj.options.length) index++;
959 // recreate the <option> tag
960 var readdOption = document.createElement('option');
961 readdOption.text = unique.possible[value];
962 readdOption.value = value;
963 // add the <option> at the right position
964 selectObj.add(readdOption, document.all ? index : selectObj.options[index]);
965 },
966
967 hideElementsWithClassName: function(selector, parentElement) {
968 this.setVisibilityOfElementsWithClassName('hide', selector, parentElement);
969 },
970
971 showElementsWithClassName: function(selector, parentElement) {
972 this.setVisibilityOfElementsWithClassName('show', selector, parentElement);
973 },
974
975 setVisibilityOfElementsWithClassName: function(action, selector, parentElement) {
976 var domObjects = Selector.findChildElements($(parentElement), [selector]);
977 if (action == 'hide') {
978 $A(domObjects).each(function(domObject) { new Effect.Fade(domObject); });
979 } else if (action == 'show') {
980 $A(domObjects).each(function(domObject) { new Effect.Appear(domObject); });
981 }
982 },
983
984 fadeOutFadeIn: function(objectId) {
985 var optIn = { duration:0.5, transition:Effect.Transitions.linear, from:0.50, to:1.00 };
986 var optOut = { duration:0.5, transition:Effect.Transitions.linear, from:1.00, to:0.50 };
987 optOut.afterFinish = function() { new Effect.Opacity(objectId, optIn); };
988 new Effect.Opacity(objectId, optOut);
989 },
990
991 isNewRecord: function(objectId) {
992 return $(objectId+'_div') && $(objectId+'_div').hasClassName('inlineIsNewRecord')
993 ? true
994 : false;
995 },
996
997 // Find and fix nested of inline and tab levels if a new element was created dynamically (it doesn't know about its nesting):
998 findContinuedNestedLevel: function(nested, objectId) {
999 if (this.data.nested && this.data.nested[objectId]) {
1000 // Remove the first element from the new nested stack, it's just a hint:
1001 nested.shift();
1002 nested = this.data.nested[objectId].concat(nested);
1003 return nested;
1004 } else {
1005 return nested;
1006 }
1007 },
1008
1009 getNumberOfRTE: function() {
1010 var number = 0;
1011 if (typeof RTEarea != 'undefined' && RTEarea.length > 0) {
1012 number = RTEarea.length-1;
1013 }
1014 return number;
1015 },
1016
1017 getObjectMD5: function(objectPrefix) {
1018 var md5 = false;
1019 if (this.data.config && this.data.config[objectPrefix] && this.data.config[objectPrefix].md5) {
1020 md5 = this.data.config[objectPrefix].md5;
1021 }
1022 return md5
1023 },
1024
1025 fadeAndRemove: function(element) {
1026 if ($(element)) {
1027 new Effect.Fade(element, { afterFinish: function() { Element.remove(element); } });
1028 }
1029 }
1030 }
1031
1032 Object.extend(Array.prototype, {
1033 diff: function(current) {
1034 var diff = new Array();
1035 if (this.length == current.length) {
1036 for (var i=0; i<this.length; i++) {
1037 if (this[i] !== current[i]) diff.push(i);
1038 }
1039 }
1040 return diff;
1041 }
1042 });
1043
1044 /*]]>*/