Syncfusion AI Assistant

How can I help you?

Mail merge in Vue Rich Text Editor component

18 Mar 202624 minutes to read

The Rich Text Editor can be customized to implement Mail Merge functionality by inserting placeholders into the editor using custom toolbar items. These placeholders are later replaced with actual data to generate personalized content such as letters, invoices, and reports.

This feature simplifies the creation of dynamic documents by allowing users to insert merge fields that are automatically populated with real data during content generation.

Adding custom toolbar items for inserting merge fields

To enable mail merge functionality, the Rich Text Editor toolbar is extended with two custom buttons: Insert Field and Merge Data. These buttons are added using the template property in toolbarSettings.items, which points to custom HTML elements (#insertField and #merge_data).

  • Insert Field: Opens a dropdown list of merge fields for inserting placeholders like {{FirstName}} into the editor.
  • Merge Data: Replaces all placeholders in the editor with actual values from a predefined data source.
<template>
    <ejs-richtexteditor ref="rteObj" id="mailMergeEditor" :toolbarSettings="toolbarSettings"></ejs-richtexteditor>
</template>

<script>
export default {
    name: "App",
    components: {
    "ejs-richtexteditor":RichTextEditorComponent
    },
    data: function() {
        return {
            toolbarSettings: {
                items: [
                'Bold', 'Italic', 'Underline', '|', 'Formats', 'Alignments', 'OrderedList', 'UnorderedList', '|', 'CreateLink', 'Image', 'CreateTable', '|',
                { tooltipText: 'Merge Data', template: '#merge_data', command: 'Custom' },
                { tooltipText: 'Insert Field', template: '#insertField', command: 'Custom' },
                'SourceCode', '|', 'Undo', 'Redo'],
            },
        };
    },
    provide:{
        richtexteditor:[Toolbar, Link, Image, HtmlEditor, QuickToolbar]
    }
}
</script>

Using DropDownButton for selecting placeholders

The DropDownButton component displays a list of merge fields such as First Name, Last Name, and Company Name. When a user selects an item, the corresponding placeholder (e.g., ) is inserted at the current cursor position using the insertHTML command.

<template>
    <ejs-dropdownbutton id="insertField" :items="itemsName" :content="dropdownContent" @select="onItemSelect($event)"></ejs-dropdownbutton>
</template>

<script>
export default {
    name: "App",
    components: {
    "ejs-richtexteditor":RichTextEditorComponent
    },
    data: function() {
        return {
            dropdownContent: `<span style="display:inline-flex;"><span class="e-rte-dropdown-btn-text">Insert Field</span></span>`,
            itemsName: [
                { text: 'First Name' },
                { text: 'Last Name' },
                { text: 'Support Email' },
                { text: 'Company Name' },
                { text: 'Promo Code' },
                { text: 'Support Phone Number' },
                { text: 'Customer ID' },
                { text: 'Expiration Date' },
                { text: 'Subscription Plan' },
            ],
        };
    },
    provide:{
        richtexteditor:[Toolbar, Link, Image, HtmlEditor, QuickToolbar]
    },
    methods: {
        onItemSelect: function(args) {
            if (args && args.item && args.item.text) {
                const value = this.textToValueMap[args.item.text.trim()];
                if (value && this.$refs.rteObj && this.$refs.rteObj.ej2Instances) {
                    this.$refs.rteObj.ej2Instances.formatter.editorManager.nodeSelection.restore();
                    this.$refs.rteObj.ej2Instances.executeCommand('insertHTML', `<span contenteditable="false" class="e-mention-chip">}</span> `, { undo: true });
                } 
                else {
                    console.log('No matching value found in textToValueMap or RichTextEditor is not initialized.');
                }
            }
        }, 
    }
}
</script>

Populating merge fields using Mention

The Mention component provides an alternative way to insert placeholders by typing the {{ character inside the editor. A popup list of merge fields appears, allowing quick selection without using the toolbar.

<ejs-mention ref="mentionObj" id="mentionObj" target="#mailMergeEditor_rte-edit-view" :mentionChar="mentionChar" :allowSpaces="true" :dataSource="mergeData" :fields="fieldsData" popupWidth="250px" popupHeight="200px" :displayTemplate="'displayTemplate'">
        <template v-slot:displayTemplate="{ data }">
            <span></span>
        </template>
</ejs-mention>

<script>
export default {
    name: "App",
    components: {
    "ejs-richtexteditor":RichTextEditorComponent
    },
    data: function() {
        return {
            fieldsData: { text: 'text', value: 'value' },
            mentionChar: <code>&#123;&#123;</code>,
            mergeData: [
                { text: 'First Name', value: 'FirstName' },
                { text: 'Last Name', value: 'LastName' },
                { text: 'Support Email', value: 'SupportEmail' },
                { text: 'Company Name', value: 'CompanyName' },
                { text: 'Promo Code', value: 'PromoCode' },
                { text: 'Support Phone Number', value: 'SupportPhoneNumber' },
                { text: 'Customer ID', value: 'CustomerID' },
                { text: 'Expiration Date', value: 'ExpirationDate' },
                { text: 'Subscription Plan', value: 'SubscriptionPlan' },
            ],
        };
    },
    provide:{
        richtexteditor:[Toolbar, Link, Image, HtmlEditor, QuickToolbar]
    },
}
</script>

Replacing placeholders with actual data dynamically

When the Merge Data button is clicked, the editor content is processed to replace all placeholders with actual values from the placeholderData object. This is done using a regular expression in the replacePlaceholders() function.

<template>
    <button class="e-control e-lib e-btn e-formats-tbar-btn e-rte-elements e-tbar-btn e-tbar-btn" tabindex="-1" id="merge_data" style="width: 100%" @click="onClickHandler">
            <span style="display: inline-flex"><span class="e-tbar-btn-text">Merge Data</span></span>
    </button>
</template>

<script>
export default {
    name: "App",
    components: {
    "ejs-richtexteditor":RichTextEditorComponent
    },
    data: function() {
        return {
            dropdownContent: `<span style="display:inline-flex;"><span class="e-rte-dropdown-btn-text">Insert Field</span></span>`,
            itemsName: [
                { text: 'First Name' },
                { text: 'Last Name' },
                { text: 'Support Email' },
                { text: 'Company Name' },
                { text: 'Promo Code' },
                { text: 'Support Phone Number' },
                { text: 'Customer ID' },
                { text: 'Expiration Date' },
                { text: 'Subscription Plan' },
            ],
        };
    },
    provide:{
        richtexteditor:[Toolbar, Link, Image, HtmlEditor, QuickToolbar]
    },
    methods: {
        onClickHandler() {
            if (this.$refs.rteObj && this.$refs.rteObj.ej2Instances) {
                    let editorContent = this.$refs.rteObj.ej2Instances.value || '';
                    let mergedContent = this.replacePlaceholders(editorContent, this.placeholderData);
                    if (this.$refs.rteObj.ej2Instances.formatter.getUndoRedoStack() && this.$refs.rteObj.ej2Instances.formatter.getUndoRedoStack().length === 0) {
                        this.$refs.rteObj.ej2Instances.formatter.saveData();
                    }
                    this.value = mergedContent;
                    this.$refs.rteObj.ej2Instances.formatter.saveData();
                    this.$refs.rteObj.trigger('change');
            } else {
                console.log('MailMergeEditor is not initialized.');
            }
        },
        replacePlaceholders(template, data) {
            return template.replace(//g, (match, key) => {
                const value = data[key.trim()];
                return value !== undefined ? value : match;
            });
        },
    }
}
</script>
<template>
  <div id="defaultRTE">
    <div class="control-section">
      <div class="sample-container">
        <div class="default-section">
          <ejs-richtexteditor ref="rteObj" id="mailMergeEditor" :value="value" :saveInterval="saveInterval"
            :toolbarSettings="toolbarSettings" :actionBegin="onActionBegin"
            :actionComplete="OnActionComplete"></ejs-richtexteditor>

          <button class="e-control e-lib e-btn e-formats-tbar-btn e-rte-elements e-tbar-btn e-tbar-btn"
            tabindex="-1" id="merge_data" style="width:100%" @click="onClickHandler">
            <span style="display:inline-flex"><span class="e-tbar-btn-text">Merge Data</span></span>
          </button>

          <ejs-dropdownbutton id="insertField" :items="itemsName" :content="dropdownContent"
            cssClass="e-rte-dropdown-btn e-rte-dropdown-popup e-rte-dropdown-items e-rte-elements e-rte-dropdown-menu"
            @select="onItemSelect" @close="onDropDownClose"></ejs-dropdownbutton>
        </div>

        <ejs-mention ref="mentionObj" id="mentionObj" target="#mailMergeEditor_rte-edit-view"
          :mentionChar="mentionChar" :allowSpaces="true" :dataSource="mergeData" :fields="fieldsData"
          popupWidth="250px" popupHeight="200px" :displayTemplate="'displayTemplate'">
          <template v-slot:displayTemplate="{ data }">
            <span>{{ formatMentionTemplate(data.value) }}</span>
          </template>
        </ejs-mention>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, provide } from "vue";
import { RichTextEditorComponent as EjsRichtexteditor, Toolbar, Table, Link, Image, HtmlEditor, QuickToolbar, Video, Audio, PasteCleanup } from '@syncfusion/ej2-vue-richtexteditor';
import { MentionComponent as EjsMention } from '@syncfusion/ej2-vue-dropdowns';
import { DropDownButtonComponent as EjsDropdownbutton } from '@syncfusion/ej2-vue-splitbuttons';
import { NodeSelection } from '@syncfusion/ej2-vue-richtexteditor';

const rteObj = ref(null);
const mentionObj = ref(null);
const value = ref(`<p>Dear <span contenteditable="false" class="e-mention-chip"><span>{{FirstName}}</span></span> <span contenteditable="false" class="e-mention-chip"><span>{{LastName}}</span></span>,</p>
<p>We are thrilled to have you with us! Your unique promotional code for this month is: <span contenteditable="false" class="e-mention-chip"><span>{{PromoCode}}</span></span>.</p>
<p>Your current subscription plan is: <span contenteditable="false" class="e-mention-chip"><span>{{SubscriptionPlan}}</span></span>.</p>
<p>Your customer ID is: <span contenteditable="false" class="e-mention-chip"><span>{{CustomerID}}</span></span>.</p>
<p>Your promotional code expires on: <span contenteditable="false" class="e-mention-chip"><span>{{ExpirationDate}}</span></span>.</p>
<p>Feel free to browse our latest offerings and updates. If you need any assistance, don't hesitate to contact us at <a href="mailto:{{SupportEmail}}"><span contenteditable="false" class="e-mention-chip"><span>{{SupportEmail}}</span></span></a> or call us at <span contenteditable="false" class="e-mention-chip"><span>{{SupportPhoneNumber}}</span></span>.</p>
<p>Best regards,<br>The <span contenteditable="false" class="e-mention-chip"><span>{{CompanyName}}</span></span> Team</p>`);

const saveInterval = 1;
const selection = new NodeSelection();

const dropdownContent = `<span style="display:inline-flex;"><span class="e-rte-dropdown-btn-text">Insert Field</span></span>`;

const itemsName = [
  { text: 'First Name' }, { text: 'Last Name' }, { text: 'Support Email' },
  { text: 'Company Name' }, { text: 'Promo Code' }, { text: 'Support Phone Number' },
  { text: 'Customer ID' }, { text: 'Expiration Date' }, { text: 'Subscription Plan' },
];

const placeholderData = {
  FirstName: 'John', LastName: 'Doe', PromoCode: 'ABC123', SubscriptionPlan: 'Premium',
  CustomerID: '123456', ExpirationDate: '2024-12-31', SupportEmail: '[email protected]',
  SupportPhoneNumber: '+1-800-555-5555', CompanyName: 'Example Inc.',
};

const textToValueMap = {
  'First Name': 'FirstName', 'Last Name': 'LastName', 'Support Email': 'SupportEmail',
  'Company Name': 'CompanyName', 'Promo Code': 'PromoCode', 'Support Phone Number': 'SupportPhoneNumber',
  'Customer ID': 'CustomerID', 'Expiration Date': 'ExpirationDate', 'Subscription Plan': 'SubscriptionPlan',
};

const mergeData = [
  { text: 'First Name', value: 'FirstName' }, { text: 'Last Name', value: 'LastName' },
  { text: 'Support Email', value: 'SupportEmail' }, { text: 'Company Name', value: 'CompanyName' },
  { text: 'Promo Code', value: 'PromoCode' }, { text: 'Support Phone Number', value: 'SupportPhoneNumber' },
  { text: 'Customer ID', value: 'CustomerID' }, { text: 'Expiration Date', value: 'ExpirationDate' },
  { text: 'Subscription Plan', value: 'SubscriptionPlan' },
];

const fieldsData = { text: 'text', value: 'value' };
const mentionChar = '{{';

const toolbarSettings = {
  items: [
    'Bold', 'Italic', 'Underline', '|', 'Formats', 'Alignments', 'OrderedList', 'UnorderedList', '|',
    'CreateLink', 'Image', 'CreateTable', '|',
    { tooltipText: 'Merge Data', template: '#merge_data', command: 'Custom' },
    { tooltipText: 'Insert Field', template: '#insertField', command: 'Custom' },
    'SourceCode', '|', 'Undo', 'Redo',
  ],
};

provide('richtexteditor', [Toolbar, Link, Image, QuickToolbar, HtmlEditor, Table, Video, Audio, PasteCleanup]);

const formatMentionTemplate = (val) => `{{${val}}}`;

const onDropDownClose = () => {
  rteObj.value?.ej2Instances?.focusIn();
};

const onItemSelect = (args) => {
  if (!args?.item?.text) return;
  const key = args.item.text.trim();
  const field = textToValueMap[key];
  if (!field || !rteObj.value?.ej2Instances) return;

  const editor = rteObj.value.ej2Instances;
  editor.formatter.editorManager.nodeSelection.restore();
  editor.executeCommand('insertHTML', `<span contenteditable="false" class="e-mention-chip">{{${field}}}</span> `, { undo: true });
};

const onActionBegin = (args) => {
  if (args.requestType === 'EnterAction' && mentionObj.value?.ej2Instances?.element.classList.contains('e-popup-open')) {
    args.cancel = true;
  }
};

const OnActionComplete = (e) => {
  const toolbar = rteObj.value?.ej2Instances?.getToolbar();
  if (!toolbar) return;

  const mergeBtn = toolbar.querySelector('#merge_data')?.parentElement;
  const fieldBtn = toolbar.querySelector('#insertField')?.parentElement;

  if (e.requestType === 'SourceCode') {
    mergeBtn?.classList.add('e-overlay');
    fieldBtn?.classList.add('e-overlay');
  } else if (e.requestType === 'Preview') {
    mergeBtn?.classList.remove('e-overlay');
    fieldBtn?.classList.remove('e-overlay');
  }
};

const onClickHandler = () => {
  const editor = rteObj.value?.ej2Instances;
  if (!editor) {
    console.log('MailMergeEditor is not initialized.');
    return;
  }

  const content = editor.value || '';
  const merged = replacePlaceholders(content, placeholderData);

  if (editor.formatter.getUndoRedoStack()?.length === 0) editor.formatter.saveData();

  value.value = merged;
  editor.formatter.saveData();
  rteObj.value.trigger('change');
};

const replacePlaceholders = (tmpl, data) => {
  return tmpl.replace(/{{\s*(\w+)\s*}}/g, (m, k) => {
    const v = data[k.trim()];
    return v !== undefined ? v : m;
  });
};
</script>


<style>
@import "../../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-inputs/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-lists/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-buttons/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-vue-richtexteditor/styles/tailwind3.css";
</style>
<template>
  <div id="defaultRTE">
    <div class="control-section">
      <div class="sample-container">
        <div class="default-section">
          <ejs-richtexteditor ref="rteObj" id="mailMergeEditor" :value="value" :saveInterval="saveInterval" :toolbarSettings="toolbarSettings" :actionBegin="onActionBegin" :actionComplete="OnActionComplete"></ejs-richtexteditor>
          <button class="e-control e-lib e-btn e-formats-tbar-btn e-rte-elements e-tbar-btn e-tbar-btn" tabindex="-1" id="merge_data" style="width: 100%" @click="onClickHandler">
            <span style="display: inline-flex"><span class="e-tbar-btn-text">Merge Data</span></span>
          </button>
          <ejs-dropdownbutton id="insertField" :items="itemsName" :content="dropdownContent" cssClass="e-rte-dropdown-btn e-rte-dropdown-popup e-rte-dropdown-items e-rte-elements e-rte-dropdown-menu" @select="onItemSelect($event)" @close="onDropDownClose"></ejs-dropdownbutton>
        </div>
        <ejs-mention ref="mentionObj" id="mentionObj" target="#mailMergeEditor_rte-edit-view" :mentionChar="mentionChar" :allowSpaces="true" :dataSource="mergeData" :fields="fieldsData" popupWidth="250px" popupHeight="200px" :displayTemplate="'displayTemplate'">
          <template v-slot:displayTemplate="{ data }">
            <span>{{ formatMentionTemplate(data.value) }}</span>
          </template>
        </ejs-mention>
      </div>
    </div>
  </div>
</template>

<script>
import { RichTextEditorComponent, Toolbar, Link, Image, QuickToolbar, HtmlEditor, Table, Video, NodeSelection, Audio, PasteCleanup } from '@syncfusion/ej2-vue-richtexteditor';
import { MentionComponent } from '@syncfusion/ej2-vue-dropdowns';
import { DropDownButtonComponent } from '@syncfusion/ej2-vue-splitbuttons';
export default {
  components: {
    'ejs-richtexteditor': RichTextEditorComponent,
    'ejs-mention': MentionComponent,
    'ejs-dropdownbutton': DropDownButtonComponent,
  },
  data() {
    return {
      selection: new NodeSelection(),
      saveInterval: 1,
      ranges: null,
      saveSelection: null,
      value: `<p>Dear <span contenteditable="false" class="e-mention-chip"><span>{{FirstName}}</span></span> <span contenteditable="false" class="e-mention-chip"><span>{{LastName}}</span></span>,</p>
<p>We are thrilled to have you with us! Your unique promotional code for this month is: <span contenteditable="false" class="e-mention-chip"><span>{{PromoCode}}</span></span>.</p>
<p>Your current subscription plan is: <span contenteditable="false" class="e-mention-chip"><span>{{SubscriptionPlan}}</span></span>.</p>
<p>Your customer ID is: <span contenteditable="false" class="e-mention-chip"><span>{{CustomerID}}</span></span>.</p>
<p>Your promotional code expires on: <span contenteditable="false" class="e-mention-chip"><span>{{ExpirationDate}}</span></span>.</p>
<p>Feel free to browse our latest offerings and updates. If you need any assistance, don't hesitate to contact us at <a href="mailto:{{SupportEmail}}"><span contenteditable="false" class="e-mention-chip"><span>{{SupportEmail}}</span></span></a> or call us at <span contenteditable="false" class="e-mention-chip"><span>{{SupportPhoneNumber}}</span></span>.</p>
<p>Best regards,<br>The <span contenteditable="false" class="e-mention-chip"><span>{{CompanyName}}</span></span> Team</p>`,
      dropdownContent: `<span style="display:inline-flex;"><span class="e-rte-dropdown-btn-text">Insert Field</span></span>`,
      itemsName: [
        { text: 'First Name' },
        { text: 'Last Name' },
        { text: 'Support Email' },
        { text: 'Company Name' },
        { text: 'Promo Code' },
        { text: 'Support Phone Number' },
        { text: 'Customer ID' },
        { text: 'Expiration Date' },
        { text: 'Subscription Plan' },
      ],
      placeholderData: {
        FirstName: 'John',
        LastName: 'Doe',
        PromoCode: 'ABC123',
        SubscriptionPlan: 'Premium',
        CustomerID: '123456',
        ExpirationDate: '2024-12-31',
        SupportEmail: '[email protected]',
        SupportPhoneNumber: '+1-800-555-5555',
        CompanyName: 'Example Inc.',
      },
      textToValueMap: {
        'First Name': 'FirstName',
        'Last Name': 'LastName',
        'Support Email': 'SupportEmail',
        'Company Name': 'CompanyName',
        'Promo Code': 'PromoCode',
        'Support Phone Number': 'SupportPhoneNumber',
        'Customer ID': 'CustomerID',
        'Expiration Date': 'ExpirationDate',
        'Subscription Plan': 'SubscriptionPlan',
      },
      mergeData: [
        { text: 'First Name', value: 'FirstName' },
        { text: 'Last Name', value: 'LastName' },
        { text: 'Support Email', value: 'SupportEmail' },
        { text: 'Company Name', value: 'CompanyName' },
        { text: 'Promo Code', value: 'PromoCode' },
        { text: 'Support Phone Number', value: 'SupportPhoneNumber' },
        { text: 'Customer ID', value: 'CustomerID' },
        { text: 'Expiration Date', value: 'ExpirationDate' },
        { text: 'Subscription Plan', value: 'SubscriptionPlan' },
      ],
      fieldsData: { text: 'text', value: 'value' },
      mentionChar: '{{',
      toolbarSettings: {
        items: [
          'Bold', 'Italic', 'Underline', '|', 'Formats', 'Alignments', 'OrderedList', 'UnorderedList', '|', 'CreateLink', 'Image', 'CreateTable', '|',
          { tooltipText: 'Merge Data', template: '#merge_data', command: 'Custom' },
          { tooltipText: 'Insert Field', template: '#insertField', command: 'Custom' },
          'SourceCode', '|', 'Undo', 'Redo',
        ],
      },
    };
  },
  provide() {
    return {
      richtexteditor: [Toolbar, Link, Image, QuickToolbar, HtmlEditor, Table, Video, Audio, PasteCleanup],
    };
  },
  methods: {
    onDropDownClose() {
      if (this.$refs.rteObj && this.$refs.rteObj.ej2Instances) {
  this.$refs.rteObj.ej2Instances.focusIn();
}
    },
    formatMentionTemplate(value) {
      return `{{${value}}}`;
    },
    displayTemplate(data) {
      return `${data.value}`;
    },
    onItemSelect: function(args) {
      if (args && args.item && args.item.text) {
        const value = this.textToValueMap[args.item.text.trim()];
        if (value && this.$refs.rteObj && this.$refs.rteObj.ej2Instances) {
          this.$refs.rteObj.ej2Instances.formatter.editorManager.nodeSelection.restore();
          this.$refs.rteObj.ej2Instances.executeCommand('insertHTML', `<span contenteditable="false" class="e-mention-chip">{{${value}}}</span> `, { undo: true });
        } else {
          console.log('No matching value found in textToValueMap or RichTextEditor is not initialized.');
        }
      }
    },
    onActionBegin(args) {
      if (args.requestType === 'EnterAction' && this.$refs.mentionObj && this.$refs.mentionObj.ej2Instances && this.$refs.mentionObj.ej2Instances.element.classList.contains('e-popup-open')) {
        args.cancel = true;
      }
    },
    OnActionComplete(e) {
      if (e.requestType === 'SourceCode') {
        this.$refs.rteObj.ej2Instances.getToolbar().querySelector('#merge_data').parentElement.classList.add('e-overlay');
        this.$refs.rteObj.ej2Instances.getToolbar().querySelector('#insertField').parentElement.classList.add('e-overlay');
      } else if (e.requestType === 'Preview') {
        this.$refs.rteObj.ej2Instances.getToolbar().querySelector('#merge_data').parentElement.classList.remove('e-overlay');
        this.$refs.rteObj.ej2Instances.getToolbar().querySelector('#insertField').parentElement.classList.remove('e-overlay');
      }
    },
    onClickHandler() {
      if (this.$refs.rteObj && this.$refs.rteObj.ej2Instances) {
        let editorContent = this.$refs.rteObj.ej2Instances.value || '';
        let mergedContent = this.replacePlaceholders(editorContent, this.placeholderData);
        if (this.$refs.rteObj.ej2Instances.formatter.getUndoRedoStack() && this.$refs.rteObj.ej2Instances.formatter.getUndoRedoStack().length === 0) {
          this.$refs.rteObj.ej2Instances.formatter.saveData();
        }
        this.value = mergedContent;
        this.$refs.rteObj.ej2Instances.formatter.saveData();
        this.$refs.rteObj.trigger('change');
      } else {
        console.log('MailMergeEditor is not initialized.');
      }
    },
    replacePlaceholders(template, data) {
      return template.replace(/{{\s*(\w+)\s*}}/g, (match, key) => {
        const value = data[key.trim()];
        return value !== undefined ? value : match;
      });
    },
  },
};
</script>


<style>
@import "../../node_modules/@syncfusion/ej2-base/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-inputs/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-lists/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-popups/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-buttons/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-navigations/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-splitbuttons/styles/tailwind3.css";
@import "../../node_modules/@syncfusion/ej2-vue-richtexteditor/styles/tailwind3.css";
</style>