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