Bladeren bron

Added: Add includeSpanned option to TableWalker.

Maciej Gołaszewski 7 jaren geleden
bovenliggende
commit
fadcbd74c6
2 gewijzigde bestanden met toevoegingen van 168 en 12 verwijderingen
  1. 74 10
      packages/ckeditor5-table/src/tablewalker.js
  2. 94 2
      packages/ckeditor5-table/tests/tablewalker.js

+ 74 - 10
packages/ckeditor5-table/src/tablewalker.js

@@ -30,7 +30,7 @@ export default class TableWalker {
 	 *		+----+----+----+----+----+----+
 	 *		| 00      | 02 | 03      | 05 |
 	 *		|         +--- +----+----+----+
-	 *		|         | 12      | 24 | 25 |
+	 *		|         | 12      | 14 | 15 |
 	 *		|         +----+----+----+----+
 	 *		|         | 22                |
 	 *		|----+----+                   +
@@ -44,11 +44,29 @@ export default class TableWalker {
 	 *		'A cell at row 1 and column 5'
 	 *		'A cell at row 2 and column 2'
 	 *
+	 *	To iterate over spanned cells also:
+	 *
+	 *		const tableWalker = new TableWalker( table, { startRow: 1, endRow: 1 } );
+	 *
+	 *		for( const cellInfo of tableWalker ) {
+	 *			console.log( 'Cell at ' + cellInfo.row + ' x ' + cellInfo.column + ' : ' + ( cellInfo.cell ? 'has data' : 'is spanned' )  );
+	 *		}
+	 *
+	 *	will log in the console for the table from previous example:
+	 *
+	 *		'Cell at 1 x 0 : is spanned'
+	 *		'Cell at 1 x 1 : is spanned'
+	 *		'Cell at 1 x 2 : has data'
+	 *		'Cell at 1 x 3 : is spanned'
+	 *		'Cell at 1 x 4 : has data'
+	 *		'Cell at 1 x 5 : has data'
+	 *
 	 * @constructor
 	 * @param {module:engine/model/element~Element} table A table over which iterate.
 	 * @param {Object} [options={}] Object with configuration.
 	 * @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.includeSpanned] Also return values for spanned cells.
 	 */
 	constructor( table, options = {} ) {
 		/**
@@ -76,6 +94,13 @@ export default class TableWalker {
 		this.endRow = typeof options.endRow == 'number' ? options.endRow : undefined;
 
 		/**
+		 * Enables output of spanned cells that are normally not yielded.
+		 *
+		 * @type {Boolean}
+		 */
+		this.includeSpanned = !!options.includeSpanned;
+
+		/**
 		 * A current row index.
 		 *
 		 * @readonly
@@ -118,6 +143,14 @@ export default class TableWalker {
 		this._cellSpans = new CellSpans();
 
 		/**
+		 * Holds spanned cells info to be outputed when {@link #includeSpanned} is set to true.
+		 *
+		 * @type {Array.<module:table/tablewalker~TableWalkerValue>}
+		 * @private
+		 */
+		this._spannedCells = [];
+
+		/**
 		 * Cached table properties - returned for every yielded value.
 		 *
 		 * @readonly
@@ -147,10 +180,14 @@ export default class TableWalker {
 	next() {
 		const row = this.table.getChild( this.row );
 
-		if ( !row ) {
+		if ( !row || ( this.endRow !== undefined && this.row > this.endRow ) ) {
 			return { done: true };
 		}
 
+		if ( this.includeSpanned && this._spannedCells.length ) {
+			return { done: false, value: this._spannedCells.shift() };
+		}
+
 		// The previous cell is defined after the first cell in a row.
 		if ( this._previousCell ) {
 			const colspan = this._updateSpans();
@@ -164,7 +201,13 @@ export default class TableWalker {
 		// If there is no cell then it's end of a row so update spans and reset indexes.
 		if ( !cell ) {
 			// Record spans of the previous cell.
-			this._updateSpans();
+			const colspan = this._updateSpans();
+
+			if ( this.includeSpanned && colspan > 1 ) {
+				for ( let i = this.column + 1; i < this.column + colspan; i++ ) {
+					this._spannedCells.push( { row: this.row, column: this.column, table: this._tableData, colspan: 1, rowspan: 1 } );
+				}
+			}
 
 			// Reset indexes and move to next row.
 			this.cell = 0;
@@ -176,25 +219,43 @@ export default class TableWalker {
 		}
 
 		// Update the column index if the current column is overlapped by cells from previous rows that have rowspan attribute set.
-		this.column = this._cellSpans.getAdjustedColumnIndex( this.row, this.column );
+		const beforeColumn = this.column;
+		this.column = this._cellSpans.getAdjustedColumnIndex( this.row, beforeColumn );
+
+		// return this.next()
+		if ( this.includeSpanned && beforeColumn !== this.column ) {
+			for ( let i = beforeColumn; i < this.column; i++ ) {
+				this._spannedCells.push( { row: this.row, column: i, table: this._tableData, colspan: 1, rowspan: 1 } );
+			}
+
+			return this.next();
+		}
 
 		// Update the cell indexes before returning value.
 		this._previousCell = cell;
 		this.cell++;
 
-		if ( this.startRow > this.row || ( this.endRow !== undefined && this.row > this.endRow ) ) {
+		// Skip rows that are before startRow.
+		if ( this.startRow > this.row ) {
 			return this.next();
 		}
 
+		const colspan = parseInt( cell.getAttribute( 'colspan' ) || 1 );
+
+		if ( this.includeSpanned && colspan > 1 ) {
+			for ( let i = this.column + 1; i < this.column + colspan; i++ ) {
+				this._spannedCells.push( { row: this.row, column: i, table: this._tableData, colspan: 1, rowspan: 1 } );
+			}
+		}
+
 		return {
 			done: false,
 			value: {
 				cell,
 				row: this.row,
 				column: this.column,
-				// TODO: parseInt tests in editable?
 				rowspan: parseInt( cell.getAttribute( 'rowspan' ) || 1 ),
-				colspan: parseInt( cell.getAttribute( 'colspan' ) || 1 ),
+				colspan,
 				table: this._tableData
 			}
 		};
@@ -311,11 +372,14 @@ class CellSpans {
  * Object returned by {@link module:table/tablewalker~TableWalker} when traversing table cells.
  *
  * @typedef {Object} module:table/tablewalker~TableWalkerValue
- * @property {module:engine/model/element~Element} cell Current table cell.
+ * @property {module:engine/model/element~Element} [cell] Current table cell. Might be empty if
+ * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
  * @property {Number} row The row index of a cell.
  * @property {Number} column The column index of a cell. Column index is adjusted to widths & heights of previous cells.
- * @property {Number} colspan The colspan attribute of a cell - always defined even if model attribute is not present.
- * @property {Number} rowspan The rowspan attribute of a cell - always defined even if model attribute is not present.
+ * @property {Number} [colspan] The colspan attribute of a cell - always defined even if model attribute is not present. Not set if
+ * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
+ * @property {Number} [rowspan] The rowspan attribute of a cell - always defined even if model attribute is not present. Not set if
+ * {@link module:table/tablewalker~TableWalker#includeSpanned} is set to true.
  * @property {Object} table Table attributes
  * @property {Object} table.headingRows The heading rows attribute of a table - always defined even if model attribute is not present.
  * @property {Object} table.headingColumns The heading columns attribute of a table - always defined even if model attribute is not present.

+ 94 - 2
packages/ckeditor5-table/tests/tablewalker.js

@@ -46,7 +46,7 @@ describe( 'TableWalker', () => {
 			} );
 	} );
 
-	function testWalker( tableData, expected, options = {} ) {
+	function testWalker( tableData, expected, options ) {
 		setData( model, modelTable( tableData ) );
 
 		const iterator = new TableWalker( root.getChild( 0 ), options );
@@ -57,7 +57,8 @@ describe( 'TableWalker', () => {
 			result.push( tableInfo );
 		}
 
-		const formattedResult = result.map( ( { row, column, cell } ) => ( { row, column, data: cell.getChild( 0 ).data } ) );
+		const formattedResult = result.map( ( { row, column, cell } ) => ( { row, column, data: cell && cell.getChild( 0 ).data } ) );
+
 		expect( formattedResult ).to.deep.equal( expected );
 	}
 
@@ -96,6 +97,97 @@ describe( 'TableWalker', () => {
 		] );
 	} );
 
+	it( 'should properly output column indexes of a table that has multiple rowspans', () => {
+		testWalker( [
+			[ { rowspan: 3, contents: '11' }, '12', '13' ],
+			[ { rowspan: 2, contents: '22' }, '23' ],
+			[ '33' ],
+			[ '41', '42', '43' ]
+		], [
+			{ row: 0, column: 0, data: '11' },
+			{ row: 0, column: 1, data: '12' },
+			{ row: 0, column: 2, data: '13' },
+			{ row: 1, column: 1, data: '22' },
+			{ row: 1, column: 2, data: '23' },
+			{ row: 2, column: 2, data: '33' },
+			{ row: 3, column: 0, data: '41' },
+			{ row: 3, column: 1, data: '42' },
+			{ row: 3, column: 2, data: '43' }
+		] );
+	} );
+
+	describe( 'option.startRow', () => {
+		it( 'should start iterating from given row but with cell spans properly calculated', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
+				[ '23' ],
+				[ '33' ],
+				[ '41', '42', '43' ]
+			], [
+				{ row: 2, column: 2, data: '33' },
+				{ row: 3, column: 0, data: '41' },
+				{ row: 3, column: 1, data: '42' },
+				{ row: 3, column: 2, data: '43' }
+			], { startRow: 2 } );
+		} );
+	} );
+
+	describe( 'option.endRow', () => {
+		it( 'should stopp iterating after given row but with cell spans properly calculated', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
+				[ '23' ],
+				[ '33' ],
+				[ '41', '42', '43' ]
+			], [
+				{ row: 0, column: 0, data: '11' },
+				{ row: 0, column: 2, data: '13' },
+				{ row: 1, column: 2, data: '23' },
+				{ row: 2, column: 2, data: '33' }
+			], { endRow: 2 } );
+		} );
+	} );
+
+	describe( 'option.includeSpanned', () => {
+		it( 'should output spanned cells as empty cell', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
+				[ '23' ],
+				[ '33' ],
+				[ '41', { colspan: 2, contents: '42' } ]
+			], [
+				{ row: 0, column: 0, data: '11' },
+				{ row: 0, column: 1, data: undefined },
+				{ row: 0, column: 2, data: '13' },
+				{ row: 1, column: 0, data: undefined },
+				{ row: 1, column: 1, data: undefined },
+				{ row: 1, column: 2, data: '23' },
+				{ row: 2, column: 0, data: undefined },
+				{ row: 2, column: 1, data: undefined },
+				{ row: 2, column: 2, data: '33' },
+				{ row: 3, column: 0, data: '41' },
+				{ row: 3, column: 1, data: '42' },
+				{ row: 3, column: 2, data: undefined }
+			], { includeSpanned: true } );
+		} );
+
+		it( 'should work with startRow & endRow options', () => {
+			testWalker( [
+				[ { colspan: 2, rowspan: 3, contents: '11' }, '13' ],
+				[ '23' ],
+				[ '33' ],
+				[ '41', '42', '43' ]
+			], [
+				{ row: 1, column: 0, data: undefined },
+				{ row: 1, column: 1, data: undefined },
+				{ row: 1, column: 2, data: '23' },
+				{ row: 2, column: 0, data: undefined },
+				{ row: 2, column: 1, data: undefined },
+				{ row: 2, column: 2, data: '33' }
+			], { includeSpanned: true, startRow: 1, endRow: 2 } );
+		} );
+	} );
+
 	describe( 'option.endRow', () => {
 		it( 'should iterate over given row 0 only', () => {
 			testWalker( [