PageRenderTime 294ms CodeModel.GetById 81ms app.highlight 166ms RepoModel.GetById 35ms app.codeStats 0ms

/static/scripts/tiny_mce/plugins/table/editor_plugin_src.js

http://n23.googlecode.com/
JavaScript | 1115 lines | 811 code | 236 blank | 68 comment | 283 complexity | d35b6a97b20268acd13920ab93006a3c MD5 | raw file
   1/**
   2 * $Id: editor_plugin_src.js 824 2008-04-28 15:12:06Z spocke $
   3 *
   4 * @author Moxiecode
   5 * @copyright Copyright Š 2004-2008, Moxiecode Systems AB, All rights reserved.
   6 */
   7
   8(function() {
   9	var each = tinymce.each;
  10
  11	tinymce.create('tinymce.plugins.TablePlugin', {
  12		init : function(ed, url) {
  13			var t = this;
  14
  15			t.editor = ed;
  16			t.url = url;
  17
  18			// Register buttons
  19			each([
  20				['table', 'table.desc', 'mceInsertTable', true],
  21				['delete_table', 'table.del', 'mceTableDelete'],
  22				['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
  23				['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
  24				['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
  25				['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
  26				['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
  27				['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
  28				['row_props', 'table.row_desc', 'mceTableRowProps', true],
  29				['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
  30				['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
  31				['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
  32			], function(c) {
  33				ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
  34			});
  35
  36			ed.onInit.add(function() {
  37				if (ed && ed.plugins.contextmenu) {
  38					ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
  39						var sm, se = ed.selection, el = se.getNode() || ed.getBody();
  40
  41						if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th')) {
  42							m.removeAll();
  43
  44							if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
  45								m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
  46								m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
  47								m.addSeparator();
  48							}
  49
  50							if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
  51								m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
  52								m.addSeparator();
  53							}
  54
  55							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true, value : {action : 'insert'}});
  56							m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable', ui : true});
  57							m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete', ui : true});
  58							m.addSeparator();
  59
  60							// Cell menu
  61							sm = m.addMenu({title : 'table.cell'});
  62							sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps', ui : true});
  63							sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells', ui : true});
  64							sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells', ui : true});
  65
  66							// Row menu
  67							sm = m.addMenu({title : 'table.row'});
  68							sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps', ui : true});
  69							sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
  70							sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
  71							sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
  72							sm.addSeparator();
  73							sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
  74							sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
  75							sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'});
  76							sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'});
  77
  78							// Column menu
  79							sm = m.addMenu({title : 'table.col'});
  80							sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
  81							sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
  82							sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
  83						} else
  84							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true});
  85					});
  86				}
  87			});
  88
  89			// Add undo level when new rows are created using the tab key
  90			ed.onKeyDown.add(function(ed, e) {
  91				if (e.keyCode == 9 && ed.dom.getParent(ed.selection.getNode(), 'TABLE')) {
  92					if (!tinymce.isGecko && !tinymce.isOpera) {
  93						tinyMCE.execInstanceCommand(ed.editorId, "mceTableMoveToNextRow", true);
  94						return tinymce.dom.Event.cancel(e);
  95					}
  96
  97					ed.undoManager.add();
  98				}
  99			});
 100
 101			// Select whole table is a table border is clicked
 102			if (!tinymce.isIE) {
 103				if (ed.getParam('table_selection', true)) {
 104					ed.onClick.add(function(ed, e) {
 105						e = e.target;
 106
 107						if (e.nodeName === 'TABLE')
 108							ed.selection.select(e);
 109					});
 110				}
 111			}
 112
 113			ed.onNodeChange.add(function(ed, cm, n) {
 114				var p = ed.dom.getParent(n, 'td,th,caption');
 115
 116				cm.setActive('table', n.nodeName === 'TABLE' || !!p);
 117				if (p && p.nodeName === 'CAPTION')
 118					p = null;
 119
 120				cm.setDisabled('delete_table', !p);
 121				cm.setDisabled('delete_col', !p);
 122				cm.setDisabled('delete_table', !p);
 123				cm.setDisabled('delete_row', !p);
 124				cm.setDisabled('col_after', !p);
 125				cm.setDisabled('col_before', !p);
 126				cm.setDisabled('row_after', !p);
 127				cm.setDisabled('row_before', !p);
 128				cm.setDisabled('row_props', !p);
 129				cm.setDisabled('cell_props', !p);
 130				cm.setDisabled('split_cells', !p || (parseInt(ed.dom.getAttrib(p, 'colspan', '1')) < 2 && parseInt(ed.dom.getAttrib(p, 'rowspan', '1')) < 2));
 131				cm.setDisabled('merge_cells', !p);
 132			});
 133
 134			// Padd empty table cells
 135			if (!tinymce.isIE) {
 136				ed.onBeforeSetContent.add(function(ed, o) {
 137					if (o.initial)
 138						o.content = o.content.replace(/<(td|th)([^>]+|)>\s*<\/(td|th)>/g, tinymce.isOpera ? '<$1$2>&nbsp;</$1>' : '<$1$2><br mce_bogus="1" /></$1>');
 139				});
 140			}
 141		},
 142
 143		execCommand : function(cmd, ui, val) {
 144			var ed = this.editor, b;
 145
 146			// Is table command
 147			switch (cmd) {
 148				case "mceTableMoveToNextRow":
 149				case "mceInsertTable":
 150				case "mceTableRowProps":
 151				case "mceTableCellProps":
 152				case "mceTableSplitCells":
 153				case "mceTableMergeCells":
 154				case "mceTableInsertRowBefore":
 155				case "mceTableInsertRowAfter":
 156				case "mceTableDeleteRow":
 157				case "mceTableInsertColBefore":
 158				case "mceTableInsertColAfter":
 159				case "mceTableDeleteCol":
 160				case "mceTableCutRow":
 161				case "mceTableCopyRow":
 162				case "mceTablePasteRowBefore":
 163				case "mceTablePasteRowAfter":
 164				case "mceTableDelete":
 165					ed.execCommand('mceBeginUndoLevel');
 166					this._doExecCommand(cmd, ui, val);
 167					ed.execCommand('mceEndUndoLevel');
 168
 169					return true;
 170			}
 171
 172			// Pass to next handler in chain
 173			return false;
 174		},
 175
 176		getInfo : function() {
 177			return {
 178				longname : 'Tables',
 179				author : 'Moxiecode Systems AB',
 180				authorurl : 'http://tinymce.moxiecode.com',
 181				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table',
 182				version : tinymce.majorVersion + "." + tinymce.minorVersion
 183			};
 184		},
 185
 186		// Private plugin internal methods
 187
 188		/**
 189		 * Executes the table commands.
 190		 */
 191		_doExecCommand : function(command, user_interface, value) {
 192			var inst = this.editor, ed = inst, url = this.url;
 193			var focusElm = inst.selection.getNode();
 194			var trElm = inst.dom.getParent(focusElm, "tr");
 195			var tdElm = inst.dom.getParent(focusElm, "td,th");
 196			var tableElm = inst.dom.getParent(focusElm, "table");
 197			var doc = inst.contentWindow.document;
 198			var tableBorder = tableElm ? tableElm.getAttribute("border") : "";
 199
 200			// Get first TD if no TD found
 201			if (trElm && tdElm == null)
 202				tdElm = trElm.cells[0];
 203
 204			function inArray(ar, v) {
 205				for (var i=0; i<ar.length; i++) {
 206					// Is array
 207					if (ar[i].length > 0 && inArray(ar[i], v))
 208						return true;
 209
 210					// Found value
 211					if (ar[i] == v)
 212						return true;
 213				}
 214
 215				return false;
 216			}
 217
 218			function select(dx, dy) {
 219				var td;
 220
 221				grid = getTableGrid(tableElm);
 222				dx = dx || 0;
 223				dy = dy || 0;
 224				dx = Math.max(cpos.cellindex + dx, 0);
 225				dy = Math.max(cpos.rowindex + dy, 0);
 226
 227				// Recalculate grid and select
 228				inst.execCommand('mceRepaint');
 229				td = getCell(grid, dy, dx);
 230
 231				if (td) {
 232					inst.selection.select(td.firstChild || td);
 233					inst.selection.collapse(1);
 234				}
 235			};
 236
 237			function makeTD() {
 238				var newTD = doc.createElement("td");
 239
 240				if (!tinymce.isIE)
 241					newTD.innerHTML = '<br mce_bogus="1"/>';
 242			}
 243
 244			function getColRowSpan(td) {
 245				var colspan = inst.dom.getAttrib(td, "colspan");
 246				var rowspan = inst.dom.getAttrib(td, "rowspan");
 247
 248				colspan = colspan == "" ? 1 : parseInt(colspan);
 249				rowspan = rowspan == "" ? 1 : parseInt(rowspan);
 250
 251				return {colspan : colspan, rowspan : rowspan};
 252			}
 253
 254			function getCellPos(grid, td) {
 255				var x, y;
 256
 257				for (y=0; y<grid.length; y++) {
 258					for (x=0; x<grid[y].length; x++) {
 259						if (grid[y][x] == td)
 260							return {cellindex : x, rowindex : y};
 261					}
 262				}
 263
 264				return null;
 265			}
 266
 267			function getCell(grid, row, col) {
 268				if (grid[row] && grid[row][col])
 269					return grid[row][col];
 270
 271				return null;
 272			}
 273
 274			function getNextCell(table, cell) {
 275				var cells = [], x = 0, i, j, cell, nextCell;
 276
 277				for (i = 0; i < table.rows.length; i++)
 278					for (j = 0; j < table.rows[i].cells.length; j++, x++)
 279						cells[x] = table.rows[i].cells[j];
 280
 281				for (i = 0; i < cells.length; i++)
 282					if (cells[i] == cell)
 283						if (nextCell = cells[i+1])
 284							return nextCell;
 285			}
 286
 287			function getTableGrid(table) {
 288				var grid = [], rows = table.rows, x, y, td, sd, xstart, x2, y2;
 289
 290				for (y=0; y<rows.length; y++) {
 291					for (x=0; x<rows[y].cells.length; x++) {
 292						td = rows[y].cells[x];
 293						sd = getColRowSpan(td);
 294
 295						// All ready filled
 296						for (xstart = x; grid[y] && grid[y][xstart]; xstart++) ;
 297
 298						// Fill box
 299						for (y2=y; y2<y+sd['rowspan']; y2++) {
 300							if (!grid[y2])
 301								grid[y2] = [];
 302
 303							for (x2=xstart; x2<xstart+sd['colspan']; x2++)
 304								grid[y2][x2] = td;
 305						}
 306					}
 307				}
 308
 309				return grid;
 310			}
 311
 312			function trimRow(table, tr, td, new_tr) {
 313				var grid = getTableGrid(table), cpos = getCellPos(grid, td);
 314				var cells, lastElm;
 315
 316				// Time to crop away some
 317				if (new_tr.cells.length != tr.childNodes.length) {
 318					cells = tr.childNodes;
 319					lastElm = null;
 320
 321					for (var x=0; td = getCell(grid, cpos.rowindex, x); x++) {
 322						var remove = true;
 323						var sd = getColRowSpan(td);
 324
 325						// Remove due to rowspan
 326						if (inArray(cells, td)) {
 327							new_tr.childNodes[x]._delete = true;
 328						} else if ((lastElm == null || td != lastElm) && sd.colspan > 1) { // Remove due to colspan
 329							for (var i=x; i<x+td.colSpan; i++)
 330								new_tr.childNodes[i]._delete = true;
 331						}
 332
 333						if ((lastElm == null || td != lastElm) && sd.rowspan > 1)
 334							td.rowSpan = sd.rowspan + 1;
 335
 336						lastElm = td;
 337					}
 338
 339					deleteMarked(tableElm);
 340				}
 341			}
 342
 343			function prevElm(node, name) {
 344				while ((node = node.previousSibling) != null) {
 345					if (node.nodeName == name)
 346						return node;
 347				}
 348
 349				return null;
 350			}
 351
 352			function nextElm(node, names) {
 353				var namesAr = names.split(',');
 354
 355				while ((node = node.nextSibling) != null) {
 356					for (var i=0; i<namesAr.length; i++) {
 357						if (node.nodeName.toLowerCase() == namesAr[i].toLowerCase() )
 358							return node;
 359					}
 360				}
 361
 362				return null;
 363			}
 364
 365			function deleteMarked(tbl) {
 366				if (tbl.rows == 0)
 367					return;
 368
 369				var tr = tbl.rows[0];
 370				do {
 371					var next = nextElm(tr, "TR");
 372
 373					// Delete row
 374					if (tr._delete) {
 375						tr.parentNode.removeChild(tr);
 376						continue;
 377					}
 378
 379					// Delete cells
 380					var td = tr.cells[0];
 381					if (td.cells > 1) {
 382						do {
 383							var nexttd = nextElm(td, "TD,TH");
 384
 385							if (td._delete)
 386								td.parentNode.removeChild(td);
 387						} while ((td = nexttd) != null);
 388					}
 389				} while ((tr = next) != null);
 390			}
 391
 392			function addRows(td_elm, tr_elm, rowspan) {
 393				// Add rows
 394				td_elm.rowSpan = 1;
 395				var trNext = nextElm(tr_elm, "TR");
 396				for (var i=1; i<rowspan && trNext; i++) {
 397					var newTD = doc.createElement("td");
 398
 399					if (!tinymce.isIE)
 400						newTD.innerHTML = '<br mce_bogus="1"/>';
 401
 402					if (tinymce.isIE)
 403						trNext.insertBefore(newTD, trNext.cells(td_elm.cellIndex));
 404					else
 405						trNext.insertBefore(newTD, trNext.cells[td_elm.cellIndex]);
 406
 407					trNext = nextElm(trNext, "TR");
 408				}
 409			}
 410
 411			function copyRow(doc, table, tr) {
 412				var grid = getTableGrid(table);
 413				var newTR = tr.cloneNode(false);
 414				var cpos = getCellPos(grid, tr.cells[0]);
 415				var lastCell = null;
 416				var tableBorder = inst.dom.getAttrib(table, "border");
 417				var tdElm = null;
 418
 419				for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
 420					var newTD = null;
 421
 422					if (lastCell != tdElm) {
 423						for (var i=0; i<tr.cells.length; i++) {
 424							if (tdElm == tr.cells[i]) {
 425								newTD = tdElm.cloneNode(true);
 426								break;
 427							}
 428						}
 429					}
 430
 431					if (newTD == null) {
 432						newTD = doc.createElement("td");
 433
 434						if (!tinymce.isIE)
 435							newTD.innerHTML = '<br mce_bogus="1"/>';
 436					}
 437
 438					// Reset col/row span
 439					newTD.colSpan = 1;
 440					newTD.rowSpan = 1;
 441
 442					newTR.appendChild(newTD);
 443
 444					lastCell = tdElm;
 445				}
 446
 447				return newTR;
 448			}
 449
 450			// ---- Commands -----
 451
 452			// Handle commands
 453			switch (command) {
 454				case "mceTableMoveToNextRow":
 455					var nextCell = getNextCell(tableElm, tdElm);
 456
 457					if (!nextCell) {
 458						inst.execCommand("mceTableInsertRowAfter", tdElm);
 459						nextCell = getNextCell(tableElm, tdElm);
 460					}
 461
 462					inst.selection.select(nextCell);
 463					inst.selection.collapse(true);
 464
 465					return true;
 466
 467				case "mceTableRowProps":
 468					if (trElm == null)
 469						return true;
 470
 471					if (user_interface) {
 472						inst.windowManager.open({
 473							url : url + '/row.htm',
 474							width : 400 + parseInt(inst.getLang('table.rowprops_delta_width', 0)),
 475							height : 295 + parseInt(inst.getLang('table.rowprops_delta_height', 0)),
 476							inline : 1
 477						}, {
 478							plugin_url : url
 479						});
 480					}
 481
 482					return true;
 483
 484				case "mceTableCellProps":
 485					if (tdElm == null)
 486						return true;
 487
 488					if (user_interface) {
 489						inst.windowManager.open({
 490							url : url + '/cell.htm',
 491							width : 400 + parseInt(inst.getLang('table.cellprops_delta_width', 0)),
 492							height : 295 + parseInt(inst.getLang('table.cellprops_delta_height', 0)),
 493							inline : 1
 494						}, {
 495							plugin_url : url
 496						});
 497					}
 498
 499					return true;
 500
 501				case "mceInsertTable":
 502					if (user_interface) {
 503						inst.windowManager.open({
 504							url : url + '/table.htm',
 505							width : 400 + parseInt(inst.getLang('table.table_delta_width', 0)),
 506							height : 320 + parseInt(inst.getLang('table.table_delta_height', 0)),
 507							inline : 1
 508						}, {
 509							plugin_url : url,
 510							action : value ? value.action : 0
 511						});
 512					}
 513
 514					return true;
 515
 516				case "mceTableDelete":
 517					var table = inst.dom.getParent(inst.selection.getNode(), "table");
 518					if (table) {
 519						table.parentNode.removeChild(table);
 520						inst.execCommand('mceRepaint');
 521					}
 522					return true;
 523
 524				case "mceTableSplitCells":
 525				case "mceTableMergeCells":
 526				case "mceTableInsertRowBefore":
 527				case "mceTableInsertRowAfter":
 528				case "mceTableDeleteRow":
 529				case "mceTableInsertColBefore":
 530				case "mceTableInsertColAfter":
 531				case "mceTableDeleteCol":
 532				case "mceTableCutRow":
 533				case "mceTableCopyRow":
 534				case "mceTablePasteRowBefore":
 535				case "mceTablePasteRowAfter":
 536					// No table just return (invalid command)
 537					if (!tableElm)
 538						return true;
 539
 540					// Table has a tbody use that reference
 541					// Changed logic by ApTest 2005.07.12 (www.aptest.com)
 542					// Now lookk at the focused element and take its parentNode.  That will be a tbody or a table.
 543					if (trElm && tableElm != trElm.parentNode)
 544						tableElm = trElm.parentNode;
 545
 546					if (tableElm && trElm) {
 547						switch (command) {
 548							case "mceTableCutRow":
 549								if (!trElm || !tdElm)
 550									return true;
 551
 552								inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
 553								inst.execCommand("mceTableDeleteRow");
 554								break;
 555
 556							case "mceTableCopyRow":
 557								if (!trElm || !tdElm)
 558									return true;
 559
 560								inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
 561								break;
 562
 563							case "mceTablePasteRowBefore":
 564								if (!trElm || !tdElm)
 565									return true;
 566
 567								var newTR = inst.tableRowClipboard.cloneNode(true);
 568
 569								var prevTR = prevElm(trElm, "TR");
 570								if (prevTR != null)
 571									trimRow(tableElm, prevTR, prevTR.cells[0], newTR);
 572
 573								trElm.parentNode.insertBefore(newTR, trElm);
 574								break;
 575
 576							case "mceTablePasteRowAfter":
 577								if (!trElm || !tdElm)
 578									return true;
 579								
 580								var nextTR = nextElm(trElm, "TR");
 581								var newTR = inst.tableRowClipboard.cloneNode(true);
 582
 583								trimRow(tableElm, trElm, tdElm, newTR);
 584
 585								if (nextTR == null)
 586									trElm.parentNode.appendChild(newTR);
 587								else
 588									nextTR.parentNode.insertBefore(newTR, nextTR);
 589
 590								break;
 591
 592							case "mceTableInsertRowBefore":
 593								if (!trElm || !tdElm)
 594									return true;
 595
 596								var grid = getTableGrid(tableElm);
 597								var cpos = getCellPos(grid, tdElm);
 598								var newTR = doc.createElement("tr");
 599								var lastTDElm = null;
 600
 601								cpos.rowindex--;
 602								if (cpos.rowindex < 0)
 603									cpos.rowindex = 0;
 604
 605								// Create cells
 606								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
 607									if (tdElm != lastTDElm) {
 608										var sd = getColRowSpan(tdElm);
 609
 610										if (sd['rowspan'] == 1) {
 611											var newTD = doc.createElement("td");
 612
 613											if (!tinymce.isIE)
 614												newTD.innerHTML = '<br mce_bogus="1"/>';
 615
 616											newTD.colSpan = tdElm.colSpan;
 617
 618											newTR.appendChild(newTD);
 619										} else
 620											tdElm.rowSpan = sd['rowspan'] + 1;
 621
 622										lastTDElm = tdElm;
 623									}
 624								}
 625
 626								trElm.parentNode.insertBefore(newTR, trElm);
 627								select(0, 1);
 628							break;
 629
 630							case "mceTableInsertRowAfter":
 631								if (!trElm || !tdElm)
 632									return true;
 633
 634								var grid = getTableGrid(tableElm);
 635								var cpos = getCellPos(grid, tdElm);
 636								var newTR = doc.createElement("tr");
 637								var lastTDElm = null;
 638
 639								// Create cells
 640								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
 641									if (tdElm != lastTDElm) {
 642										var sd = getColRowSpan(tdElm);
 643
 644										if (sd['rowspan'] == 1) {
 645											var newTD = doc.createElement("td");
 646
 647											if (!tinymce.isIE)
 648												newTD.innerHTML = '<br mce_bogus="1"/>';
 649
 650											newTD.colSpan = tdElm.colSpan;
 651
 652											newTR.appendChild(newTD);
 653										} else
 654											tdElm.rowSpan = sd['rowspan'] + 1;
 655
 656										lastTDElm = tdElm;
 657									}
 658								}
 659
 660								if (newTR.hasChildNodes()) {
 661									var nextTR = nextElm(trElm, "TR");
 662									if (nextTR)
 663										nextTR.parentNode.insertBefore(newTR, nextTR);
 664									else
 665										tableElm.appendChild(newTR);
 666								}
 667
 668								select(0, 1);
 669							break;
 670
 671							case "mceTableDeleteRow":
 672								if (!trElm || !tdElm)
 673									return true;
 674
 675								var grid = getTableGrid(tableElm);
 676								var cpos = getCellPos(grid, tdElm);
 677
 678								// Only one row, remove whole table
 679								if (grid.length == 1) {
 680									inst.dom.remove(inst.dom.getParent(tableElm, "table"));
 681									return true;
 682								}
 683
 684								// Move down row spanned cells
 685								var cells = trElm.cells;
 686								var nextTR = nextElm(trElm, "TR");
 687								for (var x=0; x<cells.length; x++) {
 688									if (cells[x].rowSpan > 1) {
 689										var newTD = cells[x].cloneNode(true);
 690										var sd = getColRowSpan(cells[x]);
 691
 692										newTD.rowSpan = sd.rowspan - 1;
 693
 694										var nextTD = nextTR.cells[x];
 695
 696										if (nextTD == null)
 697											nextTR.appendChild(newTD);
 698										else
 699											nextTR.insertBefore(newTD, nextTD);
 700									}
 701								}
 702
 703								// Delete cells
 704								var lastTDElm = null;
 705								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
 706									if (tdElm != lastTDElm) {
 707										var sd = getColRowSpan(tdElm);
 708
 709										if (sd.rowspan > 1) {
 710											tdElm.rowSpan = sd.rowspan - 1;
 711										} else {
 712											trElm = tdElm.parentNode;
 713
 714											if (trElm.parentNode)
 715												trElm._delete = true;
 716										}
 717
 718										lastTDElm = tdElm;
 719									}
 720								}
 721
 722								deleteMarked(tableElm);
 723
 724								select(0, -1);
 725							break;
 726
 727							case "mceTableInsertColBefore":
 728								if (!trElm || !tdElm)
 729									return true;
 730
 731								var grid = getTableGrid(tableElm);
 732								var cpos = getCellPos(grid, tdElm);
 733								var lastTDElm = null;
 734
 735								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
 736									if (tdElm != lastTDElm) {
 737										var sd = getColRowSpan(tdElm);
 738
 739										if (sd['colspan'] == 1) {
 740											var newTD = doc.createElement(tdElm.nodeName);
 741
 742											if (!tinymce.isIE)
 743												newTD.innerHTML = '<br mce_bogus="1"/>';
 744
 745											newTD.rowSpan = tdElm.rowSpan;
 746
 747											tdElm.parentNode.insertBefore(newTD, tdElm);
 748										} else
 749											tdElm.colSpan++;
 750
 751										lastTDElm = tdElm;
 752									}
 753								}
 754
 755								select();
 756							break;
 757
 758							case "mceTableInsertColAfter":
 759								if (!trElm || !tdElm)
 760									return true;
 761
 762								var grid = getTableGrid(tableElm);
 763								var cpos = getCellPos(grid, tdElm);
 764								var lastTDElm = null;
 765
 766								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
 767									if (tdElm != lastTDElm) {
 768										var sd = getColRowSpan(tdElm);
 769
 770										if (sd['colspan'] == 1) {
 771											var newTD = doc.createElement(tdElm.nodeName);
 772
 773											if (!tinymce.isIE)
 774												newTD.innerHTML = '<br mce_bogus="1"/>';
 775
 776											newTD.rowSpan = tdElm.rowSpan;
 777
 778											var nextTD = nextElm(tdElm, "TD,TH");
 779											if (nextTD == null)
 780												tdElm.parentNode.appendChild(newTD);
 781											else
 782												nextTD.parentNode.insertBefore(newTD, nextTD);
 783										} else
 784											tdElm.colSpan++;
 785
 786										lastTDElm = tdElm;
 787									}
 788								}
 789
 790								select(1);
 791							break;
 792
 793							case "mceTableDeleteCol":
 794								if (!trElm || !tdElm)
 795									return true;
 796
 797								var grid = getTableGrid(tableElm);
 798								var cpos = getCellPos(grid, tdElm);
 799								var lastTDElm = null;
 800
 801								// Only one col, remove whole table
 802								if (grid.length > 1 && grid[0].length <= 1) {
 803									inst.dom.remove(inst.dom.getParent(tableElm, "table"));
 804									return true;
 805								}
 806
 807								// Delete cells
 808								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
 809									if (tdElm != lastTDElm) {
 810										var sd = getColRowSpan(tdElm);
 811
 812										if (sd['colspan'] > 1)
 813											tdElm.colSpan = sd['colspan'] - 1;
 814										else {
 815											if (tdElm.parentNode)
 816												tdElm.parentNode.removeChild(tdElm);
 817										}
 818
 819										lastTDElm = tdElm;
 820									}
 821								}
 822
 823								select(-1);
 824							break;
 825
 826						case "mceTableSplitCells":
 827							if (!trElm || !tdElm)
 828								return true;
 829
 830							var spandata = getColRowSpan(tdElm);
 831
 832							var colspan = spandata["colspan"];
 833							var rowspan = spandata["rowspan"];
 834
 835							// Needs splitting
 836							if (colspan > 1 || rowspan > 1) {
 837								// Generate cols
 838								tdElm.colSpan = 1;
 839								for (var i=1; i<colspan; i++) {
 840									var newTD = doc.createElement("td");
 841
 842									if (!tinymce.isIE)
 843										newTD.innerHTML = '<br mce_bogus="1"/>';
 844
 845									trElm.insertBefore(newTD, nextElm(tdElm, "TD,TH"));
 846
 847									if (rowspan > 1)
 848										addRows(newTD, trElm, rowspan);
 849								}
 850
 851								addRows(tdElm, trElm, rowspan);
 852							}
 853
 854							// Apply visual aids
 855							tableElm = inst.dom.getParent(inst.selection.getNode(), "table");
 856							break;
 857
 858						case "mceTableMergeCells":
 859							var rows = [];
 860							var sel = inst.selection.getSel();
 861							var grid = getTableGrid(tableElm);
 862
 863							if (tinymce.isIE || sel.rangeCount == 1) {
 864								if (user_interface) {
 865									// Setup template
 866									var sp = getColRowSpan(tdElm);
 867
 868									inst.windowManager.open({
 869										url : url + '/merge_cells.htm',
 870										width : 240 + parseInt(inst.getLang('table.merge_cells_delta_width', 0)),
 871										height : 110 + parseInt(inst.getLang('table.merge_cells_delta_height', 0)),
 872										inline : 1
 873									}, {
 874										action : "update",
 875										numcols : sp.colspan,
 876										numrows : sp.rowspan,
 877										plugin_url : url
 878									});
 879
 880									return true;
 881								} else {
 882									var numRows = parseInt(value['numrows']);
 883									var numCols = parseInt(value['numcols']);
 884									var cpos = getCellPos(grid, tdElm);
 885
 886									if (("" + numRows) == "NaN")
 887										numRows = 1;
 888
 889									if (("" + numCols) == "NaN")
 890										numCols = 1;
 891
 892									// Get rows and cells
 893									var tRows = tableElm.rows;
 894									for (var y=cpos.rowindex; y<grid.length; y++) {
 895										var rowCells = [];
 896
 897										for (var x=cpos.cellindex; x<grid[y].length; x++) {
 898											var td = getCell(grid, y, x);
 899
 900											if (td && !inArray(rows, td) && !inArray(rowCells, td)) {
 901												var cp = getCellPos(grid, td);
 902
 903												// Within range
 904												if (cp.cellindex < cpos.cellindex+numCols && cp.rowindex < cpos.rowindex+numRows)
 905													rowCells[rowCells.length] = td;
 906											}
 907										}
 908
 909										if (rowCells.length > 0)
 910											rows[rows.length] = rowCells;
 911
 912										var td = getCell(grid, cpos.rowindex, cpos.cellindex);
 913										each(ed.dom.select('br', td), function(e, i) {
 914											if (i > 0 && ed.dom.getAttrib('mce_bogus'))
 915												ed.dom.remove(e);
 916										});
 917									}
 918
 919									//return true;
 920								}
 921							} else {
 922								var cells = [];
 923								var sel = inst.selection.getSel();
 924								var lastTR = null;
 925								var curRow = null;
 926								var x1 = -1, y1 = -1, x2, y2;
 927
 928								// Only one cell selected, whats the point?
 929								if (sel.rangeCount < 2)
 930									return true;
 931
 932								// Get all selected cells
 933								for (var i=0; i<sel.rangeCount; i++) {
 934									var rng = sel.getRangeAt(i);
 935									var tdElm = rng.startContainer.childNodes[rng.startOffset];
 936
 937									if (!tdElm)
 938										break;
 939
 940									if (tdElm.nodeName == "TD" || tdElm.nodeName == "TH")
 941										cells[cells.length] = tdElm;
 942								}
 943
 944								// Get rows and cells
 945								var tRows = tableElm.rows;
 946								for (var y=0; y<tRows.length; y++) {
 947									var rowCells = [];
 948
 949									for (var x=0; x<tRows[y].cells.length; x++) {
 950										var td = tRows[y].cells[x];
 951
 952										for (var i=0; i<cells.length; i++) {
 953											if (td == cells[i]) {
 954												rowCells[rowCells.length] = td;
 955											}
 956										}
 957									}
 958
 959									if (rowCells.length > 0)
 960										rows[rows.length] = rowCells;
 961								}
 962
 963								// Find selected cells in grid and box
 964								var curRow = [];
 965								var lastTR = null;
 966								for (var y=0; y<grid.length; y++) {
 967									for (var x=0; x<grid[y].length; x++) {
 968										grid[y][x]._selected = false;
 969
 970										for (var i=0; i<cells.length; i++) {
 971											if (grid[y][x] == cells[i]) {
 972												// Get start pos
 973												if (x1 == -1) {
 974													x1 = x;
 975													y1 = y;
 976												}
 977
 978												// Get end pos
 979												x2 = x;
 980												y2 = y;
 981
 982												grid[y][x]._selected = true;
 983											}
 984										}
 985									}
 986								}
 987
 988								// Is there gaps, if so deny
 989								for (var y=y1; y<=y2; y++) {
 990									for (var x=x1; x<=x2; x++) {
 991										if (!grid[y][x]._selected) {
 992											alert("Invalid selection for merge.");
 993											return true;
 994										}
 995									}
 996								}
 997							}
 998
 999							// Validate selection and get total rowspan and colspan
1000							var rowSpan = 1, colSpan = 1;
1001
1002							// Validate horizontal and get total colspan
1003							var lastRowSpan = -1;
1004							for (var y=0; y<rows.length; y++) {
1005								var rowColSpan = 0;
1006
1007								for (var x=0; x<rows[y].length; x++) {
1008									var sd = getColRowSpan(rows[y][x]);
1009
1010									rowColSpan += sd['colspan'];
1011
1012									if (lastRowSpan != -1 && sd['rowspan'] != lastRowSpan) {
1013										alert("Invalid selection for merge.");
1014										return true;
1015									}
1016
1017									lastRowSpan = sd['rowspan'];
1018								}
1019
1020								if (rowColSpan > colSpan)
1021									colSpan = rowColSpan;
1022
1023								lastRowSpan = -1;
1024							}
1025
1026							// Validate vertical and get total rowspan
1027							var lastColSpan = -1;
1028							for (var x=0; x<rows[0].length; x++) {
1029								var colRowSpan = 0;
1030
1031								for (var y=0; y<rows.length; y++) {
1032									var sd = getColRowSpan(rows[y][x]);
1033
1034									colRowSpan += sd['rowspan'];
1035
1036									if (lastColSpan != -1 && sd['colspan'] != lastColSpan) {
1037										alert("Invalid selection for merge.");
1038										return true;
1039									}
1040
1041									lastColSpan = sd['colspan'];
1042								}
1043
1044								if (colRowSpan > rowSpan)
1045									rowSpan = colRowSpan;
1046
1047								lastColSpan = -1;
1048							}
1049
1050							// Setup td
1051							tdElm = rows[0][0];
1052							tdElm.rowSpan = rowSpan;
1053							tdElm.colSpan = colSpan;
1054
1055							// Merge cells
1056							for (var y=0; y<rows.length; y++) {
1057								for (var x=0; x<rows[y].length; x++) {
1058									var html = rows[y][x].innerHTML;
1059									var chk = html.replace(/[ \t\r\n]/g, "");
1060
1061									if (chk != "<br/>" && chk != "<br>" && chk != '<br mce_bogus="1"/>' && (x+y > 0))
1062										tdElm.innerHTML += html;
1063
1064									// Not current cell
1065									if (rows[y][x] != tdElm && !rows[y][x]._deleted) {
1066										var cpos = getCellPos(grid, rows[y][x]);
1067										var tr = rows[y][x].parentNode;
1068
1069										tr.removeChild(rows[y][x]);
1070										rows[y][x]._deleted = true;
1071
1072										// Empty TR, remove it
1073										if (!tr.hasChildNodes()) {
1074											tr.parentNode.removeChild(tr);
1075
1076											var lastCell = null;
1077											for (var x=0; cellElm = getCell(grid, cpos.rowindex, x); x++) {
1078												if (cellElm != lastCell && cellElm.rowSpan > 1)
1079													cellElm.rowSpan--;
1080
1081												lastCell = cellElm;
1082											}
1083
1084											if (tdElm.rowSpan > 1)
1085												tdElm.rowSpan--;
1086										}
1087									}
1088								}
1089							}
1090
1091							// Remove all but one bogus br
1092							each(ed.dom.select('br', tdElm), function(e, i) {
1093								if (i > 0 && ed.dom.getAttrib(e, 'mce_bogus'))
1094									ed.dom.remove(e);
1095							});
1096
1097							break;
1098						}
1099
1100						tableElm = inst.dom.getParent(inst.selection.getNode(), "table");
1101						inst.addVisual(tableElm);
1102						inst.nodeChanged();
1103					}
1104
1105				return true;
1106			}
1107
1108			// Pass to next handler in chain
1109			return false;
1110		}
1111	});
1112
1113	// Register plugin
1114	tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
1115})();