Procházet zdrojové kódy

Added: Make inserted rows appear in proper table section in the view.

Maciej Gołaszewski před 7 roky
rodič
revize
17ee9e02e8

+ 24 - 31
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -31,13 +31,13 @@ export default function downcastTable() {
 		const headingRows = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
 		const tableRows = Array.from( table.getChildren() );
 
-		const cellSpans = ensureCellSpans( table, 0, conversionApi );
+		const cellSpans = ensureCellSpans( table, 0 );
 
 		for ( const tableRow of tableRows ) {
 			const rowIndex = tableRows.indexOf( tableRow );
 			const tableSectionElement = getTableSection( rowIndex, headingRows, tableElement, conversionApi );
 
-			downcastTableRow( tableRow, rowIndex, tableSectionElement, conversionApi );
+			downcastTableRow( tableRow, rowIndex, tableSectionElement, cellSpans, conversionApi );
 
 			// Drop table cell spans information for downcasted row.
 			cellSpans.drop( rowIndex );
@@ -85,44 +85,36 @@ export function downcastInsertRow() {
 		const isHeadingRow = rowIndex < headingRows;
 
 		const tableSection = Array.from( tableElement.getChildren() )
-			.filter( child => child.name === isHeadingRow ? 'thead' : 'tbody' )[ 0 ];
+			.filter( child => child.name === ( isHeadingRow ? 'thead' : 'tbody' ) )[ 0 ];
 
-		ensureCellSpans( table, rowIndex, conversionApi );
+		const cellSpans = ensureCellSpans( table, rowIndex );
 
-		downcastTableRow( tableRow, rowIndex, tableSection, conversionApi );
+		downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi );
 	}, { priority: 'normal' } );
 }
 
-function ensureCellSpans( table, currentRowIndex, conversionApi ) {
-	if ( !conversionApi.store ) {
-		conversionApi.store = {};
-	}
-
-	if ( !conversionApi.store.cellSpans ) {
-		const cellSpans = new CellSpans();
-
-		conversionApi.store.cellSpans = cellSpans;
+function ensureCellSpans( table, currentRowIndex ) {
+	const cellSpans = new CellSpans();
 
-		for ( let rowIndex = 0; rowIndex < currentRowIndex; rowIndex++ ) {
-			const row = table.getChild( rowIndex );
+	for ( let rowIndex = 0; rowIndex < currentRowIndex; rowIndex++ ) {
+		const row = table.getChild( rowIndex );
 
-			let columnIndex = 0;
+		let columnIndex = 0;
 
-			for ( const tableCell of Array.from( row.getChildren() ) ) {
-				columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
+		for ( const tableCell of Array.from( row.getChildren() ) ) {
+			columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
 
-				const colspan = tableCell.hasAttribute( 'colspan' ) ? parseInt( tableCell.getAttribute( 'colspan' ) ) : 1;
-				const rowspan = tableCell.hasAttribute( 'rowspan' ) ? parseInt( tableCell.getAttribute( 'rowspan' ) ) : 1;
+			const colspan = tableCell.hasAttribute( 'colspan' ) ? parseInt( tableCell.getAttribute( 'colspan' ) ) : 1;
+			const rowspan = tableCell.hasAttribute( 'rowspan' ) ? parseInt( tableCell.getAttribute( 'rowspan' ) ) : 1;
 
-				cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
+			cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
 
-				// Skip to next "free" column index.
-				columnIndex += colspan;
-			}
+			// Skip to next "free" column index.
+			columnIndex += colspan;
 		}
 	}
 
-	return conversionApi.store.cellSpans;
+	return cellSpans;
 }
 
 // Downcast converter for tableRow model element. Converts tableCells as well.
@@ -132,24 +124,25 @@ function ensureCellSpans( table, currentRowIndex, conversionApi ) {
 // @param {CellSpans} cellSpans
 // @param {module:engine/view/containerelement~ContainerElement} tableSection
 // @param {Object} conversionApi
-function downcastTableRow( tableRow, rowIndex, tableSection, conversionApi ) {
+function downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi ) {
 	// Will always consume since we're converting <tableRow> element from a parent <table>.
 	conversionApi.consumable.consume( tableRow, 'insert' );
 
+	const headingRows = tableRow.parent.getAttribute( 'headingRows' ) || 0;
+
 	const trElement = conversionApi.writer.createContainerElement( 'tr' );
 	conversionApi.mapper.bindElements( tableRow, trElement );
 
-	const position = ViewPosition.createAt( tableSection, 'end' );
-	conversionApi.writer.insert( position, trElement );
+	const offset = headingRows > 0 && rowIndex >= headingRows ? rowIndex - headingRows : rowIndex;
 
-	const cellSpans = conversionApi.store.cellSpans;
+	const position = ViewPosition.createAt( tableSection, offset );
+	conversionApi.writer.insert( position, trElement );
 
 	// Defines tableCell horizontal position in table.
 	// Might be different then position of tableCell in parent tableRow
 	// as tableCells from previous rows might overlaps current row's cells.
 	let columnIndex = 0;
 
-	const headingRows = tableRow.parent.getAttribute( 'headingRows' ) || 0;
 	const headingColumns = tableRow.parent.getAttribute( 'headingColumns' ) || 0;
 
 	for ( const tableCell of Array.from( tableRow.getChildren() ) ) {

+ 2 - 0
packages/ckeditor5-table/src/insertrowcommand.js

@@ -55,8 +55,10 @@ export default class InsertRowCommand extends Command {
 				writer.setAttribute( 'headingRows', headingRows + rows, table );
 			}
 
+			// TODO: test me - I'm wrong
 			for ( let rowIndex = 0; rowIndex < rows; rowIndex++ ) {
 				const row = writer.createElement( 'tableRow' );
+
 				writer.insert( row, table, insertAt );
 
 				for ( let column = 0; column < columns; column++ ) {

+ 19 - 6
packages/ckeditor5-table/tests/_utils/utils.js

@@ -9,12 +9,14 @@ function formatAttributes( attributes ) {
 	if ( attributes ) {
 		const entries = Object.entries( attributes );
 
-		attributesString = ' ' + entries.map( entry => `${ entry[ 0 ] }="${ entry[ 1 ] }"` ).join( ' ' );
+		if ( entries.length ) {
+			attributesString = ' ' + entries.map( entry => `${ entry[ 0 ] }="${ entry[ 1 ] }"` ).join( ' ' );
+		}
 	}
 	return attributesString;
 }
 
-function makeRows( tableData, cellElement, rowElement ) {
+function makeRows( tableData, cellElement, rowElement, headingElement = 'th' ) {
 	const tableRows = tableData
 		.reduce( ( previousRowsString, tableRow ) => {
 			const tableRowString = tableRow.reduce( ( tableRowString, tableCellData ) => {
@@ -22,13 +24,21 @@ function makeRows( tableData, cellElement, rowElement ) {
 
 				const isObject = typeof tableCellData === 'object';
 
+				let resultingCellElement = cellElement;
+
 				if ( isObject ) {
 					tableCell = tableCellData.contents;
+
+					if ( tableCellData.isHeading ) {
+						resultingCellElement = headingElement;
+					}
+
 					delete tableCellData.contents;
+					delete tableCellData.isHeading;
 				}
 
 				const formattedAttributes = formatAttributes( isObject ? tableCellData : '' );
-				tableRowString += `<${ cellElement }${ formattedAttributes }>${ tableCell }</${ cellElement }>`;
+				tableRowString += `<${ resultingCellElement }${ formattedAttributes }>${ tableCell }</${ resultingCellElement }>`;
 
 				return tableRowString;
 			}, '' );
@@ -58,10 +68,13 @@ export function modelTable( tableData, attributes ) {
  *
  * @returns {String}
  */
-export function viewTable( tableData, attributes ) {
-	const tableRows = makeRows( tableData, 'td', 'tr' );
+export function viewTable( tableData, attributes = {} ) {
+	const headingRows = attributes.headingRows || 0;
+
+	const thead = headingRows > 0 ? `<thead>${ makeRows( tableData.slice( 0, headingRows ), 'th', 'tr' ) }</thead>` : '';
+	const tbody = tableData.length > headingRows ? `<tbody>${ makeRows( tableData.slice( headingRows ), 'td', 'tr' ) }</tbody>` : '';
 
-	return `<table${ formatAttributes( attributes ) }><tbody>${ tableRows }</tbody></table>`;
+	return `<table>${ thead }${ tbody }</table>`;
 }
 
 export function formatModelTable( tableString ) {

+ 129 - 6
packages/ckeditor5-table/tests/converters/downcasttable.js

@@ -269,6 +269,84 @@ describe( 'downcastTable()', () => {
 			] ) );
 		} );
 
+		it( 'should insert row on proper index', () => {
+			setModelData( model, modelTable( [
+				[ '11', '12' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = writer.createElement( 'tableRow' );
+
+				writer.insert( row, table, 1 );
+
+				writer.insertElement( 'tableCell', row, 'end' );
+				writer.insertElement( 'tableCell', row, 'end' );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ '11', '12' ],
+				[ '', '' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			] ) );
+		} );
+
+		it( 'should insert row on proper index when table has heading rows defined - insert in body', () => {
+			setModelData( model, modelTable( [
+				[ '11', '12' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			], { headingRows: 1 } ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = writer.createElement( 'tableRow' );
+
+				writer.insert( row, table, 1 );
+
+				writer.insertElement( 'tableCell', row, 'end' );
+				writer.insertElement( 'tableCell', row, 'end' );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ '11', '12' ],
+				[ '', '' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			], { headingRows: 1 } ) );
+		} );
+
+		it( 'should insert row on proper index when table has heading rows defined - insert in heading', () => {
+			setModelData( model, modelTable( [
+				[ '11', '12' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			], { headingRows: 2 } ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = writer.createElement( 'tableRow' );
+
+				writer.insert( row, table, 1 );
+
+				writer.insertElement( 'tableCell', row, 'end' );
+				writer.insertElement( 'tableCell', row, 'end' );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ '11', '12' ],
+				[ '', '' ],
+				[ '21', '22' ],
+				[ '31', '32' ]
+			], { headingRows: 3 } ) );
+		} );
+
 		it( 'should react to changed rows when previous rows\' cells has rowspans', () => {
 			setModelData( model, modelTable( [
 				[ { rowspan: 3, contents: '11' }, '12' ],
@@ -281,12 +359,7 @@ describe( 'downcastTable()', () => {
 				const row = writer.createElement( 'tableRow' );
 
 				writer.insert( row, table, 2 );
-
-				for ( let i = 0; i < 1; i++ ) {
-					const cell = writer.createElement( 'tableCell' );
-
-					writer.insert( cell, row, 'end' );
-				}
+				writer.insertElement( 'tableCell', row, 'end' );
 			} );
 
 			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
@@ -295,5 +368,55 @@ describe( 'downcastTable()', () => {
 				[ '' ]
 			] ) );
 		} );
+
+		it( 'should properly create row headings', () => {
+			setModelData( model, modelTable( [
+				[ { rowspan: 3, contents: '11' }, '12' ],
+				[ '22' ]
+			], { headingColumns: 1 } ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const firstRow = writer.createElement( 'tableRow' );
+
+				writer.insert( firstRow, table, 2 );
+				writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
+
+				const secondRow = writer.createElement( 'tableRow' );
+
+				writer.insert( secondRow, table, 3 );
+				writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
+				writer.insert( writer.createElement( 'tableCell' ), secondRow, 'end' );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ { rowspan: 3, contents: '11', isHeading: true }, '12' ],
+				[ '22' ],
+				[ '' ],
+				[ { contents: '', isHeading: true }, '' ]
+			] ) );
+		} );
+
+		it( 'should properly create row headings when previous has colspan', () => {
+			setModelData( model, modelTable( [
+				[ { rowspan: 2, colspan: 2, contents: '11' }, '13', '14' ]
+			], { headingColumns: 3 } ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const firstRow = writer.createElement( 'tableRow' );
+
+				writer.insert( firstRow, table, 1 );
+				writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
+				writer.insert( writer.createElement( 'tableCell' ), firstRow, 'end' );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ { colspan: 2, rowspan: 2, contents: '11', isHeading: true }, { isHeading: true, contents: '13' }, '14' ],
+				[ { contents: '', isHeading: true }, '' ]
+			] ) );
+		} );
 	} );
 } );