8
0
Просмотр исходного кода

Docs: Update TableUtils#insertRows() and TableUtils#insertColumns() methods docs.

Maciej Gołaszewski 7 лет назад
Родитель
Сommit
6dd1d13964

+ 2 - 2
packages/ckeditor5-table/src/commands/removerowcommand.js

@@ -59,12 +59,12 @@ export default class RemoveRowCommand extends Command {
 			// Get cells from removed row that are spanned over multiple rows.
 			// Get cells from removed row that are spanned over multiple rows.
 			tableMap
 			tableMap
 				.filter( ( { row, rowspan } ) => row === currentRow && rowspan > 1 )
 				.filter( ( { row, rowspan } ) => row === currentRow && rowspan > 1 )
-				.map( ( { column, cell, rowspan } ) => cellsToMove.set( column, { cell, rowspanToSet: rowspan - 1 } ) );
+				.forEach( ( { column, cell, rowspan } ) => cellsToMove.set( column, { cell, rowspanToSet: rowspan - 1 } ) );
 
 
 			// Reduce rowspan on cells that are above removed row and overlaps removed row.
 			// Reduce rowspan on cells that are above removed row and overlaps removed row.
 			tableMap
 			tableMap
 				.filter( ( { row, rowspan } ) => row <= currentRow - 1 && row + rowspan > currentRow )
 				.filter( ( { row, rowspan } ) => row <= currentRow - 1 && row + rowspan > currentRow )
-				.map( ( { cell, rowspan } ) => updateNumericAttribute( 'rowspan', rowspan - 1, cell, writer ) );
+				.forEach( ( { cell, rowspan } ) => updateNumericAttribute( 'rowspan', rowspan - 1, cell, writer ) );
 
 
 			// Move cells to another row
 			// Move cells to another row
 			const targetRow = currentRow + 1;
 			const targetRow = currentRow + 1;

+ 87 - 51
packages/ckeditor5-table/src/tableutils.js

@@ -69,6 +69,23 @@ export default class TableUtils extends Plugin {
 	/**
 	/**
 	 * Insert rows into a table.
 	 * Insert rows into a table.
 	 *
 	 *
+	 *     editor.plugins.get( 'TableUtils' ).insertRows( table, { at: 1, rows: 2 } );
+	 *
+	 * For the table below this code
+	 *
+	 *     row index
+	 *            0 +--+--+--+                        +--+--+--+
+	 *              | a| b| c|                        | a| b| c|
+	 *            1 +  +--+--+ <--- insert here at=1  +  +--+--+
+	 *              |  | d| e|                        |  |  |  |
+	 *            2 +  +--+--+      should give:      +  +--+--+
+	 *              |  | f| g|                        |  |  |  |
+	 *            3 +--+--+--+                        +  +--+--+
+	 *                                                |  | d| e|
+	 *            4                                   +--+--+--+
+	 *                                                +  + f| g|
+	 *            5                                   +--+--+--+
+	 *
 	 * @param {module:engine/model/element~Element} table
 	 * @param {module:engine/model/element~Element} table
 	 * @param {Object} options
 	 * @param {Object} options
 	 * @param {Number} [options.at=0] Row index at which insert rows.
 	 * @param {Number} [options.at=0] Row index at which insert rows.
@@ -78,48 +95,72 @@ export default class TableUtils extends Plugin {
 		const model = this.editor.model;
 		const model = this.editor.model;
 
 
 		const insertAt = options.at || 0;
 		const insertAt = options.at || 0;
-		const rows = options.rows || 1;
-
-		const headingRows = table.getAttribute( 'headingRows' ) || 0;
+		const rowsToInsert = options.rows || 1;
 
 
 		model.change( writer => {
 		model.change( writer => {
+			const headingRows = table.getAttribute( 'headingRows' ) || 0;
+
+			// Inserting rows inside heading section requires to update table's headingRows attribute as the heading section will grow.
 			if ( headingRows > insertAt ) {
 			if ( headingRows > insertAt ) {
-				writer.setAttribute( 'headingRows', headingRows + rows, table );
+				writer.setAttribute( 'headingRows', headingRows + rowsToInsert, table );
 			}
 			}
 
 
-			const tableIterator = new TableWalker( table, { endRow: insertAt + 1 } );
+			// Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
+			if ( insertAt === 0 || insertAt === table.childCount ) {
+				createEmptyRows( writer, table, insertAt, rowsToInsert, this.getColumns( table ) );
+
+				return;
+			}
 
 
-			let tableCellToInsert = 0;
+			// Iterate over all rows below inserted rows in order to check for rowspanned cells.
+			const tableIterator = new TableWalker( table, { endRow: insertAt } );
 
 
-			for ( const tableCellInfo of tableIterator ) {
-				const { row, rowspan, colspan, cell } = tableCellInfo;
+			// Will hold number of cells needed to insert in created rows.
+			// The number might be different then table cell width when there are rowspanned cells.
+			let cellsToInsert = 0;
 
 
+			for ( const { row, rowspan, colspan, cell } of tableIterator ) {
 				const isBeforeInsertedRow = row < insertAt;
 				const isBeforeInsertedRow = row < insertAt;
 				const overlapsInsertedRow = row + rowspan > insertAt;
 				const overlapsInsertedRow = row + rowspan > insertAt;
 
 
 				if ( isBeforeInsertedRow && overlapsInsertedRow ) {
 				if ( isBeforeInsertedRow && overlapsInsertedRow ) {
-					writer.setAttribute( 'rowspan', rowspan + rows, cell );
+					// This cell overlaps inserted rows so we need to expand it further.
+					writer.setAttribute( 'rowspan', rowspan + rowsToInsert, cell );
 				}
 				}
 
 
 				// Calculate how many cells to insert based on the width of cells in a row at insert position.
 				// Calculate how many cells to insert based on the width of cells in a row at insert position.
 				// It might be lower then table width as some cells might overlaps inserted row.
 				// It might be lower then table width as some cells might overlaps inserted row.
+				// In the table above the cell 'a' overlaps inserted row so only two empty cells are need to be created.
 				if ( row === insertAt ) {
 				if ( row === insertAt ) {
-					tableCellToInsert += colspan;
+					cellsToInsert += colspan;
 				}
 				}
 			}
 			}
 
 
-			// If insertion occurs on the end of a table use table width.
-			if ( insertAt >= table.childCount ) {
-				tableCellToInsert = this.getColumns( table );
-			}
-
-			createEmptyRows( writer, table, insertAt, rows, tableCellToInsert );
+			createEmptyRows( writer, table, insertAt, rowsToInsert, cellsToInsert );
 		} );
 		} );
 	}
 	}
 
 
 	/**
 	/**
 	 * Inserts columns into a table.
 	 * Inserts columns into a table.
 	 *
 	 *
+	 *     editor.plugins.get( 'TableUtils' ).insertColumns( table, { at: 1, columns: 2 } );
+	 *
+	 * For the table below this code
+	 *
+	 *      0  1  2  3                     0  1  2  3  4  5
+	 *      +--+--+--+                     +--+--+--+--+--+
+	 *      | a   | b|                     | a         | b|
+	 *      +     +--+                     +           +--+
+	 *      |     | c|                     |           | c|
+	 *      +--+--+--+      should give:   +--+--+--+--+--+
+	 *      | d| e| f|                     | d|  |  | e| f|
+	 *      +--+  +--+                     +--+--+--+  +--+
+	 *      | g|  | h|                     | g|  |  |  | h|
+	 *      +--+--+--+                     +--+--+--+--+--+
+	 *      | i      |                     | i            |
+	 *      +--+--+--+                     +--+--+--+--+--+
+	 *         ^________ insert here at=1
+	 *
 	 * @param {module:engine/model/element~Element} table
 	 * @param {module:engine/model/element~Element} table
 	 * @param {Object} options
 	 * @param {Object} options
 	 * @param {Number} [options.at=0] Column index at which insert columns.
 	 * @param {Number} [options.at=0] Column index at which insert columns.
@@ -129,60 +170,55 @@ export default class TableUtils extends Plugin {
 		const model = this.editor.model;
 		const model = this.editor.model;
 
 
 		const insertAt = options.at || 0;
 		const insertAt = options.at || 0;
-		const columns = options.columns || 1;
+		const columnsToInsert = options.columns || 1;
 
 
 		model.change( writer => {
 		model.change( writer => {
+			const headingColumns = table.getAttribute( 'headingColumns' );
+
+			// Inserting rows inside heading section requires to update table's headingRows attribute as the heading section will grow.
+			if ( insertAt < headingColumns ) {
+				writer.setAttribute( 'headingColumns', headingColumns + columnsToInsert, table );
+			}
+
 			const tableColumns = this.getColumns( table );
 			const tableColumns = this.getColumns( table );
 
 
 			// Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
 			// Inserting at the end and at the beginning of a table doesn't require to calculate anything special.
-			if ( insertAt === 0 || tableColumns <= insertAt ) {
+			if ( insertAt === 0 || tableColumns === insertAt ) {
 				for ( const tableRow of table.getChildren() ) {
 				for ( const tableRow of table.getChildren() ) {
-					createCells( columns, writer, Position.createAt( tableRow, insertAt ? 'end' : 0 ) );
+					createCells( columnsToInsert, writer, Position.createAt( tableRow, insertAt ? 'end' : 0 ) );
 				}
 				}
 
 
 				return;
 				return;
 			}
 			}
 
 
-			const headingColumns = table.getAttribute( 'headingColumns' );
+			const tableWalker = new TableWalker( table, { column: insertAt, includeSpanned: true } );
 
 
-			if ( insertAt < headingColumns ) {
-				writer.setAttribute( 'headingColumns', headingColumns + columns, table );
-			}
+			for ( const { row, column, cell, colspan, rowspan, cellIndex } of tableWalker ) {
+				// When iterating over column the table walker outputs either:
+				// - cells at given column index (cell "e" from method docs),
+				// - spanned columns (includeSpanned option) (spanned cell from row between cells "g" and "h" - spanned by "e"),
+				// - or a cell from the same row which spans over this column (cell "a").
 
 
-			const tableMap = [ ...new TableWalker( table ) ];
+				if ( column !== insertAt ) {
+					// If column is different then insertAt it is a cell that spans over an inserted column (cell "a" & "i").
+					// For such cells expand them of number of columns inserted.
+					writer.setAttribute( 'colspan', colspan + columnsToInsert, cell );
 
 
-			// Holds row indexes of already analyzed row or rows that some rowspanned cell overlaps.
-			const skipRows = new Set();
+					// The includeSpanned option will output the "empty"/spanned column so skip this row already.
+					tableWalker.skipRow( row );
 
 
-			for ( const { row, column, cell, colspan, rowspan } of tableMap ) {
-				if ( skipRows.has( row ) ) {
-					continue;
-				}
-
-				// Check if currently analyzed cell overlaps insert position.
-				const isBeforeInsertAt = column < insertAt;
-				const expandsOverInsertAt = column + colspan > insertAt;
-
-				if ( isBeforeInsertAt && expandsOverInsertAt ) {
-					// And if so expand that table cell.
-					writer.setAttribute( 'colspan', colspan + columns, cell );
-
-					// This cell will overlap cells in rows below so skip them.
+					// This cell will overlap cells in rows below so skip them also (because of includeSpanned option) - (cell "a")
 					if ( rowspan > 1 ) {
 					if ( rowspan > 1 ) {
-						for ( let i = row; i < row + rowspan; i++ ) {
-							skipRows.add( i );
+						for ( let i = row + 1; i < row + rowspan; i++ ) {
+							tableWalker.skipRow( i );
 						}
 						}
 					}
 					}
+				} else {
+					// It's either cell at this column index or spanned cell by a rowspanned cell from row above.
+					// In table above it's cell "e" and a spanned position from row below (empty cell between cells "g" and "h")
+					const insertPosition = Position.createFromParentAndOffset( table.getChild( row ), cellIndex );
 
 
-					skipRows.add( row );
-				}
-
-				// The next cell might be not on the insertAt column - ie when there are many rowspanned cells before.
-				if ( column >= insertAt ) {
-					const insertPosition = Position.createBefore( cell );
-
-					createCells( columns, writer, insertPosition );
-					skipRows.add( row );
+					createCells( columnsToInsert, writer, insertPosition );
 				}
 				}
 			}
 			}
 		} );
 		} );

+ 25 - 4
packages/ckeditor5-table/src/tablewalker.js

@@ -64,6 +64,7 @@ export default class TableWalker {
 	 * @constructor
 	 * @constructor
 	 * @param {module:engine/model/element~Element} table A table over which iterate.
 	 * @param {module:engine/model/element~Element} table A table over which iterate.
 	 * @param {Object} [options={}] Object with configuration.
 	 * @param {Object} [options={}] Object with configuration.
+	 * @param {Number} [options.column] A column index for which this iterator will output cells.
 	 * @param {Number} [options.startRow=0] A row index for which this iterator should start.
 	 * @param {Number} [options.startRow=0] A row index for which this iterator should start.
 	 * @param {Number} [options.endRow] A row index for which this iterator should end.
 	 * @param {Number} [options.endRow] A row index for which this iterator should end.
 	 * @param {Boolean} [options.includeSpanned] Also return values for spanned cells.
 	 * @param {Boolean} [options.includeSpanned] Also return values for spanned cells.
@@ -100,6 +101,10 @@ export default class TableWalker {
 		 */
 		 */
 		this.includeSpanned = !!options.includeSpanned;
 		this.includeSpanned = !!options.includeSpanned;
 
 
+		this._ccc = typeof options.column == 'number' ? options.column : undefined;
+
+		this._skipRows = new Set();
+
 		/**
 		/**
 		 * A current row index.
 		 * A current row index.
 		 *
 		 *
@@ -178,9 +183,11 @@ export default class TableWalker {
 		}
 		}
 
 
 		if ( this._isSpanned( this.row, this.column ) ) {
 		if ( this._isSpanned( this.row, this.column ) ) {
+			const column = this.column;
+
 			const outValue = {
 			const outValue = {
 				row: this.row,
 				row: this.row,
-				column: this.column,
+				column,
 				rowspan: 1,
 				rowspan: 1,
 				colspan: 1,
 				colspan: 1,
 				cellIndex: this.cell,
 				cellIndex: this.cell,
@@ -190,7 +197,7 @@ export default class TableWalker {
 
 
 			this.column++;
 			this.column++;
 
 
-			if ( !this.includeSpanned || this.startRow > this.row ) {
+			if ( !this.includeSpanned || this.startRow > this.row || this._checkCCC( column, 1 ) || this._skipRows.has( this.row ) ) {
 				return this.next();
 				return this.next();
 			}
 			}
 
 
@@ -214,10 +221,12 @@ export default class TableWalker {
 			this._recordSpans( this.row, this.column, rowspan, colspan );
 			this._recordSpans( this.row, this.column, rowspan, colspan );
 		}
 		}
 
 
+		const column = this.column;
+
 		const outValue = {
 		const outValue = {
 			cell,
 			cell,
 			row: this.row,
 			row: this.row,
-			column: this.column,
+			column,
 			rowspan,
 			rowspan,
 			colspan,
 			colspan,
 			cellIndex: this.cell,
 			cellIndex: this.cell,
@@ -227,7 +236,7 @@ export default class TableWalker {
 		this.column++;
 		this.column++;
 		this.cell++;
 		this.cell++;
 
 
-		if ( this.startRow > this.row ) {
+		if ( this.startRow > this.row || this._skipRows.has( this.row ) || this._checkCCC( column, colspan ) ) {
 			return this.next();
 			return this.next();
 		}
 		}
 
 
@@ -237,6 +246,18 @@ export default class TableWalker {
 		};
 		};
 	}
 	}
 
 
+	skipRow( row ) {
+		this._skipRows.add( row );
+	}
+
+	_checkCCC( column, colspan ) {
+		if ( this._ccc === undefined ) {
+			return;
+		}
+
+		return !( column === this._ccc || ( column < this._ccc && column + colspan > this._ccc ) );
+	}
+
 	_isSpanned( row, column ) {
 	_isSpanned( row, column ) {
 		if ( !this._spans.has( row ) ) {
 		if ( !this._spans.has( row ) ) {
 			return false;
 			return false;

+ 6 - 6
packages/ckeditor5-table/tests/tableutils.js

@@ -361,17 +361,17 @@ describe( 'TableUtils', () => {
 
 
 		it( 'should expand spanned columns', () => {
 		it( 'should expand spanned columns', () => {
 			setData( model, modelTable( [
 			setData( model, modelTable( [
-				[ '11[]', '12' ],
-				[ { colspan: 2, contents: '21' } ],
-				[ '31', '32' ]
+				[ '00[]', '01' ],
+				[ { colspan: 2, contents: '10' } ],
+				[ '20', '21' ]
 			], { headingColumns: 2 } ) );
 			], { headingColumns: 2 } ) );
 
 
 			tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
 			tableUtils.insertColumns( root.getNodeByPath( [ 0 ] ), { at: 1 } );
 
 
 			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
 			expect( formatTable( getData( model ) ) ).to.equal( formattedModelTable( [
-				[ '11[]', '', '12' ],
-				[ { colspan: 3, contents: '21' } ],
-				[ '31', '', '32' ]
+				[ '00[]', '', '01' ],
+				[ { colspan: 3, contents: '10' } ],
+				[ '20', '', '21' ]
 			], { headingColumns: 3 } ) );
 			], { headingColumns: 3 } ) );
 		} );
 		} );
 
 

+ 26 - 14
packages/ckeditor5-table/tests/tablewalker.js

@@ -154,6 +154,18 @@ describe( 'TableWalker', () => {
 				{ row: 2, column: 2, index: 0, data: '33' }
 				{ row: 2, column: 2, index: 0, data: '33' }
 			], { endRow: 2 } );
 			], { endRow: 2 } );
 		} );
 		} );
+
+		it( 'should iterate over given row 0 only', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
+				[ '23' ],
+				[ '33' ],
+				[ '41', '42', '43' ]
+			], [
+				{ row: 0, column: 0, index: 0, data: '11' },
+				{ row: 0, column: 2, index: 1, data: '13' }
+			], { endRow: 0 } );
+		} );
 	} );
 	} );
 
 
 	describe( 'option.includeSpanned', () => {
 	describe( 'option.includeSpanned', () => {
@@ -218,20 +230,6 @@ describe( 'TableWalker', () => {
 				{ row: 2, column: 2, index: 0, data: '22' }
 				{ row: 2, column: 2, index: 0, data: '22' }
 			], { includeSpanned: true, startRow: 1, endRow: 2 } );
 			], { includeSpanned: true, startRow: 1, endRow: 2 } );
 		} );
 		} );
-	} );
-
-	describe( 'option.endRow', () => {
-		it( 'should iterate over given row 0 only', () => {
-			testWalker( [
-				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
-				[ '23' ],
-				[ '33' ],
-				[ '41', '42', '43' ]
-			], [
-				{ row: 0, column: 0, index: 0, data: '11' },
-				{ row: 0, column: 2, index: 1, data: '13' }
-			], { endRow: 0 } );
-		} );
 
 
 		it( 'should output rowspanned cells at the end of a table row', () => {
 		it( 'should output rowspanned cells at the end of a table row', () => {
 			testWalker( [
 			testWalker( [
@@ -246,4 +244,18 @@ describe( 'TableWalker', () => {
 			], { startRow: 0, endRow: 1, includeSpanned: true } );
 			], { startRow: 0, endRow: 1, includeSpanned: true } );
 		} );
 		} );
 	} );
 	} );
+
+	describe( 'options.startColumn', () => {
+		it( 'should output only cells on given column', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '00' }, '02' ],
+				[ '12' ],
+				[ '22' ],
+				[ '30', '31', '32' ]
+			], [
+				{ row: 0, column: 0, index: 0, data: '00' },
+				{ row: 3, column: 1, index: 1, data: '31' }
+			], { column: 1 } );
+		} );
+	} );
 } );
 } );