renderForm Service
Interface
The service creates a function that accepts two parameters:
this.renderForm(formAdaptable, options)
Parameters
formAdaptable
- An object that can be converted to a form adapter (typically database models, collections, or custom objects withtoFormAdapter()
method)options
- Configuration object with form settings and behavior
Description
The renderForm
service is a comprehensive form rendering and processing utility that:
- Renders interactive modal forms with automatic field generation, validation, and submission handling
- Processes form submissions when
_method=POST
, handling validation errors and success callbacks - Supports complex field types including text inputs, selects, checkboxes, textareas, file uploads, and custom components
- Provides anti-spam protection via proof-of-work challenges for sensitive forms
- Handles validation and error display with field-specific and general error messaging
- Enables conditional field rendering with
watch
functionality for dynamic forms - Supports custom success handlers for post-submission actions like redirects
The service automatically extracts field definitions from the form adaptable object and merges them with options to create a complete form interface.
Form Configuration Options
Basic Options
title
- Form modal title (string)fields
- Array of field definitions (overrides adaptable fields)submitTitle
- Submit button text (default: form adapter's submitTitle)cancelTitle
- Cancel button text (default: form adapter's cancelTitle)width
- Modal width ('small'
,'medium'
,'large'
, or custom)height
- Modal height ('auto'
,'small'
,'medium'
,'large'
, or custom)class
- Additional CSS classes for the form element
Security & Validation Options
requiresProofOfWork
- Enable anti-spam protection (boolean)validateWith
- Custom validation functionsuccess
- Success callback function for post-submission handlingunsavedChangesConfirm
- Warning message for unsaved changes
Field Configuration
Fields can be defined as strings (using defaults) or objects with detailed configuration:
String Fields
fields: ['name', 'email', 'title']
Object Field Properties
name
- Field name (required)label
- Display label (auto-generated from name if omitted)type
- Field type (see types below)value
- Default valueplaceholder
- Placeholder textcomponent
- Custom component identifierwatch
- Enable reactive updates (boolean)overlayLinks
- Array of action links displayed next to the labeloptions
- Object of value/label pairs (for select fields)
Supported Field Types
text
- Text input (default)password
- Password inputemail
- Email inputnumber
- Number inputdate
- Date pickerdatetime-local
- DateTime pickercheckbox
- Checkbox inputtextarea
- Multi-line text inputselect
- Dropdown with optionsfile
- File uploadhidden
- Hidden inputforced
- Server-side only value (not rendered)_*
- Custom component types (e.g.,_markdown_editor
)
Examples
Basic Form with Simple Fields
// Simple form with database model
this.renderForm(this.database.users, {
fields: ['name', 'email']
})
Form with Mixed Field Types
this.renderForm(this.database.posts, {
title: 'Create New Post',
fields: [
'title',
{ name: 'body', type: 'textarea', placeholder: 'Write your post content here...' },
{
name: 'status',
type: 'select',
value: 'draft',
options: {
draft: 'Draft',
published: 'Published',
archived: 'Archived'
}
},
{ name: 'featured', type: 'checkbox', label: 'Feature this post' }
],
submitTitle: 'Create Post',
width: 'large'
})
Form with Custom Validation and Success Handler
this.renderForm(this.database.users, {
title: 'Add User',
fields: [
'name',
'email',
{
name: 'role',
type: 'select',
value: 'user',
options: {
admin: 'Administrator',
user: 'Regular User'
}
}
],
// Custom validation
validateWith() {
if(!this.email.includes('@')) {
this.setValidationError('email', 'Must be a valid email address');
}
},
// Success handler with redirect
success({ slug }) {
return that.renderRedirect({
url: `/${slug}`,
target: '_top'
});
}
})
Authentication Form with Anti-Spam Protection
this.renderForm(
this.createModel({
meta() {
this.mustNotBeBlank('email');
this.mustBeAValidEmail('email');
}
}),
{
title: 'Sign In',
fields: [
{
name: 'email',
label: 'Your email',
placeholder: "We'll send a one-time-password to this address."
},
{
name: 'legal',
type: 'checkbox',
label: this.renderHtml`I agree to the <a href="/legal/terms-of-service" target="_blank">terms of service</a>.`
}
],
submitTitle: 'Next',
requiresProofOfWork: true,
width: 'small',
success: async ({ email }) => {
// Send verification email and redirect
await this.sendVerificationEmail(email);
return this.renderRedirect({
url: `/verify?email=${encodeURIComponent(email)}`
});
}
}
)
Dynamic Form with Conditional Fields
this.renderForm(newsletter, {
fields: [
{ name: 'enableMonthly', type: 'checkbox', watch: true },
// Conditionally add monthly price field
...(enableMonthly ? [{ name: 'monthlyPrice', type: 'number' }] : []),
{ name: 'enableYearly', type: 'checkbox', watch: true },
// Conditionally add yearly price field
...(enableYearly ? [{ name: 'yearlyPrice', type: 'number' }] : []),
// Currency selector appears only if paid options enabled
...(enableMonthly || enableYearly ? [{
name: 'currency',
type: 'select',
options: {
USD: 'US Dollar',
EUR: 'Euro',
GBP: 'British Pound'
}
}] : [])
]
})
Custom Component Integration
this.renderForm(this.database.posts, {
fields: [
'title',
{
name: 'body',
type: '_markdown_editor',
placeholder: 'Write your post in Markdown...'
},
{
name: 'coverImage',
type: 'file',
overlayLinks: [
{
body: 'Browse Gallery',
href: '/_actions/admin/image_gallery',
target: '_overlay'
}
]
}
]
})
Form with Hidden and Forced Fields
this.renderForm(this.database.comments, {
fields: [
// Hidden field included in form but not displayed
{ name: 'postId', type: 'hidden', value: this.params.postId },
// Forced field set server-side only
{ name: 'userId', type: 'forced', value: this.session.user.id },
// Visible fields
{ name: 'body', type: 'textarea', label: 'Your Comment' }
],
success: async ({ id }) => {
await this.notifyUsers({ commentId: id });
return this.renderRedirect({ target: '_top' });
}
})
File Upload Form
this.renderForm(this.database.images, {
title: 'Upload Image',
fields: [
{ name: 'file', type: 'file' },
{ name: 'alt', label: 'Alt Text', placeholder: 'Describe this image...' }
],
success(image) {
return this.renderHtml`
<span data-insert-content="">
<script type="pinstripe">
this.parent.trigger('click');
</script>
</span>
`;
}
})
Form Processing Flow
- GET Request: Renders the form with default/existing values
- POST Request:
- Validates proof-of-work (if enabled)
- Processes field values through appropriate filters
- Calls validation functions
- On success: executes success callback or redirects
- On error: re-renders form with error messages
Integration Patterns
With Database Models
// Direct model usage
this.renderForm(this.database.users.where({ id: userId }).first(), {
fields: ['name', 'email']
})
// Collection for new records
this.renderForm(this.database.posts, {
fields: ['title', 'body']
})
With Custom Models
this.renderForm(
this.createModel({
meta() {
this.mustNotBeBlank('name');
this.mustBeUnique('email');
}
}),
{ fields: ['name', 'email'] }
)
Modal Sizing
// Small modal for simple forms
{ width: 'small' } // Sign-in, quick edits
// Medium modal (default)
{ width: 'medium' } // Standard forms
// Large modal for complex forms
{ width: 'large' } // Content creation, settings
The renderForm
service provides a complete solution for form creation, validation, and processing within the Pinstripe framework, automatically handling common form patterns while remaining flexible for custom use cases.