# FormBuilderDSL
A clean, minimal, and extensible DSL for defining structured, dynamic forms in Elixir.
FormBuilderDSL helps teams write intuitive, maintainable forms with metadata structures that can power:
- Web-based LiveView and HTML rendering
- JSON-serializable form schemas for APIs
- Dynamic admin and internal tool UIs
---
## ✨ Features
- Declarative definitions for form fields
- Support for all common form input types
- Reusable `defenum/2` macro for dropdown/select support
- Generates metadata as `FormBuilderDSL.Field` structs
- Runtime-safe enum management using `ETS`
- Clean rendering logic that plays well with Phoenix Components
- Comprehensive validation support
- Default values and field attributes
- Form context and state management
---
## 🧪 Examples
### Basic Form
```elixir
defmodule MyForm do
use FormBuilderDSL
defenum :status, [:approved, :rejected, :pending]
form :user do
text :name
email :email
password :password
select :status, options: status_labeled_options()
end
end
```
### Form with Validations
```elixir
defmodule UserForm do
use FormBuilderDSL
form :user do
text :name, required: true, validations: [min_length: 3, max_length: 50]
email :email, required: true, validations: [format: :email]
number :age, required: true, validations: [min: 18, max: 100]
select :role, required: true, options: role_labeled_options()
end
end
# Using the form with validation
form = UserForm.fields()
context = FormBuilderDSL.Context.new(:user, form)
# Validate the form
context = FormBuilderDSL.Context.validate(context)
# Check for errors
if FormBuilderDSL.Context.valid?(context) do
# Form is valid, process the data
values = context.values
else
# Form has errors
errors = context.errors
end
```
### Advanced Form with All Input Types
```elixir
defmodule ProductForm do
use FormBuilderDSL
defenum :categories, [:tech, :science, :arts]
form :product do
# Text inputs
text :name, required: true, placeholder: "Enter product name"
email :contact_email, required: true
password :admin_password
number :price, min: 0, max: 1000, step: 0.01
tel :phone_number
url :website
search :keywords
# Date and time inputs
date :release_date
time :available_time
date_time :last_updated
month :target_month
week :target_week
# Selection inputs
select :category, options: categories_labeled_options()
multi_select :tags, options: categories_labeled_options()
radio :status, options: status_labeled_options()
checkbox :featured
switch :active
# File inputs
file :document, accept: ".pdf,.doc,.docx"
image :product_image
# Special inputs
color :theme_color
range :rating, min: 1, max: 5, step: 1
hidden :internal_id
end
end
```
### Form with Default Values
```elixir
defmodule SettingsForm do
use FormBuilderDSL
form :settings do
text :name, default: "John Doe"
email:email, default: "john@example.com"
select :role, default: "user", options: role_labeled_options()
end
end
```
### Form with Disabled Fields
```elixir
defmodule ReadOnlyForm do
use FormBuilderDSL
form :user do
text :name, disabled: true
email :email, disabled: true
select :role, disabled: true, options: role_labeled_options()
end
end
```
### Validation Rules
The DSL supports various validation rules:
```elixir
form :user do
# Required field
text :name, required: true
# String length validations
text :description, validations: [min_length: 10, max_length: 1000]
# Numeric range validations
number :age, validations: [min: 18, max: 100]
# Email format validation
email :email, validations: [format: :email]
# Value inclusion validation
select :role, validations: [in: ["admin", "user", "guest"]]
end
```
---
## 📦 Installation
Add the dependency to your `mix.exs`:
```elixir
def deps do
[
{:form_builder_dsl, "~> 0.1.0"}
]
end
```
Then run:
```bash
mix deps.get
```
---
### 🛠 Optional: Add Formatter Settings
To keep the DSL syntax clean and bracket-free, add the following to your `.formatter.exs`:
```elixir
[
import_deps: [:form_builder_dsl],
locals_without_parens: [
form: 2,
defenum: 2,
text: 1,
text: 2,
email: 1,
email: 2,
password: 1,
password: 2,
number: 1,
number: 2,
tel: 1,
tel: 2,
url: 1,
url: 2,
search: 1,
search: 2,
date: 1,
date: 2,
time: 1,
time: 2,
date_time: 1,
date_time: 2,
month: 1,
month: 2,
week: 1,
week: 2,
select: 1,
select: 2,
multi_select: 1,
multi_select: 2,
radio: 1,
radio: 2,
checkbox: 1,
checkbox: 2,
switch: 1,
switch: 2,
file: 1,
file: 2,
image: 1,
image: 2,
color: 1,
color: 2,
range: 1,
range: 2,
hidden: 1,
hidden: 2
]
]
```
> This tells the formatter not to wrap parentheses around common DSL macros.
---
## 📄 License
MIT © Pranav J