plutonium 0.23.4 → 0.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/plutonium.css +2 -2
- data/config/initializers/sqlite_json_alias.rb +1 -1
- data/docs/.vitepress/config.ts +60 -19
- data/docs/guide/cursor-rules.md +75 -0
- data/docs/guide/deep-dive/authorization.md +189 -0
- data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
- data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
- data/docs/guide/index.md +28 -0
- data/docs/guide/introduction/02-core-concepts.md +440 -0
- data/docs/guide/tutorial/01-project-setup.md +75 -0
- data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
- data/docs/guide/tutorial/03-defining-resources.md +90 -0
- data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
- data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
- data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
- data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
- data/docs/index.md +24 -31
- data/docs/modules/action.md +190 -0
- data/docs/modules/authentication.md +236 -0
- data/docs/modules/configuration.md +599 -0
- data/docs/modules/controller.md +398 -0
- data/docs/modules/core.md +316 -0
- data/docs/modules/definition.md +876 -0
- data/docs/modules/display.md +759 -0
- data/docs/modules/form.md +605 -0
- data/docs/modules/generator.md +288 -0
- data/docs/modules/index.md +167 -0
- data/docs/modules/interaction.md +470 -0
- data/docs/modules/package.md +151 -0
- data/docs/modules/policy.md +176 -0
- data/docs/modules/portal.md +710 -0
- data/docs/modules/query.md +287 -0
- data/docs/modules/resource_record.md +618 -0
- data/docs/modules/routing.md +641 -0
- data/docs/modules/table.md +293 -0
- data/docs/modules/ui.md +631 -0
- data/docs/public/plutonium.mdc +667 -0
- data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
- data/lib/plutonium/ui/display/resource.rb +7 -2
- data/lib/plutonium/ui/table/resource.rb +8 -3
- data/lib/plutonium/version.rb +1 -1
- metadata +36 -9
- data/docs/guide/getting-started/authorization.md +0 -296
- data/docs/guide/getting-started/core-concepts.md +0 -432
- data/docs/guide/getting-started/index.md +0 -21
- data/docs/guide/tutorial.md +0 -401
- /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,605 @@
|
|
1
|
+
---
|
2
|
+
title: Form Module
|
3
|
+
---
|
4
|
+
|
5
|
+
# Form Module
|
6
|
+
|
7
|
+
The Form module provides a comprehensive form building system for Plutonium applications. Built on top of `Phlexi::Form`, it offers enhanced input components, automatic field inference, secure associations, and modern UI interactions for creating rich, accessible forms.
|
8
|
+
|
9
|
+
::: tip
|
10
|
+
The Form module is located in `lib/plutonium/ui/form/`.
|
11
|
+
:::
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
- **Enhanced Input Components**: Rich input types with JavaScript integration.
|
16
|
+
- **Secure Associations**: SGID-based association handling with authorization.
|
17
|
+
- **Type Inference**: Automatic component selection based on field types.
|
18
|
+
- **Resource Integration**: Seamless integration with resource definitions.
|
19
|
+
- **Modern UI**: File uploads, date pickers, rich text editors, and more.
|
20
|
+
- **Accessibility**: ARIA-compliant forms with keyboard navigation.
|
21
|
+
|
22
|
+
## Core Components
|
23
|
+
|
24
|
+
### Base Form (`lib/plutonium/ui/form/base.rb`)
|
25
|
+
|
26
|
+
This is the foundation that all Plutonium form components inherit from. It extends `Phlexi::Form::Base` with Plutonium's specific behaviors and custom input components.
|
27
|
+
|
28
|
+
::: details Base Form Implementation
|
29
|
+
```ruby
|
30
|
+
class Plutonium::UI::Form::Base < Phlexi::Form::Base
|
31
|
+
include Plutonium::UI::Component::Behaviour
|
32
|
+
|
33
|
+
# Enhanced builder with Plutonium-specific components
|
34
|
+
class Builder < Builder
|
35
|
+
include Plutonium::UI::Form::Options::InferredTypes
|
36
|
+
|
37
|
+
def easymde_tag(**options, &block)
|
38
|
+
create_component(Plutonium::UI::Form::Components::Easymde, :easymde, **options, &block)
|
39
|
+
end
|
40
|
+
alias_method :markdown_tag, :easymde_tag
|
41
|
+
|
42
|
+
def flatpickr_tag(**options, &block)
|
43
|
+
create_component(Components::Flatpickr, :flatpickr, **options, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
def uppy_tag(**options, &block)
|
47
|
+
create_component(Components::Uppy, :uppy, **options, &block)
|
48
|
+
end
|
49
|
+
alias_method :file_tag, :uppy_tag
|
50
|
+
alias_method :attachment_tag, :uppy_tag
|
51
|
+
|
52
|
+
def secure_association_tag(**attributes, &block)
|
53
|
+
create_component(Components::SecureAssociation, :association, **attributes, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Override default association methods to use secure versions
|
57
|
+
alias_method :belongs_to_tag, :secure_association_tag
|
58
|
+
alias_method :has_many_tag, :secure_association_tag
|
59
|
+
alias_method :has_one_tag, :secure_association_tag
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
:::
|
64
|
+
|
65
|
+
### Resource Form (`lib/plutonium/ui/form/resource.rb`)
|
66
|
+
|
67
|
+
This is a specialized form for resource objects that automatically renders fields based on the resource's definition, handling nested resources and actions gracefully.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class PostForm < Plutonium::UI::Form::Resource
|
71
|
+
def initialize(post, resource_definition:)
|
72
|
+
super(post, resource_definition: resource_definition)
|
73
|
+
end
|
74
|
+
|
75
|
+
def form_template
|
76
|
+
render_resource_fields # Render configured input fields
|
77
|
+
render_nested_resources # Render nested associations
|
78
|
+
render_actions # Render submit/cancel buttons
|
79
|
+
end
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
## Enhanced Input Components
|
84
|
+
|
85
|
+
### Rich Text Editor (Easymde)
|
86
|
+
|
87
|
+
A client-side markdown editor with live preview, based on [EasyMDE](https://github.com/Ionaru/easy-markdown-editor).
|
88
|
+
|
89
|
+
::: code-group
|
90
|
+
```ruby [Basic Usage]
|
91
|
+
# Automatically used for :markdown fields
|
92
|
+
render field(:content).easymde_tag
|
93
|
+
```
|
94
|
+
|
95
|
+
```ruby [With Options]
|
96
|
+
render field(:description).easymde_tag(
|
97
|
+
toolbar: ["bold", "italic", "heading", "|", "quote"],
|
98
|
+
spellChecker: false,
|
99
|
+
autosave: { enabled: true, uniqueId: "post_content" }
|
100
|
+
)
|
101
|
+
```
|
102
|
+
:::
|
103
|
+
|
104
|
+
### Date/Time Picker (Flatpickr)
|
105
|
+
|
106
|
+
A powerful and lightweight date and time picker from [Flatpickr](https://flatpickr.js.org/).
|
107
|
+
|
108
|
+
::: code-group
|
109
|
+
```ruby [Date Picker]
|
110
|
+
# Automatically used for :date fields
|
111
|
+
render field(:published_at).flatpickr_tag
|
112
|
+
```
|
113
|
+
```ruby [Time Picker]
|
114
|
+
# Automatically used for :time fields
|
115
|
+
render field(:meeting_time).flatpickr_tag
|
116
|
+
```
|
117
|
+
```ruby [Datetime Picker]
|
118
|
+
# Automatically used for :datetime fields
|
119
|
+
render field(:deadline).flatpickr_tag
|
120
|
+
```
|
121
|
+
```ruby [With HTML Attributes]
|
122
|
+
render field(:event_date).flatpickr_tag(
|
123
|
+
class: "custom-date-picker",
|
124
|
+
placeholder: "Select date..."
|
125
|
+
)
|
126
|
+
```
|
127
|
+
:::
|
128
|
+
|
129
|
+
::: warning Flatpickr Configuration
|
130
|
+
The current implementation uses automatic configuration based on field type:
|
131
|
+
- **Date fields**: Basic date picker with `altInput: true`
|
132
|
+
- **Time fields**: Time picker with `enableTime: true, noCalendar: true`
|
133
|
+
- **Datetime fields**: Date and time picker with `enableTime: true`
|
134
|
+
|
135
|
+
Custom Flatpickr options (like `dateFormat`, `mode: "range"`) are not currently supported through tag attributes.
|
136
|
+
:::
|
137
|
+
|
138
|
+
### File Upload (Uppy)
|
139
|
+
|
140
|
+
A sleek, modern file uploader powered by [Uppy](https://uppy.io/).
|
141
|
+
|
142
|
+
::: code-group
|
143
|
+
```ruby [Single File]
|
144
|
+
# Automatically used for :file or :attachment fields
|
145
|
+
render field(:avatar).uppy_tag
|
146
|
+
```
|
147
|
+
```ruby [Multiple Files]
|
148
|
+
render field(:documents).uppy_tag(multiple: true)
|
149
|
+
```
|
150
|
+
```ruby [With Restrictions]
|
151
|
+
render field(:gallery).uppy_tag(
|
152
|
+
multiple: true,
|
153
|
+
allowed_file_types: ['.jpg', '.jpeg', '.png'],
|
154
|
+
max_file_size: 5.megabytes
|
155
|
+
)
|
156
|
+
```
|
157
|
+
```ruby [Direct to Cloud]
|
158
|
+
render field(:videos).uppy_tag(
|
159
|
+
direct_upload: true, # For S3, etc.
|
160
|
+
max_total_size: 100.megabytes
|
161
|
+
)
|
162
|
+
```
|
163
|
+
:::
|
164
|
+
|
165
|
+
::: details Uppy Component Implementation
|
166
|
+
The Uppy component automatically handles rendering existing attachments and providing an interface to upload new ones.
|
167
|
+
```ruby
|
168
|
+
class Plutonium::UI::Form::Components::Uppy
|
169
|
+
# Automatic features:
|
170
|
+
# - Drag and drop upload
|
171
|
+
# - Progress indicators
|
172
|
+
# - Image previews and thumbnails
|
173
|
+
# - File type and size validation
|
174
|
+
# - Direct-to-cloud upload support
|
175
|
+
# - Interactive preview and deletion of existing attachments
|
176
|
+
|
177
|
+
def view_template
|
178
|
+
div(class: "flex flex-col-reverse gap-2") do
|
179
|
+
render_existing_attachments
|
180
|
+
render_upload_interface
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def render_existing_attachments
|
187
|
+
Array(field.value).each do |attachment|
|
188
|
+
render_attachment_preview(attachment)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def render_attachment_preview(attachment)
|
193
|
+
# Interactive preview with delete option
|
194
|
+
div(class: "attachment-preview", data: { controller: "attachment-preview" }) do
|
195
|
+
render_thumbnail(attachment)
|
196
|
+
render_filename(attachment)
|
197
|
+
render_delete_button
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
```
|
202
|
+
:::
|
203
|
+
|
204
|
+
### International Phone Input
|
205
|
+
|
206
|
+
A user-friendly phone number input with country code selection, using [intl-tel-input](https://github.com/jackocnr/intl-tel-input).
|
207
|
+
|
208
|
+
::: code-group
|
209
|
+
```ruby [Basic Usage]
|
210
|
+
# Automatically used for :tel fields
|
211
|
+
render field(:phone).int_tel_input_tag
|
212
|
+
```
|
213
|
+
```ruby [Phone Tag Alias]
|
214
|
+
# Alias for int_tel_input_tag
|
215
|
+
render field(:mobile).phone_tag
|
216
|
+
```
|
217
|
+
```ruby [With HTML Attributes]
|
218
|
+
render field(:contact_phone).int_tel_input_tag(
|
219
|
+
class: "custom-phone-input",
|
220
|
+
placeholder: "Enter phone number"
|
221
|
+
)
|
222
|
+
```
|
223
|
+
:::
|
224
|
+
|
225
|
+
::: warning Int Tel Input Configuration
|
226
|
+
The current implementation uses a fixed configuration:
|
227
|
+
- **Strict Mode**: Enabled for validation
|
228
|
+
- **Utils Loading**: Automatically loads validation utilities
|
229
|
+
- **Hidden Input**: Creates hidden field for form submission
|
230
|
+
|
231
|
+
Custom intl-tel-input options (like `onlyCountries`, `preferredCountries`) are not currently supported through tag attributes.
|
232
|
+
:::
|
233
|
+
|
234
|
+
### Secure Association Inputs
|
235
|
+
|
236
|
+
Plutonium's association inputs are secure by default, using SGIDs to prevent parameter tampering and scoping options based on user authorization.
|
237
|
+
|
238
|
+
::: code-group
|
239
|
+
```ruby [Belongs To]
|
240
|
+
# Automatically used for belongs_to associations
|
241
|
+
render field(:author).belongs_to_tag
|
242
|
+
```
|
243
|
+
```ruby [Has Many]
|
244
|
+
# Automatically used for has_many associations
|
245
|
+
render field(:tags).has_many_tag
|
246
|
+
```
|
247
|
+
```ruby [With Custom Choices]
|
248
|
+
render field(:category).belongs_to_tag(
|
249
|
+
choices: Category.published.pluck(:name, :id)
|
250
|
+
)
|
251
|
+
```
|
252
|
+
```ruby [With Add Action]
|
253
|
+
render field(:publisher).belongs_to_tag(
|
254
|
+
add_action: new_publisher_path
|
255
|
+
)
|
256
|
+
```
|
257
|
+
:::
|
258
|
+
|
259
|
+
::: details Secure Association Implementation
|
260
|
+
```ruby
|
261
|
+
class Plutonium::UI::Form::Components::SecureAssociation
|
262
|
+
# Automatic features:
|
263
|
+
# - SGID-based value encoding for security.
|
264
|
+
# - Authorization checks before showing options.
|
265
|
+
# - "Add new record" button with `return_to` handling.
|
266
|
+
# - Polymorphic association support.
|
267
|
+
# - Search and filtering (via SlimSelect).
|
268
|
+
|
269
|
+
def choices
|
270
|
+
collection = if @skip_authorization
|
271
|
+
choices_from_association(association_reflection.klass)
|
272
|
+
else
|
273
|
+
# Only show records user is authorized to see
|
274
|
+
authorized_resource_scope(
|
275
|
+
association_reflection.klass,
|
276
|
+
with: @scope_with,
|
277
|
+
context: @scope_context
|
278
|
+
)
|
279
|
+
end
|
280
|
+
# ...
|
281
|
+
end
|
282
|
+
end
|
283
|
+
```
|
284
|
+
:::
|
285
|
+
|
286
|
+
## Type Inference
|
287
|
+
|
288
|
+
### Automatic Component Selection (`lib/plutonium/ui/form/options/inferred_types.rb`)
|
289
|
+
|
290
|
+
The system automatically selects appropriate input components:
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
# Automatic inference based on Active Record column types
|
294
|
+
render field(:title).input_tag # → input_tag (string)
|
295
|
+
render field(:content).easymde_tag # → easymde_tag (text/rich_text)
|
296
|
+
render field(:published_at).flatpickr_tag # → flatpickr_tag (datetime)
|
297
|
+
render field(:author).secure_association_tag # → secure_association_tag (belongs_to)
|
298
|
+
render field(:featured_image).uppy_tag # → uppy_tag (Active Storage)
|
299
|
+
render field(:category).slim_select_tag # → slim_select_tag (select)
|
300
|
+
|
301
|
+
# Manual override
|
302
|
+
render field(:title).input_tag(as: :string)
|
303
|
+
render field(:content).easymde_tag
|
304
|
+
render field(:published_at).flatpickr_tag
|
305
|
+
render field(:documents).uppy_tag(multiple: true)
|
306
|
+
```
|
307
|
+
|
308
|
+
### Type Mapping
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
module Plutonium::UI::Form::Options::InferredTypes
|
312
|
+
private
|
313
|
+
|
314
|
+
def infer_field_component
|
315
|
+
case inferred_field_type
|
316
|
+
when :rich_text
|
317
|
+
:markdown # Use EasyMDE for rich text
|
318
|
+
end
|
319
|
+
|
320
|
+
inferred_component = super
|
321
|
+
case inferred_component
|
322
|
+
when :select
|
323
|
+
:slim_select # Enhance selects with SlimSelect
|
324
|
+
when :date, :time, :datetime
|
325
|
+
:flatpickr # Use Flatpickr for date/time
|
326
|
+
else
|
327
|
+
inferred_component
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
```
|
332
|
+
|
333
|
+
## Theme System
|
334
|
+
|
335
|
+
### Form Theme (`lib/plutonium/ui/form/theme.rb`)
|
336
|
+
|
337
|
+
Comprehensive theming for consistent form appearance:
|
338
|
+
|
339
|
+
```ruby
|
340
|
+
class Plutonium::UI::Form::Theme < Phlexi::Form::Theme
|
341
|
+
def self.theme
|
342
|
+
super.merge({
|
343
|
+
# Layout
|
344
|
+
fields_wrapper: "space-y-6",
|
345
|
+
actions_wrapper: "flex justify-end space-x-3 pt-6 border-t",
|
346
|
+
|
347
|
+
# Input styles
|
348
|
+
input: "w-full border rounded-md shadow-sm px-3 py-2 border-gray-300 dark:border-gray-600 focus:ring-primary-500 focus:border-primary-500",
|
349
|
+
textarea: "w-full border rounded-md shadow-sm px-3 py-2 border-gray-300 dark:border-gray-600 focus:ring-primary-500 focus:border-primary-500",
|
350
|
+
select: "w-full border rounded-md shadow-sm px-3 py-2 border-gray-300 dark:border-gray-600 focus:ring-primary-500 focus:border-primary-500",
|
351
|
+
|
352
|
+
# Enhanced components
|
353
|
+
flatpickr: :input,
|
354
|
+
int_tel_input: :input,
|
355
|
+
easymde: "w-full border rounded-md border-gray-300 dark:border-gray-600",
|
356
|
+
uppy: "w-full border rounded-md border-gray-300 dark:border-gray-600",
|
357
|
+
|
358
|
+
# Association components
|
359
|
+
association: :select,
|
360
|
+
|
361
|
+
# File input
|
362
|
+
file: "w-full border rounded-md shadow-sm font-medium text-sm border-gray-300 dark:border-gray-600",
|
363
|
+
|
364
|
+
# States
|
365
|
+
valid_input: "border-green-500 focus:ring-green-500 focus:border-green-500",
|
366
|
+
invalid_input: "border-red-500 focus:ring-red-500 focus:border-red-500",
|
367
|
+
|
368
|
+
# Labels and hints
|
369
|
+
label: "block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1",
|
370
|
+
hint: "mt-2 text-sm text-gray-500 dark:text-gray-200",
|
371
|
+
error: "mt-2 text-sm text-red-600 dark:text-red-500"
|
372
|
+
})
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
## Usage Patterns
|
378
|
+
|
379
|
+
### Basic Form
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
# Simple form
|
383
|
+
class ContactForm < Plutonium::UI::Form::Base
|
384
|
+
def form_template
|
385
|
+
render field(:name).input_tag(as: :string)
|
386
|
+
render field(:email).input_tag(as: :email)
|
387
|
+
render field(:message).textarea_tag
|
388
|
+
render field(:phone).int_tel_input_tag
|
389
|
+
end
|
390
|
+
end
|
391
|
+
```
|
392
|
+
|
393
|
+
### Field Rendering and Wrappers
|
394
|
+
|
395
|
+
All fields must be explicitly rendered using the `render` method. Use wrappers to control layout and styling:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
class PostForm < Plutonium::UI::Form::Resource
|
399
|
+
def form_template
|
400
|
+
# Basic field rendering
|
401
|
+
render field(:title).input_tag
|
402
|
+
|
403
|
+
# Field with wrapper styling
|
404
|
+
render field(:content).wrapped(class: "col-span-full") do |f|
|
405
|
+
render f.easymde_tag
|
406
|
+
end
|
407
|
+
|
408
|
+
# Custom wrapper with data attributes
|
409
|
+
render field(:author).wrapped(
|
410
|
+
class: "border rounded-lg p-4",
|
411
|
+
data: { controller: "tooltip" }
|
412
|
+
) do |f|
|
413
|
+
render f.belongs_to_tag
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
### Resource Form
|
420
|
+
|
421
|
+
```ruby
|
422
|
+
# Automatic resource form based on definition
|
423
|
+
class PostsController < ApplicationController
|
424
|
+
def new
|
425
|
+
@post = Post.new
|
426
|
+
@form = Plutonium::UI::Form::Resource.new(
|
427
|
+
@post,
|
428
|
+
resource_definition: current_definition
|
429
|
+
)
|
430
|
+
end
|
431
|
+
|
432
|
+
def edit
|
433
|
+
@post = Post.find(params[:id])
|
434
|
+
@form = Plutonium::UI::Form::Resource.new(
|
435
|
+
@post,
|
436
|
+
resource_definition: current_definition
|
437
|
+
)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# In view
|
442
|
+
<%= render @form %>
|
443
|
+
```
|
444
|
+
|
445
|
+
### Custom Form Components
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
# Create custom input component
|
449
|
+
class ColorPickerComponent < Plutonium::UI::Form::Components::Input
|
450
|
+
def view_template
|
451
|
+
div(data: { controller: "color-picker" }) do
|
452
|
+
input(**attributes, type: :color)
|
453
|
+
input(**color_text_attributes, type: :text, placeholder: "#000000")
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
private
|
458
|
+
|
459
|
+
def color_text_attributes
|
460
|
+
attributes.merge(
|
461
|
+
name: "#{attributes[:name]}_text",
|
462
|
+
data: { color_picker_target: "text" }
|
463
|
+
)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# Register in form builder
|
468
|
+
class CustomFormBuilder < Plutonium::UI::Form::Base::Builder
|
469
|
+
def color_picker_tag(**options, &block)
|
470
|
+
create_component(ColorPickerComponent, :color_picker, **options, &block)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
# Use in form
|
475
|
+
render field(:brand_color).color_picker_tag
|
476
|
+
```
|
477
|
+
|
478
|
+
### Nested Resources
|
479
|
+
|
480
|
+
```ruby
|
481
|
+
class PostForm < Plutonium::UI::Form::Resource
|
482
|
+
def form_template
|
483
|
+
field(:title).input_tag
|
484
|
+
field(:content).easymde_tag
|
485
|
+
|
486
|
+
# Nested comments
|
487
|
+
nested(:comments, allow_destroy: true) do |comment_form|
|
488
|
+
comment_form.field(:content).textarea_tag
|
489
|
+
comment_form.field(:author_name).input_tag(as: :string)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
```
|
494
|
+
|
495
|
+
## JavaScript Integration
|
496
|
+
|
497
|
+
### Automatic Dependencies
|
498
|
+
|
499
|
+
Plutonium automatically includes JavaScript libraries for enhanced form components:
|
500
|
+
- **EasyMDE** for markdown editing
|
501
|
+
- **Flatpickr** for date/time picking
|
502
|
+
- **Intl-Tel-Input** for phone inputs
|
503
|
+
- **Uppy** for file uploads
|
504
|
+
|
505
|
+
### Stimulus Controllers
|
506
|
+
|
507
|
+
Each enhanced component uses a Stimulus controller for initialization and cleanup:
|
508
|
+
|
509
|
+
```javascript
|
510
|
+
// easymde_controller.js
|
511
|
+
export default class extends Controller {
|
512
|
+
connect() {
|
513
|
+
this.editor = new EasyMDE({
|
514
|
+
element: this.element,
|
515
|
+
spellChecker: false,
|
516
|
+
toolbar: ["bold", "italic", "heading", "|", "quote"]
|
517
|
+
});
|
518
|
+
}
|
519
|
+
|
520
|
+
disconnect() {
|
521
|
+
if (this.editor) {
|
522
|
+
this.editor.toTextArea();
|
523
|
+
this.editor = null;
|
524
|
+
}
|
525
|
+
}
|
526
|
+
}
|
527
|
+
|
528
|
+
// flatpickr_controller.js
|
529
|
+
export default class extends Controller {
|
530
|
+
connect() {
|
531
|
+
this.picker = new flatpickr(this.element, this.#buildOptions());
|
532
|
+
}
|
533
|
+
|
534
|
+
disconnect() {
|
535
|
+
if (this.picker) {
|
536
|
+
this.picker.destroy();
|
537
|
+
this.picker = null;
|
538
|
+
}
|
539
|
+
}
|
540
|
+
|
541
|
+
#buildOptions() {
|
542
|
+
let options = { altInput: true };
|
543
|
+
if (this.element.attributes.type.value == "datetime-local") {
|
544
|
+
options.enableTime = true;
|
545
|
+
} else if (this.element.attributes.type.value == "time") {
|
546
|
+
options.enableTime = true;
|
547
|
+
options.noCalendar = true;
|
548
|
+
}
|
549
|
+
return options;
|
550
|
+
}
|
551
|
+
}
|
552
|
+
```
|
553
|
+
|
554
|
+
## Advanced Features
|
555
|
+
|
556
|
+
### Form Validation
|
557
|
+
|
558
|
+
```ruby
|
559
|
+
class PostForm < Plutonium::UI::Form::Resource
|
560
|
+
def form_template
|
561
|
+
# Client-side validation attributes
|
562
|
+
render field(:title).input_tag(
|
563
|
+
required: true,
|
564
|
+
minlength: 3,
|
565
|
+
maxlength: 100
|
566
|
+
)
|
567
|
+
|
568
|
+
render field(:email).input_tag(
|
569
|
+
type: :email,
|
570
|
+
pattern: "[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
|
571
|
+
)
|
572
|
+
|
573
|
+
# Custom validation with JavaScript
|
574
|
+
render field(:password).input_tag(
|
575
|
+
type: :password,
|
576
|
+
data: {
|
577
|
+
controller: "password-validator",
|
578
|
+
action: "input->password-validator#validate"
|
579
|
+
}
|
580
|
+
)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
```
|
584
|
+
|
585
|
+
### Dynamic Forms
|
586
|
+
|
587
|
+
The recommended way to create dynamic forms is by using the `condition` and `pre_submit` options in your resource definition file. This keeps the logic declarative and out of custom form classes.
|
588
|
+
|
589
|
+
```ruby
|
590
|
+
# app/definitions/post_definition.rb
|
591
|
+
class PostDefinition < Plutonium::Resource::Definition
|
592
|
+
# This input will trigger a form refresh whenever its value changes.
|
593
|
+
input :send_notifications, as: :boolean, pre_submit: true
|
594
|
+
|
595
|
+
# This input will only be shown if the `condition` evaluates to true.
|
596
|
+
# The condition is re-evaluated after a pre-submit refresh.
|
597
|
+
input :notification_channel,
|
598
|
+
as: :select,
|
599
|
+
collection: %w[Email SMS Push],
|
600
|
+
condition: -> { object.send_notifications? }
|
601
|
+
end
|
602
|
+
```
|
603
|
+
::: tip
|
604
|
+
For more details on how to configure conditional inputs, see the [Definition Module documentation](./definition.md#4-add-conditional-logic).
|
605
|
+
:::
|