Browse Source

Added: Add insert table column downcast converter.

Maciej Gołaszewski 7 years ago
parent
commit
abba96ac9d

+ 78 - 15
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -93,10 +93,66 @@ export function downcastInsertRow() {
 	}, { priority: 'normal' } );
 }
 
+function getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell ) {
+	for ( const tableCellA of Array.from( tableRow.getChildren() ) ) {
+		// Check whether current columnIndex is overlapped by table cells from previous rows.
+		columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
+
+		// Up to here only!
+		if ( tableCellA === tableCell ) {
+			return columnIndex;
+		}
+
+		const colspan = tableCellA.hasAttribute( 'colspan' ) ? parseInt( tableCellA.getAttribute( 'colspan' ) ) : 1;
+		const rowspan = tableCellA.hasAttribute( 'rowspan' ) ? parseInt( tableCellA.getAttribute( 'rowspan' ) ) : 1;
+
+		cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
+
+		// Skip to next "free" column index.
+		columnIndex += colspan;
+	}
+}
+
+export function downcastInsertCell() {
+	return dispatcher => dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => {
+		const tableCell = data.item;
+
+		if ( !conversionApi.consumable.consume( tableCell, 'insert' ) ) {
+			return;
+		}
+
+		const tableRow = tableCell.parent;
+		const table = tableRow.parent;
+
+		const trElement = conversionApi.mapper.toViewElement( tableRow );
+
+		const headingRows = parseInt( table.getAttribute( 'headingRows' ) ) || 0;
+		const headingColumns = parseInt( table.getAttribute( 'headingColumns' ) ) || 0;
+
+		const rowIndex = table.getChildIndex( tableRow );
+
+		const cellIndex = tableRow.getChildIndex( tableCell );
+
+		let columnIndex = 0;
+
+		const cellSpans = ensureCellSpans( table, rowIndex, columnIndex );
+
+		// check last row up to
+		columnIndex = getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell );
+
+		// Check whether current columnIndex is overlapped by table cells from previous rows.
+		columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
+
+		const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
+
+		downcastTableCell( tableCell, rowIndex, columnIndex, cellSpans, cellElementName, trElement, conversionApi, cellIndex );
+	}, { priority: 'normal' } );
+}
+
 function ensureCellSpans( table, currentRowIndex ) {
 	const cellSpans = new CellSpans();
 
-	for ( let rowIndex = 0; rowIndex < currentRowIndex; rowIndex++ ) {
+	for ( let rowIndex = 0; rowIndex <= currentRowIndex; rowIndex++ ) {
 		const row = table.getChild( rowIndex );
 
 		let columnIndex = 0;
@@ -123,6 +179,26 @@ function ensureCellSpans( table, currentRowIndex ) {
 // @param {Number} rowIndex
 // @param {CellSpans} cellSpans
 // @param {module:engine/view/containerelement~ContainerElement} tableSection
+function downcastTableCell( tableCell, rowIndex, columnIndex, cellSpans, cellElementName, trElement, conversionApi, offset = 'end' ) {
+	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 );
+
+	// Will always consume since we're converting <tableRow> element from a parent <table>.
+	conversionApi.consumable.consume( tableCell, 'insert' );
+
+	const cellElement = conversionApi.writer.createContainerElement( cellElementName );
+
+	conversionApi.mapper.bindElements( tableCell, cellElement );
+	conversionApi.writer.insert( ViewPosition.createAt( trElement, offset ), cellElement );
+
+	// Skip to next "free" column index.
+	columnIndex += colspan;
+
+	return columnIndex;
+}
+
 // @param {Object} conversionApi
 function downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi ) {
 	// Will always consume since we're converting <tableRow> element from a parent <table>.
@@ -149,22 +225,9 @@ function downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversi
 		// Check whether current columnIndex is overlapped by table cells from previous rows.
 		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;
-
-		cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
-
-		// Will always consume since we're converting <tableRow> element from a parent <table>.
-		conversionApi.consumable.consume( tableCell, 'insert' );
-
 		const cellElementName = getCellElementName( rowIndex, columnIndex, headingRows, headingColumns );
-		const cellElement = conversionApi.writer.createContainerElement( cellElementName );
 
-		conversionApi.mapper.bindElements( tableCell, cellElement );
-		conversionApi.writer.insert( ViewPosition.createAt( trElement, 'end' ), cellElement );
-
-		// Skip to next "free" column index.
-		columnIndex += colspan;
+		columnIndex = downcastTableCell( tableCell, rowIndex, columnIndex, cellSpans, cellElementName, trElement, conversionApi );
 	}
 }
 

+ 2 - 1
packages/ckeditor5-table/src/tableediting.js

@@ -10,7 +10,7 @@
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
 import upcastTable from './converters/upcasttable';
-import downcastTable, { downcastInsertRow } from './converters/downcasttable';
+import downcastTable, { downcastInsertCell, downcastInsertRow } from './converters/downcasttable';
 import InsertTableCommand from './inserttablecommand';
 import InsertRowCommand from './insertrowcommand';
 import InsertColumnCommand from './insertcolumncommand';
@@ -57,6 +57,7 @@ export default class TablesEditing extends Plugin {
 
 		// Insert conversion
 		conversion.for( 'downcast' ).add( downcastInsertRow() );
+		conversion.for( 'downcast' ).add( downcastInsertCell() );
 
 		// Table cell conversion.
 		conversion.for( 'upcast' ).add( upcastElementToElement( { model: 'tableCell', view: 'td' } ) );

+ 10 - 0
packages/ckeditor5-table/tests/_utils/utils.js

@@ -80,7 +80,13 @@ export function viewTable( tableData, attributes = {} ) {
 export function formatModelTable( tableString ) {
 	return tableString
 		.replace( /<tableRow>/g, '\n<tableRow>\n    ' )
+		.replace( /<thead>/g, '\n<thead>\n    ' )
+		.replace( /<tbody>/g, '\n<tbody>\n    ' )
+		.replace( /<tr>/g, '\n<tr>\n    ' )
 		.replace( /<\/tableRow>/g, '\n</tableRow>' )
+		.replace( /<\/thead>/g, '\n</thead>' )
+		.replace( /<\/tbody>/g, '\n</tbody>' )
+		.replace( /<\/tr>/g, '\n</tr>' )
 		.replace( /<\/table>/g, '\n</table>' );
 }
 
@@ -89,3 +95,7 @@ export function formattedModelTable( tableData, attributes ) {
 
 	return formatModelTable( tableString );
 }
+
+export function formattedViewTable( tableData, attributes ) {
+	return formatModelTable( viewTable( tableData, attributes ) );
+}

+ 104 - 8
packages/ckeditor5-table/tests/converters/downcasttable.js

@@ -7,8 +7,8 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
 import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import { setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
-import downcastTable, { downcastInsertRow } from '../../src/converters/downcasttable';
-import { modelTable, viewTable } from '../_utils/utils';
+import downcastTable, { downcastInsertCell, downcastInsertRow } from '../../src/converters/downcasttable';
+import { formatModelTable, formattedViewTable, modelTable, viewTable } from '../_utils/utils';
 
 describe( 'downcastTable()', () => {
 	let editor, model, doc, root, viewDocument;
@@ -53,6 +53,7 @@ describe( 'downcastTable()', () => {
 
 				// Insert conversion
 				conversion.for( 'downcast' ).add( downcastInsertRow() );
+				conversion.for( 'downcast' ).add( downcastInsertCell() );
 			} );
 	} );
 
@@ -243,7 +244,7 @@ describe( 'downcastTable()', () => {
 		} );
 	} );
 
-	describe( 'model change', () => {
+	describe( 'downcastInsertRow()', () => {
 		it( 'should react to changed rows', () => {
 			setModelData( model, modelTable( [
 				[ '11', '12' ]
@@ -256,11 +257,8 @@ describe( 'downcastTable()', () => {
 
 				writer.insert( row, table, 1 );
 
-				for ( let i = 0; i < 2; i++ ) {
-					const cell = writer.createElement( 'tableCell' );
-
-					writer.insert( cell, row, 'end' );
-				}
+				writer.insertElement( 'tableCell', row, 'end' );
+				writer.insertElement( 'tableCell', row, 'end' );
 			} );
 
 			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
@@ -419,4 +417,102 @@ describe( 'downcastTable()', () => {
 			] ) );
 		} );
 	} );
+
+	describe( 'downcastInsertCell()', () => {
+		it( 'should add tableCell on proper index in tr', () => {
+			setModelData( model, modelTable( [
+				[ '11', '12' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = table.getChild( 0 );
+
+				writer.insertElement( 'tableCell', row, 1 );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ '11', '', '12' ]
+			] ) );
+		} );
+
+		it( 'should add tableCell on proper index in tr when previous have colspans', () => {
+			setModelData( model, modelTable( [
+				[ { colspan: 2, contents: '11' }, '13' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				const row = table.getChild( 0 );
+
+				writer.insertElement( 'tableCell', row, 1 );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ { colspan: 2, contents: '11' }, '', '13' ]
+			] ) );
+		} );
+
+		it( 'should add tableCell on proper index in tr when previous row have rowspans', () => {
+			setModelData( model, modelTable( [
+				[ { rowspan: 2, contents: '11' }, '13' ],
+				[ '22', '23' ]
+			] ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				writer.insertElement( 'tableCell', table.getChild( 0 ), 1 );
+				writer.insertElement( 'tableCell', table.getChild( 1 ), 0 );
+			} );
+
+			expect( getViewData( viewDocument, { withoutSelection: true } ) ).to.equal( viewTable( [
+				[ { rowspan: 2, contents: '11' }, '', '13' ],
+				[ '', '22', '23' ]
+			] ) );
+		} );
+
+		it( 'should work with heading columns', () => {
+			setModelData( model, modelTable( [
+				[ { rowspan: 2, contents: '11' }, '12', '13', '14' ],
+				[ '22', '23', '24' ],
+				[ { colspan: 2, contents: '31' }, '33', '34' ]
+			], { headingColumns: 2 } ) );
+
+			const table = root.getChild( 0 );
+
+			model.change( writer => {
+				// Inserting column in heading columns so update table's attribute also
+				writer.setAttribute( 'headingColumns', 3, table );
+
+				writer.insertElement( 'tableCell', table.getChild( 0 ), 2 );
+				writer.insertElement( 'tableCell', table.getChild( 1 ), 1 );
+				writer.insertElement( 'tableCell', table.getChild( 2 ), 1 );
+			} );
+
+			expect( formatModelTable( getViewData( viewDocument, { withoutSelection: true } ) ) ).to.equal( formattedViewTable( [
+				[
+					{ isHeading: true, rowspan: 2, contents: '11' },
+					{ isHeading: true, contents: '12' },
+					{ isHeading: true, contents: '' },
+					'13',
+					'14'
+				],
+				[
+					{ isHeading: true, contents: '22' },
+					{ isHeading: true, contents: '' },
+					'23',
+					'24'
+				],
+				[
+					{ isHeading: true, colspan: 2, contents: '31' },
+					{ isHeading: true, contents: '' },
+					'33',
+					'34'
+				]
+			] ) );
+		} );
+	} );
 } );