Add declarative column merge
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
// Example of how you would merge cells together
|
// Example of how you would merge cells together - Rows and Columns
|
||||||
// Import from 'docx' rather than '../build' if you install from npm
|
// Import from 'docx' rather than '../build' if you install from npm
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
import { Document, HeadingLevel, Packer, Paragraph, ShadingType, Table, TableCell, TableRow, WidthType } from "../build";
|
||||||
|
@ -117,8 +117,143 @@ const table = new Table({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const table2 = new Table({
|
||||||
|
rows: [
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,1")],
|
||||||
|
rowSpan: 2,
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,2")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,3")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,4")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("0,5")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,2")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,3")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,4")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("1,5")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,1")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,2")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,3")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,4")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("2,5")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,1")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,2")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,3")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,4")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("3,5")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,0")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,1")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,2")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,3")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,4")],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [new Paragraph("4,5")],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
new TableRow({
|
||||||
|
children: [
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
doc.addSection({
|
doc.addSection({
|
||||||
children: [table],
|
children: [table, new Paragraph(""), table2],
|
||||||
});
|
});
|
||||||
|
|
||||||
Packer.toBuffer(doc).then((buffer) => {
|
Packer.toBuffer(doc).then((buffer) => {
|
||||||
|
@ -36,6 +36,7 @@ const table = new Table({
|
|||||||
}),
|
}),
|
||||||
new TableCell({
|
new TableCell({
|
||||||
children: [],
|
children: [],
|
||||||
|
rowSpan: 2,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -74,8 +75,6 @@ const table = new Table({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
table.getColumn(3).mergeCells(1, 2);
|
|
||||||
|
|
||||||
doc.addSection({
|
doc.addSection({
|
||||||
children: [table],
|
children: [table],
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import { IXmlableObject, XmlComponent } from "file/xml-components";
|
|||||||
|
|
||||||
import { ITableShadingAttributesProperties } from "../shading";
|
import { ITableShadingAttributesProperties } from "../shading";
|
||||||
import { Table } from "../table";
|
import { Table } from "../table";
|
||||||
|
import { TableRow } from "../table-row";
|
||||||
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins";
|
import { ITableCellMarginOptions } from "./cell-margin/table-cell-margins";
|
||||||
import { VerticalAlign, VMergeType } from "./table-cell-components";
|
import { VerticalAlign, VMergeType } from "./table-cell-components";
|
||||||
import { TableCellProperties } from "./table-cell-properties";
|
import { TableCellProperties } from "./table-cell-properties";
|
||||||
@ -15,6 +16,7 @@ export interface ITableCellOptions {
|
|||||||
readonly verticalAlign?: VerticalAlign;
|
readonly verticalAlign?: VerticalAlign;
|
||||||
readonly verticalMerge?: VMergeType;
|
readonly verticalMerge?: VMergeType;
|
||||||
readonly columnSpan?: number;
|
readonly columnSpan?: number;
|
||||||
|
readonly rowSpan?: number;
|
||||||
readonly borders?: {
|
readonly borders?: {
|
||||||
readonly top?: {
|
readonly top?: {
|
||||||
readonly style: BorderStyle;
|
readonly style: BorderStyle;
|
||||||
@ -40,8 +42,15 @@ export interface ITableCellOptions {
|
|||||||
readonly children: Array<Paragraph | Table>;
|
readonly children: Array<Paragraph | Table>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ITableCellMetaData {
|
||||||
|
readonly column: TableCell[];
|
||||||
|
readonly row: TableRow;
|
||||||
|
}
|
||||||
|
|
||||||
export class TableCell extends XmlComponent {
|
export class TableCell extends XmlComponent {
|
||||||
private readonly properties: TableCellProperties;
|
private readonly properties: TableCellProperties;
|
||||||
|
// tslint:disable-next-line: readonly-keyword
|
||||||
|
private metaData: ITableCellMetaData;
|
||||||
|
|
||||||
constructor(readonly options: ITableCellOptions) {
|
constructor(readonly options: ITableCellOptions) {
|
||||||
super("w:tc");
|
super("w:tc");
|
||||||
@ -98,10 +107,32 @@ export class TableCell extends XmlComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public prepForXml(): IXmlableObject | undefined {
|
public prepForXml(): IXmlableObject | undefined {
|
||||||
|
// Row Span has to be added in this method and not the constructor because it needs to know information about the column which happens after Table Cell construction
|
||||||
|
// Row Span of 1 will crash word as it will add RESTART and not a corresponding CONTINUE
|
||||||
|
if (this.options.rowSpan && this.options.rowSpan > 1) {
|
||||||
|
this.properties.addVerticalMerge(VMergeType.RESTART);
|
||||||
|
|
||||||
|
const currentIndex = this.metaData.column.indexOf(this);
|
||||||
|
for (let i = currentIndex + 1; i <= currentIndex + this.options.rowSpan - 1; i++) {
|
||||||
|
this.metaData.column[i].metaData.row.Children.splice(
|
||||||
|
i,
|
||||||
|
0,
|
||||||
|
new TableCell({
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.metaData.column[i].properties.addVerticalMerge(VMergeType.CONTINUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Cells must end with a paragraph
|
// Cells must end with a paragraph
|
||||||
if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
|
if (!(this.root[this.root.length - 1] instanceof Paragraph)) {
|
||||||
this.root.push(new Paragraph({}));
|
this.root.push(new Paragraph({}));
|
||||||
}
|
}
|
||||||
return super.prepForXml();
|
return super.prepForXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public set MetaData(metaData: ITableCellMetaData) {
|
||||||
|
this.metaData = metaData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import { expect } from "chai";
|
|
||||||
|
|
||||||
// import { Formatter } from "export/formatter";
|
|
||||||
// import { EMPTY_OBJECT } from "file/xml-components";
|
|
||||||
|
|
||||||
import { TableCell } from "./table-cell";
|
|
||||||
import { TableColumn } from "./table-column";
|
|
||||||
|
|
||||||
describe("TableColumn", () => {
|
|
||||||
let cells: TableCell[];
|
|
||||||
beforeEach(() => {
|
|
||||||
cells = [
|
|
||||||
new TableCell({
|
|
||||||
children: [],
|
|
||||||
}),
|
|
||||||
new TableCell({
|
|
||||||
children: [],
|
|
||||||
}),
|
|
||||||
new TableCell({
|
|
||||||
children: [],
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("#getCell", () => {
|
|
||||||
it("should get the correct cell", () => {
|
|
||||||
const tableColumn = new TableColumn(cells);
|
|
||||||
const cell = tableColumn.getCell(0);
|
|
||||||
|
|
||||||
expect(cell).to.deep.equal(cells[0]);
|
|
||||||
|
|
||||||
const cell2 = tableColumn.getCell(1);
|
|
||||||
|
|
||||||
expect(cell2).to.deep.equal(cells[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should throw an error if index is out of bounds", () => {
|
|
||||||
const tableColumn = new TableColumn(cells);
|
|
||||||
|
|
||||||
expect(() => tableColumn.getCell(9)).to.throw();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// describe("#mergeCells", () => {
|
|
||||||
// it("should add vMerge to correct cells", () => {
|
|
||||||
// const tableColumn = new TableColumn(cells);
|
|
||||||
// tableColumn.mergeCells(0, 2);
|
|
||||||
|
|
||||||
// const tree = new Formatter().format(cells[0]);
|
|
||||||
// expect(tree).to.deep.equal({
|
|
||||||
// "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "restart" } } }] }, { "w:p": EMPTY_OBJECT }],
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const tree2 = new Formatter().format(cells[1]);
|
|
||||||
// expect(tree2).to.deep.equal({
|
|
||||||
// "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": EMPTY_OBJECT }],
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const tree3 = new Formatter().format(cells[2]);
|
|
||||||
// expect(tree3).to.deep.equal({
|
|
||||||
// "w:tc": [{ "w:tcPr": [{ "w:vMerge": { _attr: { "w:val": "continue" } } }] }, { "w:p": EMPTY_OBJECT }],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
import { TableCell } from "./table-cell";
|
|
||||||
|
|
||||||
export class TableColumn {
|
|
||||||
constructor(private readonly cells: TableCell[]) {}
|
|
||||||
|
|
||||||
public getCell(index: number): TableCell {
|
|
||||||
const cell = this.cells[index];
|
|
||||||
|
|
||||||
if (!cell) {
|
|
||||||
throw Error("Index out of bounds when trying to get cell on column");
|
|
||||||
}
|
|
||||||
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public mergeCells(startIndex: number, endIndex: number): TableCell {
|
|
||||||
// this.cells[startIndex].addVerticalMerge(VMergeType.RESTART);
|
|
||||||
|
|
||||||
// for (let i = startIndex + 1; i <= endIndex; i++) {
|
|
||||||
// this.cells[i].addVerticalMerge(VMergeType.CONTINUE);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return this.cells[startIndex];
|
|
||||||
// }
|
|
||||||
}
|
|
@ -42,18 +42,7 @@ export class TableRow extends XmlComponent {
|
|||||||
return this.options.children.length;
|
return this.options.children.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public mergeCells(startIndex: number, endIndex: number): TableCell {
|
public get Children(): TableCell[] {
|
||||||
// const cellSpan = endIndex - startIndex + 1;
|
return this.options.children;
|
||||||
|
}
|
||||||
// return this.addGridSpan(startIndex, cellSpan);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private addGridSpan(index: number, cellSpan: number): TableCell {
|
|
||||||
// const remainCell = this.options.children[index];
|
|
||||||
// remainCell.addGridSpan(cellSpan);
|
|
||||||
// this.options.children.splice(index + 1, cellSpan - 1);
|
|
||||||
// this.root.splice(index + 2, cellSpan - 1);
|
|
||||||
|
|
||||||
// return remainCell;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,13 @@ export class Table extends XmlComponent {
|
|||||||
this.root.push(new TableGrid(columnWidths));
|
this.root.push(new TableGrid(columnWidths));
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
|
row.Children.forEach((cell, i) => {
|
||||||
|
cell.MetaData = {
|
||||||
|
column: rows.map((r) => r.Children[i]),
|
||||||
|
row: row,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this.root.push(row);
|
this.root.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user