1/*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
4 *
5 * == BEGIN LICENSE ==
6 *
7 * Licensed under the terms of any of the following licenses at your
8 * choice:
9 *
10 *  - GNU General Public License Version 2 or later (the "GPL")
11 *    http://www.gnu.org/licenses/gpl.html
12 *
13 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14 *    http://www.gnu.org/licenses/lgpl.html
15 *
16 *  - Mozilla Public License Version 1.1 or later (the "MPL")
17 *    http://www.mozilla.org/MPL/MPL-1.1.html
18 *
19 * == END LICENSE ==
20 *
21 * Manage table operations.
22 */
23
24var FCKTableHandler = new Object() ;
25
26FCKTableHandler.InsertRow = function()
27{
28	// Get the row where the selection is placed in.
29	var oRow = FCKSelection.MoveToAncestorNode( 'TR' ) ;
30	if ( !oRow ) return ;
31
32	// Create a clone of the row.
33	var oNewRow = oRow.cloneNode( true ) ;
34
35	// Insert the new row (copy) before of it.
36	oRow.parentNode.insertBefore( oNewRow, oRow ) ;
37
38	// Clean the row (it seems that the new row has been added after it).
39	FCKTableHandler.ClearRow( oRow ) ;
40}
41
42FCKTableHandler.DeleteRows = function( row )
43{
44	// If no row has been passed as a parameter,
45	// then get the row where the selection is placed in.
46	if ( !row )
47		row = FCKSelection.MoveToAncestorNode( 'TR' ) ;
48	if ( !row ) return ;
49
50	// Get the row's table.
51	var oTable = FCKTools.GetElementAscensor( row, 'TABLE' ) ;
52
53	// If just one row is available then delete the entire table.
54	if ( oTable.rows.length == 1 )
55	{
56		FCKTableHandler.DeleteTable( oTable ) ;
57		return ;
58	}
59
60	// Delete the row.
61	row.parentNode.removeChild( row ) ;
62}
63
64FCKTableHandler.DeleteTable = function( table )
65{
66	// If no table has been passed as a parameer,
67	// then get the table where the selection is placed in.
68	if ( !table )
69	{
70		table = FCKSelection.GetSelectedElement() ;
71		if ( !table || table.tagName != 'TABLE' )
72			table = FCKSelection.MoveToAncestorNode( 'TABLE' ) ;
73	}
74	if ( !table ) return ;
75
76	// Delete the table.
77	FCKSelection.SelectNode( table ) ;
78	FCKSelection.Collapse();
79	table.parentNode.removeChild( table ) ;
80}
81
82FCKTableHandler.InsertColumn = function()
83{
84	// Get the cell where the selection is placed in.
85	var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ;
86
87	if ( !oCell ) return ;
88
89	// Get the cell's table.
90	var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;
91
92	// Get the index of the column to be created (based on the cell).
93	var iIndex = oCell.cellIndex + 1 ;
94
95	// Loop throw all rows available in the table.
96	for ( var i = 0 ; i < oTable.rows.length ; i++ )
97	{
98		// Get the row.
99		var oRow = oTable.rows[i] ;
100
101		// If the row doens't have enought cells, ignore it.
102		if ( oRow.cells.length < iIndex )
103			continue ;
104
105		oCell = oRow.cells[iIndex-1].cloneNode(false) ;
106
107		if ( FCKBrowserInfo.IsGecko )
108			oCell.innerHTML = GECKO_BOGUS ;
109
110		// Get the cell that is placed in the new cell place.
111		var oBaseCell = oRow.cells[iIndex] ;
112
113		// If the cell is available (we are not in the last cell of the row).
114		if ( oBaseCell )
115			oRow.insertBefore( oCell, oBaseCell ) ;	// Insert the new cell just before of it.
116		else
117			oRow.appendChild( oCell ) ;				// Append the cell at the end of the row.
118	}
119}
120
121FCKTableHandler.DeleteColumns = function()
122{
123	// Get the cell where the selection is placed in.
124	var oCell = FCKSelection.MoveToAncestorNode('TD') || FCKSelection.MoveToAncestorNode('TH') ;
125
126	if ( !oCell ) return ;
127
128	// Get the cell's table.
129	var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;
130
131	// Get the cell index.
132	var iIndex = oCell.cellIndex ;
133
134	// Loop throw all rows (from down to up, because it's possible that some
135	// rows will be deleted).
136	for ( var i = oTable.rows.length - 1 ; i >= 0 ; i-- )
137	{
138		// Get the row.
139		var oRow = oTable.rows[i] ;
140
141		// If the cell to be removed is the first one and the row has just one cell.
142		if ( iIndex == 0 && oRow.cells.length == 1 )
143		{
144			// Remove the entire row.
145			FCKTableHandler.DeleteRows( oRow ) ;
146			continue ;
147		}
148
149		// If the cell to be removed exists the delete it.
150		if ( oRow.cells[iIndex] )
151			oRow.removeChild( oRow.cells[iIndex] ) ;
152	}
153}
154
155FCKTableHandler.InsertCell = function( cell )
156{
157	// Get the cell where the selection is placed in.
158	var oCell = cell ? cell : FCKSelection.MoveToAncestorNode( 'TD' ) ;
159	if ( !oCell ) return null ;
160
161	// Create the new cell element to be added.
162	var oNewCell = FCK.EditorDocument.createElement( 'TD' ) ;
163	if ( FCKBrowserInfo.IsGecko )
164		oNewCell.innerHTML = GECKO_BOGUS ;
165//	oNewCell.innerHTML = "&nbsp;" ;
166
167	// If it is the last cell in the row.
168	if ( oCell.cellIndex == oCell.parentNode.cells.length - 1 )
169	{
170		// Add the new cell at the end of the row.
171		oCell.parentNode.appendChild( oNewCell ) ;
172	}
173	else
174	{
175		// Add the new cell before the next cell (after the active one).
176		oCell.parentNode.insertBefore( oNewCell, oCell.nextSibling ) ;
177	}
178
179	return oNewCell ;
180}
181
182FCKTableHandler.DeleteCell = function( cell )
183{
184	// If this is the last cell in the row.
185	if ( cell.parentNode.cells.length == 1 )
186	{
187		// Delete the entire row.
188		FCKTableHandler.DeleteRows( FCKTools.GetElementAscensor( cell, 'TR' ) ) ;
189		return ;
190	}
191
192	// Delete the cell from the row.
193	cell.parentNode.removeChild( cell ) ;
194}
195
196FCKTableHandler.DeleteCells = function()
197{
198	var aCells = FCKTableHandler.GetSelectedCells() ;
199
200	for ( var i = aCells.length - 1 ; i >= 0  ; i-- )
201	{
202		FCKTableHandler.DeleteCell( aCells[i] ) ;
203	}
204}
205
206FCKTableHandler.MergeCells = function()
207{
208	// Get all selected cells.
209	var aCells = FCKTableHandler.GetSelectedCells() ;
210
211	// At least 2 cells must be selected.
212	if ( aCells.length < 2 )
213		return ;
214
215	// The merge can occour only if the selected cells are from the same row.
216	if ( aCells[0].parentNode != aCells[aCells.length-1].parentNode )
217		return ;
218
219	// Calculate the new colSpan for the first cell.
220	var iColSpan = isNaN( aCells[0].colSpan ) ? 1 : aCells[0].colSpan ;
221
222	var sHtml = '' ;
223	var oCellsContents = FCK.EditorDocument.createDocumentFragment() ;
224
225	for ( var i = aCells.length - 1 ; i >= 0 ; i-- )
226	{
227		var eCell = aCells[i] ;
228
229		// Move its contents to the document fragment.
230		for ( var c = eCell.childNodes.length - 1 ; c >= 0 ; c-- )
231		{
232			var eChild = eCell.removeChild( eCell.childNodes[c] ) ;
233
234			if ( ( eChild.hasAttribute && eChild.hasAttribute('_moz_editor_bogus_node') ) || ( eChild.getAttribute && eChild.getAttribute( 'type', 2 ) == '_moz' ) )
235				continue ;
236
237				oCellsContents.insertBefore( eChild, oCellsContents.firstChild ) ;
238		}
239
240		if ( i > 0 )
241		{
242			// Accumulate the colspan of the cell.
243			iColSpan += isNaN( eCell.colSpan ) ? 1 : eCell.colSpan ;
244
245			// Delete the cell.
246			FCKTableHandler.DeleteCell( eCell ) ;
247		}
248	}
249
250	// Set the innerHTML of the remaining cell (the first one).
251	aCells[0].colSpan = iColSpan ;
252
253	if ( FCKBrowserInfo.IsGecko && oCellsContents.childNodes.length == 0 )
254		aCells[0].innerHTML = GECKO_BOGUS ;
255	else
256		aCells[0].appendChild( oCellsContents ) ;
257}
258
259FCKTableHandler.SplitCell = function()
260{
261	// Check that just one cell is selected, otherwise return.
262	var aCells = FCKTableHandler.GetSelectedCells() ;
263	if ( aCells.length != 1 )
264		return ;
265
266	var aMap = this._CreateTableMap( aCells[0].parentNode.parentNode ) ;
267	var iCellIndex = FCKTableHandler._GetCellIndexSpan( aMap, aCells[0].parentNode.rowIndex , aCells[0] ) ;
268
269	var aCollCells = this._GetCollumnCells( aMap, iCellIndex ) ;
270
271	for ( var i = 0 ; i < aCollCells.length ; i++ )
272	{
273		if ( aCollCells[i] == aCells[0] )
274		{
275			var oNewCell = this.InsertCell( aCells[0] ) ;
276			if ( !isNaN( aCells[0].rowSpan ) && aCells[0].rowSpan > 1 )
277				oNewCell.rowSpan = aCells[0].rowSpan ;
278		}
279		else
280		{
281			if ( isNaN( aCollCells[i].colSpan ) )
282				aCollCells[i].colSpan = 2 ;
283			else
284				aCollCells[i].colSpan += 1 ;
285		}
286	}
287}
288
289// Get the cell index from a TableMap.
290FCKTableHandler._GetCellIndexSpan = function( tableMap, rowIndex, cell )
291{
292	if ( tableMap.length < rowIndex + 1 )
293		return null ;
294
295	var oRow = tableMap[ rowIndex ] ;
296
297	for ( var c = 0 ; c < oRow.length ; c++ )
298	{
299		if ( oRow[c] == cell )
300			return c ;
301	}
302
303	return null ;
304}
305
306// Get the cells available in a collumn of a TableMap.
307FCKTableHandler._GetCollumnCells = function( tableMap, collumnIndex )
308{
309	var aCollCells = new Array() ;
310
311	for ( var r = 0 ; r < tableMap.length ; r++ )
312	{
313		var oCell = tableMap[r][collumnIndex] ;
314		if ( oCell && ( aCollCells.length == 0 || aCollCells[ aCollCells.length - 1 ] != oCell ) )
315			aCollCells[ aCollCells.length ] = oCell ;
316	}
317
318	return aCollCells ;
319}
320
321// This function is quite hard to explain. It creates a matrix representing all cells in a table.
322// The difference here is that the "spanned" cells (colSpan and rowSpan) are duplicated on the matrix
323// cells that are "spanned". For example, a row with 3 cells where the second cell has colSpan=2 and rowSpan=3
324// will produce a bi-dimensional matrix with the following values (representing the cells):
325//		Cell1, Cell2, Cell2, Cell 3
326//		Cell4, Cell2, Cell2, Cell 5
327FCKTableHandler._CreateTableMap = function( table )
328{
329	var aRows = table.rows ;
330
331	// Row and Collumn counters.
332	var r = -1 ;
333
334	var aMap = new Array() ;
335
336	for ( var i = 0 ; i < aRows.length ; i++ )
337	{
338		r++ ;
339		if ( !aMap[r] )
340			aMap[r] = new Array() ;
341
342		var c = -1 ;
343
344		for ( var j = 0 ; j < aRows[i].cells.length ; j++ )
345		{
346			var oCell = aRows[i].cells[j] ;
347
348			c++ ;
349			while ( aMap[r][c] )
350				c++ ;
351
352			var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ;
353			var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ;
354
355			for ( var rs = 0 ; rs < iRowSpan ; rs++ )
356			{
357				if ( !aMap[r + rs] )
358					aMap[r + rs] = new Array() ;
359
360				for ( var cs = 0 ; cs < iColSpan ; cs++ )
361				{
362					aMap[r + rs][c + cs] = aRows[i].cells[j] ;
363				}
364			}
365
366			c += iColSpan - 1 ;
367		}
368	}
369	return aMap ;
370}
371
372FCKTableHandler.ClearRow = function( tr )
373{
374	// Get the array of row's cells.
375	var aCells = tr.cells ;
376
377	// Replace the contents of each cell with "nothing".
378	for ( var i = 0 ; i < aCells.length ; i++ )
379	{
380		if ( FCKBrowserInfo.IsGecko )
381			aCells[i].innerHTML = GECKO_BOGUS ;
382		else
383			aCells[i].innerHTML = '' ;
384	}
385}
386