Skip to main content
Canvas Editor uses a structured JSON format to represent documents. Understanding this data structure is essential for saving, loading, and manipulating document content programmatically.

Document Data Overview

The editor’s data follows the IEditorData interface (from src/editor/interface/Editor.ts:37):
interface IEditorData {
  header?: IElement[]
  main: IElement[]
  footer?: IElement[]
  graffiti?: IGraffitiData[]
}
The main array is required and contains the primary document content. Header, footer, and graffiti data are optional.

Basic Document Structure

const data = {
  main: [
    { value: 'Hello World' }
  ]
}

Element Structure

Each element in the document follows the IElement interface (from src/editor/interface/Element.ts:195):
type IElement = IElementBasic &
  IElementStyle &
  IElementRule &
  IElementGroup &
  ITable &
  IHyperlinkElement &
  ISuperscriptSubscript &
  ISeparator &
  IControlElement &
  ICheckboxElement &
  IRadioElement &
  ILaTexElement &
  IDateElement &
  IImageElement &
  IBlockElement &
  ITitleElement &
  IListElement &
  IAreaElement &
  ILabelElement

Basic Element Properties

From src/editor/interface/Element.ts:19:
value
string
required
The text content of the element. Required for all elements.
id
string
Unique identifier for the element. Auto-generated if not provided.
type
ElementType
Element type (TEXT, IMAGE, TABLE, etc.). Defaults to TEXT.
extension
unknown
Custom data attached to the element.
externalId
string
External system identifier for integration.

Style Properties

From src/editor/interface/Element.ts:27:
const styledElement = {
  value: 'Styled text',
  
  // Typography
  font: 'Arial',
  size: 16,
  bold: true,
  italic: false,
  
  // Colors
  color: '#333333',
  highlight: '#FFFF00',
  
  // Text decorations
  underline: true,
  strikeout: false,
  
  // Layout
  rowFlex: RowFlex.LEFT,
  rowMargin: 10,
  letterSpacing: 1,
  
  // Advanced
  width: 200,
  height: 24
}

Element Types

Canvas Editor supports various element types, defined in src/editor/dataset/enum/Element.ts:

Text Elements

{
  value: 'Plain text content',
  type: ElementType.TEXT
}

Image Elements

From src/editor/interface/Element.ts:161:
{
  value: '',
  type: ElementType.IMAGE,
  
  // Image source (base64 or URL)
  value: 'data:image/png;base64,...',
  
  // Dimensions
  width: 300,
  height: 200,
  
  // Display mode
  imgDisplay: ImageDisplay.INLINE,
  
  // Floating position (for floating images)
  imgFloatPosition: {
    x: 100,
    y: 100,
    pageNo: 0
  },
  
  // Cropping
  imgCrop: {
    x: 10,
    y: 10,
    width: 280,
    height: 180
  },
  
  // Caption
  imgCaption: {
    value: 'Figure 1: Example image',
    color: '#666666',
    size: 12
  },
  
  // Restrictions
  imgToolDisabled: false,
  imgPreviewDisabled: false
}

Table Elements

Tables have a complex nested structure (from src/editor/interface/Element.ts:67):
{
  value: '\n',
  type: ElementType.TABLE,
  
  // Column definitions
  colgroup: [
    { width: 100 },
    { width: 200 },
    { width: 150 }
  ],
  
  // Row data
  trList: [
    {
      height: 50,
      tdList: [
        {
          colspan: 1,
          rowspan: 1,
          verticalAlign: VerticalAlign.TOP,
          value: [
            { value: 'Cell 1' }
          ]
        },
        {
          colspan: 1,
          rowspan: 1,
          value: [
            { value: 'Cell 2' }
          ]
        },
        {
          colspan: 1,
          rowspan: 1,
          value: [
            { value: 'Cell 3' }
          ]
        }
      ]
    }
  ],
  
  // Styling
  borderType: TableBorder.ALL,
  borderColor: '#000000',
  borderWidth: 1,
  borderExternalWidth: 2
}

List Elements

From src/editor/interface/Element.ts:59:
{
  value: '\n',
  type: ElementType.LIST,
  listType: ListType.UL,
  listStyle: ListStyle.DISC,
  listId: 'list-1',
  valueList: [
    { value: 'First item' },
    { value: 'Second item' },
    { value: 'Third item' }
  ]
}

Title/Heading Elements

From src/editor/interface/Element.ts:52:
{
  value: '\n',
  type: ElementType.TITLE,
  level: TitleLevel.FIRST,
  titleId: 'heading-1',
  valueList: [
    { 
      value: 'Chapter 1: Introduction',
      bold: true,
      size: 24
    }
  ]
}

Control Elements (Form Fields)

From src/editor/interface/Element.ts:107:
{
  value: '\n',
  type: ElementType.CONTROL,
  control: {
    type: ControlType.TEXT,
    value: [
      { value: 'Default text' }
    ],
    placeholder: 'Enter text here',
    conceptId: 'field-1',
    prefix: '[',
    postfix: ']',
    minWidth: 100
  },
  controlId: 'ctrl-1',
  controlComponent: ControlComponent.VALUE
}
Available control types:
  • TEXT - Single-line text input
  • SELECT - Dropdown selection
  • CHECKBOX - Checkbox group
  • RADIO - Radio button group
  • DATE - Date picker
  • NUMBER - Numeric input

Special Elements

{
  value: '\n',
  type: ElementType.PAGE_BREAK
}

Saving and Loading

Getting Document Data

const data = editor.command.getValue()
// Returns IEditorData
console.log(data)
// {
//   header: [...],
//   main: [...],
//   footer: [...]
// }

Setting Document Data

const newData = {
  main: [
    { value: 'New content' }
  ]
}

editor.command.setValue(newData)

Persistence Patterns

// Save to localStorage
function saveDocument() {
  const data = editor.command.getValue()
  localStorage.setItem('document', JSON.stringify(data))
}

// Load from localStorage
function loadDocument() {
  const saved = localStorage.getItem('document')
  if (saved) {
    const data = JSON.parse(saved)
    editor.command.setValue(data)
  }
}

// Auto-save on change
editor.listener.rangeStyleChange = () => {
  saveDocument()
}

Data Manipulation

Finding Elements

// Get element by ID
const element = editor.command.getElementById({ id: 'element-123' })

// Get control values
const controls = editor.command.getControlValue({
  conceptId: 'user-name'
})

// Get range context
const context = editor.command.getRangeContext()
console.log(context)
// {
//   isReadonly: false,
//   isTable: false,
//   isControl: false,
//   ...
// }

Updating Elements

// Update element by ID
editor.command.executeUpdateElementById({
  id: 'element-123',
  properties: {
    color: '#FF0000',
    bold: true,
    size: 18
  }
})

// Update control value
editor.command.executeSetControlValue({
  conceptId: 'user-name',
  value: 'John Doe'
})

// Delete element by ID
editor.command.executeDeleteElementById({
  id: 'element-123'
})

Batch Operations

// Insert multiple elements
const elements = [
  { value: 'Paragraph 1' },
  { value: 'Paragraph 2', bold: true },
  { value: 'Paragraph 3', color: '#0000FF' }
]

editor.command.executeInsertElementList(elements, {
  isSubmitHistory: true,
  isReplace: false
})

Data Validation

Always validate and sanitize data before loading into the editor, especially when loading from untrusted sources.
function validateEditorData(data: unknown): IEditorData {
  if (!data || typeof data !== 'object') {
    throw new Error('Invalid data format')
  }
  
  const editorData = data as IEditorData
  
  // Validate main array exists
  if (!Array.isArray(editorData.main)) {
    throw new Error('Main content array is required')
  }
  
  // Validate elements
  const validateElements = (elements: IElement[]) => {
    elements.forEach((element, index) => {
      if (typeof element.value !== 'string') {
        throw new Error(`Element ${index} missing value property`)
      }
    })
  }
  
  validateElements(editorData.main)
  if (editorData.header) validateElements(editorData.header)
  if (editorData.footer) validateElements(editorData.footer)
  
  return editorData
}

// Usage
try {
  const data = validateEditorData(untrustedData)
  editor.command.setValue(data)
} catch (error) {
  console.error('Invalid document data:', error)
}

Utility Functions

Canvas Editor provides utility functions for data manipulation (from src/editor/index.ts:168):
import {
  splitText,
  createDomFromElementList,
  getElementListByHTML,
  getTextFromElementList
} from '@hufe921/canvas-editor'

// Split text into elements
const elements = splitText('Hello\nWorld')

// Convert elements to DOM
const dom = createDomFromElementList([
  { value: 'Hello', bold: true }
])

// Convert HTML to elements
const elements = getElementListByHTML('<p><b>Bold text</b></p>')

// Extract plain text from elements
const text = getTextFromElementList([
  { value: 'Hello' },
  { value: 'World' }
])

Best Practices

Version Control

Always store the editor version with saved documents for migration compatibility.

Compression

Consider compressing large documents before storage using gzip or similar.

Incremental Saves

For large documents, implement incremental/delta saving to reduce payload size.

Validation

Always validate data structure before loading, especially from external sources.

Common Patterns

Template System

const template = {
  main: [
    {
      value: '{{userName}}',
      controlId: 'user-name',
      type: ElementType.CONTROL,
      control: {
        type: ControlType.TEXT,
        conceptId: 'userName',
        placeholder: 'Enter your name'
      }
    },
    { value: '\n' },
    {
      value: '{{date}}',
      type: ElementType.DATE,
      dateFormat: 'YYYY-MM-DD'
    }
  ]
}

// Load template and fill data
editor.command.setValue(template)
editor.command.executeSetControlValue({
  conceptId: 'userName',
  value: 'John Doe'
})

Diff and Merge

function compareDocuments(doc1: IEditorData, doc2: IEditorData) {
  const changes = []
  
  // Compare main content
  doc1.main.forEach((element, index) => {
    const element2 = doc2.main[index]
    if (JSON.stringify(element) !== JSON.stringify(element2)) {
      changes.push({ index, old: element, new: element2 })
    }
  })
  
  return changes
}

Next Steps

Configuration

Learn about editor configuration

Migration Guide

Upgrade to the latest version

API Reference

Explore data manipulation APIs

Examples

See working data structure examples

Build docs developers (and LLMs) love