37935e7dded4d1a9da7e684cf6068e3cab09d8f7
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / TableOperations / table-operations.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2002 interactivetools.com, inc. Authored by Mihai Bazon, sponsored by http://www.bloki.com.
5 * (c) 2005 Xinha, http://xinha.gogo.co.nz/ for the original toggle borders function.
6 * (c) 2004-2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This script is a modified version of a script published under the htmlArea License.
27 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /*
32 * Table Operations Plugin for TYPO3 htmlArea RTE
33 */
34 HTMLArea.TableOperations = Ext.extend(HTMLArea.Plugin, {
35 /*
36 * This function gets called by the class constructor
37 */
38 configurePlugin: function (editor) {
39
40 this.classesUrl = this.editorConfiguration.classesUrl;
41 this.buttonsConfiguration = this.editorConfiguration.buttons;
42 this.disableEnterParagraphs = this.buttonsConfiguration.table ? this.buttonsConfiguration.table.disableEnterParagraphs : false;
43 this.floatLeft = "float-left";
44 this.floatRight = "float-right";
45 this.floatDefault = "not set";
46 this.useHeaderClass = "thead";
47 if (this.buttonsConfiguration.table && this.buttonsConfiguration.table.properties) {
48 if (this.buttonsConfiguration.table.properties["float"]) {
49 var floatConfiguration = this.buttonsConfiguration.table.properties["float"];
50 this.floatLeft = (floatConfiguration.left && floatConfiguration.left.useClass) ? floatConfiguration.left.useClass : "float-left";
51 this.floatRight = (floatConfiguration.right && floatConfiguration.right.useClass) ? floatConfiguration.right.useClass : "float-right";
52 this.floatDefault = (floatConfiguration.defaultValue) ? floatConfiguration.defaultValue : "not set";
53 }
54 if (this.buttonsConfiguration.table.properties.headers && this.buttonsConfiguration.table.properties.headers.both
55 && this.buttonsConfiguration.table.properties.headers.both.useHeaderClass) {
56 this.useHeaderClass = this.buttonsConfiguration.table.properties.headers.both.useHeaderClass;
57 }
58 if (this.buttonsConfiguration.table.properties.tableClass) {
59 this.defaultClass = this.buttonsConfiguration.table.properties.tableClass.defaultValue;
60 }
61 }
62 if (this.buttonsConfiguration.blockstyle) {
63 this.tags = this.editorConfiguration.buttons.blockstyle.tags;
64 }
65 this.tableParts = ["tfoot", "thead", "tbody"];
66 this.convertAlignment = { "not set" : "none", "left" : "JustifyLeft", "center" : "JustifyCenter", "right" : "JustifyRight", "justify" : "JustifyFull" };
67 /*
68 * Registering plugin "About" information
69 */
70 var pluginInformation = {
71 version : '5.3',
72 developer : 'Mihai Bazon & Stanislas Rolland',
73 developerUrl : 'http://www.sjbr.ca/',
74 copyrightOwner : 'Mihai Bazon & Stanislas Rolland',
75 sponsor : this.localize('Technische Universitat Ilmenau') + ' & Zapatec Inc.',
76 sponsorUrl : 'http://www.tu-ilmenau.de/',
77 license : 'GPL'
78 };
79 this.registerPluginInformation(pluginInformation);
80 /*
81 * Registering the buttons
82 */
83 var hideToggleBorders = this.editorConfiguration.hideTableOperationsInToolbar && !(this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.keepInToolbar);
84 var buttonList = this.buttonList, buttonId;
85 for (var i = 0, n = buttonList.length; i < n; ++i) {
86 var button = buttonList[i];
87 buttonId = (button[0] === 'InsertTable') ? button[0] : ('TO-' + button[0]);
88 var buttonConfiguration = {
89 id : buttonId,
90 tooltip : this.localize((buttonId === 'InsertTable') ? 'Insert Table' : buttonId),
91 iconCls : 'htmlarea-action-' + button[4],
92 action : 'onButtonPress',
93 hotKey : (this.buttonsConfiguration[button[2]] ? this.buttonsConfiguration[button[2]].hotKey : null),
94 context : button[1],
95 hide : ((buttonId == 'TO-toggle-borders') ? hideToggleBorders : ((button[0] === 'InsertTable') ? false : this.editorConfiguration.hideTableOperationsInToolbar)),
96 dialog : button[3]
97 };
98 this.registerButton(buttonConfiguration);
99 }
100 return true;
101 },
102 /*
103 * The list of buttons added by this plugin
104 */
105 buttonList: [
106 ['InsertTable', null, 'table', true, 'table-insert'],
107 ['toggle-borders', null, 'toggleborders', false, 'table-show-borders'],
108 ['table-prop', 'table', 'tableproperties', true, 'table-edit-properties'],
109 ['table-restyle', 'table', 'tablerestyle', false, 'table-restyle'],
110 ['row-prop', 'tr', 'rowproperties', true, 'row-edit-properties'],
111 ['row-insert-above', 'tr', 'rowinsertabove', false, 'row-insert-above'],
112 ['row-insert-under', 'tr', 'rowinsertunder', false, 'row-insert-under'],
113 ['row-delete', 'tr', 'rowdelete', false, 'row-delete'],
114 ['row-split', 'td,th[rowSpan!=1]', 'rowsplit', false, 'row-split'],
115 ['col-prop', 'td,th', 'columnproperties', true, 'column-edit-properties'],
116 ['col-insert-before', 'td,th', 'columninsertbefore', false, 'column-insert-before'],
117 ['col-insert-after', 'td,th', 'columninsertafter', false, 'column-insert-after'],
118 ['col-delete', 'td,th', 'columndelete', false, 'column-delete'],
119 ['col-split', 'td,th[colSpan!=1]', 'columnsplit', false, 'column-split'],
120 ['cell-prop', 'td,th', 'cellproperties', true, 'cell-edit-properties'],
121 ['cell-insert-before', 'td,th', 'cellinsertbefore', false, 'cell-insert-before'],
122 ['cell-insert-after', 'td,th', 'cellinsertafter', false, 'cell-insert-after'],
123 ['cell-delete', 'td,th', 'celldelete', false, 'cell-delete'],
124 ['cell-merge', Ext.isGecko? 'tr' : 'td,th', 'cellmerge', false, 'cell-merge'],
125 ['cell-split', 'td,th[colSpan!=1,rowSpan!=1]', 'cellsplit', false, 'cell-split']
126 ],
127 /*
128 * Sets of default configuration values for dialogue form fields
129 */
130 configDefaults: {
131 combo: {
132 editable: true,
133 selectOnFocus: true,
134 typeAhead: true,
135 triggerAction: 'all',
136 forceSelection: true,
137 mode: 'local',
138 valueField: 'value',
139 displayField: 'text',
140 helpIcon: true,
141 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
142 }
143 },
144 /*
145 * Get the integer value of a string or '' if the string is not a number
146 *
147 * @param string string: the input value
148 *
149 * @return mixed a number or ''
150 */
151 getLength: function (string) {
152 var length = parseInt(string);
153 if (isNaN(length)) {
154 length = '';
155 }
156 return length;
157 },
158 /*
159 * Open properties dialogue
160 *
161 * @param string type: 'cell', 'column', 'row' or 'table'
162 * @param string buttonId: the buttonId of the button that was pressed
163 *
164 * @return void
165 */
166 openPropertiesDialogue: function (type, buttonId) {
167 // Retrieve the element being edited and set configuration according to type
168 switch (type) {
169 case 'cell':
170 case 'column':
171 var element = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
172 this.properties = (this.buttonsConfiguration.cellproperties && this.buttonsConfiguration.cellproperties.properties) ? this.buttonsConfiguration.cellproperties.properties : {};
173 var title = (type == 'column') ? 'Column Properties' : 'Cell Properties';
174 break;
175 case 'row':
176 var element = this.editor.getSelection().getFirstAncestorOfType('tr');
177 this.properties = (this.buttonsConfiguration.rowproperties && this.buttonsConfiguration.rowproperties.properties) ? this.buttonsConfiguration.rowproperties.properties : {};
178 var title = 'Row Properties';
179 break;
180 case 'table':
181 var insert = (buttonId === 'InsertTable');
182 var element = insert ? null : this.editor.getSelection().getFirstAncestorOfType('table');
183 this.properties = (this.buttonsConfiguration.table && this.buttonsConfiguration.table.properties) ? this.buttonsConfiguration.table.properties : {};
184 var title = insert ? 'Insert Table' : 'Table Properties';
185 break;
186 }
187 var propertySet = element ? type + 'properties' : 'table';
188 this.removedFieldsets = (this.buttonsConfiguration[propertySet] && this.buttonsConfiguration[propertySet].removeFieldsets) ? this.buttonsConfiguration[propertySet].removeFieldsets : '';
189 this.removedProperties = this.properties.removed ? this.properties.removed : '';
190 // Open the dialogue window
191 this.openDialogue(
192 title,
193 {
194 element: element,
195 cell: type == 'cell',
196 column: type == 'column',
197 buttonId: buttonId
198 },
199 type == 'table' ? this.getWindowDimensions({ width: 600}, buttonId) : this.getWindowDimensions({ width: 600}, buttonId),
200 this.buildTabItemsConfig(element, type, buttonId),
201 type == 'table' ? this.tablePropertiesUpdate : this.rowCellPropertiesUpdate
202 );
203 },
204 /*
205 * Build the dialogue tab items config
206 *
207 * @param object element: the element being edited, if any
208 * @param string type: 'cell', 'column', 'row' or 'table'
209 * @param string buttonId: the buttonId of the button that was pressed
210 *
211 * @return object the tab items configuration
212 */
213 buildTabItemsConfig: function (element, type, buttonId) {
214 var tabItems = [];
215 var generalTabItems = [];
216 switch (type) {
217 case 'table':
218 if (this.removedFieldsets.indexOf('description') === -1) {
219 this.addConfigElement(this.buildDescriptionFieldsetConfig(element), generalTabItems);
220 }
221 if (Ext.isEmpty(element) || this.removedProperties.indexOf('headers') === -1) {
222 this.addConfigElement(this.buildSizeAndHeadersFieldsetConfig(element), generalTabItems);
223 }
224 break;
225 case 'column':
226 if (this.removedFieldsets.indexOf('columntype') == -1) {
227 this.addConfigElement(this.buildCellTypeFieldsetConfig(element, true), generalTabItems);
228 }
229 break;
230 case 'cell':
231 if (this.removedFieldsets.indexOf('celltype') == -1) {
232 this.addConfigElement(this.buildCellTypeFieldsetConfig(element, false), generalTabItems);
233 }
234 break;
235 case 'row':
236 if (this.removedFieldsets.indexOf('rowgroup') == -1) {
237 this.addConfigElement(this.buildRowGroupFieldsetConfig(element), generalTabItems);
238 }
239 break;
240 }
241 if (this.removedFieldsets.indexOf('style') == -1 && this.getPluginInstance('BlockStyle')) {
242 this.addConfigElement(this.buildStylingFieldsetConfig(element, buttonId), generalTabItems);
243 }
244 if (!Ext.isEmpty(generalTabItems)) {
245 tabItems.push({
246 title: this.localize('General'),
247 items: generalTabItems
248 });
249 }
250 var layoutTabItems = [];
251 if (type === 'table' && this.removedFieldsets.indexOf('spacing') === -1) {
252 this.addConfigElement(this.buildSpacingFieldsetConfig(element), layoutTabItems);
253 }
254 if (this.removedFieldsets.indexOf('layout') == -1) {
255 this.addConfigElement(this.buildLayoutFieldsetConfig(element), layoutTabItems);
256 }
257 if (!Ext.isEmpty(layoutTabItems)) {
258 tabItems.push({
259 title: this.localize('Layout'),
260 items: layoutTabItems
261 });
262 }
263 var languageTabItems = [];
264 if (this.removedFieldsets.indexOf('language') === -1 && (this.removedProperties.indexOf('language') === -1 || this.removedProperties.indexOf('direction') === -1) && (this.getButton('Language') || this.getButton('LeftToRight') || this.getButton('RightToLeft'))) {
265 this.addConfigElement(this.buildLanguageFieldsetConfig(element), languageTabItems);
266 }
267 if (!Ext.isEmpty(languageTabItems)) {
268 tabItems.push({
269 title: this.localize('Language'),
270 items: languageTabItems
271 });
272 }
273 var alignmentAndBordersTabItems = [];
274 if (this.removedFieldsets.indexOf('alignment') === -1) {
275 this.addConfigElement(this.buildAlignmentFieldsetConfig(element), alignmentAndBordersTabItems);
276 }
277 if (this.removedFieldsets.indexOf('borders') === -1) {
278 this.addConfigElement(this.buildBordersFieldsetConfig(element), alignmentAndBordersTabItems);
279 }
280 if (!Ext.isEmpty(alignmentAndBordersTabItems)) {
281 tabItems.push({
282 title: this.localize('Alignment') + '/' + this.localize('Border'),
283 items: alignmentAndBordersTabItems
284 });
285 }
286 var colorTabItems = [];
287 if (this.removedFieldsets.indexOf('color') === -1) {
288 this.addConfigElement(this.buildColorsFieldsetConfig(element), colorTabItems);
289 }
290 if (!Ext.isEmpty(colorTabItems)) {
291 tabItems.push({
292 title: this.localize('Background and colors'),
293 items: colorTabItems
294 });
295 }
296 return tabItems;
297 },
298 /*
299 * Open the dialogue window
300 *
301 * @param string title: the window title
302 * @param object arguments: some arguments for the handler
303 * @param integer dimensions: the opening width of the window
304 * @param object tabItems: the configuration of the tabbed panel
305 * @param function handler: handler when the OK button if clicked
306 *
307 * @return void
308 */
309 openDialogue: function (title, arguments, dimensions, tabItems, handler) {
310 if (this.dialog) {
311 this.dialog.close();
312 }
313 this.dialog = new Ext.Window({
314 title: this.getHelpTip(arguments.buttonId, title),
315 arguments: arguments,
316 cls: 'htmlarea-window',
317 border: false,
318 width: dimensions.width,
319 height: 'auto',
320 iconCls: this.getButton(arguments.buttonId).iconCls,
321 listeners: {
322 close: {
323 fn: this.onClose,
324 scope: this
325 }
326 },
327 items: {
328 xtype: 'tabpanel',
329 activeTab: 0,
330 defaults: {
331 xtype: 'container',
332 layout: 'form',
333 defaults: {
334 labelWidth: 150
335 }
336 },
337 listeners: {
338 tabchange: {
339 fn: this.syncHeight,
340 scope: this
341 }
342 },
343 items: tabItems
344 },
345 buttons: [
346 this.buildButtonConfig('OK', handler),
347 this.buildButtonConfig('Cancel', this.onCancel)
348 ]
349 });
350 this.show();
351 },
352 /*
353 * Insert the table or update the table properties and close the dialogue
354 */
355 tablePropertiesUpdate: function () {
356 this.restoreSelection()
357 var params = {};
358 var fieldTypes = ['combo', 'textfield', 'numberfield', 'checkbox', 'colorpalettefield'];
359 this.dialog.findBy(function (item) {
360 if (fieldTypes.indexOf(item.getXType()) !== -1) {
361 params[item.getItemId()] = item.getValue();
362 return true;
363 }
364 return false;
365 });
366 var errorFlag = false;
367 if (this.properties.required) {
368 if (this.properties.required.indexOf('captionOrSummary') != -1) {
369 if (!/\S/.test(params.f_caption) && !/\S/.test(params.f_summary)) {
370 TYPO3.Dialog.ErrorDialog({
371 title: this.getButton(this.dialog.arguments.buttonId).tooltip.title,
372 msg: this.localize('captionOrSummary' + '-required')
373 });
374 var field = this.dialog.find('itemId', 'f_caption')[0];
375 var tab = field.findParentByType('container');
376 tab.ownerCt.activate(tab);
377 field.focus();
378 return false;
379 }
380 } else {
381 var required = {
382 f_caption: 'caption',
383 f_summary: 'summary'
384 };
385 Ext.iterate(required, function (item) {
386 if (!params[item] && this.properties.required.indexOf(required[item]) != -1) {
387 TYPO3.Dialog.ErrorDialog({
388 title: this.getButton(this.dialog.arguments.buttonId).tooltip.title,
389 msg: this.localize(required[item] + '-required')
390 });
391 var field = this.dialog.find('itemId', item)[0];
392 var tab = field.findParentByType('container');
393 tab.ownerCt.activate(tab);
394 field.focus();
395 errorFlag = true;
396 return false;
397 }
398 }, this);
399 if (errorFlag) {
400 return false;
401 }
402 }
403 }
404 var doc = this.editor.document;
405 if (this.dialog.arguments.buttonId === 'InsertTable') {
406 var required = {
407 f_rows: 'You must enter a number of rows',
408 f_cols: 'You must enter a number of columns'
409 };
410 Ext.iterate(required, function (item) {
411 if (!params[item]) {
412 TYPO3.Dialog.ErrorDialog({
413 title: this.getButton(this.dialog.arguments.buttonId).tooltip.title,
414 msg: this.localize(required[item])
415 });
416 var field = this.dialog.find('itemId', item)[0];
417 var tab = field.findParentByType('container');
418 tab.ownerCt.activate(tab);
419 field.focus();
420 errorFlag = true;
421 return false;
422 }
423 }, this);
424 if (errorFlag) {
425 return false;
426 }
427 var table = doc.createElement('table');
428 var tbody = doc.createElement('tbody');
429 table.appendChild(tbody);
430 for (var i = params.f_rows; --i >= 0;) {
431 var tr = doc.createElement('tr');
432 tbody.appendChild(tr);
433 for (var j = params.f_cols; --j >= 0;) {
434 var td = doc.createElement('td');
435 if (!Ext.isIE) td.innerHTML = '<br />';
436 tr.appendChild(td);
437 }
438 }
439 } else {
440 var table = this.dialog.arguments.element;
441 }
442 this.setHeaders(table, params);
443 this.processStyle(table, params);
444 table.removeAttribute('border');
445 Ext.iterate(params, function (item) {
446 var val = params[item];
447 switch (item) {
448 case "f_caption":
449 if (/\S/.test(val)) {
450 // contains non white-space characters
451 var caption = table.getElementsByTagName("caption");
452 if (caption) {
453 caption = caption[0];
454 }
455 if (!caption) {
456 var caption = doc.createElement("caption");
457 table.insertBefore(caption, table.firstChild);
458 }
459 caption.innerHTML = val;
460 } else {
461 // delete the caption if found
462 if (table.caption) table.deleteCaption();
463 }
464 break;
465 case "f_summary":
466 table.summary = val;
467 break;
468 case "f_width":
469 table.style.width = ("" + val) + params.f_unit;
470 break;
471 case "f_align":
472 table.align = val;
473 break;
474 case "f_spacing":
475 table.cellSpacing = val;
476 break;
477 case "f_padding":
478 table.cellPadding = val;
479 break;
480 case "f_frames":
481 if (val !== 'not set' && table.style.borderStyle !== 'none') {
482 table.frame = val;
483 } else {
484 table.removeAttribute('rules');
485 }
486 break;
487 case "f_rules":
488 if (val !== 'not set') {
489 table.rules = val;
490 } else {
491 table.removeAttribute('rules');
492 }
493 break;
494 case "f_st_float":
495 switch (val) {
496 case "not set":
497 HTMLArea.DOM.removeClass(table, this.floatRight);
498 HTMLArea.DOM.removeClass(table, this.floatLeft);
499 break;
500 case "right":
501 HTMLArea.DOM.removeClass(table, this.floatLeft);
502 HTMLArea.DOM.addClass(table, this.floatRight);
503 break;
504 case "left":
505 HTMLArea.DOM.removeClass(table, this.floatRight);
506 HTMLArea.DOM.addClass(table, this.floatLeft);
507 break;
508 }
509 break;
510 case "f_st_textAlign":
511 if (this.getPluginInstance('BlockElements')) {
512 this.getPluginInstance('BlockElements').toggleAlignmentClass(table, this.convertAlignment[val]);
513 table.style.textAlign = "";
514 }
515 break;
516 case "f_class":
517 case "f_class_tbody":
518 case "f_class_thead":
519 case "f_class_tfoot":
520 var tpart = table;
521 if (item.length > 7) {
522 tpart = table.getElementsByTagName(item.substring(8,13))[0];
523 }
524 if (tpart) {
525 this.getPluginInstance('BlockStyle').applyClassChange(tpart, val);
526 }
527 break;
528 case "f_lang":
529 this.getPluginInstance('Language').setLanguageAttributes(table, val);
530 break;
531 case "f_dir":
532 table.dir = (val != "not set") ? val : "";
533 break;
534 }
535 }, this);
536 if (this.dialog.arguments.buttonId === "InsertTable") {
537 if (!Ext.isIE) {
538 this.editor.getSelection().insertNode(table);
539 } else {
540 table.id = "htmlarea_table_insert";
541 this.editor.getSelection().insertNode(table);
542 table = this.editor.document.getElementById(table.id);
543 table.removeAttribute("id");
544 }
545 this.editor.getSelection().selectNodeContents(table.rows[0].cells[0], true);
546 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnTableCreation) {
547 this.toggleBorders(true);
548 }
549 }
550 this.close();
551 },
552 /*
553 * Update the row/column/cell properties
554 */
555 rowCellPropertiesUpdate: function() {
556 this.restoreSelection()
557 // Collect values from each form field
558 var params = {};
559 var fieldTypes = ['combo', 'textfield', 'numberfield', 'checkbox', 'colorpalettefield'];
560 this.dialog.findBy(function (item) {
561 if (fieldTypes.indexOf(item.getXType()) !== -1) {
562 params[item.getItemId()] = item.getValue();
563 return true;
564 }
565 return false;
566 });
567 var cell = this.dialog.arguments.cell;
568 var column = this.dialog.arguments.column;
569 var section = (cell || column) ? this.dialog.arguments.element.parentNode.parentNode : this.dialog.arguments.element.parentNode;
570 var table = section.parentNode;
571 var elements = [];
572 if (column) {
573 elements = this.getColumnCells(this.dialog.arguments.element);
574 } else {
575 elements.push(this.dialog.arguments.element);
576 }
577 Ext.each(elements, function (element) {
578 this.processStyle(element, params);
579 Ext.iterate(params, function (item) {
580 var val = params[item];
581 switch (item) {
582 case "f_cell_type":
583 if (val.substring(0,2) != element.nodeName.toLowerCase()) {
584 element = this.remapCell(element, val.substring(0,2));
585 this.editor.getSelection().selectNodeContents(element, true);
586 }
587 if (val.substring(2,10) != element.scope) {
588 element.scope = val.substring(2,10);
589 }
590 break;
591 case "f_cell_abbr":
592 if (!column) {
593 element.abbr = (element.nodeName.toLowerCase() == 'td') ? '' : val;
594 }
595 break;
596 case "f_rowgroup":
597 var nodeName = section.nodeName.toLowerCase();
598 if (val != nodeName) {
599 var newSection = table.getElementsByTagName(val)[0];
600 if (!newSection) var newSection = table.insertBefore(this.editor.document.createElement(val), table.getElementsByTagName("tbody")[0]);
601 if (nodeName == "thead" && val == "tbody") var newElement = newSection.insertBefore(element, newSection.firstChild);
602 else var newElement = newSection.appendChild(element);
603 if (!section.hasChildNodes()) table.removeChild(section);
604 }
605 if (params.f_convertCells) {
606 if (val == "thead") {
607 this.remapRowCells(element, "th");
608 } else {
609 this.remapRowCells(element, "td");
610 }
611 }
612 break;
613 case "f_st_textAlign":
614 if (this.getPluginInstance('BlockElements')) {
615 this.getPluginInstance('BlockElements').toggleAlignmentClass(element, this.convertAlignment[val]);
616 element.style.textAlign = "";
617 }
618 break;
619 case "f_class":
620 this.getPluginInstance('BlockStyle').applyClassChange(element, val);
621 break;
622 case "f_lang":
623 this.getPluginInstance('Language').setLanguageAttributes(element, val);
624 break;
625 case "f_dir":
626 element.dir = (val != "not set") ? val : "";
627 break;
628 }
629 }, this);
630 }, this);
631 this.reStyleTable(table);
632 this.close();
633 },
634 /*
635 * This function gets called when the plugin is generated
636 */
637 onGenerate: function () {
638 // Set table borders if requested by configuration
639 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnRTEOpen) {
640 this.toggleBorders(true);
641 }
642 // Register handler for the enter key for IE and Opera when buttons.table.disableEnterParagraphs is set in the editor configuration
643 if ((Ext.isIE || Ext.isOpera) && this.disableEnterParagraphs) {
644 this.editor.iframe.keyMap.addBinding({
645 key: Ext.EventObject.ENTER,
646 shift: false,
647 handler: this.onKey,
648 scope: this
649 });
650 }
651 },
652 /*
653 * This function gets called when the toolbar is being updated
654 */
655 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
656 if (mode === 'wysiwyg' && this.editor.isEditable()) {
657 switch (button.itemId) {
658 case 'TO-toggle-borders':
659 button.setInactive(!HTMLArea.DOM.hasClass(this.editor.document.body, 'htmlarea-showtableborders'));
660 break;
661 case 'TO-cell-merge':
662 if (Ext.isGecko) {
663 var selection = this.editor.getSelection().get().selection;
664 button.setDisabled(button.disabled || selection.rangeCount < 2);
665 }
666 break;
667 }
668 }
669 },
670 /*
671 * This function gets called when a Table Operations button was pressed.
672 *
673 * @param object editor: the editor instance
674 * @param string id: the button id or the key
675 *
676 * @return boolean false if action is completed
677 */
678 onButtonPress: function (editor, id, target) {
679 // Could be a button or its hotkey
680 var buttonId = this.translateHotKey(id);
681 buttonId = buttonId ? buttonId : id;
682
683 var mozbr = !Ext.isIE ? "<br />" : "";
684 var tableParts = ["tfoot", "thead", "tbody"];
685 var tablePartsIndex = { tfoot : 0, thead : 1, tbody : 2 };
686
687 // helper function that clears the content in a table row
688 function clearRow(tr) {
689 var tds = tr.getElementsByTagName("td");
690 for (var i = tds.length; --i >= 0;) {
691 var td = tds[i];
692 td.rowSpan = 1;
693 td.innerHTML = mozbr;
694 }
695 var tds = tr.getElementsByTagName("th");
696 for (var i = tds.length; --i >= 0;) {
697 var td = tds[i];
698 td.rowSpan = 1;
699 td.innerHTML = mozbr;
700 }
701 };
702
703 function splitRow(td) {
704 var n = parseInt("" + td.rowSpan);
705 var colSpan = td.colSpan;
706 var nodeName = td.nodeName.toLowerCase();
707 td.rowSpan = 1;
708 var tr = td.parentNode;
709 var sectionRowIndex = tr.sectionRowIndex;
710 var rows = tr.parentNode.rows;
711 var index = td.cellIndex;
712 while (--n > 0) {
713 tr = rows[++sectionRowIndex];
714 // Last row
715 if (!tr) tr = td.parentNode.parentNode.appendChild(editor.document.createElement("tr"));
716 var otd = editor.document.createElement(nodeName);
717 otd.colSpan = colSpan;
718 otd.innerHTML = mozbr;
719 tr.insertBefore(otd, tr.cells[index]);
720 }
721 };
722
723 function splitCol(td) {
724 var nc = parseInt("" + td.colSpan);
725 var nodeName = td.nodeName.toLowerCase();
726 td.colSpan = 1;
727 var tr = td.parentNode;
728 var ref = td.nextSibling;
729 while (--nc > 0) {
730 var otd = editor.document.createElement(nodeName);
731 otd.rowSpan = td.rowSpan;
732 otd.innerHTML = mozbr;
733 tr.insertBefore(otd, ref);
734 }
735 };
736
737 function splitCell(td) {
738 var nc = parseInt("" + td.colSpan);
739 splitCol(td);
740 var cells = td.parentNode.cells;
741 var index = td.cellIndex;
742 while (nc-- > 0) {
743 splitRow(cells[index++]);
744 }
745 };
746
747 function selectNextNode(el) {
748 var node = el.nextSibling;
749 while (node && node.nodeType != 1) {
750 node = node.nextSibling;
751 }
752 if (!node) {
753 node = el.previousSibling;
754 while (node && node.nodeType != 1) {
755 node = node.previousSibling;
756 }
757 }
758 if (!node) node = el.parentNode;
759 editor.getSelection().selectNodeContents(node);
760 };
761
762 function getSelectedCells(sel) {
763 var cell, range, i = 0, cells = [];
764 try {
765 while (range = sel.getRangeAt(i++)) {
766 cell = range.startContainer.childNodes[range.startOffset];
767 while (!/^(td|th|body)$/.test(cell.nodeName.toLowerCase())) cell = cell.parentNode;
768 if (/^(td|th)$/.test(cell.nodeName.toLowerCase())) cells.push(cell);
769 }
770 } catch(e) {
771 /* finished walking through selection */
772 }
773 return cells;
774 };
775
776 function deleteEmptyTable(table) {
777 var lastPart = true;
778 for (var j = tableParts.length; --j >= 0;) {
779 var tablePart = table.getElementsByTagName(tableParts[j])[0];
780 if (tablePart) lastPart = false;
781 }
782 if (lastPart) {
783 selectNextNode(table);
784 table.parentNode.removeChild(table);
785 }
786 };
787
788 function computeCellIndexes(table) {
789 var matrix = [];
790 var lookup = {};
791 for (var m = tableParts.length; --m >= 0;) {
792 var tablePart = table.getElementsByTagName(tableParts[m])[0];
793 if (tablePart) {
794 var rows = tablePart.rows;
795 for (var i = 0, n = rows.length; i < n; i++) {
796 var cells = rows[i].cells;
797 for (var j=0; j< cells.length; j++) {
798 var cell = cells[j];
799 var rowIndex = cell.parentNode.rowIndex;
800 var cellId = tableParts[m]+"-"+rowIndex+"-"+cell.cellIndex;
801 var rowSpan = cell.rowSpan || 1;
802 var colSpan = cell.colSpan || 1;
803 var firstAvailCol;
804 if(typeof(matrix[rowIndex])=="undefined") { matrix[rowIndex] = []; }
805 // Find first available column in the first row
806 for (var k=0; k<matrix[rowIndex].length+1; k++) {
807 if (typeof(matrix[rowIndex][k])=="undefined") {
808 firstAvailCol = k;
809 break;
810 }
811 }
812 lookup[cellId] = firstAvailCol;
813 for (var k=rowIndex; k<rowIndex+rowSpan; k++) {
814 if (typeof(matrix[k])=="undefined") { matrix[k] = []; }
815 var matrixrow = matrix[k];
816 for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) {
817 matrixrow[l] = "x";
818 }
819 }
820 }
821 }
822 }
823 }
824 return lookup;
825 };
826
827 function getActualCellIndex(cell, lookup) {
828 return lookup[cell.parentNode.parentNode.nodeName.toLowerCase()+"-"+cell.parentNode.rowIndex+"-"+cell.cellIndex];
829 };
830
831 switch (buttonId) {
832 // ROWS
833 case "TO-row-insert-above":
834 case "TO-row-insert-under":
835 var tr = this.editor.getSelection().getFirstAncestorOfType("tr");
836 if (!tr) break;
837 var otr = tr.cloneNode(true);
838 clearRow(otr);
839 otr = tr.parentNode.insertBefore(otr, (/under/.test(buttonId) ? tr.nextSibling : tr));
840 this.editor.getSelection().selectNodeContents(otr.firstChild, true);
841 this.reStyleTable(tr.parentNode.parentNode);
842 break;
843 case "TO-row-delete":
844 var tr = this.editor.getSelection().getFirstAncestorOfType("tr");
845 if (!tr) break;
846 var part = tr.parentNode;
847 var table = part.parentNode;
848 if(part.rows.length == 1) { // this the last row, delete the whole table part
849 selectNextNode(part);
850 table.removeChild(part);
851 deleteEmptyTable(table);
852 } else {
853 // set the caret first to a position that doesn't disappear.
854 selectNextNode(tr);
855 part.removeChild(tr);
856 }
857 this.reStyleTable(table);
858 break;
859 case "TO-row-split":
860 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
861 if (!cell) break;
862 var sel = editor.getSelection().get().selection;
863 if (Ext.isGecko && !sel.isCollapsed) {
864 var cells = getSelectedCells(sel);
865 for (i = 0; i < cells.length; ++i) {
866 splitRow(cells[i]);
867 }
868 } else {
869 splitRow(cell);
870 }
871 break;
872
873 // COLUMNS
874 case "TO-col-insert-before":
875 case "TO-col-insert-after":
876 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
877 if (!cell) break;
878 var index = cell.cellIndex;
879 var table = cell.parentNode.parentNode.parentNode;
880 for (var j = tableParts.length; --j >= 0;) {
881 var tablePart = table.getElementsByTagName(tableParts[j])[0];
882 if (tablePart) {
883 var rows = tablePart.rows;
884 for (var i = rows.length; --i >= 0;) {
885 var tr = rows[i];
886 var ref = tr.cells[index + (/after/.test(buttonId) ? 1 : 0)];
887 if (!ref) {
888 var otd = editor.document.createElement(tr.lastChild.nodeName.toLowerCase());
889 otd.innerHTML = mozbr;
890 tr.appendChild(otd);
891 } else {
892 var otd = editor.document.createElement(ref.nodeName.toLowerCase());
893 otd.innerHTML = mozbr;
894 tr.insertBefore(otd, ref);
895 }
896 }
897 }
898 }
899 this.reStyleTable(table);
900 break;
901 case "TO-col-split":
902 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
903 if (!cell) break;
904 var sel = this.editor.getSelection().get().selection;
905 if (Ext.isGecko && !sel.isCollapsed) {
906 var cells = getSelectedCells(sel);
907 for (i = 0; i < cells.length; ++i) {
908 splitCol(cells[i]);
909 }
910 } else {
911 splitCol(cell);
912 }
913 this.reStyleTable(table);
914 break;
915 case "TO-col-delete":
916 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
917 if (!cell) break;
918 var index = cell.cellIndex;
919 var part = cell.parentNode.parentNode;
920 var table = part.parentNode;
921 var lastPart = true;
922 for (var j = tableParts.length; --j >= 0;) {
923 var tablePart = table.getElementsByTagName(tableParts[j])[0];
924 if (tablePart) {
925 var rows = tablePart.rows;
926 var lastColumn = true;
927 for (var i = rows.length; --i >= 0;) {
928 if(rows[i].cells.length > 1) lastColumn = false;
929 }
930 if (lastColumn) {
931 // this is the last column, delete the whole tablepart
932 // set the caret first to a position that doesn't disappear
933 selectNextNode(tablePart);
934 table.removeChild(tablePart);
935 } else {
936 // set the caret first to a position that doesn't disappear
937 if (part == tablePart) selectNextNode(cell);
938 for (var i = rows.length; --i >= 0;) {
939 if(rows[i].cells[index]) rows[i].removeChild(rows[i].cells[index]);
940 }
941 lastPart = false;
942 }
943 }
944 }
945 if (lastPart) {
946 // the last table section was deleted: delete the whole table
947 // set the caret first to a position that doesn't disappear
948 selectNextNode(table);
949 table.parentNode.removeChild(table);
950 }
951 this.reStyleTable(table);
952 break;
953
954 // CELLS
955 case "TO-cell-split":
956 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
957 if (!cell) break;
958 var sel = this.editor.getSelection().get().selection;
959 if (Ext.isGecko && !sel.isCollapsed) {
960 var cells = getSelectedCells(sel);
961 for (i = 0; i < cells.length; ++i) {
962 splitCell(cells[i]);
963 }
964 } else {
965 splitCell(cell);
966 }
967 this.reStyleTable(table);
968 break;
969 case "TO-cell-insert-before":
970 case "TO-cell-insert-after":
971 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
972 if (!cell) break;
973 var tr = cell.parentNode;
974 var otd = editor.document.createElement(cell.nodeName.toLowerCase());
975 otd.innerHTML = mozbr;
976 tr.insertBefore(otd, (/after/.test(buttonId) ? cell.nextSibling : cell));
977 this.reStyleTable(tr.parentNode.parentNode);
978 break;
979 case "TO-cell-delete":
980 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
981 if (!cell) break;
982 var row = cell.parentNode;
983 if(row.cells.length == 1) { // this is the only cell in the row, delete the row
984 var part = row.parentNode;
985 var table = part.parentNode;
986 if (part.rows.length == 1) { // this the last row, delete the whole table part
987 selectNextNode(part);
988 table.removeChild(part);
989 deleteEmptyTable(table);
990 } else {
991 selectNextNode(row);
992 part.removeChild(row);
993 }
994 } else {
995 // set the caret first to a position that doesn't disappear
996 selectNextNode(cell);
997 row.removeChild(cell);
998 }
999 this.reStyleTable(table);
1000 break;
1001 case "TO-cell-merge":
1002 var sel = this.editor.getSelection().get().selection;
1003 var range, i = 0;
1004 var rows = new Array();
1005 for (var k = tableParts.length; --k >= 0;) rows[k] = [];
1006 var row = null;
1007 var cells = null;
1008 if (Ext.isGecko) {
1009 try {
1010 while (range = sel.getRangeAt(i++)) {
1011 var td = range.startContainer.childNodes[range.startOffset];
1012 if (td.parentNode != row) {
1013 (cells) && rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells);
1014 row = td.parentNode;
1015 cells = [];
1016 }
1017 cells.push(td);
1018 }
1019 } catch(e) {
1020 /* finished walking through selection */
1021 }
1022 try { rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); } catch(e) { }
1023 } else {
1024 // Internet Explorer, WebKit and Opera
1025 var cell = this.editor.getSelection().getFirstAncestorOfType(['td', 'th']);
1026 if (!cell) {
1027 TYPO3.Dialog.InformationDialog({
1028 title: this.getButton('TO-cell-merge').tooltip.title,
1029 msg: this.localize('Please click into some cell')
1030 });
1031 break;
1032 }
1033 var tr = cell.parentNode;
1034 var no_cols = parseInt(prompt(this.localize("How many columns would you like to merge?"), 2));
1035 if (!no_cols) break;
1036 var no_rows = parseInt(prompt(this.localize("How many rows would you like to merge?"), 2));
1037 if (!no_rows) break;
1038 var lookup = computeCellIndexes(cell.parentNode.parentNode.parentNode);
1039 var first_index = getActualCellIndex(cell, lookup);
1040 // Collect cells on first row
1041 var td = cell, colspan = 0;
1042 cells = [];
1043 for (var i = no_cols; --i >= 0;) {
1044 if (!td) break;
1045 cells.push(td);
1046 var last_index = getActualCellIndex(td, lookup);
1047 td = td.nextSibling;
1048 }
1049 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
1050 // Collect cells on following rows
1051 var index, first_index_found, last_index_found;
1052 for (var j = 1; j < no_rows; ++j) {
1053 tr = tr.nextSibling;
1054 if (!tr) break;
1055 cells = [];
1056 first_index_found = false;
1057 for (var i = 0; i < tr.cells.length; ++i) {
1058 td = tr.cells[i];
1059 if (!td) break;
1060 index = getActualCellIndex(td, lookup);
1061 if (index > last_index) break;
1062 if (index == first_index) first_index_found = true;
1063 if (index >= first_index) cells.push(td);
1064 }
1065 // If not rectangle, we quit!
1066 if (!first_index_found) break;
1067 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
1068 }
1069 }
1070 for (var k = tableParts.length; --k >= 0;) {
1071 var cell, row;
1072 var cellHTML = "";
1073 var cellRowSpan = 0;
1074 var cellColSpan, maxCellColSpan = 0;
1075 if (rows[k] && rows[k][0]) {
1076 for (var i = 0; i < rows[k].length; ++i) {
1077 var cells = rows[k][i];
1078 var cellColSpan = 0;
1079 if (!cells) continue;
1080 cellRowSpan += cells[0].rowSpan ? cells[0].rowSpan : 1;
1081 for (var j = 0; j < cells.length; ++j) {
1082 cell = cells[j];
1083 row = cell.parentNode;
1084 cellHTML += cell.innerHTML;
1085 cellColSpan += cell.colSpan ? cell.colSpan : 1;
1086 if (i || j) {
1087 cell.parentNode.removeChild(cell);
1088 if(!row.cells.length) row.parentNode.removeChild(row);
1089 }
1090 }
1091 if (maxCellColSpan < cellColSpan) {
1092 maxCellColSpan = cellColSpan;
1093 }
1094 }
1095 var td = rows[k][0][0];
1096 td.innerHTML = cellHTML;
1097 td.rowSpan = cellRowSpan;
1098 td.colSpan = maxCellColSpan;
1099 editor.getSelection().selectNodeContents(td);
1100 }
1101 }
1102 this.reStyleTable(table);
1103 break;
1104
1105 // CREATION AND PROPERTIES
1106 case "InsertTable":
1107 case "TO-table-prop":
1108 this.openPropertiesDialogue('table', buttonId);
1109 break;
1110 case "TO-table-restyle":
1111 this.reStyleTable(this.editor.getSelection().getFirstAncestorOfType('table'));
1112 break;
1113 case "TO-row-prop":
1114 this.openPropertiesDialogue('row', buttonId);
1115 break;
1116 case "TO-col-prop":
1117 this.openPropertiesDialogue('column', buttonId);
1118 break;
1119 case "TO-cell-prop":
1120 this.openPropertiesDialogue('cell', buttonId);
1121 break;
1122 case "TO-toggle-borders":
1123 this.toggleBorders();
1124 break;
1125 default:
1126 break;
1127 }
1128 },
1129 /*
1130 * Returns an array of all cells in the column containing the given cell
1131 *
1132 * @param object cell: the cell serving as reference point for the column
1133 *
1134 * @return array the array of cells of the column
1135 */
1136 getColumnCells : function (cell) {
1137 var cells = new Array();
1138 var index = cell.cellIndex;
1139 var table = cell.parentNode.parentNode.parentNode;
1140 for (var j = this.tableParts.length; --j >= 0;) {
1141 var tablePart = table.getElementsByTagName(this.tableParts[j])[0];
1142 if (tablePart) {
1143 var rows = tablePart.rows;
1144 for (var i = rows.length; --i >= 0;) {
1145 if(rows[i].cells.length > index) {
1146 cells.push(rows[i].cells[index]);
1147 }
1148 }
1149 }
1150 }
1151 return cells;
1152 },
1153 /*
1154 * Toggles the display of borders on tables and table cells
1155 *
1156 * @param boolean forceBorders: if set, borders are displayed whatever the current state
1157 *
1158 * @return void
1159 */
1160 toggleBorders : function (forceBorders) {
1161 var body = this.editor.document.body;
1162 if (!HTMLArea.DOM.hasClass(body, 'htmlarea-showtableborders')) {
1163 HTMLArea.DOM.addClass(body,'htmlarea-showtableborders');
1164 } else if (!forceBorders) {
1165 HTMLArea.DOM.removeClass(body,'htmlarea-showtableborders');
1166 }
1167 },
1168 /*
1169 * Applies to rows/cells the alternating and counting classes of an alternating or counting style scheme
1170 *
1171 * @param object table: the table to be re-styled
1172 *
1173 * @return void
1174 */
1175 reStyleTable: function (table) {
1176 if (table) {
1177 if (this.classesUrl && (typeof(HTMLArea.classesAlternating) === 'undefined' || typeof(HTMLArea.classesCounting) === 'undefined')) {
1178 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1179 if (success) {
1180 try {
1181 if (typeof(HTMLArea.classesAlternating) === 'undefined' || typeof(HTMLArea.classesCounting) === 'undefined') {
1182 eval(response.responseText);
1183 }
1184 this.reStyleTable(table);
1185 } catch(e) {
1186 this.appendToLog('reStyleTable', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1187 }
1188 }
1189 });
1190 } else {
1191 var classNames = table.className.trim().split(' ');
1192 for (var i = classNames.length; --i >= 0;) {
1193 var classConfiguration = HTMLArea.classesAlternating[classNames[i]];
1194 if (classConfiguration && classConfiguration.rows) {
1195 if (classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1196 this.alternateRows(table, classConfiguration);
1197 }
1198 }
1199 if (classConfiguration && classConfiguration.columns) {
1200 if (classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1201 this.alternateColumns(table, classConfiguration);
1202 }
1203 }
1204 classConfiguration = HTMLArea.classesCounting[classNames[i]];
1205 if (classConfiguration && classConfiguration.rows) {
1206 if (classConfiguration.rows.rowClass) {
1207 this.countRows(table, classConfiguration);
1208 }
1209 }
1210 if (classConfiguration && classConfiguration.columns) {
1211 if (classConfiguration.columns.columnClass) {
1212 this.countColumns(table, classConfiguration);
1213 }
1214 }
1215 }
1216 }
1217 }
1218 },
1219 /*
1220 * Removes from rows/cells the alternating classes of an alternating style scheme
1221 *
1222 * @param object table: the table to be re-styled
1223 * @param string removeClass: the name of the class that identifies the alternating style scheme
1224 *
1225 * @return void
1226 */
1227 removeAlternatingClasses: function (table, removeClass) {
1228 if (table) {
1229 if (this.classesUrl && typeof(HTMLArea.classesAlternating) === 'undefined') {
1230 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1231 if (success) {
1232 try {
1233 if (typeof(HTMLArea.classesAlternating) === 'undefined') {
1234 eval(response.responseText);
1235 }
1236 this.removeAlternatingClasses(table, removeClass);
1237 } catch(e) {
1238 this.appendToLog('removeAlternatingClasses', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1239 }
1240 }
1241 });
1242 } else {
1243 var classConfiguration = HTMLArea.classesAlternating[removeClass];
1244 if (classConfiguration) {
1245 if (classConfiguration.rows && classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1246 this.alternateRows(table, classConfiguration, true);
1247 }
1248 if (classConfiguration.columns && classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1249 this.alternateColumns(table, classConfiguration, true);
1250 }
1251 }
1252 }
1253 }
1254 },
1255 /*
1256 * Applies/removes the alternating classes of an alternating rows style scheme
1257 *
1258 * @param object table: the table to be re-styled
1259 * @param object classConfifuration: the alternating sub-array of the configuration of the class
1260 * @param boolean remove: if true, the classes are removed
1261 *
1262 * @return void
1263 */
1264 alternateRows : function (table, classConfiguration, remove) {
1265 var oddClass = { tbody : classConfiguration.rows.oddClass, thead : classConfiguration.rows.oddHeaderClass };
1266 var evenClass = { tbody : classConfiguration.rows.evenClass, thead : classConfiguration.rows.evenHeaderClass };
1267 var startAt = parseInt(classConfiguration.rows.startAt);
1268 startAt = remove ? 1 : (startAt ? startAt : 1);
1269 var rows = table.rows, type, odd, even;
1270 // Loop through the rows
1271 for (var i = startAt-1, n = rows.length; i < n; i++) {
1272 var row = rows[i];
1273 type = (row.parentNode.nodeName.toLowerCase() == "thead") ? "thead" : "tbody";
1274 odd = oddClass[type];
1275 even = evenClass[type];
1276 if (remove) {
1277 HTMLArea.DOM.removeClass(row, odd);
1278 HTMLArea.DOM.removeClass(row, even);
1279 // Check if i is even, and apply classes for both possible results
1280 } else if (odd && even) {
1281 if ((i % 2) == 0) {
1282 if (HTMLArea.DOM.hasClass(row, even)) {
1283 HTMLArea.DOM.removeClass(row, even);
1284 }
1285 HTMLArea.DOM.addClass(row, odd);
1286 } else {
1287 if (HTMLArea.DOM.hasClass(row, odd)) {
1288 HTMLArea.DOM.removeClass(row, odd);
1289 }
1290 HTMLArea.DOM.addClass(row, even);
1291 }
1292 }
1293 }
1294 },
1295 /*
1296 * Applies/removes the alternating classes of an alternating columns style scheme
1297 *
1298 * @param object table: the table to be re-styled
1299 * @param object classConfifuration: the alternating sub-array of the configuration of the class
1300 * @param boolean remove: if true, the classes are removed
1301 *
1302 * @return void
1303 */
1304 alternateColumns : function (table, classConfiguration, remove) {
1305 var oddClass = { td : classConfiguration.columns.oddClass, th : classConfiguration.columns.oddHeaderClass };
1306 var evenClass = { td : classConfiguration.columns.evenClass, th : classConfiguration.columns.evenHeaderClass };
1307 var startAt = parseInt(classConfiguration.columns.startAt);
1308 startAt = remove ? 1 : (startAt ? startAt : 1);
1309 var rows = table.rows, type, odd, even;
1310 // Loop through the rows of the table
1311 for (var i = rows.length; --i >= 0;) {
1312 // Loop through the cells
1313 var cells = rows[i].cells;
1314 for (var j = startAt-1, n = cells.length; j < n; j++) {
1315 var cell = cells[j];
1316 type = cell.nodeName.toLowerCase();
1317 odd = oddClass[type];
1318 even = evenClass[type];
1319 if (remove) {
1320 if (odd) HTMLArea.DOM.removeClass(cell, odd);
1321 if (even) HTMLArea.DOM.removeClass(cell, even);
1322 } else if (odd && even) {
1323 // Check if j+startAt is even, and apply classes for both possible results
1324 if ((j % 2) == 0) {
1325 if (HTMLArea.DOM.hasClass(cell, even)) {
1326 HTMLArea.DOM.removeClass(cell, even);
1327 }
1328 HTMLArea.DOM.addClass(cell, odd);
1329 } else{
1330 if (HTMLArea.DOM.hasClass(cell, odd)) {
1331 HTMLArea.DOM.removeClass(cell, odd);
1332 }
1333 HTMLArea.DOM.addClass(cell, even);
1334 }
1335 }
1336 }
1337 }
1338 },
1339 /*
1340 * Removes from rows/cells the counting classes of an counting style scheme
1341 *
1342 * @param object table: the table to be re-styled
1343 * @param string removeClass: the name of the class that identifies the counting style scheme
1344 *
1345 * @return void
1346 */
1347 removeCountingClasses: function (table, removeClass) {
1348 if (table) {
1349 if (this.classesUrl && typeof(HTMLArea.classesCounting) === 'undefined') {
1350 this.getJavascriptFile(this.classesUrl, function (options, success, response) {
1351 if (success) {
1352 try {
1353 if (typeof(HTMLArea.classesCounting) === 'undefined') {
1354 eval(response.responseText);
1355 }
1356 this.removeCountingClasses(table, removeClass);
1357 } catch(e) {
1358 this.appendToLog('removeCountingClasses', 'Error evaluating contents of Javascript file: ' + this.classesUrl, 'error');
1359 }
1360 }
1361 });
1362 } else {
1363 var classConfiguration = HTMLArea.classesCounting[removeClass];
1364 if (classConfiguration) {
1365 if (classConfiguration.rows && classConfiguration.rows.rowClass) {
1366 this.countRows(table, classConfiguration, true);
1367 }
1368 if (classConfiguration.columns && classConfiguration.columns.columnClass) {
1369 this.countColumns(table, classConfiguration, true);
1370 }
1371 }
1372 }
1373 }
1374 },
1375 /*
1376 * Applies/removes the counting classes of an counting rows style scheme
1377 *
1378 * @param object table: the table to be re-styled
1379 * @param object classConfifuration: the counting sub-array of the configuration of the class
1380 * @param boolean remove: if true, the classes are removed
1381 *
1382 * @return void
1383 */
1384 countRows : function (table, classConfiguration, remove) {
1385 var rowClass = { tbody : classConfiguration.rows.rowClass, thead : classConfiguration.rows.rowHeaderClass };
1386 var rowLastClass = { tbody : classConfiguration.rows.rowLastClass, thead : classConfiguration.rows.rowHeaderLastClass };
1387 var startAt = parseInt(classConfiguration.rows.startAt);
1388 startAt = remove ? 1 : (startAt ? startAt : 1);
1389 var rows = table.rows, type, baseClassName, rowClassName, lastRowClassName;
1390 // Loop through the rows
1391 for (var i = startAt-1, n = rows.length; i < n; i++) {
1392 var row = rows[i];
1393 type = (row.parentNode.nodeName.toLowerCase() == "thead") ? "thead" : "tbody";
1394 baseClassName = rowClass[type];
1395 rowClassName = baseClassName + (i+1);
1396 lastRowClassName = rowLastClass[type];
1397 if (remove) {
1398 if (baseClassName) {
1399 HTMLArea.DOM.removeClass(row, rowClassName);
1400 }
1401 if (lastRowClassName && i == n-1) {
1402 HTMLArea.DOM.removeClass(row, lastRowClassName);
1403 }
1404 } else {
1405 if (baseClassName) {
1406 if (HTMLArea.DOM.hasClass(row, baseClassName, true)) {
1407 HTMLArea.DOM.removeClass(row, baseClassName, true);
1408 }
1409 HTMLArea.DOM.addClass(row, rowClassName);
1410 }
1411 if (lastRowClassName) {
1412 if (i == n-1) {
1413 HTMLArea.DOM.addClass(row, lastRowClassName);
1414 } else if (HTMLArea.DOM.hasClass(row, lastRowClassName)) {
1415 HTMLArea.DOM.removeClass(row, lastRowClassName);
1416 }
1417 }
1418 }
1419 }
1420 },
1421 /*
1422 * Applies/removes the counting classes of a counting columns style scheme
1423 *
1424 * @param object table: the table to be re-styled
1425 * @param object classConfifuration: the counting sub-array of the configuration of the class
1426 * @param boolean remove: if true, the classes are removed
1427 *
1428 * @return void
1429 */
1430 countColumns : function (table, classConfiguration, remove) {
1431 var columnClass = { td : classConfiguration.columns.columnClass, th : classConfiguration.columns.columnHeaderClass };
1432 var columnLastClass = { td : classConfiguration.columns.columnLastClass, th : classConfiguration.columns.columnHeaderLastClass };
1433 var startAt = parseInt(classConfiguration.columns.startAt);
1434 startAt = remove ? 1 : (startAt ? startAt : 1);
1435 var rows = table.rows, type, baseClassName, columnClassName, lastColumnClassName;
1436 // Loop through the rows of the table
1437 for (var i = rows.length; --i >= 0;) {
1438 // Loop through the cells
1439 var cells = rows[i].cells;
1440 for (var j = startAt-1, n = cells.length; j < n; j++) {
1441 var cell = cells[j];
1442 type = cell.nodeName.toLowerCase();
1443 baseClassName = columnClass[type];
1444 columnClassName = baseClassName + (j+1);
1445 lastColumnClassName = columnLastClass[type];
1446 if (remove) {
1447 if (baseClassName) {
1448 HTMLArea.DOM.removeClass(cell, columnClassName);
1449 }
1450 if (lastColumnClassName && j == n-1) {
1451 HTMLArea.DOM.removeClass(cell, lastColumnClassName);
1452 }
1453 } else {
1454 if (baseClassName) {
1455 if (HTMLArea.DOM.hasClass(cell, baseClassName, true)) {
1456 HTMLArea.DOM.removeClass(cell, baseClassName, true);
1457 }
1458 HTMLArea.DOM.addClass(cell, columnClassName);
1459 }
1460 if (lastColumnClassName) {
1461 if (j == n-1) {
1462 HTMLArea.DOM.addClass(cell, lastColumnClassName);
1463 } else if (HTMLArea.DOM.hasClass(cell, lastColumnClassName)) {
1464 HTMLArea.DOM.removeClass(cell, lastColumnClassName);
1465 }
1466 }
1467 }
1468 }
1469 }
1470 },
1471 /*
1472 * This function sets the headers cells on the table (top, left, both or none)
1473 *
1474 * @param object table: the table being edited
1475 * @param object params: the field values entered in the form
1476 *
1477 * @return void
1478 */
1479 setHeaders: function (table, params) {
1480 var headers = params.f_headers;
1481 var doc = this.editor.document;
1482 var tbody = table.tBodies[0];
1483 var thead = table.tHead;
1484 if (thead && !thead.rows.length && !tbody.rows.length) {
1485 // Table is degenerate
1486 return table;
1487 }
1488 if (headers == "top") {
1489 if (!thead) {
1490 var thead = doc.createElement("thead");
1491 thead = table.insertBefore(thead, tbody);
1492 }
1493 if (!thead.rows.length) {
1494 var firstRow = thead.appendChild(tbody.rows[0]);
1495 } else {
1496 var firstRow = thead.rows[0];
1497 }
1498 HTMLArea.DOM.removeClass(firstRow, this.useHeaderClass);
1499 } else {
1500 if (thead) {
1501 var rows = thead.rows;
1502 if (rows.length) {
1503 for (var i = rows.length; --i >= 0;) {
1504 this.remapRowCells(rows[i], "td");
1505 if (tbody.rows.length) {
1506 tbody.insertBefore(rows[i], tbody.rows[0]);
1507 } else {
1508 tbody.appendChild(rows[i]);
1509 }
1510 }
1511 }
1512 table.removeChild(thead);
1513 }
1514 }
1515 if (headers == "both") {
1516 var firstRow = tbody.rows[0];
1517 HTMLArea.DOM.addClass(firstRow, this.useHeaderClass);
1518 } else if (headers != "top") {
1519 var firstRow = tbody.rows[0];
1520 HTMLArea.DOM.removeClass(firstRow, this.useHeaderClass);
1521 this.remapRowCells(firstRow, "td");
1522 }
1523 if (headers == "top" || headers == "both") {
1524 this.remapRowCells(firstRow, "th");
1525 }
1526 if (headers == "left") {
1527 var firstRow = tbody.rows[0];
1528 }
1529 if (headers == "left" || headers == "both") {
1530 var rows = tbody.rows;
1531 for (var i = rows.length; --i >= 0;) {
1532 if (i || rows[i] == firstRow) {
1533 if (rows[i].cells[0].nodeName.toLowerCase() != "th") {
1534 var th = this.remapCell(rows[i].cells[0], "th");
1535 th.scope = "row";
1536 }
1537 }
1538 }
1539 } else {
1540 var rows = tbody.rows;
1541 for (var i = rows.length; --i >= 0;) {
1542 if (rows[i].cells[0].nodeName.toLowerCase() != "td") {
1543 rows[i].cells[0].scope = "";
1544 var td = this.remapCell(rows[i].cells[0], "td");
1545 }
1546 }
1547 }
1548 this.reStyleTable(table);
1549 },
1550
1551 /*
1552 * This function remaps the given cell to the specified node name
1553 */
1554 remapCell : function(element, nodeName) {
1555 var newCell = HTMLArea.DOM.convertNode(element, nodeName);
1556 var attributes = element.attributes, attributeName, attributeValue;
1557 for (var i = attributes.length; --i >= 0;) {
1558 attributeName = attributes.item(i).nodeName;
1559 if (nodeName != 'td' || (attributeName != 'scope' && attributeName != 'abbr')) {
1560 attributeValue = element.getAttribute(attributeName);
1561 if (attributeValue) {
1562 newCell.setAttribute(attributeName, attributeValue);
1563 }
1564 }
1565 }
1566 // In IE, the above fails to update the classname and style attributes.
1567 if (Ext.isIE) {
1568 if (element.style.cssText) {
1569 newCell.style.cssText = element.style.cssText;
1570 }
1571 if (element.className) {
1572 newCell.setAttribute("class", element.className);
1573 if (!newCell.className) {
1574 // IE before IE8
1575 newCell.setAttribute("className", element.className);
1576 }
1577 } else {
1578 newCell.removeAttribute("class");
1579 // IE before IE8
1580 newCell.removeAttribute("className");
1581 }
1582 }
1583
1584 if (this.tags && this.tags[nodeName] && this.tags[nodeName].allowedClasses) {
1585 if (newCell.className && /\S/.test(newCell.className)) {
1586 var allowedClasses = this.tags[nodeName].allowedClasses;
1587 var classNames = newCell.className.trim().split(" ");
1588 for (var i = classNames.length; --i >= 0;) {
1589 if (!allowedClasses.test(classNames[i])) {
1590 HTMLArea.DOM.removeClass(newCell, classNames[i]);
1591 }
1592 }
1593 }
1594 }
1595 return newCell;
1596 },
1597
1598 remapRowCells : function (row, toType) {
1599 var cells = row.cells;
1600 if (toType === "th") {
1601 for (var i = cells.length; --i >= 0;) {
1602 if (cells[i].nodeName.toLowerCase() != "th") {
1603 var th = this.remapCell(cells[i], "th");
1604 th.scope = "col";
1605 }
1606 }
1607 } else {
1608 for (var i = cells.length; --i >= 0;) {
1609 if (cells[i].nodeName.toLowerCase() != "td") {
1610 var td = this.remapCell(cells[i], "td");
1611 td.scope = "";
1612 }
1613 }
1614 }
1615 },
1616
1617 /*
1618 * This function applies the style properties found in params to the given element
1619 *
1620 * @param object element: the element
1621 * @param object params: the properties
1622 *
1623 * @return void
1624 */
1625 processStyle: function (element, params) {
1626 var style = element.style;
1627 if (Ext.isIE) {
1628 style.styleFloat = "";
1629 } else {
1630 style.cssFloat = "";
1631 }
1632 style.textAlign = "";
1633 for (var i in params) {
1634 if (params.hasOwnProperty(i)) {
1635 var val = params[i];
1636 switch (i) {
1637 case "f_st_backgroundColor":
1638 if (/\S/.test(val)) {
1639 style.backgroundColor = ((val.charAt(0) === '#') ? '' : '#') + val;
1640 } else {
1641 style.backgroundColor = '';
1642 }
1643 break;
1644 case "f_st_color":
1645 if (/\S/.test(val)) {
1646 style.color = ((val.charAt(0) === '#') ? '' : '#') + val;
1647 } else {
1648 style.color = '';
1649 }
1650 break;
1651 case "f_st_backgroundImage":
1652 if (/\S/.test(val)) {
1653 style.backgroundImage = "url(" + val + ")";
1654 } else {
1655 style.backgroundImage = "";
1656 }
1657 break;
1658 case "f_st_borderWidth":
1659 if (/\S/.test(val)) {
1660 style.borderWidth = val + "px";
1661 } else {
1662 style.borderWidth = "";
1663 }
1664 if (params.f_st_borderStyle == "none") style.borderWidth = "0px";
1665 if (params.f_st_borderStyle == "not set") style.borderWidth = "";
1666 break;
1667 case "f_st_borderStyle":
1668 style.borderStyle = (val != "not set") ? val : "";
1669 break;
1670 case "f_st_borderColor":
1671 if (/\S/.test(val)) {
1672 style.borderColor = ((val.charAt(0) === '#') ? '' : '#') + val;
1673 } else {
1674 style.borderColor = '';
1675 }
1676 if (params.f_st_borderStyle === 'none') {
1677 style.borderColor = '';
1678 }
1679 break;
1680 case "f_st_borderCollapse":
1681 style.borderCollapse = (val !== 'not set') ? val : '';
1682 if (params.f_st_borderStyle === 'none') {
1683 style.borderCollapse = '';
1684 }
1685 break;
1686 case "f_st_width":
1687 if (/\S/.test(val)) {
1688 style.width = val + params.f_st_widthUnit;
1689 } else {
1690 style.width = "";
1691 }
1692 break;
1693 case "f_st_height":
1694 if (/\S/.test(val)) {
1695 style.height = val + params.f_st_heightUnit;
1696 } else {
1697 style.height = "";
1698 }
1699 break;
1700 case "f_st_textAlign":
1701 style.textAlign = (val != "not set") ? val : "";
1702 break;
1703 case "f_st_vertAlign":
1704 style.verticalAlign = (val != "not set") ? val : "";
1705 break;
1706 }
1707 }
1708 }
1709 },
1710 /*
1711 * This function builds the configuration object for the table Description fieldset
1712 *
1713 * @param object table: the table being edited, if any
1714 *
1715 * @return object the fieldset configuration object
1716 */
1717 buildDescriptionFieldsetConfig: function (table) {
1718 if (!Ext.isEmpty(table)) {
1719 var caption = table.getElementsByTagName('caption')[0];
1720 }
1721 return {
1722 xtype: 'fieldset',
1723 title: this.localize('Description'),
1724 defaultType: 'textfield',
1725 defaults: {
1726 labelSeparator: '',
1727 helpIcon: true
1728 },
1729 items: [{
1730 fieldLabel: this.getHelpTip('caption', 'Caption:'),
1731 itemId: 'f_caption',
1732 value: Ext.isDefined(caption) ? caption.innerHTML : '',
1733 width: 300,
1734 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Description of the nature of the table')
1735 },{
1736 fieldLabel: this.getHelpTip('summary', 'Summary:'),
1737 itemId: 'f_summary',
1738 value: !Ext.isEmpty(table) ? table.summary : '',
1739 width: 300,
1740 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Summary of the table purpose and structure')
1741 }]
1742 };
1743 },
1744 /*
1745 * This function builds the configuration object for the table Size and Headers fieldset
1746 *
1747 * @param object table: the table being edited, if any
1748 *
1749 * @return object the fieldset configuration object
1750 */
1751 buildSizeAndHeadersFieldsetConfig: function (table) {
1752 var itemsConfig = [];
1753 if (Ext.isEmpty(table)) {
1754 itemsConfig.push({
1755 fieldLabel: this.getHelpTip('numberOfRows', 'Number of rows'),
1756 labelSeparator: ':',
1757 itemId: 'f_rows',
1758 value: (this.properties.numberOfRows && this.properties.numberOfRows.defaultValue) ? this.properties.numberOfRows.defaultValue : '2',
1759 width: 30,
1760 minValue: 1,
1761 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Number of rows')
1762 });
1763 itemsConfig.push({
1764 fieldLabel: this.getHelpTip('numberOfColumns', 'Number of columns'),
1765 labelSeparator: ':',
1766 itemId: 'f_cols',
1767 value: (this.properties.numberOfColumns && this.properties.numberOfColumns.defaultValue) ? this.properties.numberOfColumns.defaultValue : '4',
1768 width: 30,
1769 minValue: 1,
1770 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Number of columns')
1771 });
1772 }
1773 if (this.removedProperties.indexOf('headers') == -1) {
1774 // Create combo store
1775 var store = new Ext.data.ArrayStore({
1776 autoDestroy: true,
1777 fields: [ { name: 'text'}, { name: 'value'}],
1778 data: [
1779 [this.localize('No header cells'), 'none'],
1780 [this.localize('Header cells on top'), 'top'],
1781 [this.localize('Header cells on left'), 'left'],
1782 [this.localize('Header cells on top and left'), 'both']
1783 ]
1784 });
1785 this.removeOptions(store, 'headers');
1786 if (Ext.isEmpty(table)) {
1787 var selected = (this.properties.headers && this.properties.headers.defaultValue) ? this.properties.headers.defaultValue : 'top';
1788 } else {
1789 var selected = 'none';
1790 var thead = table.getElementsByTagName('thead');
1791 var tbody = table.getElementsByTagName('tbody');
1792 if (thead.length && thead[0].rows.length) {
1793 selected = 'top';
1794 } else if (tbody.length && tbody[0].rows.length) {
1795 if (HTMLArea.DOM.hasClass(tbody[0].rows[0], this.useHeaderClass)) {
1796 selected = 'both';
1797 } else if (tbody[0].rows[0].cells.length && tbody[0].rows[0].cells[0].nodeName.toLowerCase() == 'th') {
1798 selected = 'left';
1799 }
1800 }
1801 }
1802 itemsConfig.push(Ext.apply({
1803 xtype: 'combo',
1804 fieldLabel: this.getHelpTip('tableHeaders', 'Headers:'),
1805 labelSeparator: '',
1806 itemId: 'f_headers',
1807 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Table headers'),
1808 store: store,
1809 width: (this.properties['headers'] && this.properties['headers'].width) ? this.properties['headers'].width : 200,
1810 value: selected
1811 }, this.configDefaults['combo']));
1812 }
1813 return {
1814 xtype: 'fieldset',
1815 title: this.localize(Ext.isEmpty(table) ? 'Size and Headers' : 'Headers'),
1816 defaultType: 'numberfield',
1817 defaults: {
1818 helpIcon: true
1819 },
1820 items: itemsConfig
1821 };
1822 },
1823 /*
1824 * This function builds the configuration object for the Style fieldset
1825 *
1826 * @param object element: the element being edited, if any
1827 * @param string buttonId: the id of the button that was pressed
1828 *
1829 * @return object the fieldset configuration object
1830 */
1831 buildStylingFieldsetConfig: function (element, buttonId) {
1832 var itemsConfig = [];
1833 var nodeName = element ? element.nodeName.toLowerCase() : 'table';
1834 var table = (nodeName == 'table');
1835 var select = this.buildStylingFieldConfig('f_class', (table ? 'Table class:' : 'Class:'), (table ? 'Table class selector' : 'Class selector'));
1836 this.setStyleOptions(select, element, nodeName, (buttonId === 'InsertTable') ? this.defaultClass : null);
1837 itemsConfig.push(select);
1838 if (element && table) {
1839 var tbody = element.getElementsByTagName('tbody')[0];
1840 if (tbody) {
1841 var tbodyStyleSelect = this.buildStylingFieldConfig('f_class_tbody', 'Table body class:', 'Table body class selector');
1842 this.setStyleOptions(tbodyStyleSelect, tbody, 'tbody');
1843 itemsConfig.push(tbodyStyleSelect);
1844 }
1845 var thead = element.getElementsByTagName('thead')[0];
1846 if (thead) {
1847 var theadStyleSelect = this.buildStylingFieldConfig('f_class_thead', 'Table header class:', 'Table header class selector');
1848 this.setStyleOptions(theadStyleSelect, thead, 'thead');
1849 itemsConfig.push(theadStyleSelect);
1850 }
1851 var tfoot = element.getElementsByTagName('tfoot')[0];
1852 if (tfoot) {
1853 var tfootStyleSelect = this.buildStylingFieldConfig('f_class_tfoot', 'Table footer class:', 'Table footer class selector');
1854 this.setStyleOptions(tfootStyleSelect, tfoot, 'tfoot');
1855 itemsConfig.push(tfootStyleSelect);
1856 }
1857 }
1858 return {
1859 xtype: 'fieldset',
1860 defaults: {
1861 labelSeparator: ''
1862 },
1863 title: table ? this.getHelpTip('tableStyle', 'CSS Style') : this.localize('CSS Style'),
1864 items: itemsConfig
1865 };
1866 },
1867 /*
1868 * This function builds a style selection field
1869 *
1870 * @param string fieldName: the name of the field
1871 * @param string fieldLabel: the label for the field
1872 * @param string fieldTitle: the title for the field tooltip
1873 *
1874 * @return object the style selection field object
1875 */
1876 buildStylingFieldConfig: function(fieldName, fieldLabel, fieldTitle) {
1877 return new Ext.form.ComboBox(Ext.apply({
1878 xtype: 'combo',
1879 itemId: fieldName,
1880 fieldLabel: this.getHelpTip(fieldTitle, fieldLabel),
1881 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize(fieldTitle),
1882 width: (this.properties['style'] && this.properties['style'].width) ? this.properties['style'].width : 300,
1883 store: new Ext.data.ArrayStore({
1884 autoDestroy: true,
1885 fields: [ { name: 'text'}, { name: 'value'}, { name: 'style'} ],
1886 data: [[this.localize('No block style'), 'none']]
1887 })
1888 }, {
1889 tpl: '<tpl for="."><div ext:qtip="{value}" style="{style}text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
1890 }, this.configDefaults['combo']
1891 ));
1892 },
1893 /*
1894 * This function populates the style store and sets the selected option
1895 *
1896 * @param object: dropDown: the combobox object
1897 * @param object element: the element being edited, if any
1898 * @param string nodeName: the type of element ('table' on table insertion)
1899 * @param string defaultClass: default class, if any is configured
1900 *
1901 * @return object the fieldset configuration object
1902 */
1903 setStyleOptions: function (dropDown, element, nodeName, defaultClass) {
1904 var blockStyle = this.getPluginInstance('BlockStyle');
1905 if (dropDown && blockStyle) {
1906 if (defaultClass) {
1907 var classNames = new Array();
1908 classNames.push(defaultClass);
1909 } else {
1910 var classNames = HTMLArea.DOM.getClassNames(element);
1911 }
1912 blockStyle.buildDropDownOptions(dropDown, nodeName);
1913 blockStyle.setSelectedOption(dropDown, classNames, 'noUnknown', defaultClass);
1914 }
1915 },
1916 /*
1917 * This function builds the configuration object for the Language fieldset
1918 *
1919 * @param object element: the element being edited, if any
1920 *
1921 * @return object the fieldset configuration object
1922 */
1923 buildLanguageFieldsetConfig: function (element) {
1924 var itemsConfig = [];
1925 var languageObject = this.getPluginInstance('Language');
1926 if (this.removedProperties.indexOf('language') == -1 && this.getButton('Language')) {
1927 var selectedLanguage = !Ext.isEmpty(element) ? languageObject.getLanguageAttribute(element) : 'none';
1928 function initLanguageStore (store) {
1929 if (selectedLanguage !== 'none') {
1930 store.removeAt(0);
1931 store.insert(0, new store.recordType({
1932 text: languageObject.localize('Remove language mark'),
1933 value: 'none'
1934 }));
1935 }
1936 }
1937 var languageStore = new Ext.data.JsonStore({
1938 autoDestroy: true,
1939 autoLoad: true,
1940 root: 'options',
1941 fields: [ { name: 'text'}, { name: 'value'} ],
1942 url: this.getDropDownConfiguration('Language').dataUrl,
1943 listeners: {
1944 load: initLanguageStore
1945 }
1946 });
1947 itemsConfig.push(Ext.apply({
1948 xtype: 'combo',
1949 fieldLabel: this.getHelpTip('languageCombo', 'Language', 'Language'),
1950 itemId: 'f_lang',
1951 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Language'),
1952 store: languageStore,
1953 width: (this.properties['language'] && this.properties['language'].width) ? this.properties['language'].width : 200,
1954 value: selectedLanguage
1955 }, this.configDefaults['combo']));
1956 }
1957 if (this.removedProperties.indexOf('direction') == -1 && (this.getButton('LeftToRight') || this.getButton('RightToLeft'))) {
1958 itemsConfig.push(Ext.apply({
1959 xtype: 'combo',
1960 fieldLabel: this.getHelpTip('directionCombo', 'Text direction', 'Language'),
1961 itemId: 'f_dir',
1962 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Text direction'),
1963 store: new Ext.data.ArrayStore({
1964 autoDestroy: true,
1965 fields: [ { name: 'text'}, { name: 'value'}],
1966 data: [
1967 [this.localize('Not set'), 'not set'],
1968 [this.localize('RightToLeft'), 'rtl'],
1969 [this.localize('LeftToRight'), 'ltr']
1970 ]
1971 }),
1972 width: (this.properties['direction'] && this.properties['dirrection'].width) ? this.properties['direction'].width : 200,
1973 value: !Ext.isEmpty(element) && element.dir ? element.dir : 'not set'
1974 }, this.configDefaults['combo']));
1975 }
1976 return {
1977 xtype: 'fieldset',
1978 title: this.localize('Language'),
1979 items: itemsConfig
1980 };
1981 },
1982 /*
1983 * This function builds the configuration object for the spacing fieldset
1984 *
1985 * @param object table: the table being edited, if any
1986 *
1987 * @return object the fieldset configuration object
1988 */
1989 buildSpacingFieldsetConfig: function (table) {
1990 return {
1991 xtype: 'fieldset',
1992 title: this.localize('Spacing and padding'),
1993 defaultType: 'numberfield',
1994 defaults: {
1995 labelSeparator: '',
1996 helpIcon: true
1997 },
1998 items: [{
1999 fieldLabel: this.getHelpTip('cellSpacing', 'Cell spacing:'),
2000 itemId: 'f_spacing',
2001 value: !Ext.isEmpty(table) ? table.cellSpacing : '',
2002 width: 30,
2003 minValue: 0,
2004 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Space between adjacent cells')
2005 },{
2006 fieldLabel: this.getHelpTip('cellPadding', 'Cell padding:'),
2007 itemId: 'f_padding',
2008 value: !Ext.isEmpty(table) ? table.cellPadding : '',
2009 width: 30,
2010 minValue: 0,
2011 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Space between content and border in cell')
2012 }]
2013 };
2014 },
2015 /*
2016 * This function builds the configuration object for the Layout fieldset
2017 *
2018 * @param object table: the element being edited, if any
2019 *
2020 * @return object the fieldset configuration object
2021 */
2022 buildLayoutFieldsetConfig: function(element) {
2023 var itemsConfig = [];
2024 var nodeName = element ? element.nodeName.toLowerCase() : 'table';
2025 switch(nodeName) {
2026 case 'table' :
2027 var widthTitle = 'Table width';
2028 var heightTitle = 'Table height';
2029 break;
2030 case 'tr' :
2031 var widthTitle = 'Row width';
2032 var heightTitle = 'Row height';
2033 break;
2034 case 'td' :
2035 case 'th' :
2036 var widthTitle = 'Cell width';
2037 var heightTitle = 'Cell height';
2038 }
2039 if (this.removedProperties.indexOf('width') === -1) {
2040 var widthUnitStore = new Ext.data.ArrayStore({
2041 autoDestroy: true,
2042 fields: [ { name: 'text'}, { name: 'value'}],
2043 data: [
2044 [this.localize('percent'), '%'],
2045 [this.localize('pixels'), 'px'],
2046 [this.localize('em'), 'em']
2047 ]
2048 });
2049 this.removeOptions(widthUnitStore, 'widthUnit');
2050 itemsConfig.push({
2051 fieldLabel: this.getHelpTip(widthTitle, 'Width:'),
2052 labelSeparator: '',
2053 itemId: 'f_st_width',
2054 value: element ? this.getLength(element.style.width) : ((this.properties.width && this.properties.width.defaultValue) ? this.properties.width.defaultValue : ''),
2055 width: 30,
2056 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize(widthTitle)
2057 });
2058 itemsConfig.push(Ext.apply({
2059 xtype: 'combo',
2060 fieldLabel: this.getHelpTip('Width unit', 'Width unit'),
2061 itemId: 'f_st_widthUnit',
2062 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Width unit'),
2063 store: widthUnitStore,
2064 width: (this.properties['widthUnit'] && this.properties['widthUnit'].width) ? this.properties['widthUnit'].width : 70,
2065 value: element ? (/%/.test(element.style.width) ? '%' : (/px/.test(element.style.width) ? 'px' : 'em')) : ((this.properties.widthUnit && this.properties.widthUnit.defaultValue) ? this.properties.widthUnit.defaultValue : '%')
2066 }, this.configDefaults['combo']));
2067 }
2068 if (this.removedProperties.indexOf('height') === -1) {
2069 var heightUnitStore = new Ext.data.ArrayStore({
2070 autoDestroy: true,
2071 fields: [ { name: 'text'}, { name: 'value'}],
2072 data: [
2073 [this.localize('percent'), '%'],
2074 [this.localize('pixels'), 'px'],
2075 [this.localize('em'), 'em']
2076 ]
2077 });
2078 this.removeOptions(heightUnitStore, 'heightUnit');
2079 itemsConfig.push({
2080 fieldLabel: this.getHelpTip(heightTitle, 'Height:'),
2081 labelSeparator: '',
2082 itemId: 'f_st_height',
2083 value: element ? this.getLength(element.style.height) : ((this.properties.height && this.properties.height.defaultValue) ? this.properties.height.defaultValue : ''),
2084 width: 30,
2085 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize(heightTitle)
2086 });
2087 itemsConfig.push(Ext.apply({
2088 xtype: 'combo',
2089 fieldLabel: this.getHelpTip('Height unit', 'Height unit'),
2090 itemId: 'f_st_heightUnit',
2091 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Height unit'),
2092 store: heightUnitStore,
2093 width: (this.properties['heightUnit'] && this.properties['heightUnit'].width) ? this.properties['heightUnit'].width : 70,
2094 value: element ? (/%/.test(element.style.height) ? '%' : (/px/.test(element.style.height) ? 'px' : 'em')) : ((this.properties.heightUnit && this.properties.heightUnit.defaultValue) ? this.properties.heightUnit.defaultValue : '%')
2095 }, this.configDefaults['combo']));
2096 }
2097 if (nodeName == 'table' && this.removedProperties.indexOf('float') === -1) {
2098 var floatStore = new Ext.data.ArrayStore({
2099 autoDestroy: true,
2100 fields: [ { name: 'text'}, { name: 'value'}],
2101 data: [
2102 [this.localize('Not set'), 'not set'],
2103 [this.localize('Left'), 'left'],
2104 [this.localize('Right'), 'right']
2105 ]
2106 });
2107 this.removeOptions(floatStore, 'float');
2108 itemsConfig.push(Ext.apply({
2109 xtype: 'combo',
2110 fieldLabel: this.getHelpTip('tableFloat', 'Float:'),
2111 labelSeparator: '',
2112 itemId: 'f_st_float',
2113 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Specifies where the table should float'),
2114 store: floatStore,
2115 width: (this.properties['float'] && this.properties['float'].width) ? this.properties['float'].width : 120,
2116 value: element ? (Ext.get(element).hasClass(this.floatLeft) ? 'left' : (Ext.get(element).hasClass(this.floatRight) ? 'right' : 'not set')) : this.floatDefault
2117 }, this.configDefaults['combo']));
2118 }
2119 return {
2120 xtype: 'fieldset',
2121 title: this.localize('Layout'),
2122 defaultType: 'numberfield',
2123 defaults: {
2124 helpIcon: true
2125 },
2126 items: itemsConfig
2127 };
2128 },
2129 /*
2130 * This function builds the configuration object for the Layout fieldset
2131 *
2132 * @param object element: the element being edited, if any
2133 *
2134 * @return object the fieldset configuration object
2135 */
2136 buildAlignmentFieldsetConfig: function (element) {
2137 var itemsConfig = [];
2138 // Text alignment
2139 var selectedTextAlign = 'not set';
2140 var blockElements = this.getPluginInstance('BlockElements');
2141 if (element && blockElements) {
2142 Ext.iterate(this.convertAlignment, function (value) {
2143 if (Ext.get(element).hasClass(blockElements.useClass[this.convertAlignment[value]])) {
2144 selectedTextAlign = value;
2145 return false;
2146 }
2147 return true;
2148 }, this);
2149 } else {
2150 selectedTextAlign = (element && element.style.textAlign) ? element.style.textAlign : 'not set';
2151 }
2152 itemsConfig.push(Ext.apply({
2153 xtype: 'combo',
2154 fieldLabel: this.getHelpTip('textAlignment', 'Text alignment:'),
2155 itemId: 'f_st_textAlign',
2156 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Horizontal alignment of text within cell'),
2157 store: new Ext.data.ArrayStore({
2158 autoDestroy: true,
2159 fields: [ { name: 'text'}, { name: 'value'}],
2160 data: [
2161 [this.localize('Not set'), 'not set'],
2162 [this.localize('Left'), 'left'],
2163 [this.localize('Center'), 'center'],
2164 [this.localize('Right'), 'right'],
2165 [this.localize('Justify'), 'justify']
2166 ]
2167 }),
2168 width: (this.properties['textAlign'] && this.properties['textAlign'].width) ? this.properties['textAlign'].width : 100,
2169 value: selectedTextAlign
2170 }, this.configDefaults['combo']));
2171 // Vertical alignment
2172 itemsConfig.push(Ext.apply({
2173 xtype: 'combo',
2174 fieldLabel: this.getHelpTip('verticalAlignment', 'Vertical alignment:'),
2175 itemId: 'f_st_vertAlign',
2176 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Vertical alignment of content within cell'),
2177 store: new Ext.data.ArrayStore({
2178 autoDestroy: true,
2179 fields: [ { name: 'text'}, { name: 'value'}],
2180 data: [
2181 [this.localize('Not set'), 'not set'],
2182 [this.localize('Top'), 'top'],
2183 [this.localize('Middle'), 'middle'],
2184 [this.localize('Bottom'), 'bottom'],
2185 [this.localize('Baseline'), 'baseline']
2186 ]
2187 }),
2188 width: (this.properties['verticalAlign'] && this.properties['verticalAlign'].width) ? this.properties['verticalAlign'].width : 100,
2189 value: (element && element.style.verticalAlign) ? element.style.verticalAlign : 'not set'
2190 }, this.configDefaults['combo']));
2191 return {
2192 xtype: 'fieldset',
2193 title: this.localize('Alignment'),
2194 defaults: {
2195 labelSeparator: ''
2196 },
2197 items: itemsConfig
2198 };
2199 },
2200 /*
2201 * This function builds the configuration object for the Borders fieldset
2202 *
2203 * @param object element: the element being edited, if any
2204 *
2205 * @return object the fieldset configuration object
2206 */
2207 buildBordersFieldsetConfig: function (element) {
2208 var itemsConfig = [];
2209 var nodeName = element ? element.nodeName.toLowerCase() : 'table';
2210 // Border style
2211 var borderStyleStore = new Ext.data.ArrayStore({
2212 autoDestroy: true,
2213 fields: [ { name: 'text'}, { name: 'value'}],
2214 data: [
2215 [this.localize('Not set'), 'not set'],
2216 [this.localize('No border'), 'none'],
2217 [this.localize('Dotted'), 'dotted'],
2218 [this.localize('Dashed'), 'dashed'],
2219 [this.localize('Solid'), 'solid'],
2220 [this.localize('Double'), 'double'],
2221 [this.localize('Groove'), 'groove'],
2222 [this.localize('Ridge'), 'ridge'],
2223 [this.localize('Inset'), 'inset'],
2224 [this.localize('Outset'), 'outset']
2225 ]
2226 });
2227 this.removeOptions(borderStyleStore, 'borderStyle');
2228 // Gecko reports "solid solid solid solid" for "border-style: solid".
2229 // That is, "top right bottom left" -- we only consider the first value.
2230 var selectedBorderStyle = element && element.style.borderStyle ? element.style.borderStyle : ((this.properties.borderWidth) ? ((this.properties.borderStyle && this.properties.borderStyle.defaultValue) ? this.properties.borderStyle.defaultValue : 'solid') : 'not set');
2231 itemsConfig.push(Ext.apply({
2232 xtype: 'combo',
2233 fieldLabel: this.getHelpTip('borderStyle', 'Border style:'),
2234 itemId: 'f_st_borderStyle',
2235 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Border style'),
2236 store: borderStyleStore,
2237 width: (this.properties.borderStyle && this.properties.borderStyle.width) ? this.properties.borderStyle.width : 150,
2238 value: selectedBorderStyle,
2239 listeners: {
2240 change: {
2241 fn: this.setBorderFieldsDisabled
2242 }
2243 }
2244 }, this.configDefaults['combo']));
2245 // Border width
2246 itemsConfig.push({
2247 fieldLabel: this.getHelpTip('borderWidth', 'Border width:'),
2248 itemId: 'f_st_borderWidth',
2249 value: element ? this.getLength(element.style.borderWidth) : ((this.properties.borderWidth && this.properties.borderWidth.defaultValue) ? this.properties.borderWidth.defaultValue : ''),
2250 width: 30,
2251 minValue: 0,
2252 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Border width'),
2253 disabled: (selectedBorderStyle === 'none')
2254 });
2255 // Border color
2256 itemsConfig.push({
2257 xtype: 'colorpalettefield',
2258 fieldLabel: this.getHelpTip('borderColor', 'Color:'),
2259 itemId: 'f_st_borderColor',
2260 colors: this.editorConfiguration.disableColorPicker ? [] : null,
2261 colorsConfiguration: this.editorConfiguration.colors,
2262 value: HTMLArea.util.Color.colorToHex(element && element.style.borderColor ? element.style.borderColor : ((this.properties.borderColor && this.properties.borderColor.defaultValue) ? this.properties.borderColor.defaultValue : '')).substr(1, 6),
2263 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Border color'),
2264 disabled: (selectedBorderStyle === 'none')
2265 });
2266 if (nodeName === 'table') {
2267 // Collapsed borders
2268 itemsConfig.push(Ext.apply({
2269 xtype: 'combo',
2270 fieldLabel: this.getHelpTip('collapsedBorders', 'Collapsed borders'),
2271 labelSeparator: ':',
2272 itemId: 'f_st_borderCollapse',
2273 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Collapsed borders'),
2274 store: new Ext.data.ArrayStore({
2275 autoDestroy: true,
2276 fields: [ { name: 'text'}, { name: 'value'}],
2277 data: [
2278 [this.localize('Not set'), 'not set'],
2279 [this.localize('Collapsed borders'), 'collapse'],
2280 [this.localize('Detached borders'), 'separate']
2281 ]
2282 }),
2283 width: (this.properties.borderCollapse && this.properties.borderCollapse.width) ? this.properties.borderCollapse.width : 150,
2284 value: element && element.style.borderCollapse ? element.style.borderCollapse : 'not set',
2285 disabled: (selectedBorderStyle === 'none')
2286 }, this.configDefaults['combo']));
2287 // Frame
2288 itemsConfig.push(Ext.apply({
2289 xtype: 'combo',
2290 fieldLabel: this.getHelpTip('frames', 'Frames:'),
2291 itemId: 'f_frames',
2292 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Specifies which sides should have a border'),
2293 store: new Ext.data.ArrayStore({
2294 autoDestroy: true,
2295 fields: [ { name: 'text'}, { name: 'value'}],
2296 data: [
2297 [this.localize('Not set'), 'not set'],
2298 [this.localize('No sides'), 'void'],
2299 [this.localize('The top side only'), 'above'],
2300 [this.localize('The bottom side only'), 'below'],
2301 [this.localize('The top and bottom sides only'), 'hsides'],
2302 [this.localize('The right and left sides only'), 'vsides'],
2303 [this.localize('The left-hand side only'), 'lhs'],
2304 [this.localize('The right-hand side only'), 'rhs'],
2305 [this.localize('All four sides'), 'box']
2306 ]
2307 }),
2308 width: (this.properties.frame && this.properties.frame.width) ? this.properties.frame.width : 250,
2309 value: (element && element.frame) ? element.frame : 'not set',
2310 disabled: (selectedBorderStyle === 'none')
2311 }, this.configDefaults['combo']));
2312 // Rules
2313 itemsConfig.push(Ext.apply({
2314 xtype: 'combo',
2315 fieldLabel: this.getHelpTip('rules', 'Rules:'),
2316 itemId: 'f_rules',
2317 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Specifies where rules should be displayed'),
2318 store: new Ext.data.ArrayStore({
2319 autoDestroy: true,
2320 fields: [ { name: 'text'}, { name: 'value'}],
2321 data: [
2322 [this.localize('Not set'), 'not set'],
2323 [this.localize('No rules'), 'none'],
2324 [this.localize('Rules will appear between rows only'), 'rows'],
2325 [this.localize('Rules will appear between columns only'), 'cols'],
2326 [this.localize('Rules will appear between all rows and columns'), 'all']
2327 ]
2328 }),
2329 width: (this.properties.rules && this.properties.rules.width) ? this.properties.rules.width : 360,
2330 value: (element && element.rules) ? element.rules : 'not set'
2331 }, this.configDefaults['combo']));
2332 }
2333 return {
2334 xtype: 'fieldset',
2335 title: this.localize('Frame and borders'),
2336 defaultType: 'numberfield',
2337 defaults: {
2338 labelSeparator: '',
2339 helpIcon: true
2340 },
2341 items: itemsConfig
2342 };
2343 },
2344 /*
2345 * onChange handler: enable/disable other fields of the same fieldset
2346 */
2347 setBorderFieldsDisabled: function (field, value) {
2348 field.ownerCt.findBy(function (item) {
2349 var itemId = item.getItemId();
2350 if (itemId == 'f_st_borderStyle' || itemId == 'f_rules') {
2351 return false;
2352 } else if (value === 'none') {
2353 switch (item.getXType()) {
2354 case 'numberfield':
2355 item.setValue(0);
2356 break;
2357 case 'colorpalettefield':
2358 item.setValue('');
2359 break;
2360 default:
2361 item.setValue('not set');
2362 break;
2363 }
2364 item.setDisabled(true);
2365 } else {
2366 item.setDisabled(false);
2367 }
2368 return true;
2369 });
2370 },
2371 /*
2372 * This function builds the configuration object for the Colors fieldset
2373 *
2374 * @param object element: the element being edited, if any
2375 *
2376 * @return object the fieldset configuration object
2377 */
2378 buildColorsFieldsetConfig: function (element) {
2379 var itemsConfig = [];
2380 // Text color
2381 itemsConfig.push({
2382 xtype: 'colorpalettefield',
2383 fieldLabel: this.getHelpTip('textColor', 'FG Color:'),
2384 itemId: 'f_st_color',
2385 colors: this.editorConfiguration.disableColorPicker ? [] : null,
2386 colorsConfiguration: this.editorConfiguration.colors,
2387 value: HTMLArea.util.Color.colorToHex(element && element.style.color ? element.style.color : ((this.properties.color && this.properties.color.defaultValue) ? this.properties.color.defaultValue : '')).substr(1, 6)
2388 });
2389 // Background color
2390 itemsConfig.push({
2391 xtype: 'colorpalettefield',
2392 fieldLabel: this.getHelpTip('backgroundColor', 'Background:'),
2393 itemId: 'f_st_backgroundColor',
2394 colors: this.editorConfiguration.disableColorPicker ? [] : null,
2395 colorsConfiguration: this.editorConfiguration.colors,
2396 value: HTMLArea.util.Color.colorToHex(element && element.style.backgroundColor ? element.style.backgroundColor : ((this.properties.backgroundColor && this.properties.backgroundColor.defaultValue) ? this.properties.backgroundColor.defaultValue : '')).substr(1, 6)
2397 });
2398 // Background image
2399 itemsConfig.push({
2400 fieldLabel: this.getHelpTip('backgroundImage', 'Image URL:'),
2401 itemId: 'f_st_backgroundImage',
2402 value: element && element.style.backgroundImage.match(/url\(\s*(.*?)\s*\)/) ? RegExp.$1 : '',
2403 width: (this.properties.backgroundImage && this.properties.backgroundImage.width) ? this.properties.backgroundImage.width : 300,
2404 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('URL of the background image'),
2405 helpIcon: true
2406 });
2407 return {
2408 xtype: 'fieldset',
2409 title: this.localize('Background and colors'),
2410 defaultType: 'textfield',
2411 defaults: {
2412 labelSeparator: ''
2413 },
2414 items: itemsConfig
2415 };
2416 },
2417 /*
2418 * This function builds the configuration object for the Cell Type fieldset
2419 *
2420 * @param object element: the element being edited, if any
2421 * @param boolean column: true if the element is a column, false if the element is a cell
2422 *
2423 * @return object the fieldset configuration object
2424 */
2425 buildCellTypeFieldsetConfig: function (element, column) {
2426 var itemsConfig = [];
2427 if (column) {
2428 var data = [
2429 [this.localize('Data cells'), 'td'],
2430 [this.localize('Headers for rows'), 'throw'],
2431 [this.localize('Headers for row groups'), 'throwgroup']
2432 ];
2433 } else {
2434 var data = [
2435 [this.localize('Normal'), 'td'],
2436 [this.localize('Header for column'), 'thcol'],
2437 [this.localize('Header for row'), 'throw'],
2438 [this.localize('Header for row group'), 'throwgroup']
2439 ];
2440 }
2441 // onChange handler: reset the CSS class dropdown and show/hide abbr field when the cell type changes
2442 // @param object cellTypeField: the combo object
2443 // @param object record: the selected record
2444 // @return void
2445 var self = this;
2446 function cellTypeChange(cellTypeField, record) {
2447 var value = record.get('value');
2448 var styleCombo = self.dialog.find('itemId', 'f_class')[0];
2449 if (styleCombo) {
2450 self.setStyleOptions(styleCombo, element, value.substring(0,2));
2451 }
2452 // abbr field present only for single cell, not for column
2453 var abbrField = self.dialog.find('itemId', 'f_cell_abbr')[0];
2454 if (abbrField) {
2455 abbrField.setVisible(value != 'td');
2456 abbrField.label.setVisible(value != 'td');
2457 }
2458 }
2459 var selected = element.nodeName.toLowerCase() + element.scope.toLowerCase();
2460 itemsConfig.push(Ext.apply({
2461 xtype: 'combo',
2462 fieldLabel: column ? this.getHelpTip('columnCellsType', 'Type of cells of the column') : this.getHelpTip('cellType', 'Type of cell'),
2463 itemId: 'f_cell_type',
2464 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize(column ? 'Specifies the type of cells' : 'Specifies the type of cell'),
2465 store: new Ext.data.ArrayStore({
2466 autoDestroy: true,
2467 fields: [ { name: 'text'}, { name: 'value'}],
2468 data: data
2469 }),
2470 width: (this.properties.cellType && this.properties.cellType.width) ? this.properties.cellType.width : 250,
2471 value: (column && selected == 'thcol') ? 'td' : selected,
2472 listeners: {
2473 select: {
2474 fn: cellTypeChange,
2475 scope: this
2476 }
2477 }
2478 }, this.configDefaults['combo']));
2479 if (!column) {
2480 itemsConfig.push({
2481 xtype: 'textfield',
2482 fieldLabel: this.getHelpTip('cellAbbreviation', 'Abbreviation'),
2483 labelSeparator: ':',
2484 itemId: 'f_cell_abbr',
2485 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Header abbreviation'),
2486 width: 300,
2487 value: element.abbr,
2488 hideMode: 'visibility',
2489 hidden: (selected == 'td'),
2490 hideLabel: (selected == 'td')
2491 });
2492 }
2493 return {
2494 xtype: 'fieldset',
2495 title: this.localize(column ? 'Type of cells' : 'Cell Type and Scope'),
2496 defaults: {
2497 labelSeparator: '',
2498 helpIcon: true
2499 },
2500 items: itemsConfig
2501 };
2502 },
2503 /*
2504 * This function builds the configuration object for the Row Group fieldset
2505 *
2506 * @param object element: the row being edited, if any
2507 *
2508 * @return object the fieldset configuration object
2509 */
2510 buildRowGroupFieldsetConfig: function (element) {
2511 var itemsConfig = [];
2512 var current = element.parentNode.nodeName.toLowerCase();
2513 // onChange handler: show/hide cell conversion checkbox with appropriate label
2514 // @param object field: the combo object
2515 // @param object record: the selected record
2516 // @return void
2517 function displayCheckbox(field, record, index) {
2518 var checkBox = field.ownerCt.getComponent('f_convertCells');
2519 var value = record.get('value');
2520 if (current !== value && (current === 'thead' || value === 'thead')) {
2521 checkBox.label.dom.innerHTML = (value === 'thead') ? this.localize('Make cells header cells') : this.localize('Make cells data cells');
2522 checkBox.show();
2523 checkBox.label.show();
2524 checkBox.setValue(true);
2525 } else {
2526 checkBox.setValue(false);
2527 checkBox.hide();
2528 checkBox.label.hide();
2529 }
2530 }
2531 itemsConfig.push(Ext.apply({
2532 xtype: 'combo',
2533 fieldLabel: this.getHelpTip('rowGroup', 'Row group:'),
2534 itemId: 'f_rowgroup',
2535 helpTitle: Ext.isDefined(TYPO3.ContextHelp) ? '' : this.localize('Table section'),
2536 store: new Ext.data.ArrayStore({
2537 autoDestroy: true,