Schema Definition
Schema definition for the Tabs node type:
{
tabs: {
content: 'block+',
group: 'block',
attrs: {
activeTab: { default: 0 }
},
}
}Model Representation
Example model representation:
{
type: 'tabs',
attrs: { activeTab: 0 },
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Tab 1 content' }]
},
{
type: 'paragraph',
children: [{ type: 'text', text: 'Tab 2 content' }]
}
]
}HTML Serialization
Converting model to HTML:
function serializeTabs(node) {
return '<div data-type="tabs" data-active="' + node.attrs.activeTab + '">' +
serializeChildren(node.children) + '</div>';
}HTML Deserialization
Parsing HTML to model:
function parseTabs(domNode) {
return {
type: 'tabs',
attrs: { activeTab: parseInt(domNode.getAttribute('data-active')) || 0 },
children: Array.from(domNode.childNodes)
.map(node => parseNode(node))
.filter(Boolean)
};
}View Integration
View Integration Notes: Pay special attention to contenteditable behavior, selection handling, and event management when implementing this node type in your view layer.
View integration code:
// Rendering tabs
const tabs = document.createElement('div');
tabs.setAttribute('data-type', 'tabs');
tabs.setAttribute('data-active', node.attrs.activeTab);
tabs.contentEditable = 'true';
node.children.forEach((child, index) => {
const tab = renderNode(child);
tab.setAttribute('data-tab-index', index);
tabs.appendChild(tab);
});
// Switch tab
function switchTab(tabs, index) {
tabs.setAttribute('data-active', index);
updateTabVisibility(tabs, index);
}Common Issues
Common Pitfalls: These are issues frequently encountered when implementing this node type. Review carefully before implementation.
Common issues and solutions:
// Issue: Tab state management
// Solution: Sync active tab between model and view
function syncTabState(tabs, model) {
tabs.setAttribute('data-active', model.attrs.activeTab);
}
// Issue: Tab visibility
// Solution: Show/hide tabs based on active state
function updateTabVisibility(tabs, activeIndex) {
tabs.querySelectorAll('[data-tab-index]').forEach((tab, index) => {
tab.style.display = index === activeIndex ? 'block' : 'none';
});
}Implementation
Complete implementation example:
class TabsNode {
constructor(attrs, children) {
this.type = 'tabs';
this.attrs = { activeTab: attrs?.activeTab || 0 };
this.children = children || [];
}
toDOM() {
const tabs = document.createElement('div');
tabs.setAttribute('data-type', 'tabs');
tabs.setAttribute('data-active', this.attrs.activeTab);
this.children.forEach((child, index) => {
const tab = child.toDOM();
tab.setAttribute('data-tab-index', index);
tabs.appendChild(tab);
});
return tabs;
}
static fromDOM(domNode) {
return new TabsNode(
{ activeTab: parseInt(domNode.getAttribute('data-active')) || 0 },
Array.from(domNode.childNodes)
.map(node => parseNode(node))
.filter(Boolean)
);
}
}