Integration Patterns
Common patterns for integrating MLForm into your applications and workflows.
Backend Integration
Simple REST API
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://your-api.com/predict');
// The form will automatically send inputs to this endpoint
mlForm.onSubmit((inputs, response) => {
// Handle the response
console.log('Prediction:', response.outputs?.[0]?.prediction);
});
await mlForm.toHTMLElement(schema, container);
Backend Endpoint Requirements:
- Method:
POST - Content-Type:
application/json - Request Body:
{
"inputs": {
"field_name": "value",
"another_field": 42
}
}
- Response Format:
{
"outputs": [
{
"type": "classifier",
"prediction": "class_A",
"confidence": 0.95
}
]
}
With Error Handling
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://your-api.com/predict');
mlForm.onSubmit(async (inputs, response) => {
try {
// Check if response is valid
if (!response?.outputs) {
throw new Error('No outputs in response');
}
const output = response.outputs[0];
if (!output?.prediction) {
throw new Error('Missing prediction field');
}
// Process valid response
displayResult(output.prediction);
} catch (error) {
console.error('Error processing response:', error);
showErrorMessage('Failed to get prediction');
}
});
Framework Integration
React
import { useEffect, useRef } from 'react';
import { MLForm } from 'mlform';
export function PredictionForm({ schema, onPrediction }) {
const containerRef = useRef<HTMLDivElement>(null);
const mlFormRef = useRef<MLForm | null>(null);
useEffect(() => {
if (!containerRef.current) return;
// Initialize MLForm
mlFormRef.current = new MLForm('https://api.example.com/predict');
// Register strategies if needed
// mlFormRef.current.register(new CustomStrategy());
// Subscribe to submissions
const unsubscribe = mlFormRef.current.onSubmit((inputs, response) => {
onPrediction({ inputs, response });
});
// Render form
mlFormRef.current.toHTMLElement(schema, containerRef.current);
// Cleanup
return () => {
unsubscribe();
};
}, [schema, onPrediction]);
return <div ref={containerRef} />;
}
// Usage
export function App() {
const handlePrediction = ({ inputs, response }) => {
console.log('Predicted:', response.outputs?.[0]?.prediction);
};
return (
<PredictionForm
schema={{
inputs: [{ type: 'text', title: 'Input' }],
outputs: [{ type: 'classifier', title: 'Output' }]
}}
onPrediction={handlePrediction}
/>
);
}
Vue
import { onMounted, onUnmounted, ref } from 'vue';
import { MLForm } from 'mlform';
export default {
props: {
schema: Object,
backendUrl: String
},
emits: ['prediction'],
setup(props, { emit }) {
const container = ref<HTMLDivElement>();
let mlForm: MLForm | null = null;
let unsubscribe: (() => void) | null = null;
onMounted(async () => {
if (!container.value) return;
mlForm = new MLForm(props.backendUrl);
unsubscribe = mlForm.onSubmit((inputs, response) => {
emit('prediction', { inputs, response });
});
await mlForm.toHTMLElement(props.schema, container.value);
});
onUnmounted(() => {
if (unsubscribe) unsubscribe();
});
return { container };
},
template: '<div ref="container" />'
};
// Usage in parent
<PredictionForm
:schema="formSchema"
:backend-url="apiEndpoint"
@prediction="handlePrediction"
/>
Angular
import { Component, Input, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { MLForm } from 'mlform';
@Component({
selector: 'app-prediction-form',
template: '<div #container></div>'
})
export class PredictionFormComponent implements OnInit, OnDestroy {
@Input() schema: any;
@Input() backendUrl: string = 'https://api.example.com/predict';
@ViewChild('container') containerRef!: ElementRef;
private mlForm: MLForm | null = null;
private unsubscribe: (() => void) | null = null;
ngOnInit() {
this.initializeForm();
}
private async initializeForm() {
if (!this.containerRef?.nativeElement) return;
this.mlForm = new MLForm(this.backendUrl);
this.unsubscribe = this.mlForm.onSubmit((inputs, response) => {
console.log('Prediction received:', response.outputs?.[0]?.prediction);
});
await this.mlForm.toHTMLElement(this.schema, this.containerRef.nativeElement);
}
ngOnDestroy() {
if (this.unsubscribe) this.unsubscribe();
}
}
Form Customization
Multiple Strategies Registration
import { MLForm } from 'mlform';
import {
ColorPickerStrategy,
SliderStrategy,
FileUploadStrategy
} from './custom-strategies';
const mlForm = new MLForm('https://api.example.com/predict');
// Register all custom strategies
mlForm.register(new ColorPickerStrategy());
mlForm.register(new SliderStrategy());
mlForm.register(new FileUploadStrategy());
// Now users can choose from all available field types
const schema = {
inputs: [
{ type: 'text', title: 'Name' },
{ type: 'color', title: 'Favorite Color' },
{ type: 'slider', title: 'Confidence Level' },
{ type: 'file', title: 'Upload Data' }
],
outputs: [{ type: 'classifier', title: 'Prediction' }]
};
Dynamic Strategy Registration
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://api.example.com/predict');
// Load strategies based on configuration
async function setupStrategies(config) {
for (const strategyName of config.strategies) {
const module = await import(`./strategies/${strategyName}`);
const Strategy = module.default;
mlForm.register(new Strategy());
}
}
// Usage
await setupStrategies({
strategies: ['text', 'number', 'custom-color']
});
Updating Strategies
import { MLForm } from 'mlform';
import { v1: TextStrategyV1, v2: TextStrategyV2 } from './text-strategies';
const mlForm = new MLForm('https://api.example.com/predict');
// Register v1
mlForm.register(new TextStrategyV1());
// Render form (uses v1)
await mlForm.toHTMLElement(schema, container);
// Later, update to v2
mlForm.update(new TextStrategyV2());
// Re-render with updated strategy
await mlForm.toHTMLElement(schema, container);
Schema Management
Schema Validation Before Rendering
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://api.example.com/predict');
const schema = {
inputs: [
{
type: 'text',
title: 'User Input',
minLength: 5,
maxLength: 100,
pattern: '^[a-zA-Z0-9]+$'
}
],
outputs: [{ type: 'classifier', title: 'Result' }]
};
// Validate before rendering
const validation = await mlForm.validateSchema(schema);
if (!validation.success) {
console.error('Schema has errors:');
console.error(validation.error.format());
return;
}
// Safe to render
await mlForm.toHTMLElement(schema, container);
JSON Schema Generation
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://api.example.com/predict');
// Register all strategies
mlForm.register(new TextStrategy());
mlForm.register(new NumberStrategy());
// Generate JSON Schema
const jsonSchema = mlForm.schema();
// Use for API documentation
console.log(JSON.stringify(jsonSchema, null, 2));
// Or save to file for tooling
fs.writeFileSync('schema.json', JSON.stringify(jsonSchema, null, 2));
TypeScript Schema Inference
import { MLForm } from 'mlform';
import { type Infer } from 'mlform';
import * as z from 'zod';
// Define with Zod
const FormSchema = z.strictObject({
inputs: z.array(z.object({
type: z.string(),
title: z.string(),
required: z.optional(z.boolean())
})),
outputs: z.array(z.object({
type: z.enum(['classifier', 'regressor']),
title: z.optional(z.string())
}))
});
// Extract TypeScript type
type FormSchemaType = Infer<typeof FormSchema>;
// Fully typed schema object
const myForm: FormSchemaType = {
inputs: [
{ type: 'text', title: 'Name', required: true }
],
outputs: [
{ type: 'classifier', title: 'Prediction' }
]
};
const mlForm = new MLForm('https://api.example.com/predict');
await mlForm.toHTMLElement(myForm, container);
Data Handling
Pre-filling Forms
import { MLForm } from 'mlform';
const schema = {
inputs: [
{ type: 'text', title: 'Name', value: 'John Doe' },
{ type: 'number', title: 'Age', value: 30, min: 0, max: 120 },
{ type: 'category', title: 'Country', options: ['USA', 'UK', 'CA'], value: 'USA' }
],
outputs: []
};
const mlForm = new MLForm('https://api.example.com/predict');
await mlForm.toHTMLElement(schema, container);
Accessing Last Submission
import { MLForm } from 'mlform';
const mlForm = new MLForm('https://api.example.com/predict');
mlForm.onSubmit((inputs, response) => {
console.log('Form submitted');
});
// Wait for user to submit
await mlForm.toHTMLElement(schema, container);
// Later, access previous data
button.addEventListener('click', () => {
const lastInputs = mlForm.lastInputs;
const lastResponse = mlForm.lastResponse;
if (lastInputs) {
console.log('Last submitted values:', lastInputs);
}
if (lastResponse?.outputs?.length) {
console.log('Last prediction:', lastResponse.outputs[0]);
}
});
Form State Management
import { MLForm } from 'mlform';
class FormManager {
private mlForm: MLForm;
private submissions: Array<{ inputs: any; response: any }> = [];
private currentIndex = -1;
constructor(backendUrl: string) {
this.mlForm = new MLForm(backendUrl);
this.setupListeners();
}
private setupListeners() {
this.mlForm.onSubmit((inputs, response) => {
this.submissions.push({ inputs, response });
this.currentIndex = this.submissions.length - 1;
});
}
async render(schema: any, container: HTMLElement) {
await this.mlForm.toHTMLElement(schema, container);
}
getCurrentSubmission() {
return this.submissions[this.currentIndex] || null;
}
getAllSubmissions() {
return this.submissions;
}
getLastInputs() {
return this.mlForm.lastInputs;
}
}
// Usage
const manager = new FormManager('https://api.example.com/predict');
await manager.render(schema, container);
// Later
console.log(manager.getAllSubmissions());
Advanced Patterns
Conditional Form Rendering
import { MLForm } from 'mlform';
async function createConditionalForm(userType: string, container: HTMLElement) {
const mlForm = new MLForm('https://api.example.com/predict');
let schema = {
inputs: [
{ type: 'text', title: 'Name', required: true }
],
outputs: []
};
// Add fields based on user type
if (userType === 'premium') {
schema.inputs.push(
{ type: 'color', title: 'Preference' },
{ type: 'slider', title: 'Confidence' }
);
} else {
schema.inputs.push(
{ type: 'category', title: 'Category', options: ['A', 'B', 'C'] }
);
}
await mlForm.toHTMLElement(schema, container);
mlForm.onSubmit((inputs) => {
console.log(`${userType} submitted:`, inputs);
});
}
// Usage
await createConditionalForm('premium', container);
Chained Predictions
import { MLForm } from 'mlform';
async function chainedPredictions() {
const mlForm1 = new MLForm('https://api.example.com/model1');
const mlForm2 = new MLForm('https://api.example.com/model2');
const schema1 = {
inputs: [{ type: 'text', title: 'Input' }],
outputs: [{ type: 'classifier', title: 'Output' }]
};
const container1 = document.getElementById('form1')!;
await mlForm1.toHTMLElement(schema1, container1);
// First model's output becomes second model's input
mlForm1.onSubmit((inputs, response) => {
const result = response.outputs?.[0]?.prediction;
const schema2 = {
inputs: [
{ type: 'text', title: 'First Result', value: String(result) },
{ type: 'number', title: 'Additional Input' }
],
outputs: [{ type: 'regressor', title: 'Final Output' }]
};
const container2 = document.getElementById('form2')!;
mlForm2.toHTMLElement(schema2, container2);
});
}
chainedPredictions();
Multi-Model Ensemble
import { MLForm } from 'mlform';
async function ensembleModels() {
const models = [
new MLForm('https://api.example.com/model1'),
new MLForm('https://api.example.com/model2'),
new MLForm('https://api.example.com/model3')
];
const schema = {
inputs: [{ type: 'text', title: 'Input', required: true }],
outputs: [{ type: 'classifier', title: 'Prediction' }]
};
const container = document.getElementById('form')!;
// Use first model's form
await models[0].toHTMLElement(schema, container);
const predictions: any[] = [];
// Collect predictions from all models
models.forEach((model, index) => {
model.onSubmit(async (inputs, response) => {
predictions[index] = response.outputs?.[0]?.prediction;
// If all models have responded, show ensemble result
if (predictions.every(p => p !== undefined)) {
const result = majorityVote(predictions);
showEnsembleResult(result);
}
});
});
// Submit to all models simultaneously
function submitToAll(inputs: any) {
models.forEach(model => {
// Trigger submission to each model
// (This would require exposing a method or simulating form submission)
});
}
}
function majorityVote(predictions: any[]): any {
const counts = new Map<any, number>();
predictions.forEach(p => {
counts.set(p, (counts.get(p) || 0) + 1);
});
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
}
Testing Patterns
Mocking for Tests
import { MLForm } from 'mlform';
import { describe, it, expect, vi } from 'vitest';
describe('Form integration', () => {
it('handles predictions correctly', async () => {
// Mock fetch
global.fetch = vi.fn(() =>
Promise.resolve(
new Response(JSON.stringify({
outputs: [{
type: 'classifier',
prediction: 'success'
}]
}))
)
);
const mlForm = new MLForm('https://api.example.com/predict');
const schema = {
inputs: [{ type: 'text', title: 'Test' }],
outputs: [{ type: 'classifier', title: 'Result' }]
};
const container = document.createElement('div');
await mlForm.toHTMLElement(schema, container);
// Simulate user input and submission
const predictions: any[] = [];
mlForm.onSubmit((inputs, response) => {
predictions.push(response.outputs?.[0]?.prediction);
});
expect(fetch).toHaveBeenCalled();
});
});
Schema Validation Testing
import { MLForm } from 'mlform';
import { describe, it, expect } from 'vitest';
describe('Schema validation', () => {
it('rejects invalid schemas', async () => {
const mlForm = new MLForm('https://api.example.com');
const invalidSchema = {
inputs: [
{
type: 'invalid-type',
title: 'Field'
}
],
outputs: []
};
const validation = await mlForm.validateSchema(invalidSchema);
expect(validation.success).toBe(false);
});
it('accepts valid schemas', async () => {
const mlForm = new MLForm('https://api.example.com');
const validSchema = {
inputs: [
{ type: 'text', title: 'Name', required: true }
],
outputs: [{ type: 'classifier', title: 'Prediction' }]
};
const validation = await mlForm.validateSchema(validSchema);
expect(validation.success).toBe(true);
});
});
Performance Optimization
Lazy Loading Strategies
import { FieldStrategy } from 'mlform/extensions';
// Strategy loads component only when needed
class OptimizedStrategy extends FieldStrategy {
constructor() {
super(
'color',
ColorSchema,
// Component only imports when strategy is used
() => import(/* webpackChunkName: "color-field" */ './color-field')
);
}
buildControl(field) {
return { tag: 'color-field', props: { value: field.value } };
}
}
Memoizing Validation
import { MLForm } from 'mlform';
class CachedMLForm {
private mlForm: MLForm;
private schemaCache = new Map<string, any>();
constructor(backendUrl: string) {
this.mlForm = new MLForm(backendUrl);
}
async validateSchemaCached(schema: any) {
const key = JSON.stringify(schema);
if (this.schemaCache.has(key)) {
return this.schemaCache.get(key);
}
const result = await this.mlForm.validateSchema(schema);
this.schemaCache.set(key, result);
return result;
}
async renderIfValid(schema: any, container: HTMLElement) {
const validation = await this.validateSchemaCached(schema);
if (!validation.success) return false;
await this.mlForm.toHTMLElement(schema, container);
return true;
}
}
Related Documentation
- Complete API Reference - Full API documentation
- Usage Guide - Step-by-step tutorial
- Basic Form Example - Simple form setup
- ML Classification Example - ML integration