Merge pull request #2189 from dolanmiu/feature/check-boxes
Feature/check boxes
This commit is contained in:
44
demo/90-check-boxes.ts
Normal file
44
demo/90-check-boxes.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Simple example to add check boxes to a document
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Document, Packer, Paragraph, TextRun, CheckBox } from "docx";
|
||||||
|
|
||||||
|
const doc = new Document({
|
||||||
|
sections: [
|
||||||
|
{
|
||||||
|
properties: {},
|
||||||
|
children: [
|
||||||
|
new Paragraph({
|
||||||
|
children: [
|
||||||
|
new TextRun("Hello World"),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox(),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true, checkedState: { value: "2611" } }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({ checked: true, checkedState: { value: "2611", font: "MS Gothic" } }),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({
|
||||||
|
checked: true,
|
||||||
|
checkedState: { value: "2611", font: "MS Gothic" },
|
||||||
|
uncheckedState: { value: "2610", font: "MS Gothic" },
|
||||||
|
}),
|
||||||
|
new TextRun({ break: 1 }),
|
||||||
|
new CheckBox({
|
||||||
|
checked: true,
|
||||||
|
checkedState: { value: "2611", font: "MS Gothic" },
|
||||||
|
uncheckedState: { value: "2610", font: "MS Gothic" },
|
||||||
|
}),
|
||||||
|
new TextRun({ text: "Are you ok?", break: 1 }),
|
||||||
|
new CheckBox({ checked: true, alias: "Are you ok?" }),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
Packer.toBuffer(doc).then((buffer) => {
|
||||||
|
fs.writeFileSync("My Document.docx", buffer);
|
||||||
|
});
|
29
src/file/checkbox/checkbox-symbol.ts
Normal file
29
src/file/checkbox/checkbox-symbol.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// This represents element type CT_SdtCheckboxSymbol element
|
||||||
|
// <xsd:complexType name="CT_SdtCheckboxSymbol">
|
||||||
|
// <xsd:attribute name="font" type="w:ST_String"/>
|
||||||
|
// <xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
|
||||||
|
// </xsd:complexType>
|
||||||
|
|
||||||
|
import { XmlAttributeComponent, XmlComponent } from "@file/xml-components";
|
||||||
|
import { shortHexNumber } from "@util/values";
|
||||||
|
|
||||||
|
class CheckboxSymbolAttributes extends XmlAttributeComponent<{
|
||||||
|
readonly val?: string | number | boolean;
|
||||||
|
readonly symbolfont?: string;
|
||||||
|
}> {
|
||||||
|
protected readonly xmlKeys = {
|
||||||
|
val: "w14:val",
|
||||||
|
symbolfont: "w14:font",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckBoxSymbolElement extends XmlComponent {
|
||||||
|
public constructor(name: string, val: string, font?: string) {
|
||||||
|
super(name);
|
||||||
|
if (font) {
|
||||||
|
this.root.push(new CheckboxSymbolAttributes({ val: shortHexNumber(val), symbolfont: font }));
|
||||||
|
} else {
|
||||||
|
this.root.push(new CheckboxSymbolAttributes({ val }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
src/file/checkbox/checkbox-util.spec.ts
Normal file
85
src/file/checkbox/checkbox-util.spec.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Formatter } from "@export/formatter";
|
||||||
|
import { CheckBoxUtil } from ".";
|
||||||
|
|
||||||
|
describe("CheckBoxUtil", () => {
|
||||||
|
describe("#constructor()", () => {
|
||||||
|
it("should create a CheckBoxUtil with proper root and default values", () => {
|
||||||
|
const checkBoxUtil = new CheckBoxUtil();
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBoxUtil);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2612",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2610",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a CheckBoxUtil with proper structure and custom values", () => {
|
||||||
|
const checkBoxUtil = new CheckBoxUtil({
|
||||||
|
checked: true,
|
||||||
|
checkedState: {
|
||||||
|
value: "2713",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBoxUtil);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2713",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
45
src/file/checkbox/checkbox-util.ts
Normal file
45
src/file/checkbox/checkbox-util.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// <xsd:complexType name="CT_SdtCheckbox">
|
||||||
|
// <xsd:sequence>
|
||||||
|
// <xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
|
||||||
|
// <xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||||
|
// <xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||||
|
// </xsd:sequence>
|
||||||
|
// </xsd:complexType>
|
||||||
|
// <xsd:element name="checkbox" type="CT_SdtCheckbox"/>
|
||||||
|
|
||||||
|
import { XmlComponent } from "@file/xml-components";
|
||||||
|
import { CheckBoxSymbolElement } from "@file/checkbox/checkbox-symbol";
|
||||||
|
|
||||||
|
export interface ICheckboxSymbolProperties {
|
||||||
|
readonly value?: string;
|
||||||
|
readonly font?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICheckboxSymbolOptions {
|
||||||
|
readonly alias?: string;
|
||||||
|
readonly checked?: boolean;
|
||||||
|
readonly checkedState?: ICheckboxSymbolProperties;
|
||||||
|
readonly uncheckedState?: ICheckboxSymbolProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CheckBoxUtil extends XmlComponent {
|
||||||
|
private readonly DEFAULT_UNCHECKED_SYMBOL: string = "2610";
|
||||||
|
private readonly DEFAULT_CHECKED_SYMBOL: string = "2612";
|
||||||
|
private readonly DEFAULT_FONT: string = "MS Gothic";
|
||||||
|
public constructor(options?: ICheckboxSymbolOptions) {
|
||||||
|
super("w14:checkbox");
|
||||||
|
|
||||||
|
const value = options?.checked ? "1" : "0";
|
||||||
|
let symbol: string;
|
||||||
|
let font: string;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:checked", value));
|
||||||
|
|
||||||
|
symbol = options?.checkedState?.value ? options?.checkedState?.value : this.DEFAULT_CHECKED_SYMBOL;
|
||||||
|
font = options?.checkedState?.font ? options?.checkedState?.font : this.DEFAULT_FONT;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:checkedState", symbol, font));
|
||||||
|
|
||||||
|
symbol = options?.uncheckedState?.value ? options?.uncheckedState?.value : this.DEFAULT_UNCHECKED_SYMBOL;
|
||||||
|
font = options?.uncheckedState?.font ? options?.uncheckedState?.font : this.DEFAULT_FONT;
|
||||||
|
this.root.push(new CheckBoxSymbolElement("w14:uncheckedState", symbol, font));
|
||||||
|
}
|
||||||
|
}
|
213
src/file/checkbox/checkbox.spec.ts
Normal file
213
src/file/checkbox/checkbox.spec.ts
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { Formatter } from "@export/formatter";
|
||||||
|
|
||||||
|
import { CheckBox } from "./checkbox";
|
||||||
|
|
||||||
|
describe("CheckBox", () => {
|
||||||
|
describe("#constructor()", () => {
|
||||||
|
it("should create a CheckBox with proper root and default values (no alias, no custom state)", () => {
|
||||||
|
const checkBox = new CheckBox();
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2612",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "MS Gothic",
|
||||||
|
"w14:val": "2610",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": "2610",
|
||||||
|
"w:font": "MS Gothic",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["2713", "Segoe UI Symbol", "2713", "Segoe UI Symbol"],
|
||||||
|
[undefined, undefined, "2612", "MS Gothic"],
|
||||||
|
])("should create a CheckBox with proper root and custom values", (inputChar, inputFont, actualChar, actualFont) => {
|
||||||
|
const checkBox = new CheckBox({
|
||||||
|
alias: "Custom Checkbox",
|
||||||
|
checked: true,
|
||||||
|
checkedState: {
|
||||||
|
value: inputChar,
|
||||||
|
font: inputFont,
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w:alias": {
|
||||||
|
_attr: {
|
||||||
|
"w:val": "Custom Checkbox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": actualFont,
|
||||||
|
"w14:val": actualChar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": actualChar,
|
||||||
|
"w:font": actualFont,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create a CheckBox with proper root, custom state, and no alias", () => {
|
||||||
|
const checkBox = new CheckBox({
|
||||||
|
checked: false,
|
||||||
|
checkedState: {
|
||||||
|
value: "2713",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
uncheckedState: {
|
||||||
|
value: "2705",
|
||||||
|
font: "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree = new Formatter().format(checkBox);
|
||||||
|
|
||||||
|
expect(tree).to.deep.equal({
|
||||||
|
"w:sdt": [
|
||||||
|
{
|
||||||
|
"w:sdtPr": [
|
||||||
|
{
|
||||||
|
"w14:checkbox": [
|
||||||
|
{
|
||||||
|
"w14:checked": {
|
||||||
|
_attr: {
|
||||||
|
"w14:val": "0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:checkedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2713",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w14:uncheckedState": {
|
||||||
|
_attr: {
|
||||||
|
"w14:font": "Segoe UI Symbol",
|
||||||
|
"w14:val": "2705",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"w:sdtContent": [
|
||||||
|
{
|
||||||
|
"w:r": [
|
||||||
|
{
|
||||||
|
"w:sym": {
|
||||||
|
_attr: {
|
||||||
|
"w:char": "2705",
|
||||||
|
"w:font": "Segoe UI Symbol",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
43
src/file/checkbox/checkbox.ts
Normal file
43
src/file/checkbox/checkbox.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { SymbolRun } from "@file/paragraph/run/symbol-run";
|
||||||
|
import { StructuredDocumentTagProperties } from "@file/table-of-contents/sdt-properties";
|
||||||
|
import { StructuredDocumentTagContent } from "@file/table-of-contents/sdt-content";
|
||||||
|
import { XmlComponent } from "@file/xml-components";
|
||||||
|
import { CheckBoxUtil, ICheckboxSymbolOptions } from "./checkbox-util";
|
||||||
|
|
||||||
|
export class CheckBox extends XmlComponent {
|
||||||
|
// default values per Microsoft
|
||||||
|
private readonly DEFAULT_UNCHECKED_SYMBOL: string = "2610";
|
||||||
|
private readonly DEFAULT_CHECKED_SYMBOL: string = "2612";
|
||||||
|
private readonly DEFAULT_FONT: string = "MS Gothic";
|
||||||
|
public constructor(options?: ICheckboxSymbolOptions) {
|
||||||
|
super("w:sdt");
|
||||||
|
|
||||||
|
const properties = new StructuredDocumentTagProperties(options?.alias);
|
||||||
|
properties.addChildElement(new CheckBoxUtil(options));
|
||||||
|
this.root.push(properties);
|
||||||
|
|
||||||
|
const content = new StructuredDocumentTagContent();
|
||||||
|
const checkedFont: string | undefined = options?.checkedState?.font;
|
||||||
|
const checkedText: string | undefined = options?.checkedState?.value;
|
||||||
|
const uncheckedFont: string | undefined = options?.uncheckedState?.font;
|
||||||
|
const uncheckedText: string | undefined = options?.uncheckedState?.value;
|
||||||
|
let symbolFont: string;
|
||||||
|
let char: string;
|
||||||
|
|
||||||
|
if (options?.checked) {
|
||||||
|
symbolFont = checkedFont ? checkedFont : this.DEFAULT_FONT;
|
||||||
|
char = checkedText ? checkedText : this.DEFAULT_CHECKED_SYMBOL;
|
||||||
|
} else {
|
||||||
|
symbolFont = uncheckedFont ? uncheckedFont : this.DEFAULT_FONT;
|
||||||
|
char = uncheckedText ? uncheckedText : this.DEFAULT_UNCHECKED_SYMBOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialRenderedChar = new SymbolRun({
|
||||||
|
char: char,
|
||||||
|
symbolfont: symbolFont,
|
||||||
|
});
|
||||||
|
|
||||||
|
content.addChildElement(initialRenderedChar);
|
||||||
|
this.root.push(content);
|
||||||
|
}
|
||||||
|
}
|
3
src/file/checkbox/index.ts
Normal file
3
src/file/checkbox/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./checkbox-util";
|
||||||
|
export * from "./checkbox-symbol";
|
||||||
|
export * from "./checkbox";
|
@ -17,3 +17,4 @@ export * from "./track-revision";
|
|||||||
export * from "./shared";
|
export * from "./shared";
|
||||||
export * from "./border";
|
export * from "./border";
|
||||||
export * from "./vertical-align";
|
export * from "./vertical-align";
|
||||||
|
export * from "./checkbox";
|
||||||
|
@ -6,6 +6,7 @@ import { FileChild } from "@file/file-child";
|
|||||||
|
|
||||||
import { TargetModeType } from "../relationships/relationship/relationship";
|
import { TargetModeType } from "../relationships/relationship/relationship";
|
||||||
import { DeletedTextRun, InsertedTextRun } from "../track-revision";
|
import { DeletedTextRun, InsertedTextRun } from "../track-revision";
|
||||||
|
import { CheckBox } from "../checkbox";
|
||||||
import { ColumnBreak, PageBreak } from "./formatting/break";
|
import { ColumnBreak, PageBreak } from "./formatting/break";
|
||||||
import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links";
|
import { Bookmark, ConcreteHyperlink, ExternalHyperlink, InternalHyperlink } from "./links";
|
||||||
import { Math } from "./math";
|
import { Math } from "./math";
|
||||||
@ -33,7 +34,8 @@ export type ParagraphChild =
|
|||||||
| Comment
|
| Comment
|
||||||
| CommentRangeStart
|
| CommentRangeStart
|
||||||
| CommentRangeEnd
|
| CommentRangeEnd
|
||||||
| CommentReference;
|
| CommentReference
|
||||||
|
| CheckBox;
|
||||||
|
|
||||||
export interface IParagraphOptions extends IParagraphPropertiesOptions {
|
export interface IParagraphOptions extends IParagraphPropertiesOptions {
|
||||||
readonly text?: string;
|
readonly text?: string;
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import { StringValueElement, XmlComponent } from "@file/xml-components";
|
import { StringValueElement, XmlComponent } from "@file/xml-components";
|
||||||
|
|
||||||
export class StructuredDocumentTagProperties extends XmlComponent {
|
export class StructuredDocumentTagProperties extends XmlComponent {
|
||||||
public constructor(alias: string) {
|
public constructor(alias?: string) {
|
||||||
super("w:sdtPr");
|
super("w:sdtPr");
|
||||||
this.root.push(new StringValueElement("w:alias", alias));
|
|
||||||
|
if (alias) {
|
||||||
|
this.root.push(new StringValueElement("w:alias", alias));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user