Skip to content

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:

  1. Inconsistent approaches between different specialized blocks
  2. Complex and error-prone lookup logic in components
  3. Disconnects between how subblocks were defined in configuration UI vs. rendered in Creative-Engine
  4. 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 blockName and appropriate blockType
  • 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:

  • CreativeTypeMixin
  • StyleAndClassNameGenerationMixin
  • BlockMixin
typescript
mixins: [CreativeTypeMixin, StyleAndClassNameGenerationMixin, BlockMixin]

Case Study: CreativeFormBlock Refactoring

Previous Implementation Issues

The previous implementation of CreativeFormBlock had several issues:

  1. Complex Lookup Logic: The component used a multi-step process to find form inputs:

    • First checking typedBlock.inputs array for references
    • Then looking up each reference in DataStore
    • Falling back to formProps.subBlocks if needed
  2. Inconsistent Approach: Unlike CreativeSliderBlock, which directly filtered block values, FormBlock used indirect references through arrays.

  3. Performance and Maintenance Issues: The complex lookup logic was difficult to maintain and could impact performance.

Refactoring Process

  1. Created a Utility Function: Implemented getFormInputBlocks in blockUtils.ts

    typescript
    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. Simplified Component Logic: Updated the FormBlock's formInputs computed property to use the utility function directly, similar to how SliderBlock handles its subblocks.

  3. Added BlockMixin: Ensured FormBlock uses the same mixins as other specialized blocks for consistency.

Benefits of the New Approach

  1. Consistency: All specialized blocks now follow the same pattern for subblock handling.

  2. Simplicity: Components use a simple, dedicated utility function instead of complex nested lookups.

  3. Maintainability: Changes to subblock structure only need to be updated in one place (the utility function).

  4. Performance: Direct filtering of block values is more efficient than indirect lookups.

  5. 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:

  1. Define the block type constant in constants/blocks.ts (both codebases)

  2. Create a utility function in blockUtils.ts to filter subblocks of the appropriate type:

    typescript
    export 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)
    }
  3. In your specialized block component, implement a computed property that uses this utility function

  4. Include the necessary mixins: CreativeTypeMixin, StyleAndClassNameGenerationMixin, and BlockMixin

Common Pitfalls and Solutions

  1. Missing blockName or blockType: Ensure all subblocks have these properties set correctly.

  2. Incorrect Import Paths: Double-check import paths for utility functions and constants.

  3. TypeScript Type Issues: When implementing new utility functions, provide proper type annotations to avoid compilation errors.

  4. Order Property: Remember that subblocks are sorted by the order property, so ensure this is set correctly when creating new subblocks.

  5. 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.

Internal documentation