a56d5ea8c4e87dc1b67fce447b55edd7bee2098f
[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-2009 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 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
70 if (this.buttonsConfiguration.blockstyle) {
71 this.tags = this.editorConfiguration.buttons.blockstyle.tags;
72 }
73
74 this.tableParts = ["tfoot", "thead", "tbody"];
75 this.convertAlignment = { "not set" : "none", "left" : "JustifyLeft", "center" : "JustifyCenter", "right" : "JustifyRight", "justify" : "JustifyFull" };
76
77 /*
78 * Registering plugin "About" information
79 */
80 var pluginInformation = {
81 version : "4.2",
82 developer : "Mihai Bazon & Stanislas Rolland",
83 developerUrl : "http://www.sjbr.ca/",
84 copyrightOwner : "Mihai Bazon & Stanislas Rolland",
85 sponsor : this.localize("Technische Universitat Ilmenau") + " & Zapatec Inc.",
86 sponsorUrl : "http://www.tu-ilmenau.de/",
87 license : "GPL"
88 };
89 this.registerPluginInformation(pluginInformation);
90
91 /*
92 * Registering the buttons
93 */
94 var hideToggleBorders = this.editorConfiguration.hideTableOperationsInToolbar && !(this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.keepInToolbar);
95 var buttonList = this.buttonList, buttonId;
96 for (var i = 0, n = buttonList.length; i < n; ++i) {
97 var button = buttonList[i];
98 buttonId = (button[0] === "InsertTable") ? button[0] : ("TO-" + button[0]);
99 var buttonConfiguration = {
100 id : buttonId,
101 tooltip : this.localize((buttonId === "InsertTable") ? "Insert Table" : buttonId),
102 action : "onButtonPress",
103 hotKey : (this.buttonsConfiguration[button[2]] ? this.buttonsConfiguration[button[2]].hotKey : null),
104 context : button[1],
105 hide : ((buttonId == "TO-toggle-borders") ? hideToggleBorders : ((button[0] === "InsertTable") ? false : this.editorConfiguration.hideTableOperationsInToolbar)),
106 dialog : button[3]
107 };
108 this.registerButton(buttonConfiguration);
109 }
110
111 return true;
112 },
113
114 /*
115 * The list of buttons added by this plugin
116 */
117 buttonList : [
118 ["InsertTable", null, "table", true],
119 ["toggle-borders", null, "toggleborders", false],
120 ["table-prop", "table", "tableproperties", true],
121 ["table-restyle", "table", "tablerestyle", false],
122 ["row-prop", "tr", "rowproperties", true],
123 ["row-insert-above", "tr", "rowinsertabove", false],
124 ["row-insert-under", "tr", "rowinsertunder", false],
125 ["row-delete", "tr", "rowdelete", false],
126 ["row-split", "td,th[rowSpan!=1]", "rowsplit", false],
127 ["col-prop", "td,th", "columnproperties", true],
128 ["col-insert-before", "td,th", "columninsertbefore", false],
129 ["col-insert-after", "td,th", "columninsertafter", false],
130 ["col-delete", "td,th", "columndelete", false],
131 ["col-split", "td,th[colSpan!=1]", "columnsplit", false],
132 ["cell-prop", "td,th", "cellproperties", true],
133 ["cell-insert-before", "td,th", "cellinsertbefore", false],
134 ["cell-insert-after", "td,th", "cellinsertafter", false],
135 ["cell-delete", "td,th", "celldelete", false],
136 ["cell-merge", "tr", "cellmerge", false],
137 ["cell-split", "td,th[colSpan!=1,rowSpan!=1]", "cellsplit", false]
138 ],
139
140 /*
141 * Retrieve the closest element having the specified nodeName in the list of
142 * ancestors of the current selection/caret.
143 */
144 getClosest : function (nodeName) {
145 var editor = this.editor;
146 var ancestors = editor.getAllAncestors();
147 var ret = null;
148 nodeName = ("" + nodeName).toLowerCase();
149 for (var i=0; i < ancestors.length; ++i) {
150 var el = ancestors[i];
151 if (el.nodeName.toLowerCase() == nodeName) {
152 ret = el;
153 break;
154 }
155 }
156 return ret;
157 },
158
159 /*
160 * Open the table properties dialogue
161 */
162 dialogTableProperties : function (buttonId) {
163 var tablePropertiesInitFunctRef = this.makeFunctionReference("tablePropertiesInit");
164 var insert = (buttonId === "InsertTable");
165 var arguments = {
166 buttonId : buttonId,
167 title : (insert ? "Insert Table" : "Table Properties"),
168 initialize : tablePropertiesInitFunctRef,
169 element : (insert ? null : this.getClosest("table"))
170 };
171 var dimensions = {
172 width : 860,
173 height : 620
174 };
175 this.dialog = this.openDialog((insert ? "InsertTable" : "TO-table-prop"), "", "tablePropertiesUpdate", arguments, dimensions);
176 },
177
178 /*
179 * Initialize the table insertion or table properties dialog
180 */
181 tablePropertiesInit : function(dialog) {
182 var doc = dialog.document;
183 var content = dialog.content;
184 var table = dialog.arguments.element;
185 this.removedFieldsets = this.buttonsConfiguration[table?"tableproperties":"table"].removeFieldsets ? this.buttonsConfiguration[table?"tableproperties":"table"].removeFieldsets : "";
186 this.properties = this.buttonsConfiguration.table.properties;
187 this.removedProperties = (this.properties && this.properties.removed) ? this.properties.removed : "";
188 TableOperations.buildTitle(doc, content, dialog.arguments.title);
189 TableOperations.insertSpace(doc, content);
190 if (this.removedFieldsets.indexOf("description") == -1) {
191 TableOperations.buildDescriptionFieldset(doc, table, content, "floating");
192 }
193 if (this.removedFieldsets.indexOf("spacing") == -1) TableOperations.buildSpacingFieldset(doc, table, content);
194 TableOperations.insertSpace(doc, content);
195 this.buildSizeAndHeadersFieldset(doc, table, content, "floating");
196 if (this.removedFieldsets.indexOf("style") == -1 && dialog.editor.config.customSelects.BlockStyle) {
197 var blockStyle = dialog.editor.plugins.BlockStyle.instance;
198 if (blockStyle && blockStyle.cssLoaded) {
199 this.buildStylingFieldset(doc, table, content, null, dialog.arguments.buttonId);
200 TableOperations.insertSpace(doc, content);
201 }
202 }
203 this.buildLanguageFieldset(doc, table, content, "floating");
204 if (this.removedFieldsets.indexOf("layout") == -1) this.buildLayoutFieldset(doc, table, content, "floating");
205 if (this.removedFieldsets.indexOf("alignment") == -1) this.buildAlignmentFieldset(doc, table, content);
206 TableOperations.insertSpace(doc, content);
207 if (this.removedFieldsets.indexOf("borders") == -1) this.buildBordersFieldset(dialog.dialogWindow, doc, dialog.editor, table, content);
208 if (this.removedFieldsets.indexOf("color") == -1) TableOperations.buildColorsFieldset(dialog.dialogWindow, doc, dialog.editor, table, content);
209 dialog.addButtons("ok", "cancel");
210 },
211
212 /*
213 * Insert the table or update the table properties and close the dialogue
214 */
215 tablePropertiesUpdate : function(dialog, params) {
216 if (this.buttonsConfiguration.table.properties && this.buttonsConfiguration.table.properties.required) {
217 if (this.buttonsConfiguration.table.properties.required.indexOf("captionOrSummary") != -1) {
218 if (!/\S/.test(params.f_caption) && !/\S/.test(params.f_summary)) {
219 dialog.dialogWindow.alert(this.localize("captionOrSummary" + "-required"));
220 var el = dialog.document.getElementById("f_caption");
221 el.focus();
222 return false;
223 }
224 } else {
225 var required = { "f_caption": "caption", "f_summary": "summary" };
226 for (var i in required) {
227 if (required.hasOwnProperty(i)) {
228 var el = dialog.document.getElementById(i);
229 if (!el.value && this.buttonsConfiguration.table.properties.required.indexOf(required[i]) != -1) {
230 dialog.dialogWindow.alert(this.localize(required[i] + "-required"));
231 el.focus();
232 return false;
233 }
234 }
235 }
236 }
237 }
238 var doc = dialog.editor._doc;
239 if (dialog.buttonId === "InsertTable") {
240 var required = { "f_rows": "You must enter a number of rows", "f_cols": "You must enter a number of columns" };
241 for (var i in required) {
242 if (required.hasOwnProperty(i)) {
243 var el = dialog.document.getElementById(i);
244 if (!el.value) {
245 dialog.dialogWindow.alert(this.localize(required[i]));
246 el.focus();
247 return false;
248 }
249 }
250 }
251 var table = doc.createElement("table");
252 var tbody = doc.createElement("tbody");
253 table.appendChild(tbody);
254 for (var i = params.f_rows; --i >= 0;) {
255 var tr = doc.createElement("tr");
256 tbody.appendChild(tr);
257 for (var j = params.f_cols; --j >= 0;) {
258 var td = doc.createElement("td");
259 if (HTMLArea.is_gecko) td.innerHTML = "<br />";
260 tr.appendChild(td);
261 }
262 }
263 } else {
264 var table = dialog.arguments.element;
265 }
266 table = this.setHeaders(table, params);
267 table = this.processStyle(table, params);
268 table.removeAttribute("border");
269 for (var i in params) {
270 if (params.hasOwnProperty(i)) {
271 var val = params[i];
272 switch (i) {
273 case "f_caption":
274 if (/\S/.test(val)) {
275 // contains non white-space characters
276 var caption = table.getElementsByTagName("caption");
277 if (caption) {
278 caption = caption[0];
279 }
280 if (!caption) {
281 var caption = doc.createElement("caption");
282 table.insertBefore(caption, table.firstChild);
283 }
284 caption.innerHTML = val;
285 } else {
286 // delete the caption if found
287 if (table.caption) table.deleteCaption();
288 }
289 break;
290 case "f_summary":
291 table.summary = val;
292 break;
293 case "f_width":
294 table.style.width = ("" + val) + params.f_unit;
295 break;
296 case "f_align":
297 table.align = val;
298 break;
299 case "f_spacing":
300 table.cellSpacing = val;
301 break;
302 case "f_padding":
303 table.cellPadding = val;
304 break;
305 case "f_frames":
306 table.frame = (val != "not set") ? val : "";
307 break;
308 case "f_rules":
309 if (val != "not set") table.rules = val;
310 else table.removeAttribute("rules");
311 break;
312 case "f_st_float":
313 switch (val) {
314 case "not set":
315 HTMLArea._removeClass(table, this.floatRight);
316 HTMLArea._removeClass(table, this.floatLeft);
317 break;
318 case "right":
319 HTMLArea._removeClass(table, this.floatLeft);
320 HTMLArea._addClass(table, this.floatRight);
321 break;
322 case "left":
323 HTMLArea._removeClass(table, this.floatRight);
324 HTMLArea._addClass(table, this.floatLeft);
325 break;
326 }
327 break;
328 case "f_st_textAlign":
329 if (this.editor.plugins.BlockElements) {
330 this.editor.plugins.BlockElements.instance.toggleAlignmentClass(table, this.convertAlignment[val]);
331 table.style.textAlign = "";
332 }
333 break;
334 case "f_class":
335 case "f_class_tbody":
336 case "f_class_thead":
337 case "f_class_tfoot":
338 var tpart = table;
339 if (i.length > 7) tpart = table.getElementsByTagName(i.substring(8,13))[0];
340 if (tpart) {
341 this.editor.plugins.BlockStyle.instance.applyClassChange(tpart, val);
342 }
343 break;
344 case "f_lang":
345 this.getPluginInstance("Language").setLanguageAttributes(table, val);
346 break;
347 case "f_dir":
348 table.dir = (val != "not set") ? val : "";
349 break;
350 }
351 }
352 }
353 if (dialog.buttonId === "InsertTable") {
354 if (HTMLArea.is_gecko) {
355 this.editor.insertNodeAtSelection(table);
356 } else {
357 table.id = "htmlarea_table_insert";
358 this.editor.insertNodeAtSelection(table);
359 table = this.editor._doc.getElementById(table.id);
360 table.id = "";
361 }
362 this.editor.selectNodeContents(table.rows[0].cells[0], true);
363 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnTableCreation) {
364 this.toggleBorders(true);
365 }
366 }
367 dialog.close();
368 },
369
370 /*
371 * Open the row/column/cell properties dialogue
372 */
373 dialogRowCellProperties : function (cell, column) {
374 // retrieve existing values
375 if (cell) {
376 var element = this.getClosest("td");
377 if (!element) var element = this.getClosest("th");
378 } else {
379 var element = this.getClosest("tr");
380 }
381 if (element) {
382 var rowCellPropertiesInitFunctRef = this.makeFunctionReference("rowCellPropertiesInit");
383 var arguments = {
384 title : (cell ? (column ? "Column Properties" : "Cell Properties") : "Row Properties"),
385 initialize : rowCellPropertiesInitFunctRef,
386 element : element,
387 cell : cell,
388 column : column
389 };
390 this.dialog = this.openDialog("TO-" + (cell ? (column ? "col-prop" : "cell-prop") :"row-prop"), "", "rowCellPropertiesUpdate", arguments, { width : 660, height : 460 });
391 }
392 },
393
394 /*
395 * Initialize the row/column/cell properties dialogue
396 */
397 rowCellPropertiesInit : function(dialog) {
398 var doc = dialog.document;
399 var content = dialog.content;
400 var element = dialog.arguments.element;
401 var cell = dialog.arguments.cell;
402 var column = dialog.arguments.column;
403 this.removedFieldsets = this.buttonsConfiguration[cell?(column?"columnproperties":"cellproperties"):"rowproperties"].removeFieldsets ? this.buttonsConfiguration[cell?(column?"columnproperties":"cellproperties"):"rowproperties"].removeFieldsets : "";
404 this.properties = this.buttonsConfiguration[(cell ||column)?"cellproperties":"rowproperties"].properties;
405 this.removedProperties = (this.properties && this.properties.removed) ? this.properties.removed : "";
406 TableOperations.buildTitle(doc, content, (cell ? (column ? "Column Properties" : "Cell Properties") : "Row Properties"));
407 TableOperations.insertSpace(doc, content);
408 if (column) {
409 if (this.removedFieldsets.indexOf("columntype") == -1) this.buildCellTypeFieldset(doc, element, content, true);
410 } else if (cell) {
411 if (this.removedFieldsets.indexOf("celltype") == -1) this.buildCellTypeFieldset(doc, element, content, false);
412 } else {
413 if (this.removedFieldsets.indexOf("rowgroup") == -1) TableOperations.buildRowGroupFieldset(dialog.dialogWindow, doc, dialog.editor, element, content);
414 }
415 if (this.removedFieldsets.indexOf("style") == -1 && this.editor.config.customSelects.BlockStyle) {
416 var blockStyle = this.editor.plugins.BlockStyle.instance;
417 if (blockStyle && blockStyle.cssLoaded) {
418 this.buildStylingFieldset(doc, element, content);
419 TableOperations.insertSpace(doc, content);
420 } else {
421 TableOperations.insertSpace(doc, content);
422 }
423 } else {
424 TableOperations.insertSpace(doc, content);
425 }
426 this.buildLanguageFieldset(doc, element, content, "floating");
427 if (this.removedFieldsets.indexOf("layout") == -1) this.buildLayoutFieldset(doc, element, content, "floating");
428 if (this.removedFieldsets.indexOf("alignment") == -1) this.buildAlignmentFieldset(doc, element, content);
429 if (this.removedFieldsets.indexOf("borders") == -1) this.buildBordersFieldset(dialog.dialogWindow, doc, dialog.editor, element, content);
430 if (this.removedFieldsets.indexOf("color") == -1) TableOperations.buildColorsFieldset(dialog.dialogWindow, doc, dialog.editor, element, content);
431 dialog.addButtons("ok", "cancel");
432 },
433
434 /*
435 * Update the row/column/cell properties
436 */
437 rowCellPropertiesUpdate : function(dialog, params) {
438 var element = dialog.arguments.element;
439 var cell = dialog.arguments.cell;
440 var column = dialog.arguments.column;
441 var section = (cell || column) ? element.parentNode.parentNode : element.parentNode;
442 var table = section.parentNode;
443 var elements = new Array();
444 if (column) {
445 elements = this.getColumnCells(dialog.arguments.element);
446 } else {
447 elements.push(dialog.arguments.element);
448 }
449 for (var k = elements.length; --k >= 0;) {
450 var element = elements[k];
451 element = this.processStyle(element, params);
452 for (var i in params) {
453 var val = params[i];
454 switch (i) {
455 case "f_cell_type":
456 if (val.substring(0,2) != element.nodeName.toLowerCase()) {
457 element = this.remapCell(element, val.substring(0,2));
458 this.editor.selectNodeContents(element, true);
459 }
460 if (val.substring(2,10) != element.scope) {
461 element.scope = val.substring(2,10);
462 }
463 break;
464 case "f_rowgroup":
465 var nodeName = section.nodeName.toLowerCase();
466 if (val != nodeName) {
467 var newSection = table.getElementsByTagName(val)[0];
468 if (!newSection) var newSection = table.insertBefore(dialog.editor._doc.createElement(val), table.getElementsByTagName("tbody")[0]);
469 if (nodeName == "thead" && val == "tbody") var newElement = newSection.insertBefore(element, newSection.firstChild);
470 else var newElement = newSection.appendChild(element);
471 if (!section.hasChildNodes()) table.removeChild(section);
472 }
473 if (params.f_convertCells) {
474 if (val == "thead") {
475 this.remapRowCells(element, "th");
476 } else {
477 this.remapRowCells(element, "td");
478 }
479 }
480 break;
481 case "f_st_textAlign":
482 if (this.editor.plugins.BlockElements) {
483 this.editor.plugins.BlockElements.instance.toggleAlignmentClass(element, this.convertAlignment[val]);
484 element.style.textAlign = "";
485 }
486 break;
487 case "f_class":
488 this.editor.plugins.BlockStyle.instance.applyClassChange(element, val);
489 break;
490 case "f_lang":
491 this.getPluginInstance("Language").setLanguageAttributes(element, val);
492 break;
493 case "f_dir":
494 element.dir = (val != "not set") ? val : "";
495 break;
496 }
497 }
498 }
499 this.reStyleTable(table);
500 dialog.close();
501 },
502
503 /*
504 * This function gets called when the plugin is generated
505 * Set table borders if requested by configuration
506 */
507 onGenerate : function() {
508 if (this.buttonsConfiguration.toggleborders && this.buttonsConfiguration.toggleborders.setOnRTEOpen) {
509 this.toggleBorders(true);
510 }
511 },
512
513 /*
514 * This function gets called when the toolbar is being updated
515 */
516 onUpdateToolbar : function() {
517 if (this.editor.getMode() === "wysiwyg" && this.editor.isEditable() && this.isButtonInToolbar("TO-toggle-borders")) {
518 this.editor._toolbarObjects["TO-toggle-borders"].state("active", HTMLArea._hasClass(this.editor._doc.body, 'htmlarea-showtableborders'));
519 }
520 },
521
522 /*
523 * This function gets called when a Table Operations button was pressed.
524 *
525 * @param object editor: the editor instance
526 * @param string id: the button id or the key
527 *
528 * @return boolean false if action is completed
529 */
530 onButtonPress : function (editor, id, target) {
531 // Could be a button or its hotkey
532 var buttonId = this.translateHotKey(id);
533 buttonId = buttonId ? buttonId : id;
534
535 var mozbr = HTMLArea.is_gecko ? "<br />" : "";
536 var tableParts = ["tfoot", "thead", "tbody"];
537 var tablePartsIndex = { tfoot : 0, thead : 1, tbody : 2 };
538
539 // helper function that clears the content in a table row
540 function clearRow(tr) {
541 var tds = tr.getElementsByTagName("td");
542 for (var i = tds.length; --i >= 0;) {
543 var td = tds[i];
544 td.rowSpan = 1;
545 td.innerHTML = mozbr;
546 }
547 var tds = tr.getElementsByTagName("th");
548 for (var i = tds.length; --i >= 0;) {
549 var td = tds[i];
550 td.rowSpan = 1;
551 td.innerHTML = mozbr;
552 }
553 };
554
555 function splitRow(td) {
556 var n = parseInt("" + td.rowSpan);
557 var colSpan = td.colSpan;
558 var nodeName = td.nodeName.toLowerCase();
559 td.rowSpan = 1;
560 var tr = td.parentNode;
561 var sectionRowIndex = tr.sectionRowIndex;
562 var rows = tr.parentNode.rows;
563 var index = td.cellIndex;
564 while (--n > 0) {
565 tr = rows[++sectionRowIndex];
566 // Last row
567 if (!tr) tr = td.parentNode.parentNode.appendChild(editor._doc.createElement("tr"));
568 var otd = editor._doc.createElement(nodeName);
569 otd.colSpan = colSpan;
570 otd.innerHTML = mozbr;
571 tr.insertBefore(otd, tr.cells[index]);
572 }
573 };
574
575 function splitCol(td) {
576 var nc = parseInt("" + td.colSpan);
577 var nodeName = td.nodeName.toLowerCase();
578 td.colSpan = 1;
579 var tr = td.parentNode;
580 var ref = td.nextSibling;
581 while (--nc > 0) {
582 var otd = editor._doc.createElement(nodeName);
583 otd.rowSpan = td.rowSpan;
584 otd.innerHTML = mozbr;
585 tr.insertBefore(otd, ref);
586 }
587 };
588
589 function splitCell(td) {
590 var nc = parseInt("" + td.colSpan);
591 splitCol(td);
592 var cells = td.parentNode.cells;
593 var index = td.cellIndex;
594 while (nc-- > 0) {
595 splitRow(cells[index++]);
596 }
597 };
598
599 function selectNextNode(el) {
600 var node = el.nextSibling;
601 while (node && node.nodeType != 1) {
602 node = node.nextSibling;
603 }
604 if (!node) {
605 node = el.previousSibling;
606 while (node && node.nodeType != 1) {
607 node = node.previousSibling;
608 }
609 }
610 if (!node) node = el.parentNode;
611 editor.selectNodeContents(node);
612 };
613
614 function getSelectedCells(sel) {
615 var cell, range, i = 0, cells = [];
616 try {
617 while (range = sel.getRangeAt(i++)) {
618 cell = range.startContainer.childNodes[range.startOffset];
619 while (!/^(td|th|body)$/.test(cell.nodeName.toLowerCase())) cell = cell.parentNode;
620 if (/^(td|th)$/.test(cell.nodeName.toLowerCase())) cells.push(cell);
621 }
622 } catch(e) {
623 /* finished walking through selection */
624 }
625 return cells;
626 };
627
628 function deleteEmptyTable(table) {
629 var lastPart = true;
630 for (var j = tableParts.length; --j >= 0;) {
631 var tablePart = table.getElementsByTagName(tableParts[j])[0];
632 if (tablePart) lastPart = false;
633 }
634 if (lastPart) {
635 selectNextNode(table);
636 table.parentNode.removeChild(table);
637 }
638 };
639
640 function computeCellIndexes(table) {
641 var matrix = [];
642 var lookup = {};
643 for (var m = tableParts.length; --m >= 0;) {
644 var tablePart = table.getElementsByTagName(tableParts[m])[0];
645 if (tablePart) {
646 var rows = tablePart.rows;
647 for (var i = 0, n = rows.length; i < n; i++) {
648 var cells = rows[i].cells;
649 for (var j=0; j< cells.length; j++) {
650 var cell = cells[j];
651 var rowIndex = cell.parentNode.rowIndex;
652 var cellId = tableParts[m]+"-"+rowIndex+"-"+cell.cellIndex;
653 var rowSpan = cell.rowSpan || 1;
654 var colSpan = cell.colSpan || 1;
655 var firstAvailCol;
656 if(typeof(matrix[rowIndex])=="undefined") { matrix[rowIndex] = []; }
657 // Find first available column in the first row
658 for (var k=0; k<matrix[rowIndex].length+1; k++) {
659 if (typeof(matrix[rowIndex][k])=="undefined") {
660 firstAvailCol = k;
661 break;
662 }
663 }
664 lookup[cellId] = firstAvailCol;
665 for (var k=rowIndex; k<rowIndex+rowSpan; k++) {
666 if (typeof(matrix[k])=="undefined") { matrix[k] = []; }
667 var matrixrow = matrix[k];
668 for (var l=firstAvailCol; l<firstAvailCol+colSpan; l++) {
669 matrixrow[l] = "x";
670 }
671 }
672 }
673 }
674 }
675 }
676 return lookup;
677 };
678
679 function getActualCellIndex(cell, lookup) {
680 return lookup[cell.parentNode.parentNode.nodeName.toLowerCase()+"-"+cell.parentNode.rowIndex+"-"+cell.cellIndex];
681 };
682
683 switch (buttonId) {
684 // ROWS
685 case "TO-row-insert-above":
686 case "TO-row-insert-under":
687 var tr = this.getClosest("tr");
688 if (!tr) break;
689 var otr = tr.cloneNode(true);
690 clearRow(otr);
691 otr = tr.parentNode.insertBefore(otr, (/under/.test(buttonId) ? tr.nextSibling : tr));
692 this.editor.selectNodeContents(otr.firstChild, true);
693 this.reStyleTable(tr.parentNode.parentNode);
694 break;
695 case "TO-row-delete":
696 var tr = this.getClosest("tr");
697 if (!tr) break;
698 var part = tr.parentNode;
699 var table = part.parentNode;
700 if(part.rows.length == 1) { // this the last row, delete the whole table part
701 selectNextNode(part);
702 table.removeChild(part);
703 deleteEmptyTable(table);
704 } else {
705 // set the caret first to a position that doesn't disappear.
706 selectNextNode(tr);
707 part.removeChild(tr);
708 }
709 this.reStyleTable(table);
710 break;
711 case "TO-row-split":
712 var cell = this.getClosest("td");
713 if (!cell) var cell = this.getClosest("th");
714 if (!cell) break;
715 var sel = editor._getSelection();
716 if (HTMLArea.is_gecko && !sel.isCollapsed && !HTMLArea.is_safari && !HTMLArea.is_opera) {
717 var cells = getSelectedCells(sel);
718 for (i = 0; i < cells.length; ++i) splitRow(cells[i]);
719 } else {
720 splitRow(cell);
721 }
722 break;
723
724 // COLUMNS
725 case "TO-col-insert-before":
726 case "TO-col-insert-after":
727 var cell = this.getClosest("td");
728 if (!cell) var cell = this.getClosest("th");
729 if (!cell) break;
730 var index = cell.cellIndex;
731 var table = cell.parentNode.parentNode.parentNode;
732 for (var j = tableParts.length; --j >= 0;) {
733 var tablePart = table.getElementsByTagName(tableParts[j])[0];
734 if (tablePart) {
735 var rows = tablePart.rows;
736 for (var i = rows.length; --i >= 0;) {
737 var tr = rows[i];
738 var ref = tr.cells[index + (/after/.test(buttonId) ? 1 : 0)];
739 if (!ref) {
740 var otd = editor._doc.createElement(tr.lastChild.nodeName.toLowerCase());
741 otd.innerHTML = mozbr;
742 tr.appendChild(otd);
743 } else {
744 var otd = editor._doc.createElement(ref.nodeName.toLowerCase());
745 otd.innerHTML = mozbr;
746 tr.insertBefore(otd, ref);
747 }
748 }
749 }
750 }
751 this.reStyleTable(table);
752 break;
753 case "TO-col-split":
754 var cell = this.getClosest("td");
755 if (!cell) var cell = this.getClosest("th");
756 if (!cell) break;
757 var sel = editor._getSelection();
758 if (HTMLArea.is_gecko && !sel.isCollapsed && !HTMLArea.is_safari && !HTMLArea.is_opera) {
759 var cells = getSelectedCells(sel);
760 for (i = 0; i < cells.length; ++i) splitCol(cells[i]);
761 } else {
762 splitCol(cell);
763 }
764 this.reStyleTable(table);
765 break;
766 case "TO-col-delete":
767 var cell = this.getClosest("td");
768 if (!cell) var cell = this.getClosest("th");
769 if (!cell) break;
770 var index = cell.cellIndex;
771 var part = cell.parentNode.parentNode;
772 var table = part.parentNode;
773 var lastPart = true;
774 for (var j = tableParts.length; --j >= 0;) {
775 var tablePart = table.getElementsByTagName(tableParts[j])[0];
776 if (tablePart) {
777 var rows = tablePart.rows;
778 var lastColumn = true;
779 for (var i = rows.length; --i >= 0;) {
780 if(rows[i].cells.length > 1) lastColumn = false;
781 }
782 if (lastColumn) {
783 // this is the last column, delete the whole tablepart
784 // set the caret first to a position that doesn't disappear
785 selectNextNode(tablePart);
786 table.removeChild(tablePart);
787 } else {
788 // set the caret first to a position that doesn't disappear
789 if (part == tablePart) selectNextNode(cell);
790 for (var i = rows.length; --i >= 0;) {
791 if(rows[i].cells[index]) rows[i].removeChild(rows[i].cells[index]);
792 }
793 lastPart = false;
794 }
795 }
796 }
797 if (lastPart) {
798 // the last table section was deleted: delete the whole table
799 // set the caret first to a position that doesn't disappear
800 selectNextNode(table);
801 table.parentNode.removeChild(table);
802 }
803 this.reStyleTable(table);
804 break;
805
806 // CELLS
807 case "TO-cell-split":
808 var cell = this.getClosest("td");
809 if (!cell) var cell = this.getClosest("th");
810 if (!cell) break;
811 var sel = editor._getSelection();
812 if (HTMLArea.is_gecko && !sel.isCollapsed && !HTMLArea.is_safari && !HTMLArea.is_opera) {
813 var cells = getSelectedCells(sel);
814 for (i = 0; i < cells.length; ++i) splitCell(cells[i]);
815 } else {
816 splitCell(cell);
817 }
818 this.reStyleTable(table);
819 break;
820 case "TO-cell-insert-before":
821 case "TO-cell-insert-after":
822 var cell = this.getClosest("td");
823 if (!cell) var cell = this.getClosest("th");
824 if (!cell) break;
825 var tr = cell.parentNode;
826 var otd = editor._doc.createElement(cell.nodeName.toLowerCase());
827 otd.innerHTML = mozbr;
828 tr.insertBefore(otd, (/after/.test(buttonId) ? cell.nextSibling : cell));
829 this.reStyleTable(tr.parentNode.parentNode);
830 break;
831 case "TO-cell-delete":
832 var cell = this.getClosest("td");
833 if (!cell) var cell = this.getClosest("th");
834 if (!cell) break;
835 var row = cell.parentNode;
836 if(row.cells.length == 1) { // this is the only cell in the row, delete the row
837 var part = row.parentNode;
838 var table = part.parentNode;
839 if (part.rows.length == 1) { // this the last row, delete the whole table part
840 selectNextNode(part);
841 table.removeChild(part);
842 deleteEmptyTable(table);
843 } else {
844 selectNextNode(row);
845 part.removeChild(row);
846 }
847 } else {
848 // set the caret first to a position that doesn't disappear
849 selectNextNode(cell);
850 row.removeChild(cell);
851 }
852 this.reStyleTable(table);
853 break;
854 case "TO-cell-merge":
855 var sel = editor._getSelection();
856 var range, i = 0;
857 var rows = new Array();
858 for (var k = tableParts.length; --k >= 0;) rows[k] = [];
859 var row = null;
860 var cells = null;
861 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
862 try {
863 while (range = sel.getRangeAt(i++)) {
864 var td = range.startContainer.childNodes[range.startOffset];
865 if (td.parentNode != row) {
866 (cells) && rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells);
867 row = td.parentNode;
868 cells = [];
869 }
870 cells.push(td);
871 }
872 } catch(e) {
873 /* finished walking through selection */
874 }
875 try { rows[tablePartsIndex[row.parentNode.nodeName.toLowerCase()]].push(cells); } catch(e) { }
876 } else {
877 // Internet Explorer, Safari and Opera
878 var cell = this.getClosest("td");
879 if (!cell) var cell = this.getClosest("th");
880 if (!cell) {
881 alert(this.localize("Please click into some cell"));
882 break;
883 }
884 var tr = cell.parentElement;
885 var no_cols = parseInt(prompt(this.localize("How many columns would you like to merge?"), 2));
886 if (!no_cols) break;
887 var no_rows = parseInt(prompt(this.localize("How many rows would you like to merge?"), 2));
888 if (!no_rows) break;
889 var lookup = computeCellIndexes(cell.parentNode.parentNode.parentNode);
890 var first_index = getActualCellIndex(cell, lookup);
891 // Collect cells on first row
892 var td = cell, colspan = 0;
893 cells = [];
894 for (var i = no_cols; --i >= 0;) {
895 if (!td) break;
896 cells.push(td);
897 var last_index = getActualCellIndex(td, lookup);
898 td = td.nextSibling;
899 }
900 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
901 // Collect cells on following rows
902 var index, first_index_found, last_index_found;
903 for (var j = 1; j < no_rows; ++j) {
904 tr = tr.nextSibling;
905 if (!tr) break;
906 cells = [];
907 first_index_found = false;
908 for (var i = 0; i < tr.cells.length; ++i) {
909 td = tr.cells[i];
910 if (!td) break;
911 index = getActualCellIndex(td, lookup);
912 if (index > last_index) break;
913 if (index == first_index) first_index_found = true;
914 if (index >= first_index) cells.push(td);
915 }
916 // If not rectangle, we quit!
917 if (!first_index_found) break;
918 rows[tablePartsIndex[tr.parentNode.nodeName.toLowerCase()]].push(cells);
919 }
920 }
921 for (var k = tableParts.length; --k >= 0;) {
922 var cell, row;
923 var cellHTML = "";
924 var cellRowSpan = 0;
925 var cellColSpan, maxCellColSpan = 0;
926 if (rows[k] && rows[k][0]) {
927 for (var i = 0; i < rows[k].length; ++i) {
928 var cells = rows[k][i];
929 var cellColSpan = 0;
930 if (!cells) continue;
931 cellRowSpan += cells[0].rowSpan ? cells[0].rowSpan : 1;
932 for (var j = 0; j < cells.length; ++j) {
933 cell = cells[j];
934 row = cell.parentNode;
935 cellHTML += cell.innerHTML;
936 cellColSpan += cell.colSpan ? cell.colSpan : 1;
937 if (i || j) {
938 cell.parentNode.removeChild(cell);
939 if(!row.cells.length) row.parentNode.removeChild(row);
940 }
941 }
942 if (maxCellColSpan < cellColSpan) {
943 maxCellColSpan = cellColSpan;
944 }
945 }
946 var td = rows[k][0][0];
947 td.innerHTML = cellHTML;
948 td.rowSpan = cellRowSpan;
949 td.colSpan = maxCellColSpan;
950 editor.selectNodeContents(td);
951 }
952 }
953 this.reStyleTable(table);
954 break;
955
956 // CREATION AND PROPERTIES
957 case "InsertTable":
958 case "TO-table-prop":
959 this.dialogTableProperties(buttonId);
960 break;
961 case "TO-table-restyle":
962 this.reStyleTable(this.getClosest("table"));
963 break;
964 case "TO-row-prop":
965 this.dialogRowCellProperties(false, false);
966 break;
967 case "TO-col-prop":
968 this.dialogRowCellProperties(true, true);
969 break;
970 case "TO-cell-prop":
971 this.dialogRowCellProperties(true, false);
972 break;
973 case "TO-toggle-borders":
974 this.toggleBorders();
975 break;
976 default:
977 alert("Button [" + buttonId + "] not yet implemented");
978 }
979 },
980
981 /*
982 * Returns an array of all cells in the column containing the given cell
983 *
984 * @param object cell: the cell serving as reference point for the column
985 *
986 * @return array the array of cells of the column
987 */
988 getColumnCells : function (cell) {
989 var cells = new Array();
990 var index = cell.cellIndex;
991 var table = cell.parentNode.parentNode.parentNode;
992 for (var j = this.tableParts.length; --j >= 0;) {
993 var tablePart = table.getElementsByTagName(this.tableParts[j])[0];
994 if (tablePart) {
995 var rows = tablePart.rows;
996 for (var i = rows.length; --i >= 0;) {
997 if(rows[i].cells.length > index) {
998 cells.push(rows[i].cells[index]);
999 }
1000 }
1001 }
1002 }
1003 return cells;
1004 },
1005
1006 /*
1007 * Toggles the display of borders on tables and table cells
1008 *
1009 * @param boolean forceBorders: if set, borders are displayed whatever the current state
1010 *
1011 * @return void
1012 */
1013 toggleBorders : function (forceBorders) {
1014 var body = this.editor._doc.body;
1015 if (!HTMLArea._hasClass(body, 'htmlarea-showtableborders')) {
1016 HTMLArea._addClass(body,'htmlarea-showtableborders');
1017 } else if (!forceBorders) {
1018 HTMLArea._removeClass(body,'htmlarea-showtableborders');
1019 }
1020 },
1021
1022 /*
1023 * Applies to rows/cells the alternating and counting classes of an alternating or counting style scheme
1024 *
1025 * @param object table: the table to be re-styled
1026 *
1027 * @return void
1028 */
1029 reStyleTable : function (table) {
1030 if (table) {
1031 if (this.classesUrl && (typeof(HTMLArea.classesAlternating) === "undefined" || typeof(HTMLArea.classesCounting) === "undefined")) {
1032 this.getJavascriptFile(this.classesUrl);
1033 }
1034 var classNames = table.className.trim().split(" ");
1035 for (var i = classNames.length; --i >= 0;) {
1036 var classConfiguration = HTMLArea.classesAlternating[classNames[i]];
1037 if (classConfiguration && classConfiguration.rows) {
1038 if (classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1039 this.alternateRows(table, classConfiguration);
1040 }
1041 }
1042 if (classConfiguration && classConfiguration.columns) {
1043 if (classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1044 this.alternateColumns(table, classConfiguration);
1045 }
1046 }
1047 classConfiguration = HTMLArea.classesCounting[classNames[i]];
1048 if (classConfiguration && classConfiguration.rows) {
1049 if (classConfiguration.rows.rowClass) {
1050 this.countRows(table, classConfiguration);
1051 }
1052 }
1053 if (classConfiguration && classConfiguration.columns) {
1054 if (classConfiguration.columns.columnClass) {
1055 this.countColumns(table, classConfiguration);
1056 }
1057 }
1058 }
1059 }
1060 },
1061
1062 /*
1063 * Removes from rows/cells the alternating classes of an alternating style scheme
1064 *
1065 * @param object table: the table to be re-styled
1066 * @param string removeClass: the name of the class that identifies the alternating style scheme
1067 *
1068 * @return void
1069 */
1070 removeAlternatingClasses : function (table, removeClass) {
1071 if (table) {
1072 if (this.classesUrl && typeof(HTMLArea.classesAlternating) === "undefined") {
1073 this.getJavascriptFile(this.classesUrl);
1074 }
1075 var classConfiguration = HTMLArea.classesAlternating[removeClass];
1076 if (classConfiguration) {
1077 if (classConfiguration.rows && classConfiguration.rows.oddClass && classConfiguration.rows.evenClass) {
1078 this.alternateRows(table, classConfiguration, true);
1079 }
1080 if (classConfiguration.columns && classConfiguration.columns.oddClass && classConfiguration.columns.evenClass) {
1081 this.alternateColumns(table, classConfiguration, true);
1082 }
1083 }
1084 }
1085 },
1086
1087 /*
1088 * Applies/removes the alternating classes of an alternating rows style scheme
1089 *
1090 * @param object table: the table to be re-styled
1091 * @param object classConfifuration: the alternating sub-array of the configuration of the class
1092 * @param boolean remove: if true, the classes are removed
1093 *
1094 * @return void
1095 */
1096 alternateRows : function (table, classConfiguration, remove) {
1097 var oddClass = { tbody : classConfiguration.rows.oddClass, thead : classConfiguration.rows.oddHeaderClass };
1098 var evenClass = { tbody : classConfiguration.rows.evenClass, thead : classConfiguration.rows.evenHeaderClass };
1099 var startAt = parseInt(classConfiguration.rows.startAt);
1100 startAt = remove ? 1 : (startAt ? startAt : 1);
1101 var rows = table.rows, type, odd, even;
1102 // Loop through the rows
1103 for (var i = startAt-1, n = rows.length; i < n; i++) {
1104 var row = rows[i];
1105 type = (row.parentNode.nodeName.toLowerCase() == "thead") ? "thead" : "tbody";
1106 odd = oddClass[type];
1107 even = evenClass[type];
1108 if (remove) {
1109 HTMLArea._removeClass(row, odd);
1110 HTMLArea._removeClass(row, even);
1111 // Check if i is even, and apply classes for both possible results
1112 } else if (odd && even) {
1113 if ((i % 2) == 0) {
1114 if (HTMLArea._hasClass(row, even)) {
1115 HTMLArea._removeClass(row, even);
1116 }
1117 HTMLArea._addClass(row, odd);
1118 } else {
1119 if (HTMLArea._hasClass(row, odd)) {
1120 HTMLArea._removeClass(row, odd);
1121 }
1122 HTMLArea._addClass(row, even);
1123 }
1124 }
1125 }
1126 },
1127
1128 /*
1129 * Applies/removes the alternating classes of an alternating columns style scheme
1130 *
1131 * @param object table: the table to be re-styled
1132 * @param object classConfifuration: the alternating sub-array of the configuration of the class
1133 * @param boolean remove: if true, the classes are removed
1134 *
1135 * @return void
1136 */
1137 alternateColumns : function (table, classConfiguration, remove) {
1138 var oddClass = { td : classConfiguration.columns.oddClass, th : classConfiguration.columns.oddHeaderClass };
1139 var evenClass = { td : classConfiguration.columns.evenClass, th : classConfiguration.columns.evenHeaderClass };
1140 var startAt = parseInt(classConfiguration.columns.startAt);
1141 startAt = remove ? 1 : (startAt ? startAt : 1);
1142 var rows = table.rows, type, odd, even;
1143 // Loop through the rows of the table
1144 for (var i = rows.length; --i >= 0;) {
1145 // Loop through the cells
1146 var cells = rows[i].cells;
1147 for (var j = startAt-1, n = cells.length; j < n; j++) {
1148 var cell = cells[j];
1149 type = cell.nodeName.toLowerCase();
1150 odd = oddClass[type];
1151 even = evenClass[type];
1152 if (remove) {
1153 if (odd) HTMLArea._removeClass(cell, odd);
1154 if (even) HTMLArea._removeClass(cell, even);
1155 } else if (odd && even) {
1156 // Check if j+startAt is even, and apply classes for both possible results
1157 if ((j % 2) == 0) {
1158 if (HTMLArea._hasClass(cell, even)) {
1159 HTMLArea._removeClass(cell, even);
1160 }
1161 HTMLArea._addClass(cell, odd);
1162 } else{
1163 if (HTMLArea._hasClass(cell, odd)) {
1164 HTMLArea._removeClass(cell, odd);
1165 }
1166 HTMLArea._addClass(cell, even);
1167 }
1168 }
1169 }
1170 }
1171 },
1172
1173 /*
1174 * Removes from rows/cells the counting classes of an counting style scheme
1175 *
1176 * @param object table: the table to be re-styled
1177 * @param string removeClass: the name of the class that identifies the counting style scheme
1178 *
1179 * @return void
1180 */
1181 removeCountingClasses : function (table, removeClass) {
1182 if (table) {
1183 if (this.classesUrl && typeof(HTMLArea.classesCounting) === "undefined") {
1184 this.getJavascriptFile(this.classesUrl);
1185 }
1186 var classConfiguration = HTMLArea.classesCounting[removeClass];
1187 if (classConfiguration) {
1188 if (classConfiguration.rows && classConfiguration.rows.rowClass) {
1189 this.countRows(table, classConfiguration, true);
1190 }
1191 if (classConfiguration.columns && classConfiguration.columns.columnClass) {
1192 this.countColumns(table, classConfiguration, true);
1193 }
1194 }
1195 }
1196 },
1197
1198 /*
1199 * Applies/removes the counting classes of an counting rows style scheme
1200 *
1201 * @param object table: the table to be re-styled
1202 * @param object classConfifuration: the counting sub-array of the configuration of the class
1203 * @param boolean remove: if true, the classes are removed
1204 *
1205 * @return void
1206 */
1207 countRows : function (table, classConfiguration, remove) {
1208 var rowClass = { tbody : classConfiguration.rows.rowClass, thead : classConfiguration.rows.rowHeaderClass };
1209 var rowLastClass = { tbody : classConfiguration.rows.rowLastClass, thead : classConfiguration.rows.rowHeaderLastClass };
1210 var startAt = parseInt(classConfiguration.rows.startAt);
1211 startAt = remove ? 1 : (startAt ? startAt : 1);
1212 var rows = table.rows, type, baseClassName, rowClassName, lastRowClassName;
1213 // Loop through the rows
1214 for (var i = startAt-1, n = rows.length; i < n; i++) {
1215 var row = rows[i];
1216 type = (row.parentNode.nodeName.toLowerCase() == "thead") ? "thead" : "tbody";
1217 baseClassName = rowClass[type];
1218 rowClassName = baseClassName + (i+1);
1219 lastRowClassName = rowLastClass[type];
1220 if (remove) {
1221 if (baseClassName) {
1222 HTMLArea._removeClass(row, rowClassName);
1223 }
1224 if (lastRowClassName && i == n-1) {
1225 HTMLArea._removeClass(row, lastRowClassName);
1226 }
1227 } else {
1228 if (baseClassName) {
1229 if (HTMLArea._hasClass(row, baseClassName, true)) {
1230 HTMLArea._removeClass(row, baseClassName, true);
1231 }
1232 HTMLArea._addClass(row, rowClassName);
1233 }
1234 if (lastRowClassName) {
1235 if (i == n-1) {
1236 HTMLArea._addClass(row, lastRowClassName);
1237 } else if (HTMLArea._hasClass(row, lastRowClassName)) {
1238 HTMLArea._removeClass(row, lastRowClassName);
1239 }
1240 }
1241 }
1242 }
1243 },
1244
1245 /*
1246 * Applies/removes the counting classes of a counting columns style scheme
1247 *
1248 * @param object table: the table to be re-styled
1249 * @param object classConfifuration: the counting sub-array of the configuration of the class
1250 * @param boolean remove: if true, the classes are removed
1251 *
1252 * @return void
1253 */
1254 countColumns : function (table, classConfiguration, remove) {
1255 var columnClass = { td : classConfiguration.columns.columnClass, th : classConfiguration.columns.columnHeaderClass };
1256 var columnLastClass = { td : classConfiguration.columns.columnLastClass, th : classConfiguration.columns.columnHeaderLastClass };
1257 var startAt = parseInt(classConfiguration.columns.startAt);
1258 startAt = remove ? 1 : (startAt ? startAt : 1);
1259 var rows = table.rows, type, baseClassName, columnClassName, lastColumnClassName;
1260 // Loop through the rows of the table
1261 for (var i = rows.length; --i >= 0;) {
1262 // Loop through the cells
1263 var cells = rows[i].cells;
1264 for (var j = startAt-1, n = cells.length; j < n; j++) {
1265 var cell = cells[j];
1266 type = cell.nodeName.toLowerCase();
1267 baseClassName = columnClass[type];
1268 columnClassName = baseClassName + (j+1);
1269 lastColumnClassName = columnLastClass[type];
1270 if (remove) {
1271 if (baseClassName) {
1272 HTMLArea._removeClass(cell, columnClassName);
1273 }
1274 if (lastColumnClassName && j == n-1) {
1275 HTMLArea._removeClass(cell, lastColumnClassName);
1276 }
1277 } else {
1278 if (baseClassName) {
1279 if (HTMLArea._hasClass(cell, baseClassName, true)) {
1280 HTMLArea._removeClass(cell, baseClassName, true);
1281 }
1282 HTMLArea._addClass(cell, columnClassName);
1283 }
1284 if (lastColumnClassName) {
1285 if (j == n-1) {
1286 HTMLArea._addClass(cell, lastColumnClassName);
1287 } else if (HTMLArea._hasClass(cell, lastColumnClassName)) {
1288 HTMLArea._removeClass(cell, lastColumnClassName);
1289 }
1290 }
1291 }
1292 }
1293 }
1294 },
1295
1296 /*
1297 * This function sets the headers cells on the table (top, left, both or none)
1298 *
1299 * @param object table: the table being edited
1300 * @param object params: the field values entered in the form
1301 *
1302 * @return object the modified table
1303 */
1304 setHeaders : function (table, params) {
1305 var headers = params.f_headers;
1306 var doc = this.editor._doc;
1307 var tbody = table.tBodies[0];
1308 var thead = table.tHead;
1309 if (thead && !thead.rows.length && !tbody.rows.length) {
1310 // Table is degenerate
1311 return table;
1312 }
1313 if (headers == "top") {
1314 if (!thead) {
1315 var thead = doc.createElement("thead");
1316 thead = table.insertBefore(thead, tbody);
1317 }
1318 if (!thead.rows.length) {
1319 var firstRow = thead.appendChild(tbody.rows[0]);
1320 } else {
1321 var firstRow = thead.rows[0];
1322 }
1323 HTMLArea._removeClass(firstRow, this.useHeaderClass);
1324 } else {
1325 if (thead) {
1326 var rows = thead.rows;
1327 if (rows.length) {
1328 for (var i = rows.length; --i >= 0 ;) {
1329 this.remapRowCells(rows[i], "td");
1330 if (tbody.rows.length) {
1331 tbody.insertBefore(rows[i], tbody.rows[0]);
1332 } else {
1333 tbody.appendChild(rows[i]);
1334 }
1335 }
1336 }
1337 table.removeChild(thead);
1338 }
1339 }
1340 if (headers == "both") {
1341 var firstRow = tbody.rows[0];
1342 HTMLArea._addClass(firstRow, this.useHeaderClass);
1343 } else if (headers != "top") {
1344 var firstRow = tbody.rows[0];
1345 HTMLArea._removeClass(firstRow, this.useHeaderClass);
1346 this.remapRowCells(firstRow, "td");
1347 }
1348 if (headers == "top" || headers == "both") {
1349 this.remapRowCells(firstRow, "th");
1350 }
1351 if (headers == "left") {
1352 var firstRow = tbody.rows[0];
1353 }
1354 if (headers == "left" || headers == "both") {
1355 var rows = tbody.rows;
1356 for (var i = rows.length; --i >= 0 ;) {
1357 if (i || rows[i] == firstRow) {
1358 if (rows[i].cells[0].nodeName.toLowerCase() != "th") {
1359 var th = this.remapCell(rows[i].cells[0], "th");
1360 th.scope = "row";
1361 }
1362 }
1363 }
1364 } else {
1365 var rows = tbody.rows;
1366 for (var i = rows.length; --i >= 0 ;) {
1367 if (rows[i].cells[0].nodeName.toLowerCase() != "td") {
1368 rows[i].cells[0].scope = "";
1369 var td = this.remapCell(rows[i].cells[0], "td");
1370 }
1371 }
1372 }
1373 this.reStyleTable(table);
1374 return table;
1375 },
1376
1377 /*
1378 * This function remaps the given cell to the specified node name
1379 */
1380 remapCell : function(element, nodeName) {
1381 var newCell = this.editor.convertNode(element, nodeName);
1382 var attributes = element.attributes, attributeName, attributeValue;
1383 for (var i = attributes.length; --i >= 0;) {
1384 attributeName = attributes.item(i).nodeName;
1385 attributeValue = element.getAttribute(attributeName);
1386 if (attributeValue) newCell.setAttribute(attributeName, attributeValue);
1387 }
1388 // In IE, the above fails to update the classname and style attributes.
1389 if (HTMLArea.is_ie) {
1390 if (element.style.cssText) {
1391 newCell.style.cssText = element.style.cssText;
1392 }
1393 if (element.className) {
1394 newCell.setAttribute("className", element.className);
1395 } else {
1396 newCell.removeAttribute("className");
1397 }
1398 }
1399
1400 if (this.tags && this.tags[nodeName] && this.tags[nodeName].allowedClasses) {
1401 if (newCell.className && /\S/.test(newCell.className)) {
1402 var allowedClasses = this.tags[nodeName].allowedClasses;
1403 var classNames = newCell.className.trim().split(" ");
1404 for (var i = classNames.length; --i >= 0;) {
1405 if (!allowedClasses.test(classNames[i])) {
1406 HTMLArea._removeClass(newCell, classNames[i]);
1407 }
1408 }
1409 }
1410 }
1411 return newCell;
1412 },
1413
1414 remapRowCells : function (row, toType) {
1415 var cells = row.cells;
1416 if (toType === "th") {
1417 for (var i = cells.length; --i >= 0 ;) {
1418 if (cells[i].nodeName.toLowerCase() != "th") {
1419 var th = this.remapCell(cells[i], "th");
1420 th.scope = "col";
1421 }
1422 }
1423 } else {
1424 for (var i = cells.length; --i >= 0 ;) {
1425 if (cells[i].nodeName.toLowerCase() != "td") {
1426 var td = this.remapCell(cells[i], "td");
1427 td.scope = "";
1428 }
1429 }
1430 }
1431 },
1432
1433 /*
1434 * This function applies the style properties found in params to the given element
1435 *
1436 * @param object element: the element
1437 * @param object params: the properties
1438 *
1439 * @return object the modified element
1440 */
1441 processStyle : function (element, params) {
1442 var style = element.style;
1443 if (HTMLArea.is_ie) {
1444 style.styleFloat = "";
1445 } else {
1446 style.cssFloat = "";
1447 }
1448 style.textAlign = "";
1449 for (var i in params) {
1450 if (params.hasOwnProperty(i)) {
1451 var val = params[i];
1452 switch (i) {
1453 case "f_st_backgroundColor":
1454 style.backgroundColor = val;
1455 break;
1456 case "f_st_color":
1457 style.color = val;
1458 break;
1459 case "f_st_backgroundImage":
1460 if (/\S/.test(val)) {
1461 style.backgroundImage = "url(" + val + ")";
1462 } else {
1463 style.backgroundImage = "";
1464 }
1465 break;
1466 case "f_st_borderWidth":
1467 if (/\S/.test(val)) {
1468 style.borderWidth = val + "px";
1469 } else {
1470 style.borderWidth = "";
1471 }
1472 if (params.f_st_borderStyle == "none") style.borderWidth = "0px";
1473 if (params.f_st_borderStyle == "not set") style.borderWidth = "";
1474 break;
1475 case "f_st_borderStyle":
1476 style.borderStyle = (val != "not set") ? val : "";
1477 break;
1478 case "f_st_borderColor":
1479 style.borderColor = val;
1480 break;
1481 case "f_st_borderCollapse":
1482 style.borderCollapse = val ? "collapse" : "";
1483 break;
1484 case "f_st_width":
1485 if (/\S/.test(val)) {
1486 style.width = val + params.f_st_widthUnit;
1487 } else {
1488 style.width = "";
1489 }
1490 break;
1491 case "f_st_height":
1492 if (/\S/.test(val)) {
1493 style.height = val + params.f_st_heightUnit;
1494 } else {
1495 style.height = "";
1496 }
1497 break;
1498 case "f_st_textAlign":
1499 style.textAlign = (val != "not set") ? val : "";
1500 break;
1501 case "f_st_vertAlign":
1502 style.verticalAlign = (val != "not set") ? val : "";
1503 break;
1504 }
1505 }
1506 }
1507 return element;
1508 },
1509
1510 /*
1511 * This function creates a Size and Headers fieldset to be added to the form
1512 *
1513 * @param object doc: the dialog document
1514 * @param object table: the table being edited
1515 * @param object content: the content div of the dialog window
1516 *
1517 * @return void
1518 */
1519 buildSizeAndHeadersFieldset : function (doc, table, content, fieldsetClass) {
1520 var fieldset = doc.createElement("fieldset");
1521 if (fieldsetClass) fieldset.className = fieldsetClass;
1522 if (!table) {
1523 TableOperations.insertLegend(doc, fieldset, "Size and Headers");
1524 TableOperations.buildInput(doc, fieldset, "f_rows", "Rows:", "Number of rows", "", "5", ((this.properties && this.properties.numberOfRows && this.properties.numberOfRows.defaultValue) ? this.properties.numberOfRows.defaultValue : "2"), "fr");
1525 TableOperations.insertSpace(doc, fieldset);
1526 TableOperations.buildInput(doc, fieldset, "f_cols", "Cols:", "Number of columns", "", "5", ((this.properties && this.properties.numberOfColumns && this.properties.numberOfColumns.defaultValue) ? this.properties.numberOfColumns.defaultValue : "4"), "fr");
1527 } else {
1528 TableOperations.insertLegend(doc, fieldset, "Headers");
1529 }
1530 if (this.removedProperties.indexOf("headers") == -1) {
1531 var ul = doc.createElement("ul");
1532 fieldset.appendChild(ul);
1533 var li = doc.createElement("li");
1534 ul.appendChild(li);
1535 if (!table) {
1536 var selected = (this.properties && this.properties.headers && this.properties.headers.defaultValue) ? this.properties.headers.defaultValue : "top";
1537 } else {
1538 var selected = "none";
1539 var thead = table.getElementsByTagName("thead");
1540 var tbody = table.getElementsByTagName("tbody");
1541 if (thead.length && thead[0].rows.length) {
1542 selected = "top";
1543 } else if (tbody.length && tbody[0].rows.length) {
1544 if (HTMLArea._hasClass(tbody[0].rows[0], this.useHeaderClass)) {
1545 selected = "both";
1546 } else if (tbody[0].rows[0].cells.length && tbody[0].rows[0].cells[0].nodeName.toLowerCase() == "th") {
1547 selected = "left";
1548 }
1549 }
1550 }
1551 var selectHeaders = TableOperations.buildSelectField(doc, li, "f_headers", "Headers:", "fr", "floating", "Table headers", ["No header cells", "Header cells on top", "Header cells on left", "Header cells on top and left"], ["none", "top", "left", "both"], new RegExp((selected ? selected : "top"), "i"));
1552 this.removeOptions(selectHeaders, "headers");
1553 }
1554 TableOperations.insertSpace(doc, fieldset);
1555 content.appendChild(fieldset);
1556 },
1557
1558 buildLayoutFieldset : function(doc, el, content, fieldsetClass) {
1559 var select, selected;
1560 var fieldset = doc.createElement("fieldset");
1561 if (fieldsetClass) fieldset.className = fieldsetClass;
1562 TableOperations.insertLegend(doc, fieldset, "Layout");
1563 var f_st_width = el ? TableOperations.getLength(el.style.width) : ((this.properties && this.properties.width && this.properties.width.defaultValue) ? this.properties.width.defaultValue : "");
1564 var f_st_height = el ? TableOperations.getLength(el.style.height) : ((this.properties && this.properties.height && this.properties.height.defaultValue) ? this.properties.height.defaultValue : "");
1565 var selectedWidthUnit = el ? (/%/.test(el.style.width) ? '%' : (/px/.test(el.style.width) ? 'px' : 'em')) : ((this.properties && this.properties.widthUnit &&this.properties.widthUnit.defaultValue) ? this.properties.widthUnit.defaultValue : "%");
1566 var selectedHeightUnit = el ? (/%/.test(el.style.height) ? '%' : (/px/.test(el.style.height) ? 'px' : 'em')) : ((this.properties && this.properties.heightUnit &&this.properties.heightUnit.defaultValue) ? this.properties.heightUnit.defaultValue : "%");
1567 var nodeName = el ? el.nodeName.toLowerCase() : "table";
1568 var ul = doc.createElement("ul");
1569 fieldset.appendChild(ul);
1570 switch(nodeName) {
1571 case "table" :
1572 var widthTitle = "Table width";
1573 var heightTitle = "Table height";
1574 break;
1575 case "tr" :
1576 var widthTitle = "Row width";
1577 var heightTitle = "Row height";
1578 break;
1579 case "td" :
1580 case "th" :
1581 var widthTitle = "Cell width";
1582 var heightTitle = "Cell height";
1583 }
1584 if (this.removedProperties.indexOf("width") == -1) {
1585 var li = doc.createElement("li");
1586 ul.appendChild(li);
1587 TableOperations.buildInput(doc, li, "f_st_width", "Width:", widthTitle, "", "5", f_st_width, "fr");
1588 select = TableOperations.buildSelectField(doc, li, "f_st_widthUnit", "", "", "", "Width unit", ["percent", "pixels", "em"], ["%", "px", "em"], new RegExp((selectedWidthUnit ? selectedWidthUnit : "%"), "i"));
1589 this.removeOptions(select, "widthUnit");
1590 }
1591 if (this.removedProperties.indexOf("height") == -1) {
1592 var li = doc.createElement("li");
1593 ul.appendChild(li);
1594 TableOperations.buildInput(doc, li, "f_st_height", "Height:", heightTitle, "", "5", f_st_height, "fr");
1595 select = TableOperations.buildSelectField(doc, li, "f_st_heightUnit", "", "", "", "Height unit", ["percent", "pixels", "em"], ["%", "px", "em"], new RegExp((selectedHeightUnit ? selectedHeightUnit : "%"), "i"));
1596 this.removeOptions(select, "heightUnit");
1597 }
1598 if (nodeName == "table" && this.removedProperties.indexOf("float") == -1) {
1599 selected = el ? (HTMLArea._hasClass(el, this.floatLeft) ? "left" : (HTMLArea._hasClass(el, this.floatRight) ? "right" : "not set")) : this.floatDefault;
1600 select = TableOperations.buildSelectField(doc, li, "f_st_float", "Float:", "", "", "Specifies where the table should float", ["Not set", "Left", "Right"], ["not set", "left", "right"], new RegExp((selected ? selected : "not set"), "i"));
1601 this.removeOptions(select, "float");
1602 }
1603 content.appendChild(fieldset);
1604 },
1605
1606 setStyleOptions : function (doc, dropDown, el, nodeName, defaultClass) {
1607 if (!dropDown) return false;
1608 if (this.editor.config.customSelects.BlockStyle) {
1609 var blockStyle = this.editor.plugins.BlockStyle.instance;
1610 if (!blockStyle || !blockStyle.cssLoaded) return false;
1611 if (defaultClass) {
1612 var classNames = new Array();
1613 classNames.push(defaultClass);
1614 } else {
1615 var classNames = blockStyle.getClassNames(el);
1616 }
1617 blockStyle.buildDropDownOptions(dropDown, nodeName);
1618 blockStyle.setSelectedOption(dropDown, classNames, "noUnknown", defaultClass);
1619 }
1620 },
1621
1622 buildStylingFieldset : function (doc, el, content, fieldsetClass, buttonId) {
1623 var nodeName = el ? el.nodeName.toLowerCase() : "table";
1624 var table = (nodeName == "table");
1625 var fieldset = doc.createElement("fieldset");
1626 if (fieldsetClass) fieldset.className = fieldsetClass;
1627 TableOperations.insertLegend(doc, fieldset, "CSS Style");
1628 TableOperations.insertSpace(doc, fieldset);
1629 var ul = doc.createElement("ul");
1630 ul.className = "floating";
1631 fieldset.appendChild(ul);
1632 var li = doc.createElement("li");
1633 ul.appendChild(li);
1634 var select = TableOperations.buildSelectField(doc, li, "f_class", (table ? "Table class:" : "Class:"), "fr", "", (table ? "Table class selector" : "Class selector"), new Array("undefined"), new Array("none"), new RegExp("none", "i"), "", false);
1635 this.setStyleOptions(doc, select, el, nodeName, (buttonId === "InsertTable") ? this.defaultClass : null);
1636 if (el && table) {
1637 var tbody = el.getElementsByTagName("tbody")[0];
1638 if (tbody) {
1639 var li = doc.createElement("li");
1640 ul.appendChild(li);
1641 var select = TableOperations.buildSelectField(doc, li, "f_class_tbody", "Table body class:", "fr", "", "Table body class selector", new Array("undefined"), new Array("none"), new RegExp("none", "i"), "", false);
1642 this.setStyleOptions(doc, select, tbody, "tbody");
1643 }
1644 var thead = el.getElementsByTagName("thead")[0];
1645 if (thead) {
1646 var li = doc.createElement("li");
1647 ul.appendChild(li);
1648 var select = TableOperations.buildSelectField(doc, li, "f_class_thead", "Table header class:", "fr", "", "Table header class selector", new Array("undefined"), new Array("none"), new RegExp("none", "i"), "", false);
1649 this.setStyleOptions(doc, select, thead, "thead");
1650 }
1651 var tfoot = el.getElementsByTagName("tfoot")[0];
1652 if (tfoot) {
1653 var li = doc.createElement("li");
1654 ul.appendChild(li);
1655 var select = TableOperations.buildSelectField(doc, li, "f_class_tfoot", "Table footer class:", "fr", "", "Table footer class selector", new Array("undefined"), new Array("none"), new RegExp("none", "i"), "", false);
1656 this.setStyleOptions(doc, select, tfoot, "tfoot");
1657 }
1658 }
1659 TableOperations.insertSpace(doc, fieldset);
1660 content.appendChild(fieldset);
1661 },
1662
1663 buildLanguageFieldset : function (doc, el, content, fieldsetClass) {
1664 if (this.removedFieldsets.indexOf("language") == -1 && (this.removedProperties.indexOf("language") == -1 || this.removedProperties.indexOf("direction") == -1) && this.getPluginInstance("Language") && (this.isButtonInToolbar("Language") || this.isButtonInToolbar("LeftToRight") || this.isButtonInToolbar("RightToLeft"))) {
1665 var languageObject = this.getPluginInstance("Language");
1666 var fieldset = doc.createElement("fieldset");
1667 if (fieldsetClass) {
1668 fieldset.className = fieldsetClass;
1669 }
1670 TableOperations.insertLegend(doc, fieldset, "Language");
1671 var ul = doc.createElement("ul");
1672 fieldset.appendChild(ul);
1673 if (this.removedProperties.indexOf("language") == -1 && this.isButtonInToolbar("Language")) {
1674 var languageOptions = this.getDropDownConfiguration("Language").options;
1675 var select,
1676 selected = "",
1677 options = new Array(),
1678 values = new Array();
1679 for (var option in languageOptions) {
1680 if (languageOptions.hasOwnProperty(option)) {
1681 options.push(option);
1682 values.push(languageOptions[option]);
1683 }
1684 }
1685 selected = el ? languageObject.getLanguageAttribute(el) : "none";
1686 if (selected != "none") {
1687 options[0] = languageObject.localize("Remove language mark");
1688 }
1689 (selected.match(/([^\s]*)\s/)) && (selected = RegExp.$1);
1690 var li = doc.createElement("li");
1691 ul.appendChild(li);
1692 select = TableOperations.buildSelectField(doc, li, "f_lang", "Language:", "fr", "", "Language", options, values, new RegExp((selected ? selected : "none"), "i"));
1693 }
1694 if (this.removedProperties.indexOf("direction") == -1 && (this.isButtonInToolbar("LeftToRight") || this.isButtonInToolbar("RightToLeft"))) {
1695 var li = doc.createElement("li");
1696 ul.appendChild(li);
1697 selected = el ? el.dir : "";
1698 (selected.match(/([^\s]*)\s/)) && (selected = RegExp.$1);
1699 select = TableOperations.buildSelectField(doc, li, "f_dir", "Text direction:", "fr", "", "Text direction", ["Not set", "Right to left", "Left to right"], ["not set", "rtl", "ltr"], new RegExp((selected ? selected : "not set"), "i"));
1700 }
1701 content.appendChild(fieldset);
1702 }
1703 },
1704
1705 buildCellTypeFieldset : function (doc, el, content, column, fieldsetClass) {
1706 var fieldset = doc.createElement("fieldset");
1707 if (fieldsetClass) fieldset.className = fieldsetClass;
1708 TableOperations.insertLegend(doc, fieldset, column ? "Type of cells" : "Cell Type and Scope");
1709 TableOperations.insertSpace(doc, fieldset);
1710 var ul = doc.createElement("ul");
1711 fieldset.appendChild(ul);
1712 var li = doc.createElement("li");
1713 ul.appendChild(li);
1714 if (column) {
1715 var selectType = TableOperations.buildSelectField(doc, li, "f_cell_type", "Type of cells of the column", "fl", "", "Specifies the type of cells", ["Data cells", "Headers for rows", "Headers for row groups"], ["td", "throw", "throwgroup"], new RegExp(el.nodeName.toLowerCase()+el.scope.toLowerCase()+"$", "i"));
1716 } else {
1717 var selectType = TableOperations.buildSelectField(doc, li, "f_cell_type", "Type of cell", "fr", "", "Specifies the type of cell", ["Normal", "Header for column", "Header for row", "Header for row group"], ["td", "thcol", "throw", "throwgroup"], new RegExp(el.nodeName.toLowerCase()+el.scope.toLowerCase()+"$", "i"));
1718 }
1719 var self = this;
1720 selectType.onchange = function() { self.setStyleOptions(doc, doc.getElementById("f_class"), el, this.value.substring(0,2)); };
1721 TableOperations.insertSpace(doc, fieldset);
1722 content.appendChild(fieldset);
1723 },
1724
1725 buildAlignmentFieldset : function (doc, el, content, fieldsetClass) {
1726 var select;
1727 var nodeName = el ? el.nodeName.toLowerCase() : "table";
1728 var fieldset = doc.createElement("fieldset");
1729 if (fieldsetClass) fieldset.className = fieldsetClass;
1730 TableOperations.insertLegend(doc, fieldset, "Alignment");
1731 var options = ["Not set", "Left", "Center", "Right", "Justify"];
1732 var values = ["not set", "left", "center", "right", "justify"];
1733 var selected = "";
1734 if (el && this.editor.plugins.BlockElements) {
1735 var blockElements = this.editor.plugins.BlockElements.instance;
1736 for (var value in this.convertAlignment) {
1737 if (this.convertAlignment.hasOwnProperty(value) && HTMLArea._hasClass(el, blockElements.useClass[this.convertAlignment[value]])) {
1738 selected = value;
1739 break;
1740 }
1741 }
1742 } else {
1743 selected = el ? el.style.verticalAlign : "";
1744 }
1745 (selected.match(/([^\s]*)\s/)) && (selected = RegExp.$1);
1746 var ul = doc.createElement("ul");
1747 fieldset.appendChild(ul);
1748 var li = doc.createElement("li");
1749 ul.appendChild(li);
1750 select = TableOperations.buildSelectField(doc, li, "f_st_textAlign", "Text alignment:", "fr", "", "Horizontal alignment of text within cell", options, values, new RegExp((selected ? selected : "not set"), "i"));
1751
1752 var li = doc.createElement("li");
1753 ul.appendChild(li);
1754 selected = el ? el.style.verticalAlign : "";
1755 (selected.match(/([^\s]*)\s/)) && (selected = RegExp.$1);
1756 select = TableOperations.buildSelectField(doc, li, "f_st_vertAlign", "Vertical alignment:", "fr", "", "Vertical alignment of content within cell", ["Not set", "Top", "Middle", "Bottom", "Baseline"], ["not set", "top", "middle", "bottom", "baseline"], new RegExp((selected ? selected : "not set"), "i"));
1757 content.appendChild(fieldset);
1758 },
1759
1760 buildBordersFieldset : function (w, doc, editor, el, content, fieldsetClass) {
1761 var nodeName = el ? el.nodeName.toLowerCase() : "table";
1762 var select;
1763 var selected;
1764 var borderFields = [];
1765 function setBorderFieldsVisibility(value) {
1766 for (var i = 0; i < borderFields.length; ++i) {
1767 var borderFieldElement = borderFields[i];
1768 borderFieldElement.style.visibility = value ? "hidden" : "visible";
1769 if (!value && (borderFieldElement.nodeName.toLowerCase() == "input")) {
1770 borderFieldElement.focus();
1771 borderFieldElement.select();
1772 }
1773 }
1774 };
1775 var fieldset = doc.createElement("fieldset");
1776 fieldset.className = fieldsetClass;
1777 TableOperations.insertLegend(doc, fieldset, "Frame and borders");
1778 TableOperations.insertSpace(doc, fieldset);
1779 // Gecko reports "solid solid solid solid" for "border-style: solid".
1780 // That is, "top right bottom left" -- we only consider the first value.
1781 var f_st_borderWidth = el ? TableOperations.getLength(el.style.borderWidth) : ((this.properties && this.properties.borderWidth && this.properties.borderWidth.defaultValue) ? this.properties.borderWidth.defaultValue : "");
1782 selected = el ? el.style.borderStyle : ((this.properties && this.properties.borderWidth) ? ((this.properties.borderStyle && this.properties.borderStyle.defaultValue) ? this.properties.borderStyle.defaultValue : "solid") : "");
1783 (selected.match(/([^\s]*)\s/)) && (selected = RegExp.$1);
1784 selectBorderStyle = TableOperations.buildSelectField(doc, fieldset, "f_st_borderStyle", "Border style:", "fr", "floating", "Border style", ["Not set", "No border", "Dotted", "Dashed", "Solid", "Double", "Groove", "Ridge", "Inset", "Outset"], ["not set", "none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"], new RegExp((selected ? selected : "not set"), "i"));
1785 selectBorderStyle.onchange = function() { setBorderFieldsVisibility(this.value == "none"); };
1786 this.removeOptions(selectBorderStyle, "borderStyle");
1787 TableOperations.buildInput(doc, fieldset, "f_st_borderWidth", "Border width:", "Border width", "pixels", "5", f_st_borderWidth, "fr", "floating", "postlabel", borderFields);
1788 TableOperations.insertSpace(doc, fieldset, borderFields);
1789
1790 if (nodeName == "table") {
1791 TableOperations.buildColorField(w, doc, editor, fieldset, "", "Color:", "fr", "colorButton", (el ? el.style.borderColor : ""), "borderColor", borderFields);
1792 var label = doc.createElement("label");
1793 label.className = "fl-borderCollapse";
1794 label.htmlFor = "f_st_borderCollapse";
1795 label.innerHTML = "Collapsed borders";
1796 fieldset.appendChild(label);
1797 borderFields.push(label);
1798 var input = doc.createElement("input");
1799 input.className = "checkbox";
1800 input.type = "checkbox";
1801 input.name = "f_st_borderCollapse";
1802 input.id = "f_st_borderCollapse";
1803 input.defaultChecked = el ? /collapse/i.test(el.style.borderCollapse) : false;
1804 input.checked = input.defaultChecked;
1805 fieldset.appendChild(input);
1806 borderFields.push(input);
1807 TableOperations.insertSpace(doc, fieldset, borderFields);
1808 select = TableOperations.buildSelectField(doc, fieldset, "f_frames", "Frames:", "fr", "floating", "Specifies which sides should have a border", ["Not set", "No sides", "The top side only", "The bottom side only", "The top and bottom sides only", "The right and left sides only", "The left-hand side only", "The right-hand side only", "All four sides"], ["not set", "void", "above", "below", "hsides", "vsides", "lhs", "rhs", "box"], new RegExp(((el && el.frame) ? el.frame : "not set"), "i"), borderFields);
1809 TableOperations.insertSpace(doc, fieldset, borderFields);
1810 select = TableOperations.buildSelectField(doc, fieldset, "f_rules", "Rules:", "fr", "floating", "Specifies where rules should be displayed", ["Not set", "No rules", "Rules will appear between rows only", "Rules will appear between columns only", "Rules will appear between all rows and columns"], ["not set", "none", "rows", "cols", "all"], new RegExp(((el && el.rules) ? el.rules : "not set"), "i"), borderFields);
1811 } else {
1812 TableOperations.insertSpace(doc, fieldset, borderFields);
1813 TableOperations.buildColorField(w, doc, editor, fieldset, "", "Color:", "fr", "colorButton", (el ? el.style.borderColor : ""), "borderColor", borderFields);
1814 }
1815 setBorderFieldsVisibility(selectBorderStyle.value == "none");
1816 TableOperations.insertSpace(doc, fieldset);
1817 content.appendChild(fieldset);
1818 },
1819
1820 removeOptions : function(select, property) {
1821 if (this.properties && this.properties[property] && this.properties[property].removeItems) {
1822 for (var i = select.options.length; --i >= 0;) {
1823 if (this.properties[property].removeItems.indexOf(select.options[i].value) != -1) {
1824 if (select.options[i].value != select.value) {
1825 select.options[i] = null;
1826 }
1827 }
1828 }
1829 }
1830 },
1831
1832 /*
1833 * This function gets called by the main editor event handler when a key was pressed.
1834 * It will process the enter key for IE when buttons.table.disableEnterParagraphs is set in the editor configuration
1835 */
1836 onKeyPress : function (ev) {
1837 if (HTMLArea.is_ie && ev.keyCode == 13 && !ev.shiftKey && this.disableEnterParagraphs) {
1838 var selection = this.editor._getSelection();
1839 var range = this.editor._createRange(selection);
1840 var parentElement = this.editor.getParentElement(selection, range);
1841 while (parentElement && !HTMLArea.isBlockElement(parentElement)) {
1842 parentElement = parentElement.parentNode;
1843 }
1844 if (/^(td|th)$/i.test(parentElement.nodeName)) {
1845 range.pasteHTML("<br />");
1846 return false;
1847 }
1848 }
1849 return true;
1850 }
1851 });
1852
1853 TableOperations.getLength = function(value) {
1854 var len = parseInt(value);
1855 if (isNaN(len)) len = "";
1856 return len;
1857 };
1858
1859 // Returns an HTML element for a widget that allows color selection. That is,
1860 // a button that contains the given color, if any, and when pressed will popup
1861 // the sooner-or-later-to-be-rewritten select_color.html dialog allowing user
1862 // to select some color. If a color is selected, an input field with the name
1863 // "f_st_"+name will be updated with the color value in #123456 format.
1864 TableOperations.createColorButton = function(w, doc, editor, color, name) {
1865 if (!color) {
1866 color = "";
1867 } else if (!/#/.test(color)) {
1868 color = HTMLArea._colorToRgb(color);
1869 }
1870
1871 var df = doc.createElement("span");
1872 var field = doc.createElement("input");
1873 field.type = "hidden";
1874 df.appendChild(field);
1875 field.name = "f_st_" + name;
1876 field.id = "f_st_" + name;
1877 field.value = color;
1878 var button = doc.createElement("span");
1879 button.className = "buttonColor";
1880 df.appendChild(button);
1881 var span = doc.createElement("span");
1882 span.className = "chooser";
1883 span.style.backgroundColor = color;
1884 var space = doc.createTextNode("\xA0");
1885 span.appendChild(space);
1886 button.appendChild(span);
1887 button.onmouseover = function() { if (!this.disabled) this.className += " buttonColor-hilite"; };
1888 button.onmouseout = function() { if (!this.disabled) this.className = "buttonColor"; };
1889 span.onclick = function() {
1890 if (this.parentNode.disabled) return false;
1891 var colorPlugin = editor.plugins.TYPO3Color;
1892 if (colorPlugin) {
1893 colorPlugin.instance.dialogSelectColor("color", span, field, w);
1894 } else {
1895 colorPlugin = editor.plugins.DefaultColor;
1896 if (colorPlugin) {
1897 w.insertColor = function (color) {
1898 if (color) {
1899 span.style.backgroundColor = color;
1900 field.value = color;
1901 }
1902 };
1903 colorPlugin.instance.onButtonPress(editor, "TableOperations");
1904 }
1905 }
1906 };
1907 var span2 = doc.createElement("span");
1908 span2.innerHTML = "&#x00d7;";
1909 span2.className = "nocolor";
1910 span2.title = "Unset color";
1911 button.appendChild(span2);
1912 span2.onmouseover = function() { if (!this.parentNode.disabled) this.className += " nocolor-hilite"; };
1913 span2.onmouseout = function() { if (!this.parentNode.disabled) this.className = "nocolor"; };
1914 span2.onclick = function() {
1915 span.style.backgroundColor = "";
1916 field.value = "";
1917 };
1918 return df;
1919 };
1920 TableOperations.buildTitle = function(doc, content, title) {
1921 var div = doc.createElement("div");
1922 div.className = "title";
1923 div.innerHTML = title;
1924 content.appendChild(div);
1925 doc.title = title;
1926 };
1927 TableOperations.buildDescriptionFieldset = function(doc, el, content, fieldsetClass) {
1928 var fieldset = doc.createElement("fieldset");
1929 if (fieldsetClass) fieldset.className = fieldsetClass;
1930 TableOperations.insertLegend(doc, fieldset, "Description");
1931 TableOperations.insertSpace(doc, fieldset);
1932 var f_caption = "";
1933 if (el) {
1934 var capel = el.getElementsByTagName("caption")[0];
1935 if (capel) f_caption = capel.innerHTML;
1936 }
1937 TableOperations.buildInput(doc, fieldset, "f_caption", "Caption:", "Description of the nature of the table", "", "", f_caption, "fr", "value", "");
1938 TableOperations.insertSpace(doc, fieldset);
1939 TableOperations.buildInput(doc, fieldset, "f_summary", "Summary:", "Summary of the table purpose and structure", "", "", (el ? el.summary : ""), "fr", "value", "");
1940 TableOperations.insertSpace(doc, fieldset);
1941 content.appendChild(fieldset);
1942 };
1943 TableOperations.buildRowGroupFieldset = function(w, doc, editor, el, content, fieldsetClass) {
1944 var fieldset = doc.createElement("fieldset");
1945 if (fieldsetClass) fieldset.className = fieldsetClass;
1946 TableOperations.insertLegend(doc, fieldset, "Row group");
1947 TableOperations.insertSpace(doc, fieldset);
1948 TableOperations.insertSpace(doc, fieldset);
1949 selected = el.parentNode.nodeName.toLowerCase();
1950 var selectScope = TableOperations.buildSelectField(doc, fieldset, "f_rowgroup", "Row group:", "fr", "floating", "Table section", ["Table body", "Table header", "Table footer"], ["tbody", "thead", "tfoot"], new RegExp((selected ? selected : "tbody"), "i"));
1951 function displayCheckbox(current, value) {
1952 if (current !== "thead" && value === "thead") {
1953 label1.style.display = "inline";
1954 label2.style.display = "none";
1955 input.style.display = "inline";
1956 input.checked = true;
1957 } else if (current === "thead" && value !== "thead") {
1958 label1.style.display = "none";
1959 label2.style.display = "inline";
1960 input.style.display = "inline";
1961 input.checked = true;
1962 } else {
1963 label1.style.display = "none";
1964 label2.style.display = "none";
1965 input.style.display = "none";
1966 input.checked = false;
1967 }
1968 }
1969 selectScope.onchange = function() { displayCheckbox(selected, this.value); };
1970 var label1 = doc.createElement("label");
1971 label1.className = "fl";
1972 label1.htmlFor = "f_convertCells";
1973 label1.innerHTML = "Make cells header cells";
1974 label1.style.display = "none";
1975 fieldset.appendChild(label1);
1976 var label2 = doc.createElement("label");
1977 label2.className = "fl";
1978 label2.htmlFor = "f_convertCells";
1979 label2.innerHTML = "Make cells data cells";
1980 label2.style.display = "none";
1981 fieldset.appendChild(label2);
1982 var input = doc.createElement("input");
1983 input.className = "checkbox";
1984 input.type = "checkbox";
1985 input.name = "f_convertCells";
1986 input.id = "f_convertCells";
1987 input.checked = false;
1988 input.style.display = "none";
1989 fieldset.appendChild(input);
1990 TableOperations.insertSpace(doc, fieldset);
1991 content.appendChild(fieldset);
1992 };
1993 TableOperations.buildSpacingFieldset = function(doc, el, content) {
1994 var fieldset = doc.createElement("fieldset");
1995 TableOperations.insertLegend(doc, fieldset, "Spacing and padding");
1996 TableOperations.buildInput(doc, fieldset, "f_spacing", "Cell spacing:", "Space between adjacent cells", "pixels", "5", (el ? el.cellSpacing : ""), "fr", "", "postlabel");
1997 TableOperations.insertSpace(doc, fieldset);
1998 TableOperations.buildInput(doc, fieldset, "f_padding", "Cell padding:", "Space between content and border in cell", "pixels", "5", (el ? el.cellPadding : ""), "fr", "", "postlabel");
1999 content.appendChild(fieldset);
2000 };
2001 TableOperations.buildColorsFieldset = function(w, doc, editor, el, content) {
2002 var fieldset = doc.createElement("fieldset");
2003 TableOperations.insertLegend(doc, fieldset, "Background and colors");
2004 var ul = doc.createElement("ul");
2005 fieldset.appendChild(ul);
2006 var li = doc.createElement("li");
2007 ul.appendChild(li);
2008 TableOperations.buildColorField(w, doc, editor, li, "", "FG Color:", "fr", "colorButtonNoFloat", (el ? el.style.color : ""), "color");
2009 var li = doc.createElement("li");
2010 ul.appendChild(li);
2011 TableOperations.buildColorField(w, doc, editor, li, "", "Background:", "fr", "colorButtonNoFloat", (el ? el.style.backgroundColor : ""), "backgroundColor");
2012 var url;
2013 if (el && el.style.backgroundImage.match(/url\(\s*(.*?)\s*\)/)) url = RegExp.$1;
2014 TableOperations.buildInput(doc, li, "f_st_backgroundImage", "Image URL:", "URL of the background image", "", "", url, "", "shorter-value");
2015 content.appendChild(fieldset);
2016 };
2017 TableOperations.insertLegend = function(doc, fieldset, legend) {
2018 var legendNode = doc.createElement("legend");
2019 legendNode.innerHTML = legend;
2020 fieldset.appendChild(legendNode);
2021 };
2022 TableOperations.insertSpace = function(doc,fieldset,fields) {
2023 var space = doc.createElement("div");
2024 space.className = "space";
2025 fieldset.appendChild(space);
2026 if(fields) fields.push(space);
2027 };
2028 TableOperations.buildInput = function(doc, fieldset,fieldName,fieldLabel,fieldTitle,postLabel,fieldSize,fieldValue,labelClass,inputClass,postClass,fields) {
2029 var label;
2030 // Field label
2031 if(fieldLabel) {
2032 label = doc.createElement("label");
2033 if(labelClass) label.className = labelClass;
2034 label.innerHTML = fieldLabel;
2035 label.htmlFor = fieldName;
2036 fieldset.appendChild(label);
2037 if(fields) fields.push(label);
2038 }
2039 // Input field
2040 var input = doc.createElement("input");
2041 input.type = "text";
2042 input.id = fieldName;
2043 input.name = fieldName;
2044 if(inputClass) input.className = inputClass;
2045 if(fieldTitle) input.title = fieldTitle;
2046 if(fieldSize) input.size = fieldSize;
2047 if(fieldValue) input.value = fieldValue;
2048 fieldset.appendChild(input);
2049 if(fields) fields.push(input);
2050 // Field post label
2051 if(postLabel) {
2052 label = doc.createElement("span");
2053 if(postClass) label.className = postClass;
2054 label.innerHTML = postLabel;
2055 fieldset.appendChild(label);
2056 if(fields) fields.push(label);
2057 }
2058 return input;
2059 };
2060 TableOperations.buildSelectField = function(doc, fieldset,fieldName,fieldLabel,labelClass,selectClass,fieldTitle,options,values,selected,fields,translateOptions) {
2061 if(typeof(translateOptions) == "undefined") var translateOptions = true;
2062 // Field Label
2063 if(fieldLabel) {
2064 var label = doc.createElement("label");
2065 if(labelClass) label.className = labelClass;
2066 label.innerHTML = fieldLabel;
2067 label.htmlFor = fieldName;
2068 fieldset.appendChild(label);
2069 if(fields) fields.push(label);
2070 }
2071 // Text Alignment Select Box
2072 var select = doc.createElement("select");
2073 if (selectClass) select.className = selectClass;
2074 select.id = fieldName;
2075 select.name = fieldName;
2076 select.title= fieldTitle;
2077 select.selectedIndex = 0;
2078 var option;
2079 for (var i = 0; i < options.length; ++i) {
2080 option = doc.createElement("option");
2081 select.appendChild(option);
2082 option.value = values[i];
2083 option.innerHTML = options[i];
2084 option.selected = selected.test(option.value);
2085 }
2086 if (select.options.length>1) select.disabled = false;
2087 else select.disabled = true;
2088 fieldset.appendChild(select);
2089 if(fields) fields.push(select);
2090 return select;
2091 };
2092 TableOperations.buildColorField = function(w, doc, editor, fieldset,fieldName,fieldLabel,labelClass, buttonClass, fieldValue,fieldType,fields) {
2093 // Field Label
2094 if(fieldLabel) {
2095 var label = doc.createElement("label");
2096 if(labelClass) label.className = labelClass;
2097 label.innerHTML = fieldLabel;
2098 fieldset.appendChild(label);
2099 if(fields) fields.push(label);
2100 }
2101 var colorButton = TableOperations.createColorButton(w, doc, editor, fieldValue, fieldType);
2102 colorButton.className = buttonClass;
2103 fieldset.appendChild(colorButton);
2104 if(fields) fields.push(colorButton);
2105 };