Introduction
When working with Object DOM - representing user interfaces as JavaScript objects rather than template strings - one of the biggest challenges developers face is maintaining readability in deeply nested structures. Unlike HTML's self-documenting closing tags (</div>
), JavaScript objects can quickly become an unreadable maze of closing brackets.
This article explores innovative formatting conventions that solve these readability problems, making Object DOM code as clear and maintainable as traditional HTML templates.
The Readability Problem
Consider this typical Object DOM structure without proper formatting:
// ❌ Hard to read and maintain
const component = {div:{className:'main',children:[{header:{className:'app-header',children:[{h1:{text:'My App'}},{nav:{children:[{a:{href:'/home',text:'Home'}},{a:{href:'/about',text:'About'}}]}}]}},{main:{className:'content',children:[{section:{children:[{h2:{text:'Welcome'}},{p:{text:'This is the main content'}}]}}]}}]}}
Where does each component end? What's the structure? It's nearly impossible to read or debug.
The Juris Formatting Philosophy
The Juris framework (GitHub | NPM) introduced a revolutionary approach to Object DOM formatting with three core principles:
1. Static vs. Reactive Property Distinction
Rule: Static, short properties stay inline. Reactive or complex properties get new lines.
// ✅ Clear distinction between static and reactive
{div:{className:'user-card', id:'user-123', // Static props: inline
text: () => getState('user.displayName'), // Reactive prop: new line
style: () => ({ // Complex reactive: new line
backgroundColor: getState('theme.cardColor', '#white'),
opacity: getState('user.isActive') ? 1 : 0.5
}),
onClick: () => selectUser() // Event handler: new line
}}
Why this works: The visual formatting immediately communicates the nature of each property:
- Inline = Won't change during component lifecycle
- New line = Will update based on state or user interaction
2. End-Bracket Labeling
Rule: Add descriptive comments to closing brackets to create visual hierarchy.
// ✅ Clear structural hierarchy
{div:{className:'dashboard',
children:[
{header:{className:'dashboard-header',
children:[
{h1:{text:'Dashboard'}},
{nav:{
children:[
{a:{href:'/home', text:'Home'}},
{a:{href:'/reports', text:'Reports'}}
]
}}//nav
]
}}//header
{main:{className:'dashboard-content',
children:[
{section:{className:'widgets',
children: () => renderWidgets()
}}//section.widgets
{aside:{className:'sidebar',
children: () => renderSidebar()
}}//aside.sidebar
]
}}//main
]
}}//div.dashboard
Labeling variations:
-
}}//button
- Simple tag name -
}}//div.user-card
- Tag with key class -
}}//section.widgets
- Tag with descriptive class -
}}//header.app-header
- Tag with component role
3. Compressed Object Structure
Rule: Minimize unnecessary whitespace while maintaining readability through strategic line breaks.
// ✅ Compressed but readable
{form:{className:'contact-form', onSubmit:handleSubmit,
children:[
{div:{className:'field-group',
children:[
{label:{htmlFor:'email', text:'Email'}},
{input:{id:'email', type:'email', required:true,
value: () => getState('form.email', ''),
onInput: (e) => setState('form.email', e.target.value)
}}
]
}}//div.field-group
{div:{className:'field-group',
children:[
{label:{htmlFor:'message', text:'Message'}},
{textarea:{id:'message', rows:4,
value: () => getState('form.message', ''),
onInput: (e) => setState('form.message', e.target.value)
}}
]
}}//div.field-group
{button:{type:'submit', className:'btn-primary',
text: () => getState('form.isSubmitting') ? 'Sending...' : 'Send Message'
}}//button
]
}}//form.contact-form
Advanced Formatting Patterns
1. Children Array Formatting
When dealing with children arrays, use consistent indentation and labeling:
// ✅ Well-formatted children array
{div:{className:'product-grid',
children:[
// Static items
{div:{className:'grid-header',
children:[
{h2:{text:'Products'}},
{button:{className:'add-btn', text:'Add Product'}}
]
}}//div.grid-header
// Dynamic items with clear separation
...() => getState('products', []).map(product => ({
div:{key:product.id, className:'product-card',
children:[
{img:{src:product.image, alt:product.name}},
{h3:{text:product.name}},
{p:{className:'price', text:`$${product.price}`}},
{button:{text:'Add to Cart',
onClick: () => addToCart(product.id)
}}//button
]
}//div.product-card
})),
// Footer section
{div:{className:'grid-footer',
children:[
{p:{text: () => `Showing ${getState('products', []).length} products`}}
]
}}//div.grid-footer
]
}}//div.product-grid
2. Conditional Rendering Patterns
Format conditional logic clearly within the structure:
// ✅ Clear conditional formatting
{div:{className:'user-profile',
children:[
{div:{className:'profile-header',
children:[
{img:{src: () => getState('user.avatar'), alt:'Profile picture'}},
{h2:{text: () => getState('user.name')}}
]
}}//div.profile-header
// Conditional premium features
...() => {
const isPremium = getState('user.isPremium', false);
return isPremium ? [
{div:{className:'premium-badge',
children:[
{span:{text:'✨ Premium Member'}},
{button:{text:'Manage Subscription',
onClick: () => navigate('/subscription')
}}//button
]
}}//div.premium-badge
{div:{className:'premium-features',
children: () => renderPremiumFeatures()
}}//div.premium-features
] : [
{div:{className:'upgrade-prompt',
children:[
{p:{text:'Upgrade to unlock premium features'}},
{button:{className:'btn-upgrade', text:'Upgrade Now',
onClick: () => navigate('/upgrade')
}}//button
]
}}//div.upgrade-prompt
];
}()
]
}}//div.user-profile
3. Complex State Dependencies
Handle complex reactive patterns with clear formatting:
// ✅ Complex state handling with clear structure
{div:{className:'shopping-cart',
children:[
{div:{className:'cart-header',
children:[
{h2:{text:'Shopping Cart'}},
{span:{className:'item-count',
text: () => {
const items = getState('cart.items', []);
const count = items.reduce((sum, item) => sum + item.quantity, 0);
return `${count} item${count !== 1 ? 's' : ''}`;
}
}}
]
}}//div.cart-header
{div:{className:'cart-items',
children: () => {
const items = getState('cart.items', []);
if (items.length === 0) {
return [{div:{className:'empty-cart',
children:[
{p:{text:'Your cart is empty'}},
{button:{text:'Continue Shopping',
onClick: () => navigate('/products')
}}//button
]
}}];//div.empty-cart
}
return items.map(item => ({
div:{key:item.id, className:'cart-item',
children:[
{img:{src:item.image, alt:item.name}},
{div:{className:'item-details',
children:[
{h4:{text:item.name}},
{p:{className:'item-price', text:`$${item.price}`}}
]
}}//div.item-details
{div:{className:'quantity-controls',
children:[
{button:{text:'-',
onClick: () => updateQuantity(item.id, item.quantity - 1)
}},
{span:{text:item.quantity}},
{button:{text:'+',
onClick: () => updateQuantity(item.id, item.quantity + 1)
}}
]
}}//div.quantity-controls
{button:{className:'remove-item', text:'Remove',
onClick: () => removeFromCart(item.id)
}}//button
]
}//div.cart-item
}));
}
}}//div.cart-items
{div:{className:'cart-footer',
children:[
{div:{className:'cart-total',
children:[
{span:{text:'Total: '}},
{strong:{
text: () => {
const items = getState('cart.items', []);
const total = items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
return `$${total.toFixed(2)}`;
}
}}
]
}}//div.cart-total
{button:{className:'checkout-btn',
disabled: () => getState('cart.items', []).length === 0,
text:'Proceed to Checkout',
onClick: () => navigate('/checkout')
}}//button
]
}}//div.cart-footer
]
}}//div.shopping-cart
Formatting Tools and Conventions
1. Indentation Rules
- Use 2 spaces for each nesting level
- Align object properties at the same level
- Break before complex functions but keep simple ones inline
// ✅ Consistent indentation
{div:{className:'container',
children:[
{header:{role:'banner',
children:[
{h1:{text:'Site Title'}},
{nav:{
children: () => getNavigationItems() // Complex function: new line
}}//nav
]
}}//header
{main:{role:'main', className:'content', // Simple props: inline
children: () => renderMainContent() // Complex function: new line
}}//main
]
}}//div.container
2. Property Ordering
Establish a consistent order for properties:
- Structural properties (key, className, id)
- Semantic properties (role, aria-*, htmlFor)
- Static content (text, src, href)
- Reactive content (dynamic text, style)
- Event handlers (onClick, onInput, etc.)
- Children (always last)
// ✅ Consistent property ordering
{button:{
key: item.id, // 1. Structural
className: 'action-btn', // 1. Structural
role: 'button', // 2. Semantic
'aria-label': 'Add to favorites', // 2. Semantic
type: 'button', // 3. Static content
disabled: () => getState('ui.isLoading'), // 4. Reactive content
onClick: () => toggleFavorite(item.id) // 5. Event handlers
}}//button
3. Comment Conventions
Use comments strategically to explain complex logic:
// ✅ Strategic commenting
{div:{className:'data-visualization',
children:[
// Chart header with dynamic title
{div:{className:'chart-header',
children:[
{h3:{
text: () => {
const timeRange = getState('chart.timeRange', '7d');
const dataType = getState('chart.dataType', 'revenue');
return `${dataType} over ${timeRange}`;
}
}}
]
}}//div.chart-header
// Main chart area - lazy loaded for performance
{div:{className:'chart-container',
children: () => {
const isVisible = getState('chart.isVisible', false);
return isVisible ? [ChartComponent(context)] : [
{div:{className:'chart-placeholder',
children:[
{button:{text:'Load Chart',
onClick: () => setState('chart.isVisible', true)
}}//button
]
}}//div.chart-placeholder
];
}
}}//div.chart-container
]
}}//div.data-visualization
Common Formatting Mistakes
1. Inconsistent Line Breaking
// ❌ Inconsistent formatting
{div:{className:'card',text: () => getState('title'),
style: {padding: '20px'},
children:[{p:{text:'Content'}}]
}}
// ✅ Consistent formatting
{div:{className:'card',
text: () => getState('title'),
style: {padding: '20px'},
children:[
{p:{text:'Content'}}
]
}}//div.card
2. Missing or Inconsistent Labels
// ❌ Missing labels make structure unclear
{div:{
children:[
{header:{
children:[{h1:{text:'Title'}}]
}}
{main:{
children:[{p:{text:'Content'}}]
}}
]
}}
// ✅ Clear labeling shows structure
{div:{className:'page',
children:[
{header:{
children:[
{h1:{text:'Title'}}
]
}}//header
{main:{
children:[
{p:{text:'Content'}}
]
}}//main
]
}}//div.page
3. Overcomplicating Simple Structures
// ❌ Unnecessarily complex for simple content
{div:{
className: 'simple-card',
children: [
{
h3: {
text: 'Simple Title'
}
},
{
p: {
text: 'Simple description'
}
}
]
}}
// ✅ Keep simple structures simple
{div:{className:'simple-card',
children:[
{h3:{text:'Simple Title'}},
{p:{text:'Simple description'}}
]
}}//div.simple-card
Team Conventions and Style Guides
1. Establishing Team Standards
Create a style guide for your team:
// Team Style Guide Example
// Property ordering (always follow this order):
// 1. key, className, id
// 2. role, aria-*, semantic attributes
// 3. type, src, href, static props
// 4. reactive props (functions)
// 5. event handlers
// 6. children (always last)
// Labeling conventions:
// - Simple tags: }}//button
// - With main class: }}//div.user-card
// - With semantic role: }}//section.hero
// - With state context: }}//form.login-form
// Line breaking rules:
// - Static props under 50 chars: inline
// - Functions and reactive props: new line
// - Complex objects: new line
// - Event handlers: new line
2. Linting and Automated Formatting
Consider creating ESLint rules or Prettier configurations:
// .eslintrc.js - Custom rules for Object DOM
module.exports = {
rules: {
// Enforce consistent Object DOM formatting
'object-dom/consistent-labeling': 'error',
'object-dom/reactive-on-new-line': 'error',
'object-dom/property-ordering': 'warn'
}
};
Performance Considerations
1. Formatting for Optimization
Good formatting can actually help with performance by making optimization opportunities more visible:
// ✅ Clear formatting reveals optimization opportunities
{div:{className:'user-list',
children: () => {
const users = getState('users.filtered', []); // Memoizable
const selectedId = getState('ui.selectedUser'); // Simple state
return users.map(user => ({
div:{key:user.id, // Stable key for reconciliation
className: selectedId === user.id ? 'user selected' : 'user',
children:[
{img:{src:user.avatar, alt:user.name}}, // Static content
{span:{text:user.name}}, // Static content
{span:{ // Dynamic content clearly separated
text: () => getState(`users.${user.id}.status`, 'offline')
}}
]
}//div.user
}));
}
}}//div.user-list
2. Avoiding Re-render Triggers
Proper formatting helps identify potential re-render issues:
// ✅ Clear separation of stable vs. dynamic props
{div:{className:'expensive-component', // Stable
key: item.id, // Stable
'data-testid': 'item-card', // Stable
style: () => ({ // Dynamic - clearly marked
backgroundColor: getState('theme.cardColor'),
borderColor: getState('ui.focusedItem') === item.id ? 'blue' : 'gray'
}),
onClick: () => selectItem(item.id) // Event handler - clearly separated
}}
Conclusion
Proper Object DOM formatting is not just about aesthetics—it's about creating maintainable, debuggable, and collaborative code. The Juris formatting philosophy provides a proven approach that:
- Makes structure immediately visible through end-bracket labeling
- Communicates intent through inline vs. new-line distinctions
- Reduces cognitive load by providing visual hierarchies
- Improves maintainability by making changes safer and easier
- Enhances team collaboration through consistent conventions
The key insight is that Object DOM formatting should serve the same purpose as HTML's visual structure—making the component hierarchy and behavior immediately clear to any developer reading the code.
By adopting these conventions, teams can harness the full power of Object DOM while maintaining the readability and maintainability that makes code sustainable over time.
Top comments (0)