Coverage for fastblocks/adapters/templates/examples.py: 0%
15 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 18:13 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-28 18:13 -0700
1"""Template integration examples demonstrating FastBlocks adapter filters.
3This module provides complete examples showing how to use FastBlocks adapter
4filters in Jinja2 templates with both sync and async patterns.
5"""
7# Example 1: Basic Image Integration
8BASIC_IMAGE_TEMPLATE = """
9[% extends "base.html" %]
11[% block content %]
12<div class="hero-section">
13 <!-- Basic image tag with adapter integration -->
14 [[ img_tag('hero-banner.jpg', 'Welcome Banner', class='hero-image', width=1200) ]]
16 <!-- Async image with transformations -->
17 [[ await async_image_with_transformations('hero-banner.jpg', 'Welcome Banner',
18 {'width': 1200, 'quality': 85, 'format': 'webp'},
19 class='hero-image', loading='eager') ]]
20</div>
21[% endblock %]
22"""
24# Example 2: Responsive Image Integration
25RESPONSIVE_IMAGE_TEMPLATE = """
26[% extends "base.html" %]
28[% block content %]
29<article class="blog-post">
30 <h1>Article Title</h1>
32 <!-- Responsive image with multiple sizes -->
33 [[ await async_responsive_image('article-hero.jpg', 'Article Hero Image', {
34 'mobile': {'width': 400, 'quality': 75, 'format': 'webp'},
35 'tablet': {'width': 800, 'quality': 80, 'format': 'webp'},
36 'desktop': {'width': 1200, 'quality': 85, 'format': 'webp'}
37 }, class='article-hero', loading='lazy') ]]
39 <div class="article-content">
40 <!-- Lazy loading image -->
41 [[ await async_lazy_image('content-image.jpg', 'Content Image',
42 width=600, height=400, class='content-img') ]]
43 </div>
44</article>
45[% endblock %]
46"""
48# Example 3: Style Framework Integration
49STYLE_FRAMEWORK_TEMPLATE = """
50[% extends "base.html" %]
52[% block content %]
53<div class="container">
54 <!-- Component with style adapter integration -->
55 [[ component_html('card', class='product-card') ]]
56 <div [[ style_class('card-header', variant='primary') ]]>
57 <h3>Product Name</h3>
58 </div>
59 <div [[ style_class('card-body') ]]>
60 [[ img_tag('product.jpg', 'Product Image', class='product-image') ]]
61 <p>Product description here...</p>
62 </div>
63 <div [[ style_class('card-footer') ]]>
64 <button [[ style_class('button', variant='primary', size='large') ]]>
65 [[ icon_with_text('shopping-cart', 'Add to Cart', position='left') ]]
66 </button>
67 </div>
68 </div>
69</div>
70[% endblock %]
71"""
73# Example 4: Font Integration
74FONT_INTEGRATION_TEMPLATE = """
75<!DOCTYPE html>
76<html lang="en">
77<head>
78 <meta charset="UTF-8">
79 <meta name="viewport" content="width=device-width, initial-scale=1.0">
80 <title>Font Integration Example</title>
82 <!-- Critical font loading (async) -->
83 [[ await async_critical_css_fonts(['Inter', 'Playfair Display']) ]]
85 <!-- Complete font stack (async) -->
86 [[ await async_optimized_font_stack() ]]
88 <!-- Fallback font imports (sync) -->
89 [[ font_import() ]]
91 <style>
92 body {
93 font-family: [[ font_family('primary') ]];
94 line-height: 1.6;
95 }
97 h1, h2, h3 {
98 font-family: [[ font_family('heading') ]];
99 }
101 code {
102 font-family: [[ font_family('monospace') ]];
103 }
104 </style>
105</head>
106<body>
107 <h1>Typography Example</h1>
108 <p>This text uses the primary font family.</p>
109 <code>This code uses the monospace font family.</code>
110</body>
111</html>
112"""
114# Example 5: HTMX Integration with Adapters
115HTMX_INTEGRATION_TEMPLATE = """
116[% extends "base.html" %]
118[% block content %]
119<div class="interactive-gallery">
120 <h2>Interactive Image Gallery</h2>
122 <!-- HTMX-powered image gallery -->
123 <div id="gallery-container" [[ style_class('gallery-grid') ]]>
124 [% for image in gallery_images %]
125 <div [[ style_class('gallery-item') ]]>
126 <!-- Image with HTMX swap on hover -->
127 <img [[ htmx_img_swap(image.id, {'width': 300, 'quality': 80},
128 trigger='mouseenter once', target='this') ]]
129 src="[[ image_url(image.id, width=300, quality=60) ]]"
130 alt="[[ image.alt ]]"
131 [[ style_class('gallery-image') ]]>
133 <!-- HTMX modal trigger -->
134 <button [[ htmx_modal('/gallery/modal/' + image.id|string, target='#modal-container') ]]
135 [[ style_class('button', variant='ghost', size='small') ]]>
136 [[ icon_with_text('expand', 'View Full Size') ]]
137 </button>
138 </div>
139 [% endfor %]
140 </div>
142 <!-- Modal container -->
143 <div id="modal-container" [[ htmx_error_container('gallery-errors') ]]></div>
145 <!-- Infinite scroll trigger -->
146 <div [[ htmx_infinite_scroll('/gallery/more?page=2', '#gallery-container') ]]
147 [[ style_class('loading-trigger') ]]>
148 Loading more images...
149 </div>
150</div>
151[% endblock %]
152"""
154# Example 6: Form Integration with Validation
155FORM_INTEGRATION_TEMPLATE = """
156[% extends "base.html" %]
158[% block content %]
159<div [[ style_class('form-container') ]]>
160 <h2>User Registration</h2>
162 <form [[ htmx_form('/users/register', target='#form-container',
163 validation_target='#form-errors') ]]>
165 <!-- Username field with validation -->
166 <div [[ style_class('form-group') ]]>
167 <label for="username">Username</label>
168 <input type="text"
169 name="username"
170 id="username"
171 [[ htmx_validation_feedback('username', validate_url='/validate/username') ]]
172 [[ style_class('form-input') ]]>
173 <div id="username-feedback" [[ style_class('form-feedback') ]]></div>
174 </div>
176 <!-- Email field with validation -->
177 <div [[ style_class('form-group') ]]>
178 <label for="email">Email</label>
179 <input type="email"
180 name="email"
181 id="email"
182 [[ htmx_validation_feedback('email', validate_url='/validate/email') ]]
183 [[ style_class('form-input') ]]>
184 <div id="email-feedback" [[ style_class('form-feedback') ]]></div>
185 </div>
187 <!-- Avatar upload -->
188 <div [[ style_class('form-group') ]]>
189 <label for="avatar">Profile Picture</label>
190 <input type="file"
191 name="avatar"
192 id="avatar"
193 accept="image/*"
194 [[ style_class('form-input') ]]>
195 <!-- Preview placeholder -->
196 <div id="avatar-preview">
197 [[ await async_image_placeholder(150, 150, 'Upload Image') ]]
198 </div>
199 </div>
201 <!-- Submit button -->
202 <button type="submit" [[ style_class('button', variant='primary', size='large') ]]>
203 [[ icon_with_text('user-plus', 'Create Account') ]]
204 </button>
206 <!-- Loading indicator -->
207 <div id="form-loading" [[ style_class('loading-indicator') ]] style="display: none;">
208 [[ icon_tag('loader', class='animate-spin') ]] Creating account...
209 </div>
210 </form>
212 <!-- Error container -->
213 <div id="form-errors" [[ htmx_error_container('form-errors') ]]></div>
214</div>
215[% endblock %]
216"""
218# Example 7: Dashboard with WebSocket Integration
219DASHBOARD_TEMPLATE = """
220[% extends "base.html" %]
222[% block content %]
223<div class="dashboard" [[ htmx_ws_connect('/ws/dashboard', listen='data-update') ]]>
225 <!-- Header with user info -->
226 <header [[ style_class('dashboard-header') ]]>
227 <div [[ style_class('user-info') ]]>
228 [[ img_tag('avatars/' + current_user.avatar, current_user.name,
229 width=40, height=40, class='user-avatar') ]]
230 <span>Welcome, [[ current_user.name ]]!</span>
231 </div>
233 <!-- Real-time notifications -->
234 <div id="notifications" [[ style_class('notifications') ]]></div>
235 </header>
237 <!-- Stats cards with lazy loading -->
238 <div [[ style_class('stats-grid') ]]>
239 [% for stat in stats %]
240 <div [[ htmx_lazy_load('/dashboard/stats/' + stat.id|string, 'Loading...',
241 trigger='revealed once') ]]
242 [[ style_class('stat-card') ]]>
243 [[ icon_tag(stat.icon, class='stat-icon') ]]
244 <div class="stat-content">
245 <h3>[[ stat.title ]]</h3>
246 <p class="stat-value">Loading...</p>
247 </div>
248 </div>
249 [% endfor %]
250 </div>
252 <!-- Chart container with search -->
253 <div [[ style_class('chart-section') ]]>
254 <div [[ style_class('chart-controls') ]]>
255 <input type="search"
256 name="chart_filter"
257 placeholder="Filter data..."
258 [[ htmx_search('/dashboard/chart-data', 300, target='#chart-container') ]]
259 [[ style_class('search-input') ]]>
260 </div>
262 <div id="chart-container" [[ style_class('chart-container') ]]>
263 <!-- Chart will be loaded here -->
264 </div>
266 <div id="search-loading" [[ style_class('loading-indicator') ]] style="display: none;">
267 [[ icon_tag('search', class='animate-pulse') ]] Filtering data...
268 </div>
269 </div>
270</div>
271[% endblock %]
272"""
274# Example 8: Complete Base Template
275BASE_TEMPLATE = """
276<!DOCTYPE html>
277<html lang="en">
278<head>
279 <meta charset="UTF-8">
280 <meta name="viewport" content="width=device-width, initial-scale=1.0">
281 <title>[% block title %]FastBlocks App[% endblock %]</title>
283 <!-- Critical resources first -->
284 [[ await async_critical_css_fonts() ]]
286 <!-- All stylesheets -->
287 [[ stylesheet_links() ]]
289 <!-- Complete font stack -->
290 [[ await async_optimized_font_stack() ]]
292 <!-- Custom styles -->
293 <style>
294 :root {
295 --font-primary: [[ font_family('primary') ]];
296 --font-heading: [[ font_family('heading') ]];
297 --font-mono: [[ font_family('monospace') ]];
298 }
300 body {
301 font-family: var(--font-primary);
302 margin: 0;
303 padding: 0;
304 }
306 .loading-indicator {
307 display: flex;
308 align-items: center;
309 gap: 0.5rem;
310 }
312 .animate-spin {
313 animation: spin 1s linear infinite;
314 }
316 .animate-pulse {
317 animation: pulse 2s ease-in-out infinite;
318 }
320 @keyframes spin {
321 from { transform: rotate(0deg); }
322 to { transform: rotate(360deg); }
323 }
325 @keyframes pulse {
326 0%, 100% { opacity: 1; }
327 50% { opacity: 0.5; }
328 }
329 </style>
331 [% block head %][% endblock %]
332</head>
333<body>
334 <!-- Navigation -->
335 <nav [[ style_class('navbar') ]]>
336 <div [[ style_class('navbar-brand') ]]>
337 [[ img_tag('logo.svg', 'FastBlocks', width=120, height=40) ]]
338 </div>
340 <div [[ style_class('navbar-menu') ]]>
341 [% block navigation %]
342 <a href="/" [[ style_class('navbar-item') ]]>
343 [[ icon_with_text('home', 'Home') ]]
344 </a>
345 <a href="/dashboard" [[ style_class('navbar-item') ]]>
346 [[ icon_with_text('dashboard', 'Dashboard') ]]
347 </a>
348 [% endblock %]
349 </div>
350 </nav>
352 <!-- Main content -->
353 <main [[ style_class('main-content') ]]>
354 [% block content %][% endblock %]
355 </main>
357 <!-- Footer -->
358 <footer [[ style_class('footer') ]]>
359 [% block footer %]
360 <p>© 2024 FastBlocks App. All rights reserved.</p>
361 [% endblock %]
362 </footer>
364 <!-- HTMX and other scripts -->
365 <script src="https://unpkg.com/htmx.org@1.9.6"></script>
366 <script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script>
368 [% block scripts %][% endblock %]
369</body>
370</html>
371"""
373# Configuration examples for different adapters
374ADAPTER_CONFIGURATIONS = {
375 "cloudinary": {
376 "settings_file": "settings/images.yml",
377 "example_config": """
378module: "cloudinary"
379cloud_name: "your-cloud-name"
380api_key: "your-api-key"
381api_secret: "your-api-secret"
382secure: true
383transformations:
384 thumbnail: {width: 150, height: 150, crop: "fill"}
385 hero: {width: 1200, height: 600, crop: "fill", quality: "auto"}
386""",
387 },
388 "google_fonts": {
389 "settings_file": "settings/fonts.yml",
390 "example_config": """
391module: "google"
392families: ["Inter", "Playfair Display"]
393weights: ["400", "600", "700"]
394subsets: ["latin", "latin-ext"]
395display: "swap"
396preconnect: true
397""",
398 },
399 "bulma": {
400 "settings_file": "settings/styles.yml",
401 "example_config": """
402module: "bulma"
403version: "0.9.4"
404components:
405 button: "button"
406 card: "card"
407 navbar: "navbar"
408 form_input: "input"
409utilities:
410 primary: "is-primary"
411 large: "is-large"
412""",
413 },
414 "fontawesome": {
415 "settings_file": "settings/icons.yml",
416 "example_config": """
417module: "fontawesome"
418version: "6.4.0"
419style: "solid" # solid, regular, light, brands
420prefix: "fas"
421cdn: true
422""",
423 },
424}
427def get_example_template(template_name: str) -> str:
428 """Get an example template by name.
430 Args:
431 template_name: Name of the template example
433 Returns:
434 Template content string
436 Available templates:
437 - basic_image
438 - responsive_image
439 - style_framework
440 - font_integration
441 - htmx_integration
442 - form_integration
443 - dashboard
444 - base
445 """
446 templates = {
447 "basic_image": BASIC_IMAGE_TEMPLATE,
448 "responsive_image": RESPONSIVE_IMAGE_TEMPLATE,
449 "style_framework": STYLE_FRAMEWORK_TEMPLATE,
450 "font_integration": FONT_INTEGRATION_TEMPLATE,
451 "htmx_integration": HTMX_INTEGRATION_TEMPLATE,
452 "form_integration": FORM_INTEGRATION_TEMPLATE,
453 "dashboard": DASHBOARD_TEMPLATE,
454 "base": BASE_TEMPLATE,
455 }
457 return templates.get(template_name, "")
460def get_adapter_config_example(adapter_name: str) -> dict[str, str]:
461 """Get configuration example for an adapter.
463 Args:
464 adapter_name: Name of the adapter (cloudinary, google_fonts, bulma, fontawesome)
466 Returns:
467 Dictionary with settings_file and example_config
468 """
469 return ADAPTER_CONFIGURATIONS.get(adapter_name, {})
472# Template usage patterns documentation
473USAGE_PATTERNS = """
474# FastBlocks Template Integration Patterns
476## 1. Sync vs Async Filter Usage
478### Sync Filters (Basic functionality)
479```jinja2
480<!-- Basic image tag -->
481[[ img_tag('photo.jpg', 'Description', width=300) ]]
483<!-- Basic font family -->
484<style>
485 body { font-family: [[ font_family('primary') ]]; }
486</style>
488<!-- Style classes -->
489<button [[ style_class('button', variant='primary') ]]>Click Me</button>
490```
492### Async Filters (Full functionality)
493```jinja2
494<!-- Image with transformations -->
495[[ await async_image_url('photo.jpg', width=300, quality=80, format='webp') ]]
497<!-- Complete font import -->
498[[ await async_font_import() ]]
500<!-- Responsive images -->
501[[ await async_responsive_image('hero.jpg', 'Hero', {
502 'mobile': {'width': 400},
503 'desktop': {'width': 1200}
504}) ]]
505```
507## 2. HTMX Integration Patterns
509### Basic HTMX Attributes
510```jinja2
511<div [[ htmx_attrs(get='/api/data', target='#content', swap='innerHTML') ]]>
512 Content will be replaced
513</div>
514```
516### Component Integration
517```jinja2
518<div [[ htmx_component('card', get='/api/card-data', target='this') ]]>
519 Card content
520</div>
521```
523### Form Handling
524```jinja2
525<form [[ htmx_form('/submit', target='#result', validation_target='#errors') ]]>
526 <!-- form fields -->
527</form>
528```
530## 3. Error Handling
532### Error Containers
533```jinja2
534<div [[ htmx_error_container('form-errors') ]]></div>
535```
537### Retry Mechanisms
538```jinja2
539<div [[ htmx_retry_trigger(3, 'exponential') ]]>
540 Content with retry logic
541</div>
542```
544## 4. Performance Optimization
546### Lazy Loading
547```jinja2
548[[ await async_lazy_image('large-image.jpg', 'Description', loading='lazy') ]]
549```
551### Critical Resource Loading
552```jinja2
553[[ await async_critical_css_fonts(['Inter']) ]]
554```
556### Image Placeholders
557```jinja2
558[[ await async_image_placeholder(400, 300, 'Loading...') ]]
559```
561## 5. Template Organization
563### Base Template Structure
5641. Critical resources first (fonts, critical CSS)
5652. Stylesheet links
5663. Custom styles with CSS variables
5674. Content blocks
5685. Scripts last
570### Component Templates
571- Use partial templates for reusable components
572- Combine adapter filters with HTMX for interactivity
573- Include error handling and loading states
575### Layout Templates
576- Extend base template
577- Define navigation and footer blocks
578- Include adapter-specific configurations
579"""