Enhance Form and Process Builder Documentation
- Updated the Form Builder documentation to include integration details with the Process Builder, outlining how to connect forms to processes and manage form data. - Added a new section on Process Builder integration, detailing API endpoints for form selection and loading forms by URL parameters. - Improved the Process Builder documentation with instructions for adding form tasks, selecting forms, and handling form data within processes. - Updated last modified dates for documentation files to reflect recent changes.
This commit is contained in:
parent
bb5e4c0637
commit
668e08884e
@ -2,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
The Form Builder is a powerful drag-and-drop interface for creating dynamic forms. It provides an intuitive, visual way to build forms by selecting components, configuring their properties, and arranging them in your desired layout. Perfect for creating everything from simple contact forms to complex multi-step surveys.
|
||||
The Form Builder is a powerful drag-and-drop interface for creating dynamic forms. It provides an intuitive, visual way to build forms by selecting components, configuring their properties, and arranging them in your desired layout. Perfect for creating everything from simple contact forms to complex multi-step surveys. Forms can be used independently or integrated with the Process Builder for workflow automation.
|
||||
|
||||
> For technical implementation details, please refer to [Form Builder Technical Appendix](FORM_BUILDER_TECHNICAL_APPENDIX.md)
|
||||
|
||||
@ -136,6 +136,31 @@ Form structure and organization:
|
||||
- Custom CSS classes
|
||||
- Event handlers
|
||||
|
||||
## Process Builder Integration
|
||||
|
||||
The Form Builder integrates with the Process Builder to create workflow-driven forms:
|
||||
|
||||
### Connecting Forms to Processes
|
||||
1. **Create and Save Your Form**
|
||||
- Design your form in the Form Builder
|
||||
- Save the form with a clear name and description
|
||||
- Forms must be saved before they can be used in processes
|
||||
|
||||
2. **Using Forms in Process Builder**
|
||||
- In Process Builder, add a Form Task to your process
|
||||
- Select your form from the Form Selector dropdown
|
||||
- The form will be presented to users when they reach this task in the workflow
|
||||
|
||||
3. **Form Data in Processes**
|
||||
- Data submitted through forms becomes available as process variables
|
||||
- Use form data to drive process decisions in gateways
|
||||
- Reference form fields in scripts and conditions
|
||||
|
||||
### Form URL Parameters
|
||||
- Forms can accept URL parameters to pre-populate fields
|
||||
- Process variables can be passed to forms as parameters
|
||||
- Use the `formId` parameter to load a specific form
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Form Design
|
||||
@ -196,6 +221,11 @@ Form structure and organization:
|
||||
- Check field names are unique
|
||||
- Ensure validation is enabled
|
||||
|
||||
4. **Form Not Appearing in Process Builder**
|
||||
- Verify the form was saved successfully
|
||||
- Check user permissions
|
||||
- Refresh the Process Builder page
|
||||
|
||||
### Getting Help
|
||||
- Check the technical documentation
|
||||
- Contact support team
|
||||
@ -206,4 +236,4 @@ Form structure and organization:
|
||||
|
||||
For technical details about implementation, component structure, and development guidelines, please refer to the [Technical Appendix](FORM_BUILDER_TECHNICAL_APPENDIX.md).
|
||||
|
||||
Last updated: April 9, 2025
|
||||
Last updated: June 10, 2024
|
@ -43,6 +43,11 @@ composables/
|
||||
└── useToast.js # Toast notifications
|
||||
types/
|
||||
└── form-builder.d.ts # TypeScript definitions
|
||||
server/
|
||||
└── api/
|
||||
└── forms/
|
||||
├── index.js # Form API endpoints
|
||||
└── [id].js # Form by ID endpoints
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
@ -129,6 +134,8 @@ interface FormState {
|
||||
selectedComponentId: string | null;
|
||||
formName: string;
|
||||
formDescription: string;
|
||||
formId: string | null;
|
||||
formUUID: string | null;
|
||||
isDraggingOver: boolean;
|
||||
savedForms: SavedForm[];
|
||||
}
|
||||
@ -139,20 +146,89 @@ export const useFormBuilderStore = defineStore('formBuilder', {
|
||||
selectedComponentId: null,
|
||||
formName: 'New Form',
|
||||
formDescription: '',
|
||||
formId: null,
|
||||
formUUID: null,
|
||||
isDraggingOver: false,
|
||||
savedForms: []
|
||||
}),
|
||||
|
||||
getters: {
|
||||
selectedComponent: (state) => // Implementation
|
||||
formConfig: (state) => // Implementation
|
||||
selectedComponent: (state) => {
|
||||
if (!state.selectedComponentId) return null;
|
||||
return state.formComponents.find(c => c.id === state.selectedComponentId);
|
||||
},
|
||||
|
||||
formConfig: (state) => {
|
||||
return {
|
||||
id: state.formId,
|
||||
uuid: state.formUUID,
|
||||
name: state.formName,
|
||||
description: state.formDescription,
|
||||
components: state.formComponents
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
addComponent(component: FormComponent) // Implementation
|
||||
updateComponent(id: string, updates: Partial<FormComponent>) // Implementation
|
||||
deleteComponent(id: string) // Implementation
|
||||
moveComponent(oldIndex: number, newIndex: number) // Implementation
|
||||
addComponent(component: FormComponent) {
|
||||
this.formComponents.push({
|
||||
...component,
|
||||
id: component.id || uuidv4()
|
||||
});
|
||||
},
|
||||
|
||||
updateComponent(id: string, updates: Partial<FormComponent>) {
|
||||
const index = this.formComponents.findIndex(c => c.id === id);
|
||||
if (index !== -1) {
|
||||
this.formComponents[index] = {
|
||||
...this.formComponents[index],
|
||||
...updates
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
deleteComponent(id: string) {
|
||||
const index = this.formComponents.findIndex(c => c.id === id);
|
||||
if (index !== -1) {
|
||||
this.formComponents.splice(index, 1);
|
||||
if (this.selectedComponentId === id) {
|
||||
this.selectedComponentId = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
moveComponent(oldIndex: number, newIndex: number) {
|
||||
if (oldIndex === newIndex) return;
|
||||
const component = this.formComponents.splice(oldIndex, 1)[0];
|
||||
this.formComponents.splice(newIndex, 0, component);
|
||||
},
|
||||
|
||||
saveForm() {
|
||||
return fetch('/api/forms', {
|
||||
method: this.formId ? 'PUT' : 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(this.formConfig)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.formId = data.formID;
|
||||
this.formUUID = data.formUUID;
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
loadForm(id: string) {
|
||||
return fetch(`/api/forms/${id}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.formId = data.formID;
|
||||
this.formUUID = data.formUUID;
|
||||
this.formName = data.formName;
|
||||
this.formDescription = data.formDescription;
|
||||
this.formComponents = data.components;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
@ -226,6 +302,104 @@ const gridSystem = {
|
||||
};
|
||||
```
|
||||
|
||||
## Process Builder Integration
|
||||
|
||||
### API Endpoints for Form Selection
|
||||
```javascript
|
||||
// server/api/forms/index.js
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Get all forms for selection in Process Builder
|
||||
const forms = await prisma.form.findMany({
|
||||
select: {
|
||||
formID: true,
|
||||
formUUID: true,
|
||||
formName: true,
|
||||
formDescription: true,
|
||||
createdAt: true,
|
||||
updatedAt: true
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: 'desc'
|
||||
}
|
||||
});
|
||||
|
||||
return { forms };
|
||||
} catch (error) {
|
||||
console.error('Error fetching forms:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to fetch forms'
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Form Loading by URL Parameter
|
||||
```javascript
|
||||
// pages/form-builder/index.vue
|
||||
const route = useRoute();
|
||||
const formStore = useFormBuilderStore();
|
||||
|
||||
// Check for formId parameter to load a specific form
|
||||
onMounted(async () => {
|
||||
const formId = route.query.formId;
|
||||
if (formId) {
|
||||
try {
|
||||
await formStore.loadForm(formId);
|
||||
} catch (error) {
|
||||
console.error('Error loading form:', error);
|
||||
useToast().error('Failed to load form');
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Form Selection Component
|
||||
```vue
|
||||
<!-- components/process-flow/FormSelector.vue -->
|
||||
<template>
|
||||
<div class="form-selector">
|
||||
<h4 class="text-sm font-medium mb-2">Select Form</h4>
|
||||
|
||||
<div v-if="loading" class="text-center py-4">
|
||||
<span class="loading"></span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" class="text-center py-4 text-red-500">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="forms.length === 0" class="text-center py-4 text-gray-500">
|
||||
No forms available
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-2">
|
||||
<div
|
||||
v-for="form in forms"
|
||||
:key="form.formID"
|
||||
class="form-item p-2 border rounded hover:bg-gray-50 cursor-pointer"
|
||||
:class="{ 'border-blue-400 bg-blue-50': modelValue === form.formID }"
|
||||
@click="selectForm(form)"
|
||||
>
|
||||
<div class="font-medium">{{ form.formName }}</div>
|
||||
<div class="text-xs text-gray-500">{{ form.formDescription }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button
|
||||
v-if="modelValue"
|
||||
@click="clearSelection"
|
||||
class="text-sm text-red-500 hover:underline"
|
||||
>
|
||||
Clear Selection
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Event Handling
|
||||
|
||||
### Component Events
|
||||
@ -355,4 +529,4 @@ npm run preview
|
||||
|
||||
For user documentation and usage guidelines, please refer to [Form Builder Documentation](FORM_BUILDER_DOCUMENTATION.md).
|
||||
|
||||
Last updated: April 9, 2025
|
||||
Last updated: June 10, 2024
|
@ -62,12 +62,13 @@ Activities represent work performed in a process:
|
||||
- **Form Task** (Purple Icon)
|
||||
- A task that requires form data
|
||||
- Has both input and output handles
|
||||
- Properties: Form name, description
|
||||
- Properties: Form selection, description
|
||||
- Connects to forms created in the Form Builder
|
||||
|
||||
- **Script Task** (Grey Icon)
|
||||
- Automated task that executes code
|
||||
- Has both input and output handles
|
||||
- Properties: Language, description
|
||||
- Properties: Language, description, script content
|
||||
|
||||
### Gateways
|
||||
Gateways control flow divergence and convergence:
|
||||
@ -75,7 +76,7 @@ Gateways control flow divergence and convergence:
|
||||
- **Gateway** (Orange Icon)
|
||||
- Creates alternative paths based on conditions
|
||||
- Has both input and output handles
|
||||
- Properties: Conditions, description
|
||||
- Properties: Conditions, default path, description
|
||||
|
||||
## Working with the Process Canvas
|
||||
|
||||
@ -88,7 +89,7 @@ Gateways control flow divergence and convergence:
|
||||
- **Select**: Click on an element
|
||||
- **Multi-select**: Hold Shift while clicking elements
|
||||
- **Move**: Drag selected elements
|
||||
- **Delete**: Press Delete key or double-click element
|
||||
- **Delete**: Press Delete key, double-click element, or use the Delete button in the properties panel
|
||||
- **Connect**: Drag from one node's handle to another's
|
||||
|
||||
### Keyboard Shortcuts
|
||||
@ -123,6 +124,24 @@ Gateways control flow divergence and convergence:
|
||||
- Role-based assignments
|
||||
- Group assignments
|
||||
|
||||
## Form Integration
|
||||
|
||||
The Process Builder integrates with the Form Builder to allow forms to be attached to process tasks:
|
||||
|
||||
1. **Adding a Form Task**
|
||||
- Drag a Form Task component onto the canvas
|
||||
- Select the task to open its properties
|
||||
|
||||
2. **Selecting a Form**
|
||||
- In the properties panel, use the Form Selector to choose a form
|
||||
- Forms are listed from those created in the Form Builder
|
||||
- Selected forms will be displayed to users when they reach this task
|
||||
|
||||
3. **Form Data in Process**
|
||||
- Form submissions become available as process variables
|
||||
- Data can be referenced in gateway conditions
|
||||
- Form fields can be pre-populated with process data
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Process Design
|
||||
@ -155,13 +174,19 @@ Gateways control flow divergence and convergence:
|
||||
2. **Node Won't Delete**
|
||||
- Make sure the node is selected
|
||||
- Try using the Delete key
|
||||
- Alternative: double-click the node
|
||||
- Use the Delete button in the properties panel
|
||||
- Double-click the node to remove it
|
||||
|
||||
3. **Connection Looks Wrong**
|
||||
- Try repositioning nodes for better flow
|
||||
- Check that connections are made to correct handles
|
||||
- Consider using different connection types
|
||||
|
||||
4. **Form Not Showing in Selector**
|
||||
- Verify the form was saved in the Form Builder
|
||||
- Check that you have permission to access the form
|
||||
- Refresh the page to update the form list
|
||||
|
||||
### Getting Help
|
||||
- Use the control panel hints in top-right
|
||||
- Review this documentation
|
||||
@ -171,4 +196,4 @@ Gateways control flow divergence and convergence:
|
||||
|
||||
For technical details about implementation and integration, please refer to the [Process Builder Technical Documentation](PROCESS_BUILDER_TECHNICAL_APPENDIX.md).
|
||||
|
||||
Last updated: May 15, 2024
|
||||
Last updated: June 10, 2024
|
@ -37,7 +37,9 @@ pages/
|
||||
components/
|
||||
├── process-flow/
|
||||
│ ├── ProcessFlowCanvas.vue # Flow canvas
|
||||
│ └── ProcessFlowNodes.js # Custom node types
|
||||
│ ├── ProcessFlowNodes.js # Custom node types
|
||||
│ ├── FormSelector.vue # Form selection component
|
||||
│ └── GatewayConditionManager.vue # Gateway conditions UI
|
||||
stores/
|
||||
└── processBuilder.js # State management
|
||||
composables/
|
||||
@ -121,6 +123,39 @@ const handleConnect = (connection) => {
|
||||
|
||||
addEdges([newEdge]);
|
||||
};
|
||||
|
||||
// Handle node deletion
|
||||
const onNodeDelete = (event) => {
|
||||
if (event && event.node) {
|
||||
removeNodes([event.node]);
|
||||
emit('nodesChange', nodes.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle edge deletion
|
||||
const onEdgeDelete = (event) => {
|
||||
if (event && event.edge) {
|
||||
removeEdges([event.edge]);
|
||||
emit('edgesChange', edges.value);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle delete key press
|
||||
const onDeleteKeyPress = () => {
|
||||
const { getSelectedNodes, getSelectedEdges } = flowInstance.value;
|
||||
const selectedNodes = getSelectedNodes();
|
||||
const selectedEdges = getSelectedEdges();
|
||||
|
||||
if (selectedNodes.length > 0) {
|
||||
removeNodes(selectedNodes);
|
||||
emit('nodesChange', nodes.value);
|
||||
}
|
||||
|
||||
if (selectedEdges.length > 0) {
|
||||
removeEdges(selectedEdges);
|
||||
emit('edgesChange', edges.value);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
@ -172,6 +207,62 @@ export const nodeTypes = markRaw({
|
||||
});
|
||||
```
|
||||
|
||||
3. **FormSelector.vue**
|
||||
```vue
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'select']);
|
||||
|
||||
const forms = ref([]);
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Load available forms from the API
|
||||
const loadForms = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/forms');
|
||||
if (!response.ok) throw new Error('Failed to load forms');
|
||||
|
||||
const data = await response.json();
|
||||
forms.value = data.forms || [];
|
||||
} catch (err) {
|
||||
error.value = err.message;
|
||||
console.error('Error loading forms:', err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Select a form
|
||||
const selectForm = (form) => {
|
||||
emit('update:modelValue', form.formID);
|
||||
emit('select', form);
|
||||
};
|
||||
|
||||
// Clear form selection
|
||||
const clearSelection = () => {
|
||||
emit('update:modelValue', null);
|
||||
emit('select', null);
|
||||
};
|
||||
|
||||
// Load forms on component mount
|
||||
onMounted(() => {
|
||||
loadForms();
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Process Builder Store
|
||||
@ -182,9 +273,27 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
currentProcess: null,
|
||||
selectedNodeId: null,
|
||||
selectedEdgeId: null,
|
||||
history: [],
|
||||
historyIndex: -1,
|
||||
unsavedChanges: false
|
||||
}),
|
||||
|
||||
getters: {
|
||||
selectedNode: (state) => {
|
||||
if (!state.currentProcess || !state.selectedNodeId) return null;
|
||||
return state.currentProcess.nodes.find(node => node.id === state.selectedNodeId);
|
||||
},
|
||||
|
||||
selectedEdge: (state) => {
|
||||
if (!state.currentProcess || !state.selectedEdgeId) return null;
|
||||
return state.currentProcess.edges.find(edge => edge.id === state.selectedEdgeId);
|
||||
},
|
||||
|
||||
hasUnsavedChanges: (state) => {
|
||||
return state.unsavedChanges;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
createProcess(name, description) {
|
||||
const process = {
|
||||
@ -196,26 +305,74 @@ export const useProcessBuilderStore = defineStore('processBuilder', {
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
this.processes.push(process);
|
||||
this.currentProcess = process;
|
||||
this.currentProcess = JSON.parse(JSON.stringify(process)); // Deep clone
|
||||
this.clearHistory();
|
||||
this.unsavedChanges = false;
|
||||
},
|
||||
|
||||
updateNode(nodeData) {
|
||||
if (!this.currentProcess || !nodeData.id) return;
|
||||
|
||||
const nodeIndex = this.currentProcess.nodes.findIndex(
|
||||
node => node.id === nodeData.id
|
||||
);
|
||||
|
||||
if (nodeIndex > -1) {
|
||||
this.currentProcess.nodes[nodeIndex] = {
|
||||
...this.currentProcess.nodes[nodeIndex],
|
||||
...nodeData
|
||||
};
|
||||
addNode(node) {
|
||||
if (!this.currentProcess) return;
|
||||
|
||||
const newNode = {
|
||||
id: node.id || uuidv4(),
|
||||
type: node.type,
|
||||
label: node.label || 'New Node',
|
||||
position: node.position || { x: 0, y: 0 },
|
||||
data: node.data || {}
|
||||
};
|
||||
|
||||
this.currentProcess.nodes.push(newNode);
|
||||
this.selectedNodeId = newNode.id;
|
||||
this.saveToHistory('Add node');
|
||||
this.unsavedChanges = true;
|
||||
|
||||
return newNode;
|
||||
},
|
||||
|
||||
updateNode(nodeId, updates) {
|
||||
if (!this.currentProcess) return;
|
||||
|
||||
const node = this.currentProcess.nodes.find(n => n.id === nodeId);
|
||||
if (node) {
|
||||
Object.assign(node, updates);
|
||||
this.saveToHistory('Update node');
|
||||
this.unsavedChanges = true;
|
||||
}
|
||||
},
|
||||
|
||||
// Additional actions...
|
||||
deleteNode(nodeId) {
|
||||
if (!this.currentProcess) return;
|
||||
|
||||
const index = this.currentProcess.nodes.findIndex(n => n.id === nodeId);
|
||||
if (index !== -1) {
|
||||
// Remove the node
|
||||
this.currentProcess.nodes.splice(index, 1);
|
||||
|
||||
// Remove any edges connected to this node
|
||||
const edgesToRemove = this.currentProcess.edges.filter(
|
||||
edge => edge.source === nodeId || edge.target === nodeId
|
||||
);
|
||||
|
||||
edgesToRemove.forEach(edge => {
|
||||
const edgeIndex = this.currentProcess.edges.findIndex(e => e.id === edge.id);
|
||||
if (edgeIndex !== -1) {
|
||||
this.currentProcess.edges.splice(edgeIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
// Clear selection if the deleted node was selected
|
||||
if (this.selectedNodeId === nodeId) {
|
||||
this.selectedNodeId = null;
|
||||
}
|
||||
|
||||
this.saveToHistory('Delete node');
|
||||
this.unsavedChanges = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
@ -232,9 +389,11 @@ interface NodeConfig {
|
||||
data: {
|
||||
description?: string;
|
||||
assignee?: string;
|
||||
formId?: string;
|
||||
formName?: string;
|
||||
language?: string;
|
||||
conditions?: string[];
|
||||
conditions?: Condition[];
|
||||
defaultPath?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@ -253,6 +412,18 @@ const nodeConfigs: Record<string, NodeConfig> = {
|
||||
iconColor: 'text-blue-500',
|
||||
data: { description: 'Task node', assignee: '' }
|
||||
},
|
||||
form: {
|
||||
type: 'form',
|
||||
label: 'Form Task',
|
||||
icon: 'description',
|
||||
iconColor: 'text-purple-500',
|
||||
data: {
|
||||
description: 'Form submission task',
|
||||
formId: null,
|
||||
formName: null,
|
||||
formUuid: null
|
||||
}
|
||||
},
|
||||
// Additional node configurations...
|
||||
};
|
||||
```
|
||||
@ -291,26 +462,154 @@ function createConnection(connection: Connection): Edge {
|
||||
}
|
||||
```
|
||||
|
||||
## Form Integration
|
||||
|
||||
### Form Task Implementation
|
||||
```typescript
|
||||
// Form task node implementation
|
||||
const FormNode = markRaw({
|
||||
props: ['id', 'type', 'label', 'selected', 'data'],
|
||||
render() {
|
||||
// Check if we have a form selected
|
||||
const hasForm = this.data?.formId && this.data?.formName;
|
||||
|
||||
// Create badge content based on form selection status
|
||||
const badgeContent = hasForm ?
|
||||
h('span', { class: 'node-badge bg-purple-100 text-purple-600 px-1 text-xs rounded' }, 'Form') :
|
||||
null;
|
||||
|
||||
return h(CustomNode, {
|
||||
id: this.id,
|
||||
type: 'form',
|
||||
label: this.label || 'Form Task',
|
||||
selected: this.selected,
|
||||
data: this.data,
|
||||
onClick: () => this.$emit('node-click', this.id)
|
||||
}, {
|
||||
icon: () => h('i', { class: 'material-icons text-purple-500' }, 'description'),
|
||||
badge: () => badgeContent,
|
||||
default: () => h('div', { class: 'node-details' }, [
|
||||
h('p', { class: 'node-description' }, this.data?.description || 'Form submission task'),
|
||||
h('div', { class: 'node-form-info' }, [
|
||||
h('span', { class: 'node-form-label' }, 'Form:'),
|
||||
h('span', {
|
||||
class: hasForm ? 'node-form-value text-purple-600 font-medium' : 'node-form-value text-gray-400 italic'
|
||||
}, hasForm ? this.data.formName : 'None selected')
|
||||
])
|
||||
])
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Form Selection in Process Builder
|
||||
```vue
|
||||
<!-- Form selection in process properties panel -->
|
||||
<div v-if="selectedNodeData.type === 'form'" class="space-y-3">
|
||||
<FormSelector
|
||||
v-model="selectedNodeData.data.formId"
|
||||
@select="handleFormSelection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<script setup>
|
||||
// Form selection handler
|
||||
const handleFormSelection = (form) => {
|
||||
if (selectedNodeData.value && form) {
|
||||
selectedNodeData.value.data.formId = form.formID;
|
||||
selectedNodeData.value.data.formName = form.formName;
|
||||
selectedNodeData.value.data.formUuid = form.formUUID;
|
||||
updateNodeInStore();
|
||||
}
|
||||
};
|
||||
|
||||
// Clear form selection
|
||||
const clearFormSelection = () => {
|
||||
if (selectedNodeData.value) {
|
||||
selectedNodeData.value.data.formId = null;
|
||||
selectedNodeData.value.data.formName = '';
|
||||
selectedNodeData.value.data.formUuid = null;
|
||||
updateNodeInStore();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Event Handling
|
||||
|
||||
### Node Events
|
||||
```typescript
|
||||
// Node selection
|
||||
function onNodeClick(node: Node): void {
|
||||
selectedNode.value = node;
|
||||
emit('nodeSelected', node);
|
||||
function onNodeClick({ node }): void {
|
||||
try {
|
||||
// Create a plain object copy of the node to avoid reactivity issues
|
||||
const nodeData = {
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
data: node.data ? JSON.parse(JSON.stringify(node.data)) : {},
|
||||
position: node.dimensions ? {
|
||||
x: node.dimensions.x || 0,
|
||||
y: node.dimensions.y || 0
|
||||
} : { x: 0, y: 0 }
|
||||
};
|
||||
|
||||
selectedNode.value = nodeData;
|
||||
emit('nodeSelected', nodeData);
|
||||
} catch (error) {
|
||||
console.error('Error processing node data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Node deletion
|
||||
function onNodeDelete(nodes: Node[]): void {
|
||||
removeNodes(nodes);
|
||||
emit('nodesChange', nodes.value);
|
||||
function onNodeDelete(event): void {
|
||||
// Check if we have a node in the event
|
||||
if (event && event.node) {
|
||||
removeNodes([event.node]);
|
||||
emit('nodesChange', nodes.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Node dragging
|
||||
function onNodeDragStop(node: Node): void {
|
||||
updateNodePosition(node);
|
||||
emit('nodePositionChange', node);
|
||||
// Handle delete key press
|
||||
function onDeleteKeyPress(): void {
|
||||
const { getSelectedNodes, getSelectedEdges } = flowInstance.value;
|
||||
const selectedNodes = getSelectedNodes();
|
||||
const selectedEdges = getSelectedEdges();
|
||||
|
||||
if (selectedNodes.length > 0) {
|
||||
removeNodes(selectedNodes);
|
||||
emit('nodesChange', nodes.value);
|
||||
}
|
||||
|
||||
if (selectedEdges.length > 0) {
|
||||
removeEdges(selectedEdges);
|
||||
emit('edgesChange', edges.value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edge Events
|
||||
```typescript
|
||||
// Edge selection
|
||||
function onEdgeClick(event, edge): void {
|
||||
// Create a simplified copy of the edge data
|
||||
const edgeData = {
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
label: edge.label || '',
|
||||
sourceNode: nodes.value.find(node => node.id === edge.source),
|
||||
targetNode: nodes.value.find(node => node.id === edge.target)
|
||||
};
|
||||
|
||||
emit('edgeSelected', edgeData);
|
||||
}
|
||||
|
||||
// Edge deletion
|
||||
function onEdgeDelete(event): void {
|
||||
if (event && event.edge) {
|
||||
removeEdges([event.edge]);
|
||||
emit('edgesChange', edges.value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -341,4 +640,4 @@ function onNodeDragStop(node: Node): void {
|
||||
|
||||
For user documentation and usage guidelines, please refer to [Process Builder Documentation](PROCESS_BUILDER_DOCUMENTATION.md).
|
||||
|
||||
Last updated: May 15, 2024
|
||||
Last updated: June 10, 2024
|
Loading…
x
Reference in New Issue
Block a user