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