Skip to content

Guide for blocks (form module) # Adding Block Components to the Builder

This document outlines the step-by-step process for adding new block components to the Cavai Application-Frontend BuilderVisuals system. The guide uses the recently implemented Form block as a practical example.

## Block Architecture Overview

There are **two types of blocks** in the system:

### 1. Visual Elements **Simple blocks without subblocks** (Text, Graphic, HTML, Button)

  • Rendered through CreativeVisualElements.vue
  • Must be included in VISUAL_ELEMENTS array
  • blockName in defaults.ts must contain a string from VISUAL_ELEMENTS
  • Can have multiple instances with dynamic keys (e.g., 'textProperties-1')

### 2. Specialized Blocks Complex blocks with subblocks (Video, Slider, Conversation, Form)

  • Have dedicated rendering components
  • Imported directly in CreativeBody.vue
  • Render their subblocks internally
  • NOT included in VISUAL_ELEMENTS array
  • Use static keys (e.g., 'formProperties', not 'formProperties-1')
  • Usually only one instance allowed per creative
  • Show tooltip and disable add button when one exists
  • Must be included in the isDeletable function in utils.ts to get full context menu functionality

## Adding a Visual Element (Simple Block)

### 1. Define the Block Type Constant

Add a new constant in src/constants/blocks.ts and register it in the VISUAL_ELEMENTS array:

typescript
export const BLOCKS = {
  // Existing blocks...
  SIMPLE_BLOCK: 'simpleBlockProperties',
};

export const VISUAL_ELEMENTS = [
  BLOCKS.TEXT,
  BLOCKS.GRAPHIC,
  BLOCKS.HTML,
  BLOCKS.BUTTON,
  BLOCKS.SIMPLE_BLOCK,
];

## Adding a Specialized Block (Complex Block with Subblocks)

### 1. Define the Block Type Constant

Add a new constant in src/constants/blocks.ts (do NOT add to VISUAL_ELEMENTS array):

typescript
export const BLOCKS = {
  // Existing blocks...
  FORM: 'formProperties',
};

// Note: FORM is NOT added to VISUAL_ELEMENTS array
export const VISUAL_ELEMENTS = [BLOCKS.TEXT, BLOCKS.GRAPHIC, BLOCKS.HTML, BLOCKS.BUTTON];

### 2. Define the Type Structure

Create a new type in src/pages/Chatbots/components/BuilderVisuals/Blocks/data/types.ts:

typescript
export type FormProperties = BlockBase & {
  // Form-specific properties
  formAction?: string;
  formMethod?: string;
  // Subblocks will be added as properties following allowedSubBlocks pattern
  // Example: inputSection1?: InputProperties
  //          buttonSection1?: ButtonProperties
};

Make sure to also import this type in any files that need it.

### 3. Define Default Values with Subblocks

Create default values for your block in src/pages/Chatbots/components/BuilderVisuals/Blocks/data/defaults.ts:

typescript
export const formDefaults = (): FormProperties => ({
  ...baseDefaults(),
  blockType: BLOCKS.FORM,
  blockName: 'formProperties', // Important: Static key, not dynamic like 'formProperties-1'
  formAction: '',
  formMethod: 'POST',
});

Register your defaults in the blockDefaults map:

typescript
export const blockDefaults: { [key: string]: () => BlockBase } = {
  // Existing defaults...
  [BLOCKS.FORM]: formDefaults,
};

### 4. Configure Allowed Subblocks

Add to allowedSubBlocks in src/pages/Chatbots/components/BuilderVisuals/Blocks/data/defaults.ts:

typescript
export const allowedSubBlocks = {
  // Existing subblocks...
  [BLOCKS.FORM]: {
    inputSection: { type: BLOCKS.TEXT, count: 10 },
    buttonSection: { type: BLOCKS.BUTTON, count: 5 },
  },
};

### 5. Add UI Support

Add the block to the blocks array in src/pages/Chatbots/components/BuilderVisuals/Blocks/AddBlockTool.vue:

javascript
blocks() {
  const blocks = [
    // Existing blocks...
    { name: BLOCKS.FORM, icon: 'form' },
  ]
  // Rest of function...
}

### 6. Add i18n Support

Add new keys in src/assets/i18n/en.js:

}],

formButtons: [{ order: 1, blockName: 'formButtonProperties', displayName: 'Submit Button', parent: 'formProperties', blockType: 'formButtonProperties', // ... other properties }],

// Subblock configuration allowedSubBlocks: ['formInputProperties', 'formButtonProperties'], subBlocksReorderable: true, addSubBlockButtonTextPath: 'visuals.form.addFormBlock', // ... other properties }

`

8.2 Add Subblock Methods to Configuration ​

In your configuration component, add methods to manage subblocks:

typescript
import { getSubBlocks } from '@/pages/Chatbots/components/BuilderVisuals/Blocks/utils';

export default {
  computed: {
    // Essential: Add subBlocks computed property
    subBlocks() {
      return getSubBlocks(this.blockData);
    },
  },
  methods: {
    // Add subblock
    addFormInput() {
      const newInput = {
        order: this.blockData.formInputs.length,
        blockName: 'formInputProperties',
        displayName: `Input Field ${this.blockData.formInputs.length + 1}`,
        parent: 'formProperties',
        blockType: 'formInputProperties',
        // ... other default properties
      };

      const updatedInputs = [...this.blockData.formInputs, newInput];
      this.updateValue(updatedInputs, 'formInputs');
    },

    // Remove subblock
    removeFormInput(index) {
      if (this.blockData.formInputs.length > 1) {
        const updatedInputs = this.blockData.formInputs.filter((_, i) => i !== index);
        // Update order for remaining inputs
        updatedInputs.forEach((input, i) => {
          input.order = i;
        });
        this.updateValue(updatedInputs, 'formInputs');
      }
    },

    // Update subblock properties
    updateFormInput(index, property, value) {
      const updatedInputs = cloneDeep(this.blockData.formInputs);
      updatedInputs[index][property] = value;
      this.updateValue(updatedInputs, 'formInputs');
    },
  },
};

### 8.3 Key Subblock Patterns

  1. **getSubBlocks Utility**: The getSubBlocks(block) function finds all values in a block that have a blockName property and sorts them by order.

  2. **Subblock Properties**: Each subblock must have:

    • blockName: Identifier for the subblock type
    • order: Number for sorting
    • parent: Parent block's blockName
    • blockType: Type identifier
    • displayName: Human-readable name
  3. **Array Management**: Subblocks are stored in arrays (e.g., formInputs, formButtons) and managed using standard array operations.

  4. **Order Management**: When removing subblocks, update the order property of remaining items to maintain correct sorting.

## 9. Implement Rendering Logic

### 9.1 For Standard Visual Elements

Implement the logic to render the block in the UI according to its properties within CreativeVisualElements.vue.

### 9.2 For Specialized Blocks (like Form, Slider)

  1. Create a dedicated component in src/components/creative/VisualElements/ (e.g., CreativeFormBlock.vue)

  2. Import it directly in CreativeBody.vue:

javascript
import CreativeFormBlock from '@/components/creative/VisualElements/CreativeFormBlock.vue';
  1. Register in components:
javascript
components: {
  CreativeFormBlock,
  // other components
},
  1. Add a showFormBlock computed property:
javascript
showFormBlock(): boolean {
  return blockVisible(BLOCKS.FORM)
},
  1. Add to blockOrders in CreativeBody.vue:
javascript
blockOrders() {
  const { creativeBlocks } = DataStore?.creativeSettings
  if (!creativeBlocks) {
    return {}
  }

  return {
    // other blocks
    form: creativeBlocks.formProperties?.order ?? -1,
  }
},
  1. Add to template in CreativeBody.vue:
html
<template v-if="showFormBlock">
  <CreativeFormBlock :order="blockOrders.form" />
</template>
  1. Note that specialized blocks like Form use static keys in payload:
javascript
// For specialized blocks like Form, the key is static:
// creativeBlocks.formProperties (not formProperties-1)

## 10. Test and Validate

  • Verify the block appears in AddBlockTool
  • For specialized blocks, verify that:
    • The add button is disabled with tooltip when one instance exists
    • Only one instance is allowed
  • Check that configuration works as expected
  • Ensure the block renders correctly
  • Test subblock functionality (if implemented)
    • Add/remove subblocks
    • Configure subblock properties
    • Verify subblock rendering

## 11. Common Issues and Debugging

### UI Behavior Issues

  • **Add button not disabling**: Make sure the block is correctly categorized (visual element or specialized) and properly removed from VISUAL_ELEMENTS array if it's a specialized block

### Rendering Issues

  • **Block not showing**: Check blockVisible implementation and whether the correct key is used
  • **Missing in blockOrders**: Make sure specialized block is added to blockOrders in CreativeBody.vue
  • **Not imported**: Ensure the block component is imported and registered in CreativeBody.vue

### Payload Issues

  • Missing in payload: Check for console logs in LocalBuildPreview.vue to verify block structure
  • Wrong keys: For specialized blocks, use static key ('formProperties') not dynamic ('formProperties-1')

Advanced Subblock Management: Form Block Example ​

This section provides a detailed example of implementing a specialized block with subblocks, using the Form block implementation as a practical reference.

📖 For complete form system documentation, see Form System Complete Guide

Creating a Factory Function for Subblocks ​

For blocks with multiple subblocks of the same type, it's recommended to create a factory function that generates default subblock values. This makes it easier to maintain consistency and add new subblocks:

typescript
// Factory function for creating form input subblocks
const formInputDefault = (): FormInputProperties => ({
  order: 0,
  blockName: 'formInput',
  displayName: 'Form Input',
  parent: 'formProperties',
  hidden: false,
  blockType: 'formInputProperties',
  type: 'text',
  label: 'Label',
  placeholder: 'Enter text here',
  required: false,
  // Additional styling and properties...
});

Managing Subblock Arrays in Parent Block ​

Store subblocks within an array property in the parent block. This allows for dynamic management of multiple subblocks:

typescript
const formDefaults: FormProperties = {
  // Standard block properties
  blockName: 'formProperties',
  blockType: 'formProperties',
  displayName: 'Form',
  // Important: Define the allowed subblock types
  allowedSubBlocks: ['formInputProperties'],
  subBlocksReorderable: true,
  // i18n path for "Add Input" button
  addSubBlockButtonTextPath: 'visuals.blocks.addInput',
  // Array of subblocks created using the factory function
  formInputs: [formInputDefault()],
  // Additional styling and properties...
};

Subblock Type Structure Best Practices ​

When defining TypeScript types for blocks with subblocks, make sure to:

  1. Include blockType in the subblock type definition
  2. Define clear parent-child relationships via the parent property
  3. Use type unions or enums for properties that have specific allowed values
typescript
export type FormInputProperties = SubBlockBase & {
  blockType: string; // Matches the key used in allowedSubBlocks
  type: 'text' | 'email' | 'number' | 'checkbox' | 'select'; // Type-safe input types
  // Other properties...
};

Best Practices ​

  • Follow naming conventions from existing code
  • Use TypeScript for types and interfaces
  • Use i18n for all UI text
  • Follow Vue component structure (template, script, style)
  • Make commits in logical, small units
  • For specialized blocks with subblocks:
    • Use static keys (e.g., 'formProperties')
    • Do NOT add to VISUAL_ELEMENTS array
    • Import and use block directly in CreativeBody.vue
    • Implement showXxxBlock computed property using blockVisible
    • Add to blockOrders computed property in CreativeBody.vue

Cross-Codebase Implementation: Creative-Engine ​

After implementing a new block in Application-Frontend, you also need to implement it in the Creative-Engine to enable proper rendering in previews and ads. This is especially important for specialized blocks like Form, Slider, etc.

1. Register Block in Constants ​

Add your block to the BLOCKS constant in Creative-Engine's src/utils/constants.ts:

typescript
export const BLOCKS = {
  // Existing blocks
  TEXT: 'textProperties',
  GRAPHIC: 'graphicProperties',
  // Add your new block
  FORM: 'formProperties'
}

2. Define Interfaces in payload-v2 ​

Define the block interfaces in src/interfaces/jsonTypes/payload-v2/index.ts:

typescript
// Define interface for subblocks (if any)
export type FormInputProperties = {
  blockName: string
  blockType: string
  parent: string
  order: number
  type: 'text' | 'email' | 'number' | 'textarea' | 'checkbox' | 'select'
  label: string
  placeholder: string
  required: boolean
  options?: string[]
}

// Define the main block interface
export type FormProperties = BoxShadowConfig &
  BasicConfig &
  OptionalSizeConfig &
  FlexAlignConfig &
  CustomStylesConfig &
  BackgroundBlurConfig & {
    formTitle?: string
    submitButtonText?: string
    submitButtonBgColor?: string
    submitButtonTextColor?: string
    successMessage?: string
    rotate: number
    // Include any styling properties used in the component
    position?: string
    backgroundColor?: string
    borderRadius?: string
    padding?: string
    // etc.
  }

3. Update CreativeBlocks Interface ​

Add your block to the CreativeBlocks interface in the same file:

typescript
export type CreativeBlocks = {
  // Existing blocks
  textProperties: TextProperties
  graphicProperties: GraphicProperties
  // Add your new block
  formProperties?: FormProperties  // Optional since not all creatives will have a form
}

4. Create the Rendering Component ​

Create a new Vue component in src/components/creative/VisualElements/ (e.g., CreativeFormBlock.vue) that handles the rendering of your block:

vue
<template>
  <div :class="[blockClassNames.wrap]">
    <!-- Your block template here -->
    <form @submit.prevent="handleSubmit">
      <!-- Form inputs from subblocks -->
      <button type="submit">{{ typedBlock.submitButtonText || 'Submit' }}</button>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { FormProperties } from '@/interfaces/jsonTypes/payload-v2'
import CreativeTypeMixin from '@/mixins/CreativeTypeMixin'
import { StyleAndClassNameGenerationMixin } from '@/mixins/StyleAndClassNameGenerationMixin'
import { VisualElementMixin } from '@/mixins/VisualElementMixin'
import { BlockMixin } from '@/mixins/BlockMixin'
import { getSubBlocks } from '@/utils/blockUtils'

export default defineComponent({
  name: 'CreativeFormBlock',
  mixins: [CreativeTypeMixin, VisualElementMixin, BlockMixin, StyleAndClassNameGenerationMixin],
  // Rest of the component...
})
</script>

5. Register in CreativeVisualElements ​

If your block is rendered through CreativeVisualElements.vue (like most visual elements), update it to include your block:

  1. Import your component at the top of CreativeVisualElements.vue:
typescript
import CreativeFormBlock from '@/components/creative/VisualElements/CreativeFormBlock.vue'
  1. Add it to the components registration:
typescript
components: { CreativeButtonBlock, CreativeHtmlBlock, CreativeGraphicBlock, CreativeTextBlock, CreativeFormBlock },
  1. Add a computed property for finding your block type:
typescript
formBlocks(): Partial<CreativeBlocks> {
  const callback = (block: any) => block.blockType === BLOCKS.FORM
  return pickBy(this.visualElements, callback)
},
  1. Add the template element for rendering your blocks:
html
<template v-for="block in formBlocks" :key="block.blockName">
  <creative-form-block :block="block" />
</template>

6. For Specialized Blocks: Add to CreativeBody ​

For specialized blocks like Form, Slider, etc. that need direct importing in CreativeBody.vue:

  1. Import your component in CreativeBody.vue
  2. Add a showFormBlock computed property that uses blockVisible to determine visibility
  3. Add the block's order to the blockOrders computed property
  4. Add the template rendering section in the appropriate position

IMPORTANT: When adding the component to the template, make sure to pass both the :block and :order props:

vue
<template v-if="showFormBlock">
  <CreativeFormBlock 
    :block="DataStore.creativeSettings.creativeBlocks.formProperties" 
    :order="blockOrders.form" 
  />
</template>

Missing the :block prop will cause runtime errors like Cannot read properties of undefined since the component relies on this prop to access block properties.

7. Build Creative-Engine Library ​

After making changes to Creative-Engine, rebuild the library:

bash
npm run build:library

8. Verify Integration ​

Check that your block renders correctly in the preview by:

  1. Adding the block in BuilderVisuals
  2. Configuring it with relevant properties
  3. Checking the preview rendering
  4. Inspecting any console errors

Common Integration Issues ​

  1. Missing interfaces in payload-v2 leading to TypeScript errors
  2. Inconsistent property names between Application-Frontend and Creative-Engine
  3. Missing block registration in BLOCKS constant
  4. Incomplete component implementation in CreativeVisualElements.vue
  5. Forgotten library rebuild after Creative-Engine changes

## Commit Structure Example

  1. First commit: Add block type definition and register as visual element

    • Changes to blocks.ts
    • Changes to types.ts
  2. Second commit: Add UI support and i18n

    • Changes to AddBlockTool.vue
    • Changes to en.js
  3. Third commit: Add icon

    • Create form.svg
  4. Additional commits for configuration UI, rendering, etc.


This document will be updated as the implementation of the Form block progresses and as the process evolves.

Internal documentation