Appearance
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_ELEMENTSarray - 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_ELEMENTSarray - 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
isDeletablefunction inutils.tsto 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
**getSubBlocks Utility**: The
getSubBlocks(block)function finds all values in a block that have ablockNameproperty and sorts them by order.**Subblock Properties**: Each subblock must have:
blockName: Identifier for the subblock typeorder: Number for sortingparent: Parent block's blockNameblockType: Type identifierdisplayName: Human-readable name
**Array Management**: Subblocks are stored in arrays (e.g.,
formInputs,formButtons) and managed using standard array operations.**Order Management**: When removing subblocks, update the
orderproperty 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)
Create a dedicated component in
src/components/creative/VisualElements/(e.g., CreativeFormBlock.vue)Import it directly in CreativeBody.vue:
javascript
import CreativeFormBlock from '@/components/creative/VisualElements/CreativeFormBlock.vue';- Register in components:
javascript
components: {
CreativeFormBlock,
// other components
},- Add a showFormBlock computed property:
javascript
showFormBlock(): boolean {
return blockVisible(BLOCKS.FORM)
},- Add to blockOrders in CreativeBody.vue:
javascript
blockOrders() {
const { creativeBlocks } = DataStore?.creativeSettings
if (!creativeBlocks) {
return {}
}
return {
// other blocks
form: creativeBlocks.formProperties?.order ?? -1,
}
},- Add to template in CreativeBody.vue:
html
<template v-if="showFormBlock">
<CreativeFormBlock :order="blockOrders.form" />
</template>- 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:
- Include
blockTypein the subblock type definition - Define clear parent-child relationships via the
parentproperty - 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:
- Import your component at the top of
CreativeVisualElements.vue:
typescript
import CreativeFormBlock from '@/components/creative/VisualElements/CreativeFormBlock.vue'- Add it to the components registration:
typescript
components: { CreativeButtonBlock, CreativeHtmlBlock, CreativeGraphicBlock, CreativeTextBlock, CreativeFormBlock },- 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)
},- 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:
- Import your component in
CreativeBody.vue - Add a
showFormBlockcomputed property that usesblockVisibleto determine visibility - Add the block's order to the
blockOrderscomputed property - 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:library8. Verify Integration
Check that your block renders correctly in the preview by:
- Adding the block in BuilderVisuals
- Configuring it with relevant properties
- Checking the preview rendering
- Inspecting any console errors
Common Integration Issues
- Missing interfaces in payload-v2 leading to TypeScript errors
- Inconsistent property names between Application-Frontend and Creative-Engine
- Missing block registration in BLOCKS constant
- Incomplete component implementation in CreativeVisualElements.vue
- Forgotten library rebuild after Creative-Engine changes
## Commit Structure Example
First commit: Add block type definition and register as visual element
- Changes to
blocks.ts - Changes to
types.ts
- Changes to
Second commit: Add UI support and i18n
- Changes to
AddBlockTool.vue - Changes to
en.js
- Changes to
Third commit: Add icon
- Create
form.svg
- Create
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.