1 | /*
|
---|
2 | * FCKeditor - The text editor for Internet - http://www.fckeditor.net
|
---|
3 | * Copyright (C) 2003-2009 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 |
|
---|
24 | var FCKTableHandler = new Object() ;
|
---|
25 |
|
---|
26 | FCKTableHandler.InsertRow = function( insertBefore )
|
---|
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 one of the rows to produce the illusion of inserting an empty row before or after.
|
---|
39 | FCKTableHandler.ClearRow( insertBefore ? oNewRow : oRow ) ;
|
---|
40 | }
|
---|
41 |
|
---|
42 | FCKTableHandler.DeleteRows = function( row )
|
---|
43 | {
|
---|
44 | // If no row has been passed as a parameter,
|
---|
45 | // then get the row( s ) containing the cells where the selection is placed in.
|
---|
46 | // If user selected multiple rows ( by selecting multiple cells ), walk
|
---|
47 | // the selected cell list and delete the rows containing the selected cells
|
---|
48 | if ( ! row )
|
---|
49 | {
|
---|
50 | var aCells = FCKTableHandler.GetSelectedCells() ;
|
---|
51 | var aRowsToDelete = new Array() ;
|
---|
52 | //queue up the rows -- it's possible ( and likely ) that we may get duplicates
|
---|
53 | for ( var i = 0; i < aCells.length; i++ )
|
---|
54 | {
|
---|
55 | var oRow = aCells[i].parentNode ;
|
---|
56 | aRowsToDelete[oRow.rowIndex] = oRow ;
|
---|
57 | }
|
---|
58 | for ( var i = aRowsToDelete.length; i >= 0; i-- )
|
---|
59 | {
|
---|
60 | if ( aRowsToDelete[i] )
|
---|
61 | FCKTableHandler.DeleteRows( aRowsToDelete[i] );
|
---|
62 | }
|
---|
63 | return ;
|
---|
64 | }
|
---|
65 |
|
---|
66 | // Get the row's table.
|
---|
67 | var oTable = FCKTools.GetElementAscensor( row, 'TABLE' ) ;
|
---|
68 |
|
---|
69 | // If just one row is available then delete the entire table.
|
---|
70 | if ( oTable.rows.length == 1 )
|
---|
71 | {
|
---|
72 | FCKTableHandler.DeleteTable( oTable ) ;
|
---|
73 | return ;
|
---|
74 | }
|
---|
75 |
|
---|
76 | // Delete the row.
|
---|
77 | row.parentNode.removeChild( row ) ;
|
---|
78 | }
|
---|
79 |
|
---|
80 | FCKTableHandler.DeleteTable = function( table )
|
---|
81 | {
|
---|
82 | // If no table has been passed as a parameter,
|
---|
83 | // then get the table where the selection is placed in.
|
---|
84 | if ( !table )
|
---|
85 | {
|
---|
86 | table = FCKSelection.GetSelectedElement() ;
|
---|
87 | if ( !table || table.tagName != 'TABLE' )
|
---|
88 | table = FCKSelection.MoveToAncestorNode( 'TABLE' ) ;
|
---|
89 | }
|
---|
90 | if ( !table ) return ;
|
---|
91 |
|
---|
92 | // Delete the table.
|
---|
93 | FCKSelection.SelectNode( table ) ;
|
---|
94 | FCKSelection.Collapse();
|
---|
95 |
|
---|
96 | // if the table is wrapped with a singleton <p> ( or something similar ), remove
|
---|
97 | // the surrounding tag -- which likely won't show after deletion anyway
|
---|
98 | if ( table.parentNode.childNodes.length == 1 )
|
---|
99 | table.parentNode.parentNode.removeChild( table.parentNode );
|
---|
100 | else
|
---|
101 | table.parentNode.removeChild( table ) ;
|
---|
102 | }
|
---|
103 |
|
---|
104 | FCKTableHandler.InsertColumn = function( insertBefore )
|
---|
105 | {
|
---|
106 | // Get the cell where the selection is placed in.
|
---|
107 | var oCell = null ;
|
---|
108 | var nodes = this.GetSelectedCells() ;
|
---|
109 |
|
---|
110 | if ( nodes && nodes.length )
|
---|
111 | oCell = nodes[ insertBefore ? 0 : ( nodes.length - 1 ) ] ;
|
---|
112 |
|
---|
113 | if ( ! oCell )
|
---|
114 | return ;
|
---|
115 |
|
---|
116 | // Get the cell's table.
|
---|
117 | var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;
|
---|
118 |
|
---|
119 | var iIndex = oCell.cellIndex ;
|
---|
120 |
|
---|
121 | // Loop through all rows available in the table.
|
---|
122 | for ( var i = 0 ; i < oTable.rows.length ; i++ )
|
---|
123 | {
|
---|
124 | // Get the row.
|
---|
125 | var oRow = oTable.rows[i] ;
|
---|
126 |
|
---|
127 | // If the row doesn't have enough cells, ignore it.
|
---|
128 | if ( oRow.cells.length < ( iIndex + 1 ) )
|
---|
129 | continue ;
|
---|
130 |
|
---|
131 | oCell = oRow.cells[iIndex].cloneNode(false) ;
|
---|
132 |
|
---|
133 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
134 | FCKTools.AppendBogusBr( oCell ) ;
|
---|
135 |
|
---|
136 | // Get back the currently selected cell.
|
---|
137 | var oBaseCell = oRow.cells[iIndex] ;
|
---|
138 |
|
---|
139 | oRow.insertBefore( oCell, ( insertBefore ? oBaseCell : oBaseCell.nextSibling ) ) ;
|
---|
140 | }
|
---|
141 | }
|
---|
142 |
|
---|
143 | FCKTableHandler.DeleteColumns = function( oCell )
|
---|
144 | {
|
---|
145 | // if user selected multiple cols ( by selecting multiple cells ), walk
|
---|
146 | // the selected cell list and delete the rows containing the selected cells
|
---|
147 | if ( !oCell )
|
---|
148 | {
|
---|
149 | var aColsToDelete = FCKTableHandler.GetSelectedCells();
|
---|
150 | for ( var i = aColsToDelete.length; i >= 0; i-- )
|
---|
151 | {
|
---|
152 | if ( aColsToDelete[i] )
|
---|
153 | FCKTableHandler.DeleteColumns( aColsToDelete[i] );
|
---|
154 | }
|
---|
155 | return;
|
---|
156 | }
|
---|
157 |
|
---|
158 | if ( !oCell ) return ;
|
---|
159 |
|
---|
160 | // Get the cell's table.
|
---|
161 | var oTable = FCKTools.GetElementAscensor( oCell, 'TABLE' ) ;
|
---|
162 |
|
---|
163 | // Get the cell index.
|
---|
164 | var iIndex = oCell.cellIndex ;
|
---|
165 |
|
---|
166 | // Loop throw all rows (from down to up, because it's possible that some
|
---|
167 | // rows will be deleted).
|
---|
168 | for ( var i = oTable.rows.length - 1 ; i >= 0 ; i-- )
|
---|
169 | {
|
---|
170 | // Get the row.
|
---|
171 | var oRow = oTable.rows[i] ;
|
---|
172 |
|
---|
173 | // If the cell to be removed is the first one and the row has just one cell.
|
---|
174 | if ( iIndex == 0 && oRow.cells.length == 1 )
|
---|
175 | {
|
---|
176 | // Remove the entire row.
|
---|
177 | FCKTableHandler.DeleteRows( oRow ) ;
|
---|
178 | continue ;
|
---|
179 | }
|
---|
180 |
|
---|
181 | // If the cell to be removed exists the delete it.
|
---|
182 | if ( oRow.cells[iIndex] )
|
---|
183 | oRow.removeChild( oRow.cells[iIndex] ) ;
|
---|
184 | }
|
---|
185 | }
|
---|
186 |
|
---|
187 | FCKTableHandler.InsertCell = function( cell, insertBefore )
|
---|
188 | {
|
---|
189 | // Get the cell where the selection is placed in.
|
---|
190 | var oCell = null ;
|
---|
191 | var nodes = this.GetSelectedCells() ;
|
---|
192 | if ( nodes && nodes.length )
|
---|
193 | oCell = nodes[ insertBefore ? 0 : ( nodes.length - 1 ) ] ;
|
---|
194 | if ( ! oCell )
|
---|
195 | return null ;
|
---|
196 |
|
---|
197 | // Create the new cell element to be added.
|
---|
198 | var oNewCell = FCK.EditorDocument.createElement( 'TD' ) ;
|
---|
199 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
200 | FCKTools.AppendBogusBr( oNewCell ) ;
|
---|
201 |
|
---|
202 | if ( !insertBefore && oCell.cellIndex == oCell.parentNode.cells.length - 1 )
|
---|
203 | oCell.parentNode.appendChild( oNewCell ) ;
|
---|
204 | else
|
---|
205 | oCell.parentNode.insertBefore( oNewCell, insertBefore ? oCell : oCell.nextSibling ) ;
|
---|
206 |
|
---|
207 | return oNewCell ;
|
---|
208 | }
|
---|
209 |
|
---|
210 | FCKTableHandler.DeleteCell = function( cell )
|
---|
211 | {
|
---|
212 | // If this is the last cell in the row.
|
---|
213 | if ( cell.parentNode.cells.length == 1 )
|
---|
214 | {
|
---|
215 | // Delete the entire row.
|
---|
216 | FCKTableHandler.DeleteRows( cell.parentNode ) ;
|
---|
217 | return ;
|
---|
218 | }
|
---|
219 |
|
---|
220 | // Delete the cell from the row.
|
---|
221 | cell.parentNode.removeChild( cell ) ;
|
---|
222 | }
|
---|
223 |
|
---|
224 | FCKTableHandler.DeleteCells = function()
|
---|
225 | {
|
---|
226 | var aCells = FCKTableHandler.GetSelectedCells() ;
|
---|
227 |
|
---|
228 | for ( var i = aCells.length - 1 ; i >= 0 ; i-- )
|
---|
229 | {
|
---|
230 | FCKTableHandler.DeleteCell( aCells[i] ) ;
|
---|
231 | }
|
---|
232 | }
|
---|
233 |
|
---|
234 | FCKTableHandler._MarkCells = function( cells, label )
|
---|
235 | {
|
---|
236 | for ( var i = 0 ; i < cells.length ; i++ )
|
---|
237 | cells[i][label] = true ;
|
---|
238 | }
|
---|
239 |
|
---|
240 | FCKTableHandler._UnmarkCells = function( cells, label )
|
---|
241 | {
|
---|
242 | for ( var i = 0 ; i < cells.length ; i++ )
|
---|
243 | {
|
---|
244 | FCKDomTools.ClearElementJSProperty(cells[i], label ) ;
|
---|
245 | }
|
---|
246 | }
|
---|
247 |
|
---|
248 | FCKTableHandler._ReplaceCellsByMarker = function( tableMap, marker, substitute )
|
---|
249 | {
|
---|
250 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
251 | {
|
---|
252 | for ( var j = 0 ; j < tableMap[i].length ; j++ )
|
---|
253 | {
|
---|
254 | if ( tableMap[i][j][marker] )
|
---|
255 | tableMap[i][j] = substitute ;
|
---|
256 | }
|
---|
257 | }
|
---|
258 | }
|
---|
259 |
|
---|
260 | FCKTableHandler._GetMarkerGeometry = function( tableMap, rowIdx, colIdx, markerName )
|
---|
261 | {
|
---|
262 | var selectionWidth = 0 ;
|
---|
263 | var selectionHeight = 0 ;
|
---|
264 | var cellsLeft = 0 ;
|
---|
265 | var cellsUp = 0 ;
|
---|
266 | for ( var i = colIdx ; tableMap[rowIdx][i] && tableMap[rowIdx][i][markerName] ; i++ )
|
---|
267 | selectionWidth++ ;
|
---|
268 | for ( var i = colIdx - 1 ; tableMap[rowIdx][i] && tableMap[rowIdx][i][markerName] ; i-- )
|
---|
269 | {
|
---|
270 | selectionWidth++ ;
|
---|
271 | cellsLeft++ ;
|
---|
272 | }
|
---|
273 | for ( var i = rowIdx ; tableMap[i] && tableMap[i][colIdx] && tableMap[i][colIdx][markerName] ; i++ )
|
---|
274 | selectionHeight++ ;
|
---|
275 | for ( var i = rowIdx - 1 ; tableMap[i] && tableMap[i][colIdx] && tableMap[i][colIdx][markerName] ; i-- )
|
---|
276 | {
|
---|
277 | selectionHeight++ ;
|
---|
278 | cellsUp++ ;
|
---|
279 | }
|
---|
280 | return { 'width' : selectionWidth, 'height' : selectionHeight, 'x' : cellsLeft, 'y' : cellsUp } ;
|
---|
281 | }
|
---|
282 |
|
---|
283 | FCKTableHandler.CheckIsSelectionRectangular = function()
|
---|
284 | {
|
---|
285 | // If every row and column in an area on a plane are of the same width and height,
|
---|
286 | // Then the area is a rectangle.
|
---|
287 | var cells = FCKTableHandler.GetSelectedCells() ;
|
---|
288 | if ( cells.length < 1 )
|
---|
289 | return false ;
|
---|
290 |
|
---|
291 | // Check if the selected cells are all in the same table section (thead, tfoot or tbody)
|
---|
292 | for (var i = 0; i < cells.length; i++)
|
---|
293 | {
|
---|
294 | if ( cells[i].parentNode.parentNode != cells[0].parentNode.parentNode )
|
---|
295 | return false ;
|
---|
296 | }
|
---|
297 |
|
---|
298 | this._MarkCells( cells, '_CellSelected' ) ;
|
---|
299 |
|
---|
300 | var tableMap = this._CreateTableMap( cells[0] ) ;
|
---|
301 | var rowIdx = cells[0].parentNode.rowIndex ;
|
---|
302 | var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, cells[0] ) ;
|
---|
303 |
|
---|
304 | var geometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ;
|
---|
305 | var baseColIdx = colIdx - geometry.x ;
|
---|
306 | var baseRowIdx = rowIdx - geometry.y ;
|
---|
307 |
|
---|
308 | if ( geometry.width >= geometry.height )
|
---|
309 | {
|
---|
310 | for ( colIdx = baseColIdx ; colIdx < baseColIdx + geometry.width ; colIdx++ )
|
---|
311 | {
|
---|
312 | rowIdx = baseRowIdx + ( colIdx - baseColIdx ) % geometry.height ;
|
---|
313 | if ( ! tableMap[rowIdx] || ! tableMap[rowIdx][colIdx] )
|
---|
314 | {
|
---|
315 | this._UnmarkCells( cells, '_CellSelected' ) ;
|
---|
316 | return false ;
|
---|
317 | }
|
---|
318 | var g = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ;
|
---|
319 | if ( g.width != geometry.width || g.height != geometry.height )
|
---|
320 | {
|
---|
321 | this._UnmarkCells( cells, '_CellSelected' ) ;
|
---|
322 | return false ;
|
---|
323 | }
|
---|
324 | }
|
---|
325 | }
|
---|
326 | else
|
---|
327 | {
|
---|
328 | for ( rowIdx = baseRowIdx ; rowIdx < baseRowIdx + geometry.height ; rowIdx++ )
|
---|
329 | {
|
---|
330 | colIdx = baseColIdx + ( rowIdx - baseRowIdx ) % geometry.width ;
|
---|
331 | if ( ! tableMap[rowIdx] || ! tableMap[rowIdx][colIdx] )
|
---|
332 | {
|
---|
333 | this._UnmarkCells( cells, '_CellSelected' ) ;
|
---|
334 | return false ;
|
---|
335 | }
|
---|
336 | var g = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_CellSelected' ) ;
|
---|
337 | if ( g.width != geometry.width || g.height != geometry.height )
|
---|
338 | {
|
---|
339 | this._UnmarkCells( cells, '_CellSelected' ) ;
|
---|
340 | return false ;
|
---|
341 | }
|
---|
342 | }
|
---|
343 | }
|
---|
344 |
|
---|
345 | this._UnmarkCells( cells, '_CellSelected' ) ;
|
---|
346 | return true ;
|
---|
347 | }
|
---|
348 |
|
---|
349 | FCKTableHandler.MergeCells = function()
|
---|
350 | {
|
---|
351 | // Get all selected cells.
|
---|
352 | var cells = this.GetSelectedCells() ;
|
---|
353 | if ( cells.length < 2 )
|
---|
354 | return ;
|
---|
355 |
|
---|
356 | // Assume the selected cells are already in a rectangular geometry.
|
---|
357 | // Because the checking is already done by FCKTableCommand.
|
---|
358 | var refCell = cells[0] ;
|
---|
359 | var tableMap = this._CreateTableMap( refCell ) ;
|
---|
360 | var rowIdx = refCell.parentNode.rowIndex ;
|
---|
361 | var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ;
|
---|
362 |
|
---|
363 | this._MarkCells( cells, '_SelectedCells' ) ;
|
---|
364 | var selectionGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SelectedCells' ) ;
|
---|
365 |
|
---|
366 | var baseColIdx = colIdx - selectionGeometry.x ;
|
---|
367 | var baseRowIdx = rowIdx - selectionGeometry.y ;
|
---|
368 | var cellContents = FCKTools.GetElementDocument( refCell ).createDocumentFragment() ;
|
---|
369 | for ( var i = 0 ; i < selectionGeometry.height ; i++ )
|
---|
370 | {
|
---|
371 | var rowChildNodesCount = 0 ;
|
---|
372 | for ( var j = 0 ; j < selectionGeometry.width ; j++ )
|
---|
373 | {
|
---|
374 | var currentCell = tableMap[baseRowIdx + i][baseColIdx + j] ;
|
---|
375 | while ( currentCell.childNodes.length > 0 )
|
---|
376 | {
|
---|
377 | var node = currentCell.removeChild( currentCell.firstChild ) ;
|
---|
378 | if ( node.nodeType != 1
|
---|
379 | || ( node.getAttribute( 'type', 2 ) != '_moz' && node.getAttribute( '_moz_dirty' ) != null ) )
|
---|
380 | {
|
---|
381 | cellContents.appendChild( node ) ;
|
---|
382 | rowChildNodesCount++ ;
|
---|
383 | }
|
---|
384 | }
|
---|
385 | }
|
---|
386 | if ( rowChildNodesCount > 0 )
|
---|
387 | cellContents.appendChild( FCK.EditorDocument.createElement( 'br' ) ) ;
|
---|
388 | }
|
---|
389 |
|
---|
390 | this._ReplaceCellsByMarker( tableMap, '_SelectedCells', refCell ) ;
|
---|
391 | this._UnmarkCells( cells, '_SelectedCells' ) ;
|
---|
392 | this._InstallTableMap( tableMap, refCell.parentNode.parentNode.parentNode ) ;
|
---|
393 | refCell.appendChild( cellContents ) ;
|
---|
394 |
|
---|
395 | if ( FCKBrowserInfo.IsGeckoLike && ( ! refCell.firstChild ) )
|
---|
396 | FCKTools.AppendBogusBr( refCell ) ;
|
---|
397 |
|
---|
398 | this._MoveCaretToCell( refCell, false ) ;
|
---|
399 | }
|
---|
400 |
|
---|
401 | FCKTableHandler.MergeRight = function()
|
---|
402 | {
|
---|
403 | var target = this.GetMergeRightTarget() ;
|
---|
404 | if ( target == null )
|
---|
405 | return ;
|
---|
406 | var refCell = target.refCell ;
|
---|
407 | var tableMap = target.tableMap ;
|
---|
408 | var nextCell = target.nextCell ;
|
---|
409 |
|
---|
410 | var cellContents = FCK.EditorDocument.createDocumentFragment() ;
|
---|
411 | while ( nextCell && nextCell.childNodes && nextCell.childNodes.length > 0 )
|
---|
412 | cellContents.appendChild( nextCell.removeChild( nextCell.firstChild ) ) ;
|
---|
413 |
|
---|
414 | nextCell.parentNode.removeChild( nextCell ) ;
|
---|
415 | refCell.appendChild( cellContents ) ;
|
---|
416 | this._MarkCells( [nextCell], '_Replace' ) ;
|
---|
417 | this._ReplaceCellsByMarker( tableMap, '_Replace', refCell ) ;
|
---|
418 | this._InstallTableMap( tableMap, refCell.parentNode.parentNode.parentNode ) ;
|
---|
419 |
|
---|
420 | this._MoveCaretToCell( refCell, false ) ;
|
---|
421 | }
|
---|
422 |
|
---|
423 | FCKTableHandler.MergeDown = function()
|
---|
424 | {
|
---|
425 | var target = this.GetMergeDownTarget() ;
|
---|
426 | if ( target == null )
|
---|
427 | return ;
|
---|
428 | var refCell = target.refCell ;
|
---|
429 | var tableMap = target.tableMap ;
|
---|
430 | var nextCell = target.nextCell ;
|
---|
431 |
|
---|
432 | var cellContents = FCKTools.GetElementDocument( refCell ).createDocumentFragment() ;
|
---|
433 | while ( nextCell && nextCell.childNodes && nextCell.childNodes.length > 0 )
|
---|
434 | cellContents.appendChild( nextCell.removeChild( nextCell.firstChild ) ) ;
|
---|
435 | if ( cellContents.firstChild )
|
---|
436 | cellContents.insertBefore( FCK.EditorDocument.createElement( 'br' ), cellContents.firstChild ) ;
|
---|
437 | refCell.appendChild( cellContents ) ;
|
---|
438 | this._MarkCells( [nextCell], '_Replace' ) ;
|
---|
439 | this._ReplaceCellsByMarker( tableMap, '_Replace', refCell ) ;
|
---|
440 | this._InstallTableMap( tableMap, refCell.parentNode.parentNode.parentNode ) ;
|
---|
441 |
|
---|
442 | this._MoveCaretToCell( refCell, false ) ;
|
---|
443 | }
|
---|
444 |
|
---|
445 | FCKTableHandler.HorizontalSplitCell = function()
|
---|
446 | {
|
---|
447 | var cells = FCKTableHandler.GetSelectedCells() ;
|
---|
448 | if ( cells.length != 1 )
|
---|
449 | return ;
|
---|
450 |
|
---|
451 | var refCell = cells[0] ;
|
---|
452 | var tableMap = this._CreateTableMap( refCell ) ;
|
---|
453 | var rowIdx = refCell.parentNode.rowIndex ;
|
---|
454 | var colIdx = FCKTableHandler._GetCellIndexSpan( tableMap, rowIdx, refCell ) ;
|
---|
455 | var cellSpan = isNaN( refCell.colSpan ) ? 1 : refCell.colSpan ;
|
---|
456 |
|
---|
457 | if ( cellSpan > 1 )
|
---|
458 | {
|
---|
459 | // Splitting a multi-column cell - original cell gets ceil(colSpan/2) columns,
|
---|
460 | // new cell gets floor(colSpan/2).
|
---|
461 | var newCellSpan = Math.ceil( cellSpan / 2 ) ;
|
---|
462 | var newCell = FCK.EditorDocument.createElement( refCell.nodeName ) ;
|
---|
463 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
464 | FCKTools.AppendBogusBr( newCell ) ;
|
---|
465 | var startIdx = colIdx + newCellSpan ;
|
---|
466 | var endIdx = colIdx + cellSpan ;
|
---|
467 | var rowSpan = isNaN( refCell.rowSpan ) ? 1 : refCell.rowSpan ;
|
---|
468 | for ( var r = rowIdx ; r < rowIdx + rowSpan ; r++ )
|
---|
469 | {
|
---|
470 | for ( var i = startIdx ; i < endIdx ; i++ )
|
---|
471 | tableMap[r][i] = newCell ;
|
---|
472 | }
|
---|
473 | }
|
---|
474 | else
|
---|
475 | {
|
---|
476 | // Splitting a single-column cell - add a new cell, and expand
|
---|
477 | // cells crossing the same column.
|
---|
478 | var newTableMap = [] ;
|
---|
479 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
480 | {
|
---|
481 | var newRow = tableMap[i].slice( 0, colIdx ) ;
|
---|
482 | if ( tableMap[i].length <= colIdx )
|
---|
483 | {
|
---|
484 | newTableMap.push( newRow ) ;
|
---|
485 | continue ;
|
---|
486 | }
|
---|
487 | if ( tableMap[i][colIdx] == refCell )
|
---|
488 | {
|
---|
489 | newRow.push( refCell ) ;
|
---|
490 | newRow.push( FCK.EditorDocument.createElement( refCell.nodeName ) ) ;
|
---|
491 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
492 | FCKTools.AppendBogusBr( newRow[newRow.length - 1] ) ;
|
---|
493 | }
|
---|
494 | else
|
---|
495 | {
|
---|
496 | newRow.push( tableMap[i][colIdx] ) ;
|
---|
497 | newRow.push( tableMap[i][colIdx] ) ;
|
---|
498 | }
|
---|
499 | for ( var j = colIdx + 1 ; j < tableMap[i].length ; j++ )
|
---|
500 | newRow.push( tableMap[i][j] ) ;
|
---|
501 | newTableMap.push( newRow ) ;
|
---|
502 | }
|
---|
503 | tableMap = newTableMap ;
|
---|
504 | }
|
---|
505 |
|
---|
506 | this._InstallTableMap( tableMap, refCell.parentNode.parentNode.parentNode ) ;
|
---|
507 | }
|
---|
508 |
|
---|
509 | FCKTableHandler.VerticalSplitCell = function()
|
---|
510 | {
|
---|
511 | var cells = FCKTableHandler.GetSelectedCells() ;
|
---|
512 | if ( cells.length != 1 )
|
---|
513 | return ;
|
---|
514 |
|
---|
515 | var currentCell = cells[0] ;
|
---|
516 | var tableMap = this._CreateTableMap( currentCell ) ;
|
---|
517 | var currentRowIndex = currentCell.parentNode.rowIndex ;
|
---|
518 | var cellIndex = FCKTableHandler._GetCellIndexSpan( tableMap, currentRowIndex, currentCell ) ;
|
---|
519 | // Save current cell colSpan
|
---|
520 | var currentColSpan = isNaN( currentCell.colSpan ) ? 1 : currentCell.colSpan ;
|
---|
521 | var currentRowSpan = currentCell.rowSpan ;
|
---|
522 | if ( isNaN( currentRowSpan ) )
|
---|
523 | currentRowSpan = 1 ;
|
---|
524 |
|
---|
525 | if ( currentRowSpan > 1 )
|
---|
526 | {
|
---|
527 | // 1. Set the current cell's rowSpan to 1.
|
---|
528 | currentCell.rowSpan = Math.ceil( currentRowSpan / 2 ) ;
|
---|
529 |
|
---|
530 | // 2. Find the appropriate place to insert a new cell at the next row.
|
---|
531 | var newCellRowIndex = currentRowIndex + Math.ceil( currentRowSpan / 2 ) ;
|
---|
532 | var oRow = tableMap[newCellRowIndex] ;
|
---|
533 | var insertMarker = null ;
|
---|
534 | for ( var i = cellIndex+1 ; i < oRow.length ; i++ )
|
---|
535 | {
|
---|
536 | if ( oRow[i].parentNode.rowIndex == newCellRowIndex )
|
---|
537 | {
|
---|
538 | insertMarker = oRow[i] ;
|
---|
539 | break ;
|
---|
540 | }
|
---|
541 | }
|
---|
542 |
|
---|
543 | // 3. Insert the new cell to the indicated place, with the appropriate rowSpan and colSpan, next row.
|
---|
544 | var newCell = FCK.EditorDocument.createElement( currentCell.nodeName ) ;
|
---|
545 | newCell.rowSpan = Math.floor( currentRowSpan / 2 ) ;
|
---|
546 | if ( currentColSpan > 1 )
|
---|
547 | newCell.colSpan = currentColSpan ;
|
---|
548 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
549 | FCKTools.AppendBogusBr( newCell ) ;
|
---|
550 | currentCell.parentNode.parentNode.parentNode.rows[newCellRowIndex].insertBefore( newCell, insertMarker ) ;
|
---|
551 | }
|
---|
552 | else
|
---|
553 | {
|
---|
554 | // 1. Insert a new row.
|
---|
555 | var newSectionRowIdx = currentCell.parentNode.sectionRowIndex + 1 ;
|
---|
556 | var newRow = FCK.EditorDocument.createElement( 'tr' ) ;
|
---|
557 | var tSection = currentCell.parentNode.parentNode ;
|
---|
558 | if ( tSection.rows.length > newSectionRowIdx )
|
---|
559 | tSection.insertBefore( newRow, tSection.rows[newSectionRowIdx] ) ;
|
---|
560 | else
|
---|
561 | tSection.appendChild( newRow ) ;
|
---|
562 |
|
---|
563 | // 2. +1 to rowSpan for all cells crossing currentCell's row.
|
---|
564 | for ( var i = 0 ; i < tableMap[currentRowIndex].length ; )
|
---|
565 | {
|
---|
566 | var colSpan = tableMap[currentRowIndex][i].colSpan ;
|
---|
567 | if ( isNaN( colSpan ) || colSpan < 1 )
|
---|
568 | colSpan = 1 ;
|
---|
569 | if ( i == cellIndex )
|
---|
570 | {
|
---|
571 | i += colSpan ;
|
---|
572 | continue ;
|
---|
573 | }
|
---|
574 | var rowSpan = tableMap[currentRowIndex][i].rowSpan ;
|
---|
575 | if ( isNaN( rowSpan ) )
|
---|
576 | rowSpan = 1 ;
|
---|
577 | tableMap[currentRowIndex][i].rowSpan = rowSpan + 1 ;
|
---|
578 | i += colSpan ;
|
---|
579 | }
|
---|
580 |
|
---|
581 | // 3. Insert a new cell to new row. Set colSpan on the new cell.
|
---|
582 | var newCell = FCK.EditorDocument.createElement( currentCell.nodeName ) ;
|
---|
583 | if ( currentColSpan > 1 )
|
---|
584 | newCell.colSpan = currentColSpan ;
|
---|
585 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
586 | FCKTools.AppendBogusBr( newCell ) ;
|
---|
587 | newRow.appendChild( newCell ) ;
|
---|
588 | }
|
---|
589 | }
|
---|
590 |
|
---|
591 | // Get the cell index from a TableMap.
|
---|
592 | FCKTableHandler._GetCellIndexSpan = function( tableMap, rowIndex, cell )
|
---|
593 | {
|
---|
594 | if ( tableMap.length < rowIndex + 1 )
|
---|
595 | return null ;
|
---|
596 |
|
---|
597 | var oRow = tableMap[ rowIndex ] ;
|
---|
598 |
|
---|
599 | for ( var c = 0 ; c < oRow.length ; c++ )
|
---|
600 | {
|
---|
601 | if ( oRow[c] == cell )
|
---|
602 | return c ;
|
---|
603 | }
|
---|
604 |
|
---|
605 | return null ;
|
---|
606 | }
|
---|
607 |
|
---|
608 | // Get the cell location from a TableMap. Returns an array with an [x,y] location
|
---|
609 | FCKTableHandler._GetCellLocation = function( tableMap, cell )
|
---|
610 | {
|
---|
611 | for ( var i = 0 ; i < tableMap.length; i++ )
|
---|
612 | {
|
---|
613 | for ( var c = 0 ; c < tableMap[i].length ; c++ )
|
---|
614 | {
|
---|
615 | if ( tableMap[i][c] == cell ) return [i,c];
|
---|
616 | }
|
---|
617 | }
|
---|
618 | return null ;
|
---|
619 | }
|
---|
620 |
|
---|
621 | // This function is quite hard to explain. It creates a matrix representing all cells in a table.
|
---|
622 | // The difference here is that the "spanned" cells (colSpan and rowSpan) are duplicated on the matrix
|
---|
623 | // cells that are "spanned". For example, a row with 3 cells where the second cell has colSpan=2 and rowSpan=3
|
---|
624 | // will produce a bi-dimensional matrix with the following values (representing the cells):
|
---|
625 | // Cell1, Cell2, Cell2, Cell3
|
---|
626 | // Cell4, Cell2, Cell2, Cell5
|
---|
627 | // Cell6, Cell2, Cell2, Cell7
|
---|
628 | FCKTableHandler._CreateTableMap = function( refCell )
|
---|
629 | {
|
---|
630 | var table = (refCell.nodeName == 'TABLE' ? refCell : refCell.parentNode.parentNode.parentNode ) ;
|
---|
631 |
|
---|
632 | var aRows = table.rows ;
|
---|
633 |
|
---|
634 | // Row and Column counters.
|
---|
635 | var r = -1 ;
|
---|
636 |
|
---|
637 | var aMap = new Array() ;
|
---|
638 |
|
---|
639 | for ( var i = 0 ; i < aRows.length ; i++ )
|
---|
640 | {
|
---|
641 | r++ ;
|
---|
642 | if ( !aMap[r] )
|
---|
643 | aMap[r] = new Array() ;
|
---|
644 |
|
---|
645 | var c = -1 ;
|
---|
646 |
|
---|
647 | for ( var j = 0 ; j < aRows[i].cells.length ; j++ )
|
---|
648 | {
|
---|
649 | var oCell = aRows[i].cells[j] ;
|
---|
650 |
|
---|
651 | c++ ;
|
---|
652 | while ( aMap[r][c] )
|
---|
653 | c++ ;
|
---|
654 |
|
---|
655 | var iColSpan = isNaN( oCell.colSpan ) ? 1 : oCell.colSpan ;
|
---|
656 | var iRowSpan = isNaN( oCell.rowSpan ) ? 1 : oCell.rowSpan ;
|
---|
657 |
|
---|
658 | for ( var rs = 0 ; rs < iRowSpan ; rs++ )
|
---|
659 | {
|
---|
660 | if ( !aMap[r + rs] )
|
---|
661 | aMap[r + rs] = new Array() ;
|
---|
662 |
|
---|
663 | for ( var cs = 0 ; cs < iColSpan ; cs++ )
|
---|
664 | {
|
---|
665 | aMap[r + rs][c + cs] = aRows[i].cells[j] ;
|
---|
666 | }
|
---|
667 | }
|
---|
668 |
|
---|
669 | c += iColSpan - 1 ;
|
---|
670 | }
|
---|
671 | }
|
---|
672 | return aMap ;
|
---|
673 | }
|
---|
674 |
|
---|
675 | // This function is the inverse of _CreateTableMap - it takes in a table map and converts it to an HTML table.
|
---|
676 | FCKTableHandler._InstallTableMap = function( tableMap, table )
|
---|
677 | {
|
---|
678 | // Workaround for #1917 : MSIE will always report a cell's rowSpan as 1 as long
|
---|
679 | // as the cell is not attached to a row. So we'll need an alternative attribute
|
---|
680 | // for storing the calculated rowSpan in IE.
|
---|
681 | var rowSpanAttr = FCKBrowserInfo.IsIE ? "_fckrowspan" : "rowSpan" ;
|
---|
682 |
|
---|
683 | // Disconnect all the cells in tableMap from their parents, set all colSpan and rowSpan attributes to 1.
|
---|
684 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
685 | {
|
---|
686 | for ( var j = 0 ; j < tableMap[i].length ; j++ )
|
---|
687 | {
|
---|
688 | var cell = tableMap[i][j] ;
|
---|
689 | if ( cell.parentNode )
|
---|
690 | cell.parentNode.removeChild( cell ) ;
|
---|
691 | cell.colSpan = cell[rowSpanAttr] = 1 ;
|
---|
692 | }
|
---|
693 | }
|
---|
694 |
|
---|
695 | // Scan by rows and set colSpan.
|
---|
696 | var maxCol = 0 ;
|
---|
697 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
698 | {
|
---|
699 | for ( var j = 0 ; j < tableMap[i].length ; j++ )
|
---|
700 | {
|
---|
701 | var cell = tableMap[i][j] ;
|
---|
702 | if ( ! cell)
|
---|
703 | continue ;
|
---|
704 | if ( j > maxCol )
|
---|
705 | maxCol = j ;
|
---|
706 | if ( cell._colScanned === true )
|
---|
707 | continue ;
|
---|
708 | if ( tableMap[i][j-1] == cell )
|
---|
709 | cell.colSpan++ ;
|
---|
710 | if ( tableMap[i][j+1] != cell )
|
---|
711 | cell._colScanned = true ;
|
---|
712 | }
|
---|
713 | }
|
---|
714 |
|
---|
715 | // Scan by columns and set rowSpan.
|
---|
716 | for ( var i = 0 ; i <= maxCol ; i++ )
|
---|
717 | {
|
---|
718 | for ( var j = 0 ; j < tableMap.length ; j++ )
|
---|
719 | {
|
---|
720 | if ( ! tableMap[j] )
|
---|
721 | continue ;
|
---|
722 | var cell = tableMap[j][i] ;
|
---|
723 | if ( ! cell || cell._rowScanned === true )
|
---|
724 | continue ;
|
---|
725 | if ( tableMap[j-1] && tableMap[j-1][i] == cell )
|
---|
726 | cell[rowSpanAttr]++ ;
|
---|
727 | if ( ! tableMap[j+1] || tableMap[j+1][i] != cell )
|
---|
728 | cell._rowScanned = true ;
|
---|
729 | }
|
---|
730 | }
|
---|
731 |
|
---|
732 | // Clear all temporary flags.
|
---|
733 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
734 | {
|
---|
735 | for ( var j = 0 ; j < tableMap[i].length ; j++)
|
---|
736 | {
|
---|
737 | var cell = tableMap[i][j] ;
|
---|
738 | FCKDomTools.ClearElementJSProperty(cell, '_colScanned' ) ;
|
---|
739 | FCKDomTools.ClearElementJSProperty(cell, '_rowScanned' ) ;
|
---|
740 | }
|
---|
741 | }
|
---|
742 |
|
---|
743 | // Insert physical rows and columns to the table.
|
---|
744 | for ( var i = 0 ; i < tableMap.length ; i++ )
|
---|
745 | {
|
---|
746 | var rowObj = FCK.EditorDocument.createElement( 'tr' ) ;
|
---|
747 | for ( var j = 0 ; j < tableMap[i].length ; )
|
---|
748 | {
|
---|
749 | var cell = tableMap[i][j] ;
|
---|
750 | if ( tableMap[i-1] && tableMap[i-1][j] == cell )
|
---|
751 | {
|
---|
752 | j += cell.colSpan ;
|
---|
753 | continue ;
|
---|
754 | }
|
---|
755 | rowObj.appendChild( cell ) ;
|
---|
756 | if ( rowSpanAttr != 'rowSpan' )
|
---|
757 | {
|
---|
758 | cell.rowSpan = cell[rowSpanAttr] ;
|
---|
759 | cell.removeAttribute( rowSpanAttr ) ;
|
---|
760 | }
|
---|
761 | j += cell.colSpan ;
|
---|
762 | if ( cell.colSpan == 1 )
|
---|
763 | cell.removeAttribute( 'colspan' ) ;
|
---|
764 | if ( cell.rowSpan == 1 )
|
---|
765 | cell.removeAttribute( 'rowspan' ) ;
|
---|
766 | }
|
---|
767 | if ( FCKBrowserInfo.IsIE )
|
---|
768 | {
|
---|
769 | table.rows[i].replaceNode( rowObj ) ;
|
---|
770 | }
|
---|
771 | else
|
---|
772 | {
|
---|
773 | table.rows[i].innerHTML = '' ;
|
---|
774 | FCKDomTools.MoveChildren( rowObj, table.rows[i] ) ;
|
---|
775 | }
|
---|
776 | }
|
---|
777 | }
|
---|
778 |
|
---|
779 | FCKTableHandler._MoveCaretToCell = function ( refCell, toStart )
|
---|
780 | {
|
---|
781 | var range = new FCKDomRange( FCK.EditorWindow ) ;
|
---|
782 | range.MoveToNodeContents( refCell ) ;
|
---|
783 | range.Collapse( toStart ) ;
|
---|
784 | range.Select() ;
|
---|
785 | }
|
---|
786 |
|
---|
787 | FCKTableHandler.ClearRow = function( tr )
|
---|
788 | {
|
---|
789 | // Get the array of row's cells.
|
---|
790 | var aCells = tr.cells ;
|
---|
791 |
|
---|
792 | // Replace the contents of each cell with "nothing".
|
---|
793 | for ( var i = 0 ; i < aCells.length ; i++ )
|
---|
794 | {
|
---|
795 | aCells[i].innerHTML = '' ;
|
---|
796 |
|
---|
797 | if ( FCKBrowserInfo.IsGeckoLike )
|
---|
798 | FCKTools.AppendBogusBr( aCells[i] ) ;
|
---|
799 | }
|
---|
800 | }
|
---|
801 |
|
---|
802 | FCKTableHandler.GetMergeRightTarget = function()
|
---|
803 | {
|
---|
804 | var cells = this.GetSelectedCells() ;
|
---|
805 | if ( cells.length != 1 )
|
---|
806 | return null ;
|
---|
807 |
|
---|
808 | var refCell = cells[0] ;
|
---|
809 | var tableMap = this._CreateTableMap( refCell ) ;
|
---|
810 | var rowIdx = refCell.parentNode.rowIndex ;
|
---|
811 | var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ;
|
---|
812 | var nextColIdx = colIdx + ( isNaN( refCell.colSpan ) ? 1 : refCell.colSpan ) ;
|
---|
813 | var nextCell = tableMap[rowIdx][nextColIdx] ;
|
---|
814 |
|
---|
815 | if ( ! nextCell )
|
---|
816 | return null ;
|
---|
817 |
|
---|
818 | // The two cells must have the same vertical geometry, otherwise merging does not make sense.
|
---|
819 | this._MarkCells( [refCell, nextCell], '_SizeTest' ) ;
|
---|
820 | var refGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SizeTest' ) ;
|
---|
821 | var nextGeometry = this._GetMarkerGeometry( tableMap, rowIdx, nextColIdx, '_SizeTest' ) ;
|
---|
822 | this._UnmarkCells( [refCell, nextCell], '_SizeTest' ) ;
|
---|
823 |
|
---|
824 | if ( refGeometry.height != nextGeometry.height || refGeometry.y != nextGeometry.y )
|
---|
825 | return null ;
|
---|
826 |
|
---|
827 | return { 'refCell' : refCell, 'nextCell' : nextCell, 'tableMap' : tableMap } ;
|
---|
828 | }
|
---|
829 |
|
---|
830 | FCKTableHandler.GetMergeDownTarget = function()
|
---|
831 | {
|
---|
832 | var cells = this.GetSelectedCells() ;
|
---|
833 | if ( cells.length != 1 )
|
---|
834 | return null ;
|
---|
835 |
|
---|
836 | var refCell = cells[0] ;
|
---|
837 | var tableMap = this._CreateTableMap( refCell ) ;
|
---|
838 | var rowIdx = refCell.parentNode.rowIndex ;
|
---|
839 | var colIdx = this._GetCellIndexSpan( tableMap, rowIdx, refCell ) ;
|
---|
840 | var newRowIdx = rowIdx + ( isNaN( refCell.rowSpan ) ? 1 : refCell.rowSpan ) ;
|
---|
841 | if ( ! tableMap[newRowIdx] )
|
---|
842 | return null ;
|
---|
843 |
|
---|
844 | var nextCell = tableMap[newRowIdx][colIdx] ;
|
---|
845 |
|
---|
846 | if ( ! nextCell )
|
---|
847 | return null ;
|
---|
848 |
|
---|
849 | // Check if the selected cells are both in the same table section (thead, tfoot or tbody).
|
---|
850 | if ( refCell.parentNode.parentNode != nextCell.parentNode.parentNode )
|
---|
851 | return null ;
|
---|
852 |
|
---|
853 | // The two cells must have the same horizontal geometry, otherwise merging does not makes sense.
|
---|
854 | this._MarkCells( [refCell, nextCell], '_SizeTest' ) ;
|
---|
855 | var refGeometry = this._GetMarkerGeometry( tableMap, rowIdx, colIdx, '_SizeTest' ) ;
|
---|
856 | var nextGeometry = this._GetMarkerGeometry( tableMap, newRowIdx, colIdx, '_SizeTest' ) ;
|
---|
857 | this._UnmarkCells( [refCell, nextCell], '_SizeTest' ) ;
|
---|
858 |
|
---|
859 | if ( refGeometry.width != nextGeometry.width || refGeometry.x != nextGeometry.x )
|
---|
860 | return null ;
|
---|
861 |
|
---|
862 | return { 'refCell' : refCell, 'nextCell' : nextCell, 'tableMap' : tableMap } ;
|
---|
863 | }
|
---|