Appearance
Template Modification System
Basic Concept
Using the API call this.api.getDocumentModifier(), you can access the TemplateModifier object.
The TemplateModifier provides a controlled way to modify templates:
javascript
this.api.getDocumentModifier()
.modifyHtml(immutableNode) // Select what to modify
.setInnerHtml('New content') // Define the modification
.apply(description); // Apply with tracking
Modification Flow
1. Get Modifier 2. Select Node 3. Chain Modifications
↓ ↓ ↓
getDocumentModifier() → modifyHtml(node) → setAttribute().setStyle()...
↓
5. Sync 4. Apply
↓ ↓
Propagate to Users ← apply(ModificationDescription)
This flow ensures that all modifications are:
- Tracked - Each change is recorded for version history
- Atomic - Related changes are applied together
- Collaborative - Changes are synchronized across all users
- Reversible - Full undo/redo support is maintained
Modification Types
HTML Modifications
The system supports various HTML modifications:
Content Modifications
javascript
.setInnerHtml(html) // Replace inner HTML
.append(html) // Add to end
.prepend(html) // Add to beginning
.replaceWith(html) // Replace entire element
Attribute Modifications
javascript
.setAttribute(name, value) // Set attribute
.removeAttribute(name) // Remove attribute
Text Modifications
javascript
.setText(text) // Updates the text content of the HTML text node
Style Modifications
javascript
.setStyle(property, value) // Set CSS property
.removeStyle(property) // Remove CSS property
Class Modifications
javascript
.setClass(className) // Add CSS class
.removeClass(className) // Remove CSS class
Structural Modifications
javascript
.delete() // Remove current node
CSS Modifications
The system also supports CSS modifications:
javascript
this.api.getDocumentModifier()
.modifyCss(immutableCssNode)
.setProperty('color', 'blue') // Set a CSS property
.setProperty('font-size', '16px') // Set a CSS property
.removeProperty('text-decoration') // Remove a CSS property
.apply(description);
MultiRowStructureModifier
Overview
The MultiRowStructureModifier
is a specialized helper interface for creating and modifying complex email layouts with multiple rows and columns. It provides high-level methods to manage email structure containers, handling the intricate details of email-compatible HTML generation.
When to Use MultiRowStructureModifier
Use the MultiRowStructureModifier
when you need to:
- Create multi-row custom blocks - Build responsive custom blocks with multiple structure rows
- Modify existing structures - Change the layout while preserving content
- Manage container distribution - Control how content is distributed across containers within the structure
Why Use MultiRowStructureModifier
Traditional email HTML is complex due to:
- Table-based layouts - Email clients require nested tables for consistent rendering
- MSO compatibility - Outlook needs special conditional comments and markup
- Responsive challenges - Mobile and desktop layouts require different approaches
The MultiRowStructureModifier
abstracts these complexities by providing:
- Automatic table structure generation - No need to manually create complex nested tables
- Built-in MSO/Outlook compatibility - Handles all necessary conditional comments and markup
- Responsive design handling - Automatically generates mobile-friendly layouts
- Smart content distribution - Intelligently places content across containers
- Image scaling and optimization - Ensures images render correctly across email clients
Access and Usage
Access the MultiRowStructureModifier
through the multiRowStructureModifier()
method:
javascript
const modifier = this.api.getDocumentModifier()
.modifyHtml(structureNode)
.multiRowStructureModifier();
Methods
updateLayoutWithContent
Creates a new structure by replacing the current one with specified containers and content.
javascript
// Create a three-column layout with mixed container types
modifier.multiRowStructureModifier()
.updateLayoutWithContent(
[
{width: '25%', contentType: 'EMPTY'}, // Empty placeholder
'50%', // Content column 1
'25%' // Content column 2
],
[
`<${BlockType.BLOCK_TEXT}>
<p>Content 1</p>
</${BlockType.BLOCK_TEXT}>`,
`<${BlockType.BLOCK_TEXT}>
<p>Content 2</p>
</${BlockType.BLOCK_TEXT}>`
]
)
.apply(new ModificationDescription('Created three-column layout'));
Parameters:
layout
: Array ofStructureLayout
defining container widths and typescontainerContent
: Array of HTML strings for content containers
Container Types:
- Content Container (string) - Width percentage for content-holding containers
- Empty Container -
{width: string, contentType: 'EMPTY'}
for placeholders awaiting future content - Spacer Container -
{width: string, contentType: 'SPACER'}
for creating empty space
updateLayout
Modifies the container layout of an existing structure while preserving content.
javascript
// Change from two columns to three columns
modifier.multiRowStructureModifier()
.updateLayout([
'33%', // First column
'34%', // Second column
'33%' // Third column
])
.apply(new ModificationDescription('Changed to three-column layout'));
Parameters:
layout
: New container layout configuration
Behavior:
- Preserves existing content when possible during layout changes
- Redistributes content intelligently among new containers
- Manages content overflow by automatically creating additional structure beneath the current one when needed
Best Practices
- Plan Your Layout - Design your container structure before implementation
- Use Meaningful Widths - Ensure widths add up to 100% for proper rendering
- Test Content Distribution - Verify content appears in the correct containers
- Leverage Spacers - Use spacer containers for consistent margins and padding
Modification Descriptions
Purpose
Every modification requires a description that:
- Documents the change for comprehensive version history
- Provides context for other users in collaborative editing
- Enables meaningful labels for undo/redo operations
- Supports internationalization for multi-language environments
Basic Usage
javascript
.apply(new ModificationDescription('Changed text color'));
With Parameters
javascript
.apply(new ModificationDescription('Changed color to {color}')
.withParams({ color: '#ff0000' }));
Internationalization
javascript
.apply(new ModificationDescription('color_changed')
.withParams({ color: '#ff0000' }));
// Uses translation key 'color_changed'
Transaction Model
Atomic Operations
All modifications in a single chain are atomic:
javascript
this.api.getDocumentModifier()
.modifyHtml(container)
.setStyle('background', 'blue') // All three
.setClass('highlighted') // happen
.setAttribute('data-modified', 'true') // together
.apply(description);
Benefits of Transactions
- Consistency - All related changes succeed together or fail as a unit
- Performance - Batch processing reduces overhead and improves responsiveness
- History - Single undo/redo operation for logically related changes
- Synchronization - Fewer network requests improve collaborative editing performance
Collaborative Editing Support
How It Works
When a modification is applied:
- Local Application: Changes apply immediately locally
- Serialization: Modifications are serialized to operations
- Transmission: Operations sent to collaboration server
- Transformation: Server resolves conflicts if needed
- Broadcast: Changes sent to all other users
- Application: Remote changes applied to all clients
Conflict Resolution
The system automatically handles conflicts:
User A: Changes heading color to red
User B: Changes heading color to blue (simultaneously)
Result: Last write wins (User B's change)
Both users see: Heading is blue
History shows: Both changes with timestamps
Complex conflicts are resolved using operational transformation:
User A: Inserts text at position 10
User B: Deletes text at position 5-8
System transforms User A's operation:
Original: Insert at position 10
Transformed: Insert at position 7 (adjusted for deletion)
Version History Integration
Automatic Tracking
Every modification is automatically tracked:
javascript
// This single modification creates a history entry
this.api.getDocumentModifier()
.modifyHtml(element)
.setInnerHtml('Updated content')
.apply(new ModificationDescription('Updated welcome message'));
// Version history shows:
// - Timestamp
// - "Updated welcome message"
// - Username
// - Restore option
Undo/Redo Support
The modification system provides built-in undo/redo:
javascript
// User performs modification
modifier.apply(description);
// User clicks undo
// System automatically reverses the modification
// User clicks redo
// System reapplies the modification
Performance Considerations
Batching Strategies
Batch related modifications for better performance:
javascript
// Good - Single transaction
const modifier = this.api.getDocumentModifier();
elements.forEach(el => {
modifier.modifyHtml(el).setStyle('color', 'blue');
});
modifier.apply(description);
// Avoid - Multiple transactions
elements.forEach(el => {
this.api.getDocumentModifier()
.modifyHtml(el)
.setStyle('color', 'blue')
.apply(description);
});
Debouncing
For high-frequency updates (like color pickers):
javascript
let modificationTimeout;
onColorChange(color) {
clearTimeout(modificationTimeout);
modificationTimeout = setTimeout(() => {
this.applyColorChange(color);
}, 100);
}
Best Practices
1. Use Descriptive Messages
javascript
// Good - specific and informative
.apply(new ModificationDescription('Changed button color to match brand'));
// Avoid - too vague
.apply(new ModificationDescription('Updated'));
2. Group Related Changes Logically
javascript
// Good - related changes in a single transaction
modifier
.modifyHtml(button)
.setStyle('background', color)
.setStyle('border-color', darkenColor(color))
.apply(description);
// Avoid - unrelated changes in the same transaction
modifier
.modifyHtml(button)
.setStyle('background', color)
.modifyHtml(heading) // Different element - should be separate
.setInnerHtml(title)
.apply(description);
3. Minimize Unnecessary Modifications
javascript
// Good - only modify when values actually change
if (newValue !== oldValue) {
modifier.modifyHtml(element)
.setAttribute('data-value', newValue)
.apply(description);
}
// Avoid - modifying even when values haven't changed
modifier.modifyHtml(element)
.setAttribute('data-value', newValue) // Wasteful if unchanged
.apply(description);
4. Use Appropriate Methods for Each Task
javascript
// Good - use specialized methods for their intended purpose
modifier.modifyHtml(element)
.setDisplayCondition(condition) // For visibility control
.setNodeConfig(widgetConfig) // For custom data storage
.apply(description);
// Avoid - misusing generic attributes for specialized functionality
modifier.modifyHtml(element)
.setAttribute('data-config', JSON.stringify(config)) // Use setNodeConfig instead
.apply(description);
Summary
The template modification system provides:
- Safety - Immutable nodes prevent direct manipulation and ensure data integrity
- Tracking - All changes are recorded and attributed for complete audit trails
- Collaboration - Automatic conflict resolution and real-time synchronization
- History - Built-in undo/redo functionality with comprehensive version tracking
- Performance - Batched operations and optimized synchronization for scalability
- Advanced Features - Display conditions and node configurations for complex use cases
- Layout Management - MultiRowStructureModifier for sophisticated email structures
This system is fundamental to creating reliable, collaborative extensions that maintain template integrity while providing powerful modification capabilities.