Ticket #908: table-operations.4.js

File table-operations.4.js, 40.3 kB (added by ray, 5 years ago)

table-operations.js

Line 
1// Table Operations Plugin for HTMLArea-3.0
2// Implementation by Mihai Bazon.  Sponsored by http://www.bloki.com
3//
4// htmlArea v3.0 - Copyright (c) 2002 interactivetools.com, inc.
5// This notice MUST stay intact for use (see license.txt).
6//
7// A free WYSIWYG editor replacement for <textarea> fields.
8// For full source code and docs, visit http://www.interactivetools.com/
9//
10// Version 3.0 developed by Mihai Bazon for InteractiveTools.
11//   http://dynarch.com/mishoo
12//
13// $Id: table-operations.js 719 2007-02-08 17:42:35Z htanaka $
14
15// Object that will encapsulate all the table operations provided by
16// HTMLArea-3.0 (except "insert table" which is included in the main file)
17function TableOperations(editor) {
18        this.editor = editor;
19
20        var cfg = editor.config;
21        var bl = TableOperations.btnList;
22        var self = this;
23
24        // register the toolbar buttons provided by this plugin
25
26  // Remove existing inserttable and toggleborders, we will replace it in our group 
27  cfg.removeToolbarElement(' inserttable toggleborders ');
28 
29        var toolbar = ["linebreak", "inserttable", "toggleborders"];
30   
31        for (var i = 0; i < bl.length; ++i) {
32                var btn = bl[i];
33                if (!btn) {
34                        toolbar.push("separator");
35                } else {
36                        var id = "TO-" + btn[0];
37                        cfg.registerButton(id, HTMLArea._lc(btn[2], "TableOperations"), editor.imgURL(btn[0] + ".gif", "TableOperations"), false,
38                                           function(editor, id) {
39                                                   // dispatch button press event
40                                                   self.buttonPress(editor, id);
41                                           }, btn[1]);
42                        toolbar.push(id);
43                }
44        }
45
46        // add a new line in the toolbar
47        cfg.toolbar.push(toolbar);
48       
49  if ( typeof PopupWin == 'undefined' )
50  {
51    Xinha._loadback(_editor_url + 'modules/Dialogs/popupwin.js');
52  }
53}
54
55TableOperations._pluginInfo = {
56        name          : "TableOperations",
57        version       : "1.0",
58        developer     : "Mihai Bazon",
59        developer_url : "http://dynarch.com/mishoo/",
60        c_owner       : "Mihai Bazon",
61        sponsor       : "Zapatec Inc.",
62        sponsor_url   : "http://www.bloki.com",
63        license       : "htmlArea"
64};
65
66TableOperations.prototype._lc = function(string) {
67    return HTMLArea._lc(string, 'TableOperations');
68};
69
70/************************
71 * UTILITIES
72 ************************/
73
74// retrieves the closest element having the specified tagName in the list of
75// ancestors of the current selection/caret.
76TableOperations.prototype.getClosest = function(tagName) {
77        var editor = this.editor;
78        var ancestors = editor.getAllAncestors();
79        var ret = null;
80        tagName = ("" + tagName).toLowerCase();
81        for (var i = 0; i < ancestors.length; ++i) {
82                var el = ancestors[i];
83                if (el.tagName.toLowerCase() == tagName) {
84                        ret = el;
85                        break;
86                }
87        }
88        return ret;
89};
90
91// this function requires the file PopupDiv/PopupWin to be loaded from browser
92TableOperations.prototype.dialogTableProperties = function() {
93        // retrieve existing values
94        var table = this.getClosest("table");
95        // this.editor.selectNodeContents(table);
96        // this.editor.updateToolbar();
97
98        var dialog = new PopupWin(this.editor, HTMLArea._lc("Table Properties", "TableOperations"), function(dialog, params) {
99                TableOperations.processStyle(params, table);
100                for (var i in params) {
101      if(typeof params[i] == 'function') continue;
102                        var val = params[i];
103                        switch (i) {
104                            case "f_caption":
105                                if (/\S/.test(val)) {
106                                        // contains non white-space characters
107                                        var caption = table.getElementsByTagName("caption")[0];
108                                        if (!caption) {
109                                                caption = dialog.editor._doc.createElement("caption");
110                                                table.insertBefore(caption, table.firstChild);
111                                        }
112                                        caption.innerHTML = val;
113                                } else {
114                                        // search for caption and delete it if found
115                                        var caption = table.getElementsByTagName("caption")[0];
116                                        if (caption) {
117                                                caption.parentNode.removeChild(caption);
118                                        }
119                                }
120                                break;
121                            case "f_summary":
122                                table.summary = val;
123                                break;
124                            case "f_width":
125                                table.style.width = ("" + val) + params.f_unit;
126                                break;
127                            case "f_align":
128                                table.align = val;
129                                break;
130                            case "f_spacing":
131                                table.cellSpacing = val;
132                                break;
133                            case "f_padding":
134                                table.cellPadding = val;
135                                break;
136                            case "f_borders":
137                                table.border = val;
138                                break;
139                            case "f_frames":
140                                table.frame = val;
141                                break;
142                            case "f_rules":
143                                table.rules = val;
144                                break;
145                        }
146                }
147                // various workarounds to refresh the table display (Gecko,
148                // what's going on?! do not disappoint me!)
149                dialog.editor.forceRedraw();
150                dialog.editor.focusEditor();
151                dialog.editor.updateToolbar();
152                var save_collapse = table.style.borderCollapse;
153                table.style.borderCollapse = "collapse";
154                table.style.borderCollapse = "separate";
155                table.style.borderCollapse = save_collapse;
156        },
157
158        // this function gets called when the dialog needs to be initialized
159        function (dialog) {
160
161                var f_caption = "";
162                var capel = table.getElementsByTagName("caption")[0];
163                if (capel) {
164                        f_caption = capel.innerHTML;
165                }
166                var f_summary = table.summary;
167                var f_width = parseInt(table.style.width);
168                isNaN(f_width) && (f_width = "");
169                var f_unit = /%/.test(table.style.width) ? 'percent' : 'pixels';
170                var f_align = table.align;
171                var f_spacing = table.cellSpacing;
172                var f_padding = table.cellPadding;
173                var f_borders = table.border;
174                var f_frames = table.frame;
175                var f_rules = table.rules;
176
177                function selected(val) {
178                        return val ? " selected" : "";
179                }
180
181                // dialog contents
182                dialog.content.style.width = "400px";
183                dialog.content.innerHTML = " \
184<div class='title'>" + HTMLArea._lc("Table Properties", "TableOperations") + "\
185</div> \
186<table style='width:100%'> \
187  <tr> \
188    <td> \
189      <fieldset><legend>" + HTMLArea._lc("Description", "TableOperations") + "</legend> \
190       <table style='width:100%'> \
191        <tr> \
192          <td class='label'>" + HTMLArea._lc("Caption", "TableOperations") + ":</td> \
193          <td class='value'><input type='text' name='f_caption' value='" + f_caption + "'/></td> \
194        </tr><tr> \
195          <td class='label'>" + HTMLArea._lc("Summary", "TableOperations") + ":</td> \
196          <td class='value'><input type='text' name='f_summary' value='" + f_summary + "'/></td> \
197        </tr> \
198       </table> \
199      </fieldset> \
200    </td> \
201  </tr> \
202  <tr><td id='--HA-layout'></td></tr> \
203  <tr> \
204    <td> \
205      <fieldset><legend>" + HTMLArea._lc("Spacing and padding", "TableOperations") + "</legend> \
206       <table style='width:100%'> \
207"+//        <tr> \
208//           <td class='label'>" + HTMLArea._lc("Width", "TableOperations") + ":</td> \
209//           <td><input type='text' name='f_width' value='" + f_width + "' size='5' /> \
210//             <select name='f_unit'> \
211//               <option value='%'" + selected(f_unit == "percent") + ">" + HTMLArea._lc("percent", "TableOperations") + "</option> \
212//               <option value='px'" + selected(f_unit == "pixels") + ">" + HTMLArea._lc("pixels", "TableOperations") + "</option> \
213//             </select> &nbsp;&nbsp;" + HTMLArea._lc("Align", "TableOperations") + ": \
214//             <select name='f_align'> \
215//               <option value='left'" + selected(f_align == "left") + ">" + HTMLArea._lc("Left", "TableOperations") + "</option> \
216//               <option value='center'" + selected(f_align == "center") + ">" + HTMLArea._lc("Center", "TableOperations") + "</option> \
217//               <option value='right'" + selected(f_align == "right") + ">" + HTMLArea._lc("Right", "TableOperations") + "</option> \
218//             </select> \
219//           </td> \
220//         </tr> \
221"        <tr> \
222          <td class='label'>" + HTMLArea._lc("Spacing", "TableOperations") + ":</td> \
223          <td><input type='text' name='f_spacing' size='5' value='" + f_spacing + "' /> &nbsp;" + HTMLArea._lc("Padding", "TableOperations") + ":\
224            <input type='text' name='f_padding' size='5' value='" + f_padding + "' /> &nbsp;&nbsp;" + HTMLArea._lc("pixels", "TableOperations") + "\
225          </td> \
226        </tr> \
227       </table> \
228      </fieldset> \
229    </td> \
230  </tr> \
231  <tr> \
232    <td> \
233      <fieldset><legend>" + HTMLArea._lc("Frame and borders", "TableOperations") + "</legend> \
234        <table width='100%'> \
235          <tr> \
236            <td class='label'>" + HTMLArea._lc("Borders", "TableOperations") + ":</td> \
237            <td><input name='f_borders' type='text' size='5' value='" + f_borders + "' /> &nbsp;&nbsp;" + HTMLArea._lc("pixels", "TableOperations") + "</td> \
238          </tr> \
239          <tr> \
240            <td class='label'>" + HTMLArea._lc("Frames", "TableOperations") + ":</td> \
241            <td> \
242              <select name='f_frames'> \
243                <option value='void'" + selected(f_frames == "void") + ">" + HTMLArea._lc("No sides", "TableOperations") + "</option> \
244                <option value='above'" + selected(f_frames == "above") + ">" + HTMLArea._lc("The top side only", "TableOperations") + "</option> \
245                <option value='below'" + selected(f_frames == "below") + ">" + HTMLArea._lc("The bottom side only", "TableOperations") + "</option> \
246                <option value='hsides'" + selected(f_frames == "hsides") + ">" + HTMLArea._lc("The top and bottom sides only", "TableOperations") + "</option> \
247                <option value='vsides'" + selected(f_frames == "vsides") + ">" + HTMLArea._lc("The right and left sides only", "TableOperations") + "</option> \
248                <option value='lhs'" + selected(f_frames == "lhs") + ">" + HTMLArea._lc("The left-hand side only", "TableOperations") + "</option> \
249                <option value='rhs'" + selected(f_frames == "rhs") + ">" + HTMLArea._lc("The right-hand side only", "TableOperations") + "</option> \
250                <option value='box'" + selected(f_frames == "box") + ">" + HTMLArea._lc("All four sides", "TableOperations") + "</option> \
251              </select> \
252            </td> \
253          </tr> \
254          <tr> \
255            <td class='label'>" + HTMLArea._lc("Rules", "TableOperations") + ":</td> \
256            <td> \
257              <select name='f_rules'> \
258                <option value='none'" + selected(f_rules == "none") + ">" + HTMLArea._lc("No rules", "TableOperations") + "</option> \
259                <option value='rows'" + selected(f_rules == "rows") + ">" + HTMLArea._lc("Rules will appear between rows only", "TableOperations") + "</option> \
260                <option value='cols'" + selected(f_rules == "cols") + ">" + HTMLArea._lc("Rules will appear between columns only", "TableOperations") + "</option> \
261                <option value='all'" + selected(f_rules == "all") + ">" + HTMLArea._lc("Rules will appear between all rows and columns", "TableOperations") + "</option> \
262              </select> \
263            </td> \
264          </tr> \
265        </table> \
266      </fieldset> \
267    </td> \
268  </tr> \
269  <tr> \
270    <td id='--HA-style'></td> \
271  </tr> \
272</table> \
273";
274                var st_prop = TableOperations.createStyleFieldset(dialog.doc, dialog.editor, table);
275                var p = dialog.doc.getElementById("--HA-style");
276                p.appendChild(st_prop);
277                var st_layout = TableOperations.createStyleLayoutFieldset(dialog.doc, dialog.editor, table);
278                p = dialog.doc.getElementById("--HA-layout");
279                p.appendChild(st_layout);
280                dialog.modal = true;
281                dialog.addButtons("OK", "Cancel");
282                dialog.showAtElement(dialog.editor._iframe, "c");
283        });
284};
285
286// this function requires the file PopupDiv/PopupWin to be loaded from browser
287TableOperations.prototype.dialogRowCellProperties = function(cell) {
288        // retrieve existing values
289        var element = this.getClosest(cell ? "td" : "tr");
290        var table = this.getClosest("table");
291        // this.editor.selectNodeContents(element);
292        // this.editor.updateToolbar();
293
294        var dialog = new PopupWin(this.editor, cell ? HTMLArea._lc("Cell Properties", "TableOperations") : HTMLArea._lc("Row Properties", "TableOperations"), function(dialog, params) {
295                TableOperations.processStyle(params, element);
296                for (var i in params) {
297      if(typeof params[i] == 'function') continue;
298                        var val = params[i];
299                        switch (i) {
300                            case "f_align":
301                                element.align = val;
302                                break;
303                            case "f_char":
304                                element.ch = val;
305                                break;
306                            case "f_valign":
307                                element.vAlign = val;
308                                break;
309                        }
310                }
311                // various workarounds to refresh the table display (Gecko,
312                // what's going on?! do not disappoint me!)
313                dialog.editor.forceRedraw();
314                dialog.editor.focusEditor();
315                dialog.editor.updateToolbar();
316                var save_collapse = table.style.borderCollapse;
317                table.style.borderCollapse = "collapse";
318                table.style.borderCollapse = "separate";
319                table.style.borderCollapse = save_collapse;
320        },
321
322        // this function gets called when the dialog needs to be initialized
323        function (dialog) {
324
325                var f_align = element.align;
326                var f_valign = element.vAlign;
327                var f_char = element.ch;
328
329                function selected(val) {
330                        return val ? " selected" : "";
331                }
332
333                // dialog contents
334                dialog.content.style.width = "400px";
335                dialog.content.innerHTML = " \
336<div class='title'>" + HTMLArea._lc(cell ? "Cell Properties" : "Row Properties", "TableOperations") + "</div> \
337<table style='width:100%'> \
338  <tr> \
339    <td id='--HA-layout'> \
340"+//      <fieldset><legend>" + HTMLArea._lc("Layout", "TableOperations") + "</legend> \
341//        <table style='width:100%'> \
342//         <tr> \
343//           <td class='label'>" + HTMLArea._lc("Align", "TableOperations") + ":</td> \
344//           <td> \
345//             <select name='f_align'> \
346//               <option value='left'" + selected(f_align == "left") + ">" + HTMLArea._lc("Left", "TableOperations") + "</option> \
347//               <option value='center'" + selected(f_align == "center") + ">" + HTMLArea._lc("Center", "TableOperations") + "</option> \
348//               <option value='right'" + selected(f_align == "right") + ">" + HTMLArea._lc("Right", "TableOperations") + "</option> \
349//               <option value='char'" + selected(f_align == "char") + ">" + HTMLArea._lc("Char", "TableOperations") + "</option> \
350//             </select> \
351//             &nbsp;&nbsp;" + HTMLArea._lc("Char", "TableOperations") + ": \
352//             <input type='text' style='font-family: monospace; text-align: center' name='f_char' size='1' value='" + f_char + "' /> \
353//           </td> \
354//         </tr><tr> \
355//           <td class='label'>" + HTMLArea._lc("Vertical align", "TableOperations") + ":</td> \
356//           <td> \
357//             <select name='f_valign'> \
358//               <option value='top'" + selected(f_valign == "top") + ">" + HTMLArea._lc("Top", "TableOperations") + "</option> \
359//               <option value='middle'" + selected(f_valign == "middle") + ">" + HTMLArea._lc("Middle", "TableOperations") + "</option> \
360//               <option value='bottom'" + selected(f_valign == "bottom") + ">" + HTMLArea._lc("Bottom", "TableOperations") + "</option> \
361//               <option value='baseline'" + selected(f_valign == "baseline") + ">" + HTMLArea._lc("Baseline", "TableOperations") + "</option> \
362//             </select> \
363//           </td> \
364//         </tr> \
365//        </table> \
366//       </fieldset> \
367"    </td> \
368  </tr> \
369  <tr> \
370    <td id='--HA-style'></td> \
371  </tr> \
372</table> \
373";
374                var st_prop = TableOperations.createStyleFieldset(dialog.doc, dialog.editor, element);
375                var p = dialog.doc.getElementById("--HA-style");
376                p.appendChild(st_prop);
377                var st_layout = TableOperations.createStyleLayoutFieldset(dialog.doc, dialog.editor, element);
378                p = dialog.doc.getElementById("--HA-layout");
379                p.appendChild(st_layout);
380                dialog.modal = true;
381                dialog.addButtons("OK", "Cancel");
382                dialog.showAtElement(dialog.editor._iframe, "c");
383        });
384};
385
386// this function gets called when some button from the TableOperations toolbar
387// was pressed.
388TableOperations.prototype.buttonPress = function(editor, button_id) {
389        this.editor = editor;
390        var mozbr = HTMLArea.is_gecko ? "<br />" : "";
391
392        // helper function that clears the content in a table row
393        function clearRow(tr) {
394                var tds = tr.getElementsByTagName("td");
395                for (var i = tds.length; --i >= 0;) {
396                        var td = tds[i];
397                        td.rowSpan = 1;
398                        td.innerHTML = mozbr;
399                }
400        }
401
402        function splitRow(td) {
403                var n = parseInt("" + td.rowSpan);
404                var nc = parseInt("" + td.colSpan);
405                td.rowSpan = 1;
406                tr = td.parentNode;
407                var itr = tr.rowIndex;
408                var trs = tr.parentNode.rows;
409                var index = td.cellIndex;
410                while (--n > 0) {
411                        tr = trs[++itr];
412                        var otd = editor._doc.createElement("td");
413                        otd.colSpan = td.colSpan;
414                        otd.innerHTML = mozbr;
415                        tr.insertBefore(otd, tr.cells[index]);
416                }
417                editor.forceRedraw();
418                editor.updateToolbar();
419        }
420
421        function splitCol(td) {
422                var nc = parseInt("" + td.colSpan);
423                td.colSpan = 1;
424                tr = td.parentNode;
425                var ref = td.nextSibling;
426                while (--nc > 0) {
427                        var otd = editor._doc.createElement("td");
428                        otd.rowSpan = td.rowSpan;
429                        otd.innerHTML = mozbr;
430                        tr.insertBefore(otd, ref);
431                }
432                editor.forceRedraw();
433                editor.updateToolbar();
434        }
435
436        function splitCell(td) {
437                var nc = parseInt("" + td.colSpan);
438                splitCol(td);
439                var items = td.parentNode.cells;
440                var index = td.cellIndex;
441                while (nc-- > 0) {
442                        splitRow(items[index++]);
443                }
444        }
445
446        function selectNextNode(el) {
447                var node = el.nextSibling;
448                while (node && node.nodeType != 1) {
449                        node = node.nextSibling;
450                }
451                if (!node) {
452                        node = el.previousSibling;
453                        while (node && node.nodeType != 1) {
454                                node = node.previousSibling;
455                        }
456                }
457                if (!node) {
458                        node = el.parentNode;
459                }
460                editor.selectNodeContents(node);
461        }
462
463        function cellMerge(table, cell_index, row_index, no_cols, no_rows) {
464                var rows = [];
465                var cells = [];
466                try {
467                        for (i=row_index; i<row_index+no_rows && table.rows[i]; i++) {
468                                var row = table.rows[i];
469                                for (j=cell_index; j<cell_index+no_cols && row.cells[j]; j++) {
470                                        if (row.cells[j].colSpan > 1 || row.cells[j].rowSpan > 1) {
471                                                splitCell(row.cells[j]);
472                                        }
473                                        cells.push(row.cells[j]);
474                                }
475                                if (cells.length > 0) {
476                                        rows.push(cells);
477                                        cells = [];
478                                }
479                        }
480                } catch(e) {
481                        alert("Invalid selection");
482                        return false;
483                }
484                var row_index1 = rows[0][0].parentNode.rowIndex;
485                var row_index2 = rows[rows.length-1][0].parentNode.rowIndex;
486                var row_span2 = rows[rows.length-1][0].rowSpan;
487                var HTML = "";
488                for (i = 0; i < rows.length; ++i) {
489                        var cells = rows[i];
490                        for (var j = 0; j < cells.length; ++j) {
491                                var cell = cells[j];
492                                HTML += cell.innerHTML;
493                                (i || j) && (cell.parentNode.removeChild(cell));
494                        }
495                }
496                var td = rows[0][0];
497                td.innerHTML = HTML;
498                td.rowSpan = row_index2 - row_index1 + row_span2;
499                var col_span = 0;
500                for(j=0; j<rows[0].length; j++) {
501                        col_span += rows[0][j].colSpan;
502                }
503                td.colSpan = col_span;
504                editor.selectNodeContents(td);
505                editor.forceRedraw();
506                editor.focusEditor();
507        }
508
509        switch (button_id) {
510                // ROWS
511
512            case "TO-row-insert-above":
513            case "TO-row-insert-under":
514                var tr = this.getClosest("tr");
515                if (!tr) {
516                        break;
517                }
518                var otr = tr.cloneNode(true);
519                clearRow(otr);
520                tr.parentNode.insertBefore(otr, /under/.test(button_id) ? tr.nextSibling : tr);
521                editor.forceRedraw();
522                editor.focusEditor();
523                break;
524            case "TO-row-delete":
525                var tr = this.getClosest("tr");
526                if (!tr) {
527                        break;
528                }
529                var par = tr.parentNode;
530                if (par.rows.length == 1) {
531                        alert(HTMLArea._lc("HTMLArea cowardly refuses to delete the last row in table.", "TableOperations"));
532                        break;
533                }
534                // set the caret first to a position that doesn't
535                // disappear.
536                selectNextNode(tr);
537                par.removeChild(tr);
538                editor.forceRedraw();
539                editor.focusEditor();
540                editor.updateToolbar();
541                break;
542            case "TO-row-split":
543                var td = this.getClosest("td");
544                if (!td) {
545                        break;
546                }
547                splitRow(td);
548                break;
549
550                // COLUMNS
551
552            case "TO-col-insert-before":
553            case "TO-col-insert-after":
554                var td = this.getClosest("td");
555                if (!td) {
556                        break;
557                }
558                var rows = td.parentNode.parentNode.rows;
559                var index = td.cellIndex;
560    var lastColumn = (td.parentNode.cells.length == index + 1);
561                for (var i = rows.length; --i >= 0;) {
562                        var tr = rows[i];                       
563                        var otd = editor._doc.createElement("td");
564                        otd.innerHTML = mozbr;
565      if (lastColumn && HTMLArea.is_ie)
566      {
567        tr.insertBefore(otd);
568      }
569      else 
570      {
571        var ref = tr.cells[index + (/after/.test(button_id) ? 1 : 0)];
572        tr.insertBefore(otd, ref);
573      }
574                }
575                editor.focusEditor();
576                break;
577            case "TO-col-split":
578                var td = this.getClosest("td");
579                if (!td) {
580                        break;
581                }
582                splitCol(td);
583                break;
584            case "TO-col-delete":
585                var td = this.getClosest("td");
586                if (!td) {
587                        break;
588                }
589                var index = td.cellIndex;
590                if (td.parentNode.cells.length == 1) {
591                        alert(HTMLArea._lc("HTMLArea cowardly refuses to delete the last column in table.", "TableOperations"));
592                        break;
593                }
594                // set the caret first to a position that doesn't disappear
595                selectNextNode(td);
596                var rows = td.parentNode.parentNode.rows;
597                for (var i = rows.length; --i >= 0;) {
598                        var tr = rows[i];
599                        tr.removeChild(tr.cells[index]);
600                }
601                editor.forceRedraw();
602                editor.focusEditor();
603                editor.updateToolbar();
604                break;
605
606                // CELLS
607
608            case "TO-cell-split":
609                var td = this.getClosest("td");
610                if (!td) {
611                        break;
612                }
613                splitCell(td);
614                break;
615            case "TO-cell-insert-before":
616            case "TO-cell-insert-after":
617                var td = this.getClosest("td");
618                if (!td) {
619                        break;
620                }
621                var tr = td.parentNode;
622                var otd = editor._doc.createElement("td");
623                otd.innerHTML = mozbr;
624                tr.insertBefore(otd, /after/.test(button_id) ? td.nextSibling : td);
625                editor.forceRedraw();
626                editor.focusEditor();
627                break;
628            case "TO-cell-delete":
629                var td = this.getClosest("td");
630                if (!td) {
631                        break;
632                }
633                if (td.parentNode.cells.length == 1) {
634                        alert(HTMLArea._lc("HTMLArea cowardly refuses to delete the last cell in row.", "TableOperations"));
635                        break;
636                }
637                // set the caret first to a position that doesn't disappear
638                selectNextNode(td);
639                td.parentNode.removeChild(td);
640                editor.forceRedraw();
641                editor.updateToolbar();
642                break;
643            case "TO-cell-merge":
644                //Mozilla, as opposed to IE, allows the selection of several cells, which is fine :)
645                if (!HTMLArea.is_ie && sel.rangeCount > 1) {
646                  var sel = editor._getSelection();
647                  var range = sel.getRangeAt(0);
648                        var td = range.startContainer.childNodes[range.startOffset];
649                        var tr = td.parentNode;
650                        var cell_index = td.cellIndex;         
651                        var row_index = tr.rowIndex;
652                        var row_index2 = 0;
653                        var rownum = row_index;
654                        var no_cols = 0;
655                        var row_colspan = 0;
656                        var td2, tr2;
657                        for(i=0; i<sel.rangeCount; i++) {
658                                range = sel.getRangeAt(i);
659                                        td2 = range.startContainer.childNodes[range.startOffset];
660                                        tr2 = td2.parentNode;   
661                                        if(tr2.rowIndex != rownum) {
662                                                rownum = tr2.rowIndex;
663                                                row_colspan = 0;
664                                        }
665                                        row_colspan += td2.colSpan;
666                                        if(row_colspan > no_cols) {
667                                                no_cols = row_colspan;
668                                        }
669                                        if(tr2.rowIndex + td2.rowSpan - 1 > row_index2) {
670                                                row_index2 = tr2.rowIndex + td2.rowSpan - 1;
671                                        }
672                                }
673                        var no_rows = row_index2 - row_index + 1;
674                        var table = tr.parentNode;
675                        cellMerge(table, cell_index, row_index, no_cols, no_rows);
676                } else {
677                        // Internet Explorer "browser" or not more than one cell selected in Moz
678                        var td = this.getClosest("td");
679                        if (!td) {
680                                alert(HTMLArea._lc("Please click into some cell", "TableOperations"));
681                                break;
682                        }
683                        editor._popupDialog("plugin://TableOperations/merge_cells.html", function(param) {
684                                if (!param) {   // user pressed Cancel
685                                        return false;
686                                }
687                                no_cols = parseInt(param['f_cols'],10) + 1;
688                                no_rows = parseInt(param['f_rows'],10) + 1;
689                                var tr = td.parentNode;
690                                var cell_index = td.cellIndex;
691                                var row_index = tr.rowIndex;
692                                var table = tr.parentNode;
693                                cellMerge(table, cell_index, row_index, no_cols, no_rows);
694                        }, null);       
695                }
696                break;
697
698                // PROPERTIES
699
700            case "TO-table-prop":
701                this.dialogTableProperties();
702                break;
703
704            case "TO-row-prop":
705                this.dialogRowCellProperties(false);
706                break;
707
708            case "TO-cell-prop":
709                this.dialogRowCellProperties(true);
710                break;
711
712            default:
713                alert("Button [" + button_id + "] not yet implemented");
714        }
715};
716
717// the list of buttons added by this plugin
718TableOperations.btnList = [
719        // table properties button
720    ["table-prop",       "table", "Table properties"],
721        null,                   // separator
722
723        // ROWS
724        ["row-prop",         "tr", "Row properties"],
725        ["row-insert-above", "tr", "Insert row before"],
726        ["row-insert-under", "tr", "Insert row after"],
727        ["row-delete",       "tr", "Delete row"],
728        ["row-split",        "td[rowSpan!=1]", "Split row"],
729        null,
730
731        // COLS
732        ["col-insert-before", "td", "Insert column before"],
733        ["col-insert-after""td", "Insert column after"],
734        ["col-delete",        "td", "Delete column"],
735        ["col-split",         "td[colSpan!=1]", "Split column"],
736        null,
737
738        // CELLS
739        ["cell-prop",          "td", "Cell properties"],
740        ["cell-insert-before", "td", "Insert cell before"],
741        ["cell-insert-after""td", "Insert cell after"],
742        ["cell-delete",        "td", "Delete cell"],
743        ["cell-merge",         "tr", "Merge cells"],
744        ["cell-split",         "td[colSpan!=1,rowSpan!=1]", "Split cell"]
745        ];
746
747
748
749//// GENERIC CODE [style of any element; this should be moved into a separate
750//// file as it'll be very useful]
751//// BEGIN GENERIC CODE -----------------------------------------------------
752
753TableOperations.getLength = function(value) {
754        var len = parseInt(value);
755        if (isNaN(len)) {
756                len = "";
757        }
758        return len;
759};
760
761// Applies the style found in "params" to the given element.
762TableOperations.processStyle = function(params, element) {
763        var style = element.style;
764        for (var i in params) {
765    if(typeof params[i] == 'function') continue;
766                var val = params[i];
767                switch (i) {
768                    case "f_st_backgroundColor":
769                        style.backgroundColor = val;
770                        break;
771                    case "f_st_color":
772                        style.color = val;
773                        break;
774                    case "f_st_backgroundImage":
775                        if (/\S/.test(val)) {
776                                style.backgroundImage = "url(" + val + ")";
777                        } else {
778                                style.backgroundImage = "none";
779                        }
780                        break;
781                    case "f_st_borderWidth":
782                        style.borderWidth = val;
783                        break;
784                    case "f_st_borderStyle":
785                        style.borderStyle = val;
786                        break;
787                    case "f_st_borderColor":
788                        style.borderColor = val;
789                        break;
790                    case "f_st_borderCollapse":
791                        style.borderCollapse = val ? "collapse" : "";
792                        break;
793                    case "f_st_width":
794                        if (/\S/.test(val)) {
795                                style.width = val + params["f_st_widthUnit"];
796                        } else {
797                                style.width = "";
798                        }
799                        break;
800                    case "f_st_height":
801                        if (/\S/.test(val)) {
802                                style.height = val + params["f_st_heightUnit"];
803                        } else {
804                                style.height = "";
805                        }
806                        break;
807                    case "f_st_textAlign":
808                        if (val == "char") {
809                                var ch = params["f_st_textAlignChar"];
810                                if (ch == '"') {
811                                        ch = '\\"';
812                                }
813                                style.textAlign = '"' + ch + '"';
814                        } else if (val == "-") {
815                            style.textAlign = "";
816                        } else {
817                                style.textAlign = val;
818                        }
819                        break;
820                    case "f_st_verticalAlign":
821                    element.vAlign = "";
822                        if (val == "-") {
823                            style.verticalAlign = "";
824                           
825                    } else {
826                            style.verticalAlign = val;
827                        }
828                        break;
829                    case "f_st_float":
830                        style.cssFloat = val;
831                        break;
832//                  case "f_st_margin":
833//                      style.margin = val + "px";
834//                      break;
835//                  case "f_st_padding":
836//                      style.padding = val + "px";
837//                      break;
838                }
839        }
840};
841
842// Returns an HTML element for a widget that allows color selection.  That is,
843// a button that contains the given color, if any, and when pressed will popup
844// the sooner-or-later-to-be-rewritten select_color.html dialog allowing user
845// to select some color.  If a color is selected, an input field with the name
846// "f_st_"+name will be updated with the color value in #123456 format.
847TableOperations.createColorButton = function(doc, editor, color, name) {
848        if (!color) {
849                color = "";
850        } else if (!/#/.test(color)) {
851                color = HTMLArea._colorToRgb(color);
852        }
853
854        var df = doc.createElement("span");
855        var field = doc.createElement("input");
856        field.type = "hidden";
857        df.appendChild(field);
858        field.name = "f_st_" + name;
859        field.value = color;
860        var button = doc.createElement("span");
861        button.className = "buttonColor";
862        df.appendChild(button);
863        var span = doc.createElement("span");
864        span.className = "chooser";
865        // span.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
866        span.style.backgroundColor = color;
867        button.appendChild(span);
868        button.onmouseover = function() { if (!this.disabled) { this.className += " buttonColor-hilite"; }};
869        button.onmouseout = function() { if (!this.disabled) { this.className = "buttonColor"; }};
870        span.onclick = function() {
871                if (this.parentNode.disabled) {
872                        return false;
873                }
874                editor._popupDialog("select_color.html", function(color) {
875                        if (color) {
876                                span.style.backgroundColor = "#" + color;
877                                field.value = "#" + color;
878                        }
879                }, color);
880        };
881        var span2 = doc.createElement("span");
882        span2.innerHTML = "&#x00d7;";
883        span2.className = "nocolor";
884        span2.title = HTMLArea._lc("Unset color", "TableOperations");
885        button.appendChild(span2);
886        span2.onmouseover = function() { if (!this.parentNode.disabled) { this.className += " nocolor-hilite"; }};
887        span2.onmouseout = function() { if (!this.parentNode.disabled) { this.className = "nocolor"; }};
888        span2.onclick = function() {
889                span.style.backgroundColor = "";
890                field.value = "";
891        };
892        return df;
893};
894
895TableOperations.createStyleLayoutFieldset = function(doc, editor, el) {
896        var fieldset = doc.createElement("fieldset");
897        var legend = doc.createElement("legend");
898        fieldset.appendChild(legend);
899        legend.innerHTML = HTMLArea._lc("Layout", "TableOperations");
900        var table = doc.createElement("table");
901        fieldset.appendChild(table);
902        table.style.width = "100%";
903        var tbody = doc.createElement("tbody");
904        table.appendChild(tbody);
905
906        var tagname = el.tagName.toLowerCase();
907        var tr, td, input, select, option, options, i;
908
909        if (tagname != "td" && tagname != "tr" && tagname != "th") {
910                tr = doc.createElement("tr");
911                tbody.appendChild(tr);
912                td = doc.createElement("td");
913                td.className = "label";
914                tr.appendChild(td);
915                td.innerHTML = HTMLArea._lc("Float", "TableOperations") + ":";
916                td = doc.createElement("td");
917                tr.appendChild(td);
918                select = doc.createElement("select");
919                td.appendChild(select);
920                select.name = "f_st_float";
921                options = ["None", "Left", "Right"];
922                for (var i = 0; i < options.length; ++i) {
923                        var Val = options[i];
924                        var val = options[i].toLowerCase();
925                        option = doc.createElement("option");
926                        option.innerHTML = HTMLArea._lc(Val, "TableOperations");
927                        option.value = val;
928                        option.selected = (("" + el.style.cssFloat).toLowerCase() == val);
929                        select.appendChild(option);
930                }
931        }
932
933        tr = doc.createElement("tr");
934        tbody.appendChild(tr);
935        td = doc.createElement("td");
936        td.className = "label";
937        tr.appendChild(td);
938        td.innerHTML = HTMLArea._lc("Width", "TableOperations") + ":";
939        td = doc.createElement("td");
940        tr.appendChild(td);
941        input = doc.createElement("input");
942        input.type = "text";
943        input.value = TableOperations.getLength(el.style.width);
944        input.size = "5";
945        input.name = "f_st_width";
946        input.style.marginRight = "0.5em";
947        td.appendChild(input);
948        select = doc.createElement("select");
949        select.name = "f_st_widthUnit";
950        option = doc.createElement("option");
951        option.innerHTML = HTMLArea._lc("percent", "TableOperations");
952        option.value = "%";
953        option.selected = /%/.test(el.style.width);
954        select.appendChild(option);
955        option = doc.createElement("option");
956        option.innerHTML = HTMLArea._lc("pixels", "TableOperations");
957        option.value = "px";
958        option.selected = /px/.test(el.style.width);
959        select.appendChild(option);
960        td.appendChild(select);
961
962        select.style.marginRight = "0.5em";
963        td.appendChild(doc.createTextNode(HTMLArea._lc("Text align", "TableOperations") + ":"));
964        select = doc.createElement("select");
965        select.style.marginLeft = select.style.marginRight = "0.5em";
966        td.appendChild(select);
967        select.name = "f_st_textAlign";
968        options = ["Left", "Center", "Right", "Justify", "-"];
969        if (tagname == "td") {
970                options.push("Char");
971        }
972        input = doc.createElement("input");
973        input.name = "f_st_textAlignChar";
974        input.size = "1";
975        input.style.fontFamily = "monospace";
976        td.appendChild(input);
977        for (var i = 0; i < options.length; ++i) {
978                var Val = options[i];
979                var val = Val.toLowerCase();
980                option = doc.createElement("option");
981                option.value = val;
982                option.innerHTML = HTMLArea._lc(Val, "TableOperations");
983                option.selected = ((el.style.textAlign.toLowerCase() == val) || (el.style.textAlign == "" && Val == "-"));
984                select.appendChild(option);
985        }
986        function setCharVisibility(value) {
987                input.style.visibility = value ? "visible" : "hidden";
988                if (value) {
989                        input.focus();
990                        input.select();
991                }
992        }
993        select.onchange = function() { setCharVisibility(this.value == "char"); };
994        setCharVisibility(select.value == "char");
995
996        tr = doc.createElement("tr");
997        tbody.appendChild(tr);
998        td = doc.createElement("td");
999        td.className = "label";
1000        tr.appendChild(td);
1001        td.innerHTML = HTMLArea._lc("Height", "TableOperations") + ":";
1002        td = doc.createElement("td");
1003        tr.appendChild(td);
1004        input = doc.createElement("input");
1005        input.type = "text";
1006        input.value = TableOperations.getLength(el.style.height);
1007        input.size = "5";
1008        input.name = "f_st_height";
1009        input.style.marginRight = "0.5em";
1010        td.appendChild(input);
1011        select = doc.createElement("select");
1012        select.name = "f_st_heightUnit";
1013        option = doc.createElement("option");
1014        option.innerHTML = HTMLArea._lc("percent", "TableOperations");
1015        option.value = "%";
1016        option.selected = /%/.test(el.style.height);
1017        select.appendChild(option);
1018        option = doc.createElement("option");
1019        option.innerHTML = HTMLArea._lc("pixels", "TableOperations");
1020        option.value = "px";
1021        option.selected = /px/.test(el.style.height);
1022        select.appendChild(option);
1023        td.appendChild(select);
1024
1025        select.style.marginRight = "0.5em";
1026        td.appendChild(doc.createTextNode(HTMLArea._lc("Vertical align", "TableOperations") + ":"));
1027        select = doc.createElement("select");
1028        select.name = "f_st_verticalAlign";
1029        select.style.marginLeft = "0.5em";
1030        td.appendChild(select);
1031        options = ["Top", "Middle", "Bottom", "Baseline", "-"];
1032        for (var i = 0; i < options.length; ++i) {
1033                var Val = options[i];
1034                var val = Val.toLowerCase();
1035                option = doc.createElement("option");
1036                option.value = val;
1037                option.innerHTML = HTMLArea._lc(Val, "TableOperations");
1038                option.selected = ((el.style.verticalAlign.toLowerCase() == val) || (el.style.verticalAlign == "" && Val == "-"));
1039                select.appendChild(option);
1040        }
1041
1042        return fieldset;
1043};
1044
1045// Returns an HTML element containing the style attributes for the given
1046// element.  This can be easily embedded into any dialog; the functionality is
1047// also provided.
1048TableOperations.createStyleFieldset = function(doc, editor, el) {
1049        var fieldset = doc.createElement("fieldset");
1050        var legend = doc.createElement("legend");
1051        fieldset.appendChild(legend);
1052        legend.innerHTML = HTMLArea._lc("CSS Style", "TableOperations");
1053        var table = doc.createElement("table");
1054        fieldset.appendChild(table);
1055        table.style.width = "100%";
1056        var tbody = doc.createElement("tbody");
1057        table.appendChild(tbody);
1058
1059        var tr, td, input, select, option, options, i;
1060
1061        tr = doc.createElement("tr");
1062        tbody.appendChild(tr);
1063        td = doc.createElement("td");
1064        tr.appendChild(td);
1065        td.className = "label";
1066        td.innerHTML = HTMLArea._lc("Background", "TableOperations") + ":";
1067        td = doc.createElement("td");
1068        tr.appendChild(td);
1069        var df = TableOperations.createColorButton(doc, editor, el.style.backgroundColor, "backgroundColor");
1070        df.firstChild.nextSibling.style.marginRight = "0.5em";
1071        td.appendChild(df);
1072        td.appendChild(doc.createTextNode(HTMLArea._lc("Image URL", "TableOperations") + ": "));
1073        input = doc.createElement("input");
1074        input.type = "text";
1075        input.name = "f_st_backgroundImage";
1076        if (el.style.backgroundImage.match(/url\(\s*(.*?)\s*\)/)) {
1077                input.value = RegExp.$1;
1078        }
1079        // input.style.width = "100%";
1080        td.appendChild(input);
1081
1082        tr = doc.createElement("tr");
1083        tbody.appendChild(tr);
1084        td = doc.createElement("td");
1085        tr.appendChild(td);
1086        td.className = "label";
1087        td.innerHTML = HTMLArea._lc("FG Color", "TableOperations") + ":";
1088        td = doc.createElement("td");
1089        tr.appendChild(td);
1090        td.appendChild(TableOperations.createColorButton(doc, editor, el.style.color, "color"));
1091
1092        // for better alignment we include an invisible field.
1093        input = doc.createElement("input");
1094        input.style.visibility = "hidden";
1095        input.type = "text";
1096        td.appendChild(input);
1097
1098        tr = doc.createElement("tr");
1099        tbody.appendChild(tr);
1100        td = doc.createElement("td");
1101        tr.appendChild(td);
1102        td.className = "label";
1103        td.innerHTML = HTMLArea._lc("Border", "TableOperations") + ":";
1104        td = doc.createElement("td");
1105        tr.appendChild(td);
1106
1107        var colorButton = TableOperations.createColorButton(doc, editor, el.style.borderColor, "borderColor");
1108        var btn = colorButton.firstChild.nextSibling;
1109        td.appendChild(colorButton);
1110        // borderFields.push(btn);
1111        btn.style.marginRight = "0.5em";
1112
1113        select = doc.createElement("select");
1114        var borderFields = [];
1115        td.appendChild(select);
1116        select.name = "f_st_borderStyle";
1117        options = ["none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"];
1118        var currentBorderStyle = el.style.borderStyle;
1119        // Gecko reports "solid solid solid solid" for "border-style: solid".
1120        // That is, "top right bottom left" -- we only consider the first
1121        // value.
1122        (currentBorderStyle.match(/([^\s]*)\s/)) && (currentBorderStyle = RegExp.$1);
1123        for (var i in options) {
1124    if(typeof options[i] == 'function') continue;
1125                var val = options[i];
1126                option = doc.createElement("option");
1127                option.value = val;
1128                option.innerHTML = val;
1129                (val == currentBorderStyle) && (option.selected = true);
1130                select.appendChild(option);
1131        }
1132        select.style.marginRight = "0.5em";
1133        function setBorderFieldsStatus(value) {
1134                for (var i = 0; i < borderFields.length; ++i) {
1135                        var el = borderFields[i];
1136                        el.style.visibility = value ? "hidden" : "visible";
1137                        if (!value && (el.tagName.toLowerCase() == "input")) {
1138                                el.focus();
1139                                el.select();
1140                        }
1141                }
1142        }
1143        select.onchange = function() { setBorderFieldsStatus(this.value == "none"); };
1144
1145        input = doc.createElement("input");
1146        borderFields.push(input);
1147        input.type = "text";
1148        input.name = "f_st_borderWidth";
1149        input.value = TableOperations.getLength(el.style.borderWidth);
1150        input.size = "5";
1151        td.appendChild(input);
1152        input.style.marginRight = "0.5em";
1153        var span = doc.createElement("span");
1154        span.innerHTML = HTMLArea._lc("pixels", "TableOperations");
1155        td.appendChild(span);
1156        borderFields.push(span);
1157
1158        setBorderFieldsStatus(select.value == "none");
1159
1160        if (el.tagName.toLowerCase() == "table") {
1161                // the border-collapse style is only for tables
1162                tr = doc.createElement("tr");
1163                tbody.appendChild(tr);
1164                td = doc.createElement("td");
1165                td.className = "label";
1166                tr.appendChild(td);
1167                input = doc.createElement("input");
1168                input.type = "checkbox";
1169                input.name = "f_st_borderCollapse";
1170                input.id = "f_st_borderCollapse";
1171                var val = (/collapse/i.test(el.style.borderCollapse));
1172                input.checked = val ? 1 : 0;
1173                td.appendChild(input);
1174
1175                td = doc.createElement("td");
1176                tr.appendChild(td);
1177                var label = doc.createElement("label");
1178                label.htmlFor = "f_st_borderCollapse";
1179                label.innerHTML = HTMLArea._lc("Collapsed borders", "TableOperations");
1180                td.appendChild(label);
1181        }
1182
1183//      tr = doc.createElement("tr");
1184//      tbody.appendChild(tr);
1185//      td = doc.createElement("td");
1186//      td.className = "label";
1187//      tr.appendChild(td);
1188//      td.innerHTML = HTMLArea._lc("Margin", "TableOperations") + ":";
1189//      td = doc.createElement("td");
1190//      tr.appendChild(td);
1191//      input = doc.createElement("input");
1192//      input.type = "text";
1193//      input.size = "5";
1194//      input.name = "f_st_margin";
1195//      td.appendChild(input);
1196//      input.style.marginRight = "0.5em";
1197//      td.appendChild(doc.createTextNode(HTMLArea._lc("Padding", "TableOperations") + ":"));
1198
1199//      input = doc.createElement("input");
1200//      input.type = "text";
1201//      input.size = "5";
1202//      input.name = "f_st_padding";
1203//      td.appendChild(input);
1204//      input.style.marginLeft = "0.5em";
1205//      input.style.marginRight = "0.5em";
1206//      td.appendChild(doc.createTextNode(HTMLArea._lc("pixels", "TableOperations")));
1207
1208        return fieldset;
1209};
1210
1211//// END GENERIC CODE -------------------------------------------------------