Appearance
How to Handle Template Modifications
Understanding the Template Modification System
Before exploring practical examples, we strongly recommend reviewing the Architecture Overview and Template Modification System documentation. These resources will help you understand the foundational concepts behind template modifications.
Examples
Dynamic Attribute Updates
One of the most common tasks is adding or updating HTML attributes. For example, consider the following block content:
html
<td>
<a href="https://stripo.email" target="_blank">Stripo</a>
<a href="https://github.com" target="_blank">Github</a>
</td>
The following example demonstrates how to add tracking attributes to all links within the selected node:
javascript
const campaignId = '123456';
const modifier = this.api.getDocumentModifier();
this.node.querySelectorAll('a').forEach(link => {
const href = link.getAttribute('href');
if (href && !href.includes('utm_campaign')) {
const separator = href.includes('?') ? '&' : '?';
const trackedUrl = `${href}${separator}utm_campaign=${campaignId}&utm_source=email&utm_medium=stripo`;
modifier.modifyHtml(link)
.setAttribute('href', trackedUrl)
.setAttribute('data-campaign-id', campaignId);
}
});
modifier.apply(new ModificationDescription(`Added tracking parameters to links`));
HTML Node Manipulation
Template modifications often require adding or removing HTML nodes. While node addition is straightforward, node removal requires careful consideration of the template's structural integrity and user experience.
There are two primary approaches to node removal:
1. Direct Node Deletion
This method permanently removes nodes from the template using the delete()
method:
javascript
const blocks = this.api.getDocumentRoot().querySelectorAll('.esd-block-image');
const modifier = this.api.getDocumentModifier();
blocks.forEach(block => {
modifier.modifyHtml(block).delete();
});
modifier.apply(new ModificationDescription('Removed all image blocks'));
Important Considerations: Direct deletion removes the node completely from the template structure. However, this approach eliminates the drag-and-drop zone at the deleted position, creating non-interactive blank spaces. This behavior can negatively impact the user experience and template editing workflow.
2. Node Replacement with Empty Container (Recommended)
This method replaces nodes with empty containers, preserving the template's interactive structure:
javascript
const blocks = this.api.getDocumentRoot().querySelectorAll('.esd-block-image');
const modifier = this.api.getDocumentModifier();
blocks.forEach(block => {
modifier.modifyHtml(block).replaceWith(`<${BlockType.EMPTY_CONTAINER}/>`);
});
modifier.apply(new ModificationDescription('Replaced image blocks with empty containers'));
Advantages: This approach maintains the template's drag-and-drop functionality by preserving interactive zones where users can add new blocks. The empty containers act as placeholders that maintain the template's structural integrity while allowing for future content additions.
Text Content Manipulation
Consider a scenario where you have a block with content like this:
html
<td>
<b>Hello,<span> dear </span>{NAME}</b>
</td>
and you want to replace the placeholder {NAME}
with the actual user's name.
There are several ways to achieve this. The simplest approach is to use the setInnerHtml
method:
javascript
const originalText = this.node.getInnerHTML();
const updatedText = originalText.replace('{NAME}', 'John');
this.api.getDocumentModifier()
.modifyHtml(this.node)
.setInnerHtml(updatedText)
.apply(new ModificationDescription(`Updated text to ${updatedText}`))
The disadvantage of this approach is that it replaces the entire content of the block, which can be substantial for larger blocks. A more elegant solution is to use the setText
method on the specific text node:
javascript
const nameMergeTagTextNode = this.node.querySelector('b').childNodes()
.find(c => c.getType() === 'text' && c.getTextContent() === '{NAME}');
if (nameMergeTagTextNode) {
this.api.getDocumentModifier()
.modifyHtml(nameMergeTagTextNode)
.setText('John')
.apply(new ModificationDescription('Updated placeholder with actual name'));
}
Node Configuration Storage
You can store service metadata and settings for individual template nodes in JSON format to reuse them later. For example, you may want to store the campaign ID in the root node configuration storage of the selected block. This can be accomplished with the following code:
javascript
const campaignId = '123456';
const originalConfig = this.currentNode.getNodeConfig();
const newConfig = {
...originalConfig,
campaignId: campaignId,
};
this.api.getDocumentModifier()
.modifyHtml(this.currentNode)
.setNodeConfig(newConfig)
.apply(new ModificationDescription('Stored campaign ID in node config'));
Conditional Display Logic
Sometimes you may want to hide or show a block based on specific conditions. For example, you may want to display a block exclusively for female recipients. Here's how you can achieve this:
javascript
const nodeDisplayCondition = {
id: 'id_1',
name: 'Female',
description: 'Only female customers will see this part of the email.',
beforeScript: '{% if contact.gender == \"Female\" %}',
afterScript: '{% endif %}',
extraData: JSON.stringify({campaignId: '123456'})
}
this.api.getDocumentModifier()
.modifyHtml(this.currentNode)
.setDisplayCondition(nodeDisplayCondition)
.apply(new ModificationDescription('Set block visibility for females only'));
CSS Style Modifications
Sometimes HTML modifications are not sufficient. You can programmatically modify custom CSS styles as well. For example, you may have the following custom CSS:
css
h1 {
font-family: 'Courier New', Courier, monospace;
}
@media only screen and (max-width: 600px) {
h1 {
font-family: 'Times New Roman', Times, serif;
}
}
and you want to change the font-family for all h1 tags to 'Helvetica'. You can achieve this with the following code:
javascript
const desktopH1CssNode = this.api.getDocumentRootCssNode().querySelector('h1');
const mobileH1CssNode = this.api.getDocumentRootCssNode().querySelector('@{media only screen and (max-width: 600px)} h1');
// Get current values if you need to compare them
const currentDesktopH1Value = desktopH1CssNode.querySelector('{font-family}').getAttributeValue();
const currentMobileH1Value = mobileH1CssNode.querySelector('{font-family}').getAttributeValue();
this.api.getDocumentModifier()
.modifyCss(desktopH1CssNode)
.setProperty('font-family', 'Helvetica')
.modifyCss(mobileH1CssNode)
.setProperty('font-family', 'Helvetica')
.apply(new ModificationDescription('Updated font family for h1'));
Structure Container Width Adjustment
Sometimes you may need to change structure container dimensions. For example, you may want to change a base two-column structure from a 50%/50% layout to a 70%/30% layout. You can achieve this with the following code:
javascript
this.api.getDocumentModifier()
.modifyHtml(this.currentNode)
.multiRowStructureModifier()
.updateLayout(['70%', '30%'])
.apply(new ModificationDescription('Updated structure layout'));
Structure Layout Reorganization with Content
There are cases where initially your block is not configured properly and is dragged and dropped into the template as a blank block. During configuration actions from the settings panel, it becomes a complete structure with content. In this case, you can use the following code to reorganize the structure:
javascript
this.api.getDocumentModifier()
.modifyHtml(this.currentNode)
.multiRowStructureModifier()
.updateLayoutWithContent(
['70%', '30%'],
[
`<${BlockType.BLOCK_TEXT}><p>Lorem ipsum dolor sit amet</p></${BlockType.BLOCK_TEXT}>`,
`<${BlockType.BLOCK_BUTTON}>Click Me</${BlockType.BLOCK_BUTTON}>`,
])
.apply(new ModificationDescription('Updated structure layout with content'));
Localized Version History
Every template change requires a description to be displayed in the version history. You can add localization files to your extension configuration and use them to provide localized descriptions. For example, if you have a localization file uk.js
:
javascript
export default {
"Add campaign id {campaignId} to params": "Додано ID компанії {campaignId} до параметрів"
}
and the following modification code:
javascript
const campaignId = this.api.translate('123456');
this.api.getDocumentModifier()
.modifyHtml(this.currentNode)
.setAttribute('data-campaign-id', campaignId)
.apply(new ModificationDescription('Add campaign id {campaignId} to params')
.withParams({ campaignId: campaignId }));
then the resulting message in the version history for Ukrainian users will look like:
text
Додано ID компанії 123456 до параметрів