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