浏览代码

Other: Reorder methods in table/converters/downcasttable and add some docs.

Maciej Gołaszewski 7 年之前
父节点
当前提交
f938ad3f1d
共有 1 个文件被更改,包括 148 次插入106 次删除
  1. 148 106
      packages/ckeditor5-table/src/converters/downcasttable.js

+ 148 - 106
packages/ckeditor5-table/src/converters/downcasttable.js

@@ -36,7 +36,7 @@ export default function downcastTable() {
 		const headingRows = getNumericAttribute( table, 'headingRows', 0 );
 		const tableRows = Array.from( table.getChildren() );
 
-		const cellSpans = ensureCellSpans( table, 0 );
+		const cellSpans = createPreviousCellSpans( table, 0 );
 
 		for ( const tableRow of tableRows ) {
 			const rowIndex = tableRows.indexOf( tableRow );
@@ -57,6 +57,13 @@ export default function downcastTable() {
 	}, { priority: 'normal' } );
 }
 
+/**
+ * Model row element to view <tr> element conversion helper.
+ *
+ * This conversion helper creates whole <tr> element with child elements.
+ *
+ * @returns {Function} Conversion helper.
+ */
 export function downcastInsertRow() {
 	return dispatcher => dispatcher.on( 'insert:tableRow', ( evt, data, conversionApi ) => {
 		const tableRow = data.item;
@@ -77,32 +84,19 @@ export function downcastInsertRow() {
 		const tableSection = Array.from( tableElement.getChildren() )
 			.filter( child => child.name === ( isHeadingRow ? 'thead' : 'tbody' ) )[ 0 ];
 
-		const cellSpans = ensureCellSpans( table, rowIndex );
+		const cellSpans = createPreviousCellSpans( table, rowIndex );
 
 		downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversionApi );
 	}, { 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 = getNumericAttribute( tableCellA, 'colspan', 1 );
-		const rowspan = getNumericAttribute( tableCellA, 'rowspan', 1 );
-
-		cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
-
-		// Skip to next "free" column index.
-		columnIndex += colspan;
-	}
-}
-
+/**
+ * Model row element to view <tr> element conversion helper.
+ *
+ * This conversion helper creates whole <tr> element with child elements.
+ *
+ * @returns {Function} Conversion helper.
+ */
 export function downcastInsertCell() {
 	return dispatcher => dispatcher.on( 'insert:tableCell', ( evt, data, conversionApi ) => {
 		const tableCell = data.item;
@@ -125,7 +119,7 @@ export function downcastInsertCell() {
 
 		let columnIndex = 0;
 
-		const cellSpans = ensureCellSpans( table, rowIndex, columnIndex );
+		const cellSpans = createPreviousCellSpans( table, rowIndex, columnIndex );
 
 		// check last row up to
 		columnIndex = getColumnIndex( tableRow, columnIndex, cellSpans, rowIndex, tableCell );
@@ -139,6 +133,16 @@ export function downcastInsertCell() {
 	}, { priority: 'normal' } );
 }
 
+/**
+ * Conversion helper that acts on attribute change for headingColumns and headingRows attributes.
+ *
+ * Depending on changed attributes this converter will:
+ * - rename <td> to <th> elements or vice versa
+ * - create <thead> or <tbody> elements
+ * - remove empty <thead> or <tbody>
+ *
+ * @returns {Function} Conversion helper.
+ */
 export function downcastAttributeChange( attribute ) {
 	return dispatcher => dispatcher.on( `attribute:${ attribute }:table`, ( evt, data, conversionApi ) => {
 		const table = data.item;
@@ -153,7 +157,7 @@ export function downcastAttributeChange( attribute ) {
 
 		const tableRows = Array.from( table.getChildren() );
 
-		const cellSpans = ensureCellSpans( table, 0 );
+		const cellSpans = createPreviousCellSpans( table, 0 );
 
 		const cachedTableSections = {};
 
@@ -164,10 +168,8 @@ export function downcastAttributeChange( attribute ) {
 
 			const desiredParentName = rowIndex < headingRows ? 'thead' : 'tbody';
 
-			const actualParentName = tr.parent.name;
-
-			if ( desiredParentName !== actualParentName ) {
-				const moveToParent = getTableSection( desiredParentName, tableElement, conversionApi, cachedTableSections );
+			if ( desiredParentName !== tr.parent.name ) {
+				const tableSection = getTableSection( desiredParentName, tableElement, conversionApi, cachedTableSections );
 
 				let targetPosition;
 
@@ -175,14 +177,13 @@ export function downcastAttributeChange( attribute ) {
 					rowIndex === data.attributeNewValue &&
 					data.attributeNewValue < data.attributeOldValue
 				) {
-					targetPosition = ViewPosition.createAt( moveToParent, 'start' );
+					targetPosition = ViewPosition.createAt( tableSection, 'start' );
 				} else if ( rowIndex > 0 ) {
 					const previousTr = conversionApi.mapper.toViewElement( table.getChild( rowIndex - 1 ) );
 
 					targetPosition = ViewPosition.createAfter( previousTr );
 				} else {
-					// TODO: ???
-					targetPosition = ViewPosition.createAt( moveToParent, 'start' );
+					targetPosition = ViewPosition.createAt( tableSection, 'start' );
 				}
 
 				conversionApi.writer.move( ViewRange.createOn( tr ), targetPosition );
@@ -212,37 +213,13 @@ export function downcastAttributeChange( attribute ) {
 
 		// TODO: maybe a postfixer?
 		if ( headingRows === 0 ) {
-			removeIfExistsAndEmpty( tableElement, 'thead', conversionApi );
+			removeTableSectionIfEmpty( 'thead', tableElement, conversionApi );
 		} else if ( headingRows === table.childCount ) {
-			removeIfExistsAndEmpty( tableElement, 'tbody', conversionApi );
+			removeTableSectionIfEmpty( 'tbody', tableElement, conversionApi );
 		}
 	}, { priority: 'normal' } );
 }
 
-function ensureCellSpans( table, currentRowIndex ) {
-	const cellSpans = new CellSpans();
-
-	for ( let rowIndex = 0; rowIndex <= currentRowIndex; rowIndex++ ) {
-		const row = table.getChild( rowIndex );
-
-		let columnIndex = 0;
-
-		for ( const tableCell of Array.from( row.getChildren() ) ) {
-			columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
-
-			const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
-			const rowspan = getNumericAttribute( tableCell, 'rowspan', 1 );
-
-			cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
-
-			// Skip to next "free" column index.
-			columnIndex += colspan;
-		}
-	}
-
-	return cellSpans;
-}
-
 // Downcast converter for tableRow model element. Converts tableCells as well.
 //
 // @param {module:engine/model/element~Element} tableRow
@@ -301,20 +278,6 @@ function downcastTableRow( tableRow, rowIndex, tableSection, cellSpans, conversi
 	}
 }
 
-// Creates table section at the end of a table.
-//
-// @param {String} elementName
-// @param {module:engine/view/element~Element} tableElement
-// @param conversionApi
-// @return {module:engine/view/containerelement~ContainerElement}
-function createTableSection( elementName, tableElement, conversionApi ) {
-	const tableChildElement = conversionApi.writer.createContainerElement( elementName );
-
-	conversionApi.writer.insert( ViewPosition.createAt( tableElement, elementName == 'tbody' ? 'end' : 'start' ), tableChildElement );
-
-	return tableChildElement;
-}
-
 // Returns `th` for heading cells and `td` for other cells.
 // It is based on tableCell location (rowIndex x columnIndex) and the sizes of column & row headings sizes.
 //
@@ -338,6 +301,120 @@ function getCellElementName( rowIndex, columnIndex, headingRows, headingColumns
 	return isHeadingForARow ? 'th' : 'td';
 }
 
+// Creates or returns an existing <tbody> or <thead> element witch caching.
+//
+// @param {String} sectionName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+// @param {Object} cachedTableSection An object on which store cached elements.
+// @return {module:engine/view/containerelement~ContainerElement}
+function getTableSection( sectionName, tableElement, conversionApi, cachedTableSections ) {
+	if ( cachedTableSections[ sectionName ] ) {
+		return cachedTableSections[ sectionName ];
+	}
+
+	cachedTableSections[ sectionName ] = getOrCreateTableSection( sectionName, tableElement, conversionApi );
+
+	return cachedTableSections[ sectionName ];
+}
+
+// Creates or returns an existing <tbody> or <thead> element.
+//
+// @param {String} sectionName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+function getOrCreateTableSection( sectionName, tableElement, conversionApi ) {
+	return getExistingTableSectionElement( sectionName, tableElement ) || createTableSection( sectionName, tableElement, conversionApi );
+}
+
+// Finds an existing <tbody> or <thead> element or returns undefined.
+//
+// @param {String} sectionName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+function getExistingTableSectionElement( sectionName, tableElement ) {
+	for ( const tableSection of tableElement.getChildren() ) {
+		if ( tableSection.name == sectionName ) {
+			return tableSection;
+		}
+	}
+}
+
+// Creates table section at the end of a table.
+//
+// @param {String} sectionName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+// @return {module:engine/view/containerelement~ContainerElement}
+function createTableSection( sectionName, tableElement, conversionApi ) {
+	const tableChildElement = conversionApi.writer.createContainerElement( sectionName );
+
+	conversionApi.writer.insert( ViewPosition.createAt( tableElement, sectionName == 'tbody' ? 'end' : 'start' ), tableChildElement );
+
+	return tableChildElement;
+}
+
+// Removes an existing <tbody> or <thead> element if it is empty.
+//
+// @param {String} sectionName
+// @param {module:engine/view/element~Element} tableElement
+// @param conversionApi
+function removeTableSectionIfEmpty( sectionName, tableElement, conversionApi ) {
+	const tHead = getExistingTableSectionElement( sectionName, tableElement );
+
+	if ( tHead && tHead.childCount === 0 ) {
+		conversionApi.writer.remove( ViewRange.createOn( tHead ) );
+	}
+}
+
+function getNumericAttribute( element, attribute, defaultValue ) {
+	return element.hasAttribute( attribute ) ? parseInt( element.getAttribute( attribute ) ) : defaultValue;
+}
+
+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 = getNumericAttribute( tableCellA, 'colspan', 1 );
+		const rowspan = getNumericAttribute( tableCellA, 'rowspan', 1 );
+
+		cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
+
+		// Skip to next "free" column index.
+		columnIndex += colspan;
+	}
+}
+
+function createPreviousCellSpans( table, currentRowIndex ) {
+	const cellSpans = new CellSpans();
+
+	for ( let rowIndex = 0; rowIndex <= currentRowIndex; rowIndex++ ) {
+		const row = table.getChild( rowIndex );
+
+		let columnIndex = 0;
+
+		for ( const tableCell of Array.from( row.getChildren() ) ) {
+			columnIndex = cellSpans.getNextFreeColumnIndex( rowIndex, columnIndex );
+
+			const colspan = getNumericAttribute( tableCell, 'colspan', 1 );
+			const rowspan = getNumericAttribute( tableCell, 'rowspan', 1 );
+
+			cellSpans.recordSpans( rowIndex, columnIndex, rowspan, colspan );
+
+			// Skip to next "free" column index.
+			columnIndex += colspan;
+		}
+	}
+
+	return cellSpans;
+}
+
 /**
  * Holds information about spanned table cells.
  *
@@ -454,38 +531,3 @@ export class CellSpans {
 		return rowSpans.has( columnIndex ) ? rowSpans.get( columnIndex ) : false;
 	}
 }
-
-function getNumericAttribute( tableCell, attribute, defaultValue ) {
-	return tableCell.hasAttribute( attribute ) ? parseInt( tableCell.getAttribute( attribute ) ) : defaultValue;
-}
-
-function getOrCreate( tableElement, childName, conversionApi ) {
-	return getChildElement( tableElement, childName ) || createTableSection( childName, tableElement, conversionApi );
-}
-
-function getChildElement( tableElement, childName ) {
-	for ( const tableSection of tableElement.getChildren() ) {
-		if ( tableSection.name == childName ) {
-			return tableSection;
-		}
-	}
-}
-
-function removeIfExistsAndEmpty( tableElement, childName, conversionApi ) {
-	const tHead = getChildElement( tableElement, childName );
-
-	if ( tHead && tHead.childCount === 0 ) {
-		conversionApi.writer.remove( ViewRange.createOn( tHead ) );
-	}
-}
-
-// Creates if not existing and returns <tbody> or <thead> element for given rowIndex.
-function getTableSection( name, tableElement, conversionApi, cachedTableSections ) {
-	if ( cachedTableSections[ name ] ) {
-		return cachedTableSections[ name ];
-	}
-
-	cachedTableSections[ name ] = getOrCreate( tableElement, name, conversionApi );
-
-	return cachedTableSections[ name ];
-}