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