Appearance
Specialized Block Subblock Handling Pattern
Overview
This document outlines the recommended pattern for handling subblocks in specialized block components (such as Form, Slider, etc.) within the Cavai Creative-Engine. It explains the implementation of a consistent subblock handling mechanism that aligns with the frontend configuration system.
Problem Statement
Specialized blocks in the BuilderVisuals system often contain child blocks (subblocks) that need to be managed, rendered, and configured consistently across both the Application-Frontend and Creative-Engine codebases. Previously, different block types implemented their own custom logic for subblock discovery and rendering, leading to:
- Inconsistent approaches between different specialized blocks
- Complex and error-prone lookup logic in components
- Disconnects between how subblocks were defined in configuration UI vs. rendered in Creative-Engine
- Regression issues when code was updated in one place but not the other
Solution: Standardized Subblock Handling Pattern
We've established a standardized pattern for subblock handling, exemplified by the CreativeSliderBlock and CreativeFormBlock components:
1. Create a Dedicated Utility Function
For each specialized block type, add a utility function in blockUtils.ts that:
- Takes a parent block as input
- Filters its values for objects with
blockNameand appropriateblockType - Returns them sorted by order
typescript
// Example for Form Input blocks
export const getFormInputBlocks = (block: Record<string, any>): any[] => {
if (!block) {
return []
}
return Object.values(block)
.filter((value: any) => {
return value?.blockName && value?.blockType === 'formInputProperties'
})
.sort((a: any, b: any) => a.order - b.order)
}2. Use the Utility in the Component's Computed Property
In the specialized block component, implement a computed property that uses the utility function:
typescript
// In CreativeFormBlock.vue
formInputs() {
if (!this.typedBlock) {
console.debug('[FormBlock] No typedBlock found')
return []
}
try {
// Get form input blocks directly using the utility function
const formInputBlocks = getFormInputBlocks(this.typedBlock)
return formInputBlocks
} catch (err) {
console.error('[FormBlock] Error getting form inputs:', err)
return []
}
}3. Include Necessary Mixins
Specialized blocks should include these mixins:
CreativeTypeMixinStyleAndClassNameGenerationMixinBlockMixin
typescript
mixins: [CreativeTypeMixin, StyleAndClassNameGenerationMixin, BlockMixin]Case Study: CreativeFormBlock Refactoring
Previous Implementation Issues
The previous implementation of CreativeFormBlock had several issues:
Complex Lookup Logic: The component used a multi-step process to find form inputs:
- First checking
typedBlock.inputsarray for references - Then looking up each reference in DataStore
- Falling back to
formProps.subBlocksif needed
- First checking
Inconsistent Approach: Unlike CreativeSliderBlock, which directly filtered block values, FormBlock used indirect references through arrays.
Performance and Maintenance Issues: The complex lookup logic was difficult to maintain and could impact performance.
Refactoring Process
Created a Utility Function: Implemented
getFormInputBlocksin blockUtils.tstypescriptexport const getFormInputBlocks = (block: Record<string, any>): any[] => { if (!block) { return [] } return Object.values(block) .filter((value: any) => { return value?.blockName && value?.blockType === 'formInputProperties' }) .sort((a: any, b: any) => a.order - b.order) }Simplified Component Logic: Updated the FormBlock's
formInputscomputed property to use the utility function directly, similar to how SliderBlock handles its subblocks.Added BlockMixin: Ensured FormBlock uses the same mixins as other specialized blocks for consistency.
Benefits of the New Approach
Consistency: All specialized blocks now follow the same pattern for subblock handling.
Simplicity: Components use a simple, dedicated utility function instead of complex nested lookups.
Maintainability: Changes to subblock structure only need to be updated in one place (the utility function).
Performance: Direct filtering of block values is more efficient than indirect lookups.
Alignment with Frontend: The approach aligns with how blocks are structured in Application-Frontend's configuration system.
Implementation Guide for New Specialized Blocks
To implement this pattern for a new specialized block type:
Define the block type constant in
constants/blocks.ts(both codebases)Create a utility function in
blockUtils.tsto filter subblocks of the appropriate type:typescriptexport const getMySpecializedBlockSubblocks = (block: Record<string, any>): any[] => { if (!block) { return [] } return Object.values(block) .filter((value: any) => { return value?.blockName && value?.blockType === 'mySubblockType' }) .sort((a: any, b: any) => a.order - b.order) }In your specialized block component, implement a computed property that uses this utility function
Include the necessary mixins:
CreativeTypeMixin,StyleAndClassNameGenerationMixin, andBlockMixin
Common Pitfalls and Solutions
Missing blockName or blockType: Ensure all subblocks have these properties set correctly.
Incorrect Import Paths: Double-check import paths for utility functions and constants.
TypeScript Type Issues: When implementing new utility functions, provide proper type annotations to avoid compilation errors.
Order Property: Remember that subblocks are sorted by the
orderproperty, so ensure this is set correctly when creating new subblocks.BlockMixin Requirements: BlockMixin expects certain properties in block configs. If TypeScript errors occur, you may need to add these properties or use type assertions.
Conclusion
By following this standardized pattern for subblock handling, we ensure that all specialized blocks in the Creative-Engine behave consistently and align with the frontend configuration system. This improves maintainability, reduces bugs, and makes the codebase easier to understand for new developers.
The specific case of the CreativeFormBlock refactoring demonstrates how complex, error-prone code can be simplified by applying this pattern, resulting in more maintainable and efficient code.