Component Generator
The Component Generator creates reusable UI components that can be shared across different pages and templates in your Azu application.
Usage
azu generate component COMPONENT_NAME [OPTIONS]
Description
Components in Azu applications are reusable UI elements that encapsulate both presentation logic and styling. They can be used across multiple pages to maintain consistency and reduce code duplication. Components can include forms, cards, navigation elements, and other UI patterns.
Options
COMPONENT_NAME
- Name of the component to generate (required)-d, --description DESCRIPTION
- Description of the component-t, --template TEMPLATE
- Template to use (default: basic)-f, --force
- Overwrite existing files-h, --help
- Show help message
Examples
Generate a basic component
azu generate component UserCard
This creates:
src/components/user_card.cr
- The component classsrc/components/user_card.jinja
- The component templatespec/components/user_card_spec.cr
- Test file
Generate a component with description
azu generate component NavigationBar --description "Main navigation component with user menu"
Generate specific component types
azu generate component ContactForm --template form
azu generate component Pagination --template pagination
Generated Files
Component Class (src/components/COMPONENT_NAME.cr
)
src/components/COMPONENT_NAME.cr
)# <%= @description || @name.underscore.humanize %> component
class <%= @name %>Component < Azu::Component
def initialize
end
def call(context : Azu::Context, **args) : String
# Add your component logic here
# Example:
# @user = args[:user]
# @show_avatar = args[:show_avatar]? || true
render "components/<%= @name.underscore %>.jinja"
end
end
Component Template (src/components/COMPONENT_NAME.jinja
)
src/components/COMPONENT_NAME.jinja
)<!-- <%= @name.underscore.humanize %> component -->
<div class="<%= @name.underscore %>-component">
<!-- Add your component content here -->
<h3><%= @name.underscore.humanize %></h3>
<p>This is the <%= @name.underscore.humanize %> component.</p>
</div>
Test File (spec/components/COMPONENT_NAME_spec.cr
)
spec/components/COMPONENT_NAME_spec.cr
)require "../spec_helper"
describe <%= @name %>Component do
describe "#call" do
it "renders the component" do
component = <%= @name %>Component.new
context = Azu::Context.new
result = component.call(context)
result.should be_a(String)
result.should contain("<%= @name.underscore.humanize %>")
end
end
end
Component Patterns
Basic Component Pattern
class UserCardComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@user = args[:user]
@show_avatar = args[:show_avatar]? || true
@show_email = args[:show_email]? || false
render "components/user_card.jinja"
end
end
Component with Data Processing
class PostListComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@posts = args[:posts] || Post.all
@limit = args[:limit]? || 10
@show_author = args[:show_author]? || true
# Process posts
@posts = @posts.limit(@limit)
render "components/post_list.jinja"
end
end
Form Component Pattern
class ContactFormComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@form_data = args[:form_data]? || {} of String => String
@errors = args[:errors]? || {} of String => Array(String)
@action = args[:action]? || "/contact"
@method = args[:method]? || "POST"
render "components/contact_form.jinja"
end
end
Navigation Component Pattern
class NavigationComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@current_user = context.current_user
@current_path = context.request.path
@menu_items = build_menu_items
render "components/navigation.jinja"
end
private def build_menu_items : Array(MenuItem)
items = [] of MenuItem
items << MenuItem.new("Home", "/", "home")
items << MenuItem.new("About", "/about", "info")
if @current_user
items << MenuItem.new("Dashboard", "/dashboard", "dashboard")
items << MenuItem.new("Profile", "/profile", "user")
end
items
end
end
Template Patterns
Basic Component Template
<!-- User Card Component -->
<div class="user-card">
{% if show_avatar %}
<div class="user-avatar">
<img src="{{ user.avatar_url }}" alt="{{ user.name }}">
</div>
{% endif %}
<div class="user-info">
<h3>{{ user.name }}</h3>
{% if show_email %}
<p>{{ user.email }}</p>
{% endif %}
<p>Joined: {{ user.created_at.strftime("%B %Y") }}</p>
</div>
</div>
List Component Template
<!-- Post List Component -->
<div class="post-list">
{% for post in posts %}
<div class="post-item">
<h4><a href="/posts/{{ post.slug }}">{{ post.title }}</a></h4>
<p>{{ post.excerpt }}</p>
{% if show_author %}
<div class="post-meta">
<span>By {{ post.author.name }}</span>
<span>{{ post.created_at.strftime("%B %d, %Y") }}</span>
</div>
{% endif %}
</div>
{% endfor %}
{% if posts.empty? %}
<p class="no-posts">No posts found.</p>
{% endif %}
</div>
Form Component Template
<!-- Contact Form Component -->
<form method="{{ method }}" action="{{ action }}" class="contact-form">
{% if errors %}
<div class="form-errors">
<ul>
{% for field, field_errors in errors %}
{% for error in field_errors %}
<li>{{ field }}: {{ error }}</li>
{% endfor %}
{% endfor %}
</ul>
</div>
{% endif %}
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" value="{{ form_data.name }}" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" value="{{ form_data.email }}" required>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="5" required>{{ form_data.message }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
Navigation Component Template
<!-- Navigation Component -->
<nav class="main-navigation">
<div class="nav-brand">
<a href="/">My App</a>
</div>
<ul class="nav-menu">
{% for item in menu_items %}
<li class="nav-item {% if current_path == item.path %}active{% endif %}">
<a href="{{ item.path }}" class="nav-link">
<i class="icon-{{ item.icon }}"></i>
{{ item.label }}
</a>
</li>
{% endfor %}
</ul>
{% if current_user %}
<div class="nav-user">
<span>Welcome, {{ current_user.name }}</span>
<a href="/logout" class="btn btn-logout">Logout</a>
</div>
{% else %}
<div class="nav-auth">
<a href="/login" class="btn btn-login">Login</a>
<a href="/register" class="btn btn-register">Register</a>
</div>
{% endif %}
</nav>
Using Components
In Pages
Use components in your page classes:
class UsersPage < Azu::Page
def call(context : Azu::Context) : String
@users = User.all
@user_card_component = UserCardComponent.new
render "pages/users.jinja"
end
end
In Templates
Include components in your templates:
{% extends "layout.jinja" %}
{% block content %}
<div class="container">
<h1>Users</h1>
<div class="users-grid">
{% for user in users %}
{{ user_card_component.call(context, user: user, show_avatar: true, show_email: false) }}
{% endfor %}
</div>
</div>
{% endblock %}
In Layouts
Use components in your layout templates:
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
{{ navigation_component.call(context) }}
<main>
{% block content %}{% endblock %}
</main>
{{ footer_component.call(context) }}
</body>
</html>
Best Practices
1. Keep Components Focused
Each component should have a single responsibility:
# Good: Focused on user display
class UserCardComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@user = args[:user]
render "components/user_card.jinja"
end
end
# Good: Focused on user actions
class UserActionsComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@user = args[:user]
@current_user = context.current_user
render "components/user_actions.jinja"
end
end
2. Use Flexible Parameters
Make components configurable through parameters:
class PostCardComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@post = args[:post]
@show_author = args[:show_author]? || true
@show_date = args[:show_date]? || true
@show_excerpt = args[:show_excerpt]? || true
@truncate_length = args[:truncate_length]? || 150
render "components/post_card.jinja"
end
end
3. Handle Missing Data Gracefully
class UserAvatarComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@user = args[:user]
@size = args[:size]? || "medium"
@default_avatar = args[:default_avatar]? || "/images/default-avatar.png"
render "components/user_avatar.jinja"
end
end
4. Use Semantic HTML
<!-- Good: Semantic HTML structure -->
<article class="post-card">
<header class="post-header">
<h2 class="post-title">{{ post.title }}</h2>
<time class="post-date">{{ post.created_at.strftime("%B %d, %Y") }}</time>
</header>
<div class="post-content">
{{ post.excerpt }}
</div>
<footer class="post-footer">
<a href="/posts/{{ post.slug }}" class="read-more">Read More</a>
</footer>
</article>
Testing Components
Unit Testing
describe UserCardComponent do
describe "#call" do
it "renders user card with avatar" do
component = UserCardComponent.new
context = Azu::Context.new
user = User.new(name: "John Doe", email: "john@example.com")
result = component.call(context, user: user, show_avatar: true)
result.should contain("John Doe")
result.should contain("john@example.com")
result.should contain("user-avatar")
end
it "renders user card without avatar" do
component = UserCardComponent.new
context = Azu::Context.new
user = User.new(name: "John Doe", email: "john@example.com")
result = component.call(context, user: user, show_avatar: false)
result.should_not contain("user-avatar")
end
end
end
Integration Testing
describe "Component integration" do
it "displays user cards on users page" do
get "/users"
response.status_code.should eq(200)
response.body.should contain("user-card")
response.body.should contain("John Doe")
end
end
Common Component Types
1. Display Components
Show data in a consistent format:
class DataCardComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@title = args[:title]
@value = args[:value]
@icon = args[:icon]?
@color = args[:color]? || "primary"
render "components/data_card.jinja"
end
end
2. Form Components
Reusable form elements:
class FormFieldComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@name = args[:name]
@label = args[:label]
@type = args[:type]? || "text"
@value = args[:value]?
@errors = args[:errors]? || [] of String
@required = args[:required]? || false
render "components/form_field.jinja"
end
end
3. Navigation Components
Site navigation elements:
class BreadcrumbComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@items = args[:items] || [] of BreadcrumbItem
@separator = args[:separator]? || ">"
render "components/breadcrumb.jinja"
end
end
4. Layout Components
Structural layout elements:
class SidebarComponent < Azu::Component
def call(context : Azu::Context, **args) : String
@title = args[:title]?
@items = args[:items] || [] of SidebarItem
@collapsible = args[:collapsible]? || false
render "components/sidebar.jinja"
end
end
Related Commands
azu generate page
- Generate page componentsazu generate endpoint
- Generate API endpointsazu generate model
- Generate data modelsazu generate service
- Generate business logic services
Templates
The component generator supports different templates:
basic
- Simple component with basic structurecard
- Card display component templateform
- Form component templatenavigation
- Navigation component templatelist
- List display component template
To use a specific template:
azu generate component ProductCard --template card
Last updated