Coverage for fastblocks/adapters/templates/examples_advanced.py: 0%
9 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 00:51 -0700
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 00:51 -0700
1"""Advanced Template Examples for FastBlocks Week 7-8.
3This module provides example templates demonstrating the advanced features:
4- Template validation and error reporting
5- Fragment and partial templates for HTMX
6- Block rendering with autocomplete
7- Secondary adapter integration
8- Performance optimization patterns
10Author: lesleslie <les@wedgwoodwebworks.com>
11Created: 2025-01-12
12"""
14# Example template with validation and autocomplete support
15EXAMPLE_TEMPLATE_WITH_VALIDATION = """
16<!DOCTYPE html>
17<html lang="en">
18<head>
19 <meta charset="UTF-8">
20 <meta name="viewport" content="width=device-width, initial-scale=1.0">
21 <title>[[ title | default('FastBlocks App') ]]</title>
23 [# Enhanced font loading with optimization #]
24 [[ await async_optimized_font_loading(['Inter', 'Roboto Mono'], critical=True) ]]
26 [# Stylesheet links from all adapters #]
27 [[ stylesheet_links() ]]
29 [# WebAwesome icon library #]
30 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css">
32 <style>
33 [[ font_face_declaration('CustomFont', {
34 'woff2': '/fonts/custom.woff2',
35 'woff': '/fonts/custom.woff'
36 }, weight='400', style='normal') ]]
38 body {
39 font-family: [[ font_family('primary') ]];
40 margin: 0;
41 padding: 0;
42 }
44 .htmx-container {
45 min-height: 200px;
46 border: 1px solid #ddd;
47 border-radius: 4px;
48 padding: 20px;
49 }
51 .loading {
52 opacity: 0.6;
53 pointer-events: none;
54 }
55 </style>
56</head>
57<body>
58 [% block header %]
59 <header class="[[ style_class('header', variant='primary') ]]">
60 <nav class="[[ style_class('navbar') ]]">
61 <div class="[[ style_class('navbar-brand') ]]">
62 [[ wa_icon('home', size='24', class='brand-icon') ]]
63 FastBlocks Advanced
64 </div>
66 <div class="[[ style_class('navbar-nav') ]]">
67 <a href="/" class="[[ style_class('nav-link') ]]">
68 [[ phosphor_icon('house', 'bold', size='20') ]] Home
69 </a>
70 <a href="/dashboard" class="[[ style_class('nav-link') ]]">
71 [[ heroicon('chart-bar', 'outline', size='20') ]] Dashboard
72 </a>
73 <a href="/profile" class="[[ style_class('nav-link') ]]">
74 [[ material_icon('person', 'outlined', size='20') ]] Profile
75 </a>
76 </div>
77 </nav>
78 </header>
79 [% endblock %]
81 [% block main %]
82 <main class="container">
83 [% block content %]
84 <div class="hero-section">
85 [# Cloudflare Images with responsive design #]
86 [[ cf_responsive_image('hero-banner.jpg', 'Hero Banner', {
87 'mobile': {'width': 400, 'quality': 75, 'format': 'webp'},
88 'tablet': {'width': 800, 'quality': 80, 'format': 'webp'},
89 'desktop': {'width': 1200, 'quality': 85, 'format': 'webp'}
90 }, class='hero-image', loading='eager') ]]
92 <div class="hero-content">
93 <h1>[[ title | default('Welcome') ]]</h1>
94 <p class="hero-description">
95 [[ description | default('Advanced template management with FastBlocks') ]]
96 </p>
98 [# KelpUI button component #]
99 [[ kelp_component('button', 'Get Started', variant='primary', size='large',
100 class='hero-btn') ]]
101 </div>
102 </div>
104 [# HTMX-powered content sections #]
105 <div class="content-sections">
106 [% include "_user_profile_block.html" %]
107 [% include "_dashboard_widgets.html" %]
108 [% include "_activity_feed.html" %]
109 </div>
110 [% endblock %]
111 </main>
112 [% endblock %]
114 [% block footer %]
115 <footer class="[[ style_class('footer') ]]">
116 <div class="footer-content">
117 <p>© 2025 FastBlocks. Built with advanced template management.</p>
119 [# Social media icons with different icon libraries #]
120 <div class="social-icons">
121 [[ wa_icon_with_text('twitter', 'Twitter', position='bottom') ]]
122 [[ remix_icon('github-line', size='24', class='social-icon') ]]
123 [[ phosphor_icon('linkedin-logo', 'fill', size='24', class='social-icon') ]]
124 </div>
125 </div>
126 </footer>
127 [% endblock %]
129 [# HTMX and advanced features scripts #]
130 <script src="https://unpkg.com/htmx.org@1.9.6"></script>
131 <script>
132 // Enhanced HTMX configuration
133 htmx.config.globalViewTransitions = true;
134 htmx.config.useTemplateFragments = true;
136 // Custom event handlers
137 document.addEventListener('htmx:beforeRequest', function(evt) {
138 evt.detail.elt.classList.add('loading');
139 });
141 document.addEventListener('htmx:afterRequest', function(evt) {
142 evt.detail.elt.classList.remove('loading');
143 });
144 </script>
145</body>
146</html>
147"""
149# Example fragment template for user profile
150EXAMPLE_USER_PROFILE_FRAGMENT = """
151[# _user_profile_block.html - HTMX fragment template #]
152<div id="user-profile-block" class="[[ style_class('card', variant='elevated') ]]"
153 [[ htmx_component('card', get='/api/user/profile', target='#user-profile-content',
154 trigger='load once', swap='innerHTML') ]]>
156 [% block user_profile_header %]
157 <div class="[[ style_class('card-header') ]]">
158 <h3 class="[[ style_class('card-title') ]]">
159 [[ phosphor_icon('user-circle', 'fill', size='24') ]]
160 User Profile
161 </h3>
163 [# Progressive enhancement with fallback #]
164 [[ htmx_progressive_enhancement(
165 '<button class="refresh-btn">Refresh</button>',
166 {'hx-get': '/api/user/profile', 'hx-target': '#user-profile-content'},
167 fallback_action='/user/profile'
168 ) ]]
169 </div>
170 [% endblock %]
172 [% block user_profile_content %]
173 <div id="user-profile-content" class="[[ style_class('card-content') ]]">
174 [% if user %]
175 <div class="user-info">
176 [# TwicPics smart cropping for profile image #]
177 [[ twicpics_smart_crop(user.avatar or 'default-avatar.jpg',
178 80, 80, 'face', class='profile-avatar') ]]
180 <div class="user-details">
181 <h4>[[ user.name | title ]]</h4>
182 <p class="user-email">[[ user.email ]]</p>
183 <p class="user-role">
184 [[ material_icon('badge', 'outlined', size='16') ]]
185 [[ user.role | title ]]
186 </p>
187 </div>
188 </div>
190 <div class="user-stats">
191 [[ kelp_component('stat', user.posts_count, label='Posts', variant='info') ]]
192 [[ kelp_component('stat', user.followers_count, label='Followers', variant='success') ]]
193 [[ kelp_component('stat', user.following_count, label='Following', variant='warning') ]]
194 </div>
195 [% else %]
196 <div class="user-placeholder">
197 [[ wa_icon('user-circle', size='48', class='placeholder-icon') ]]
198 <p>Loading user profile...</p>
199 </div>
200 [% endif %]
201 </div>
202 [% endblock %]
203</div>
204"""
206# Example dashboard widgets with polling
207EXAMPLE_DASHBOARD_WIDGETS = """
208[# _dashboard_widgets.html - Auto-refreshing dashboard widgets #]
209<div class="dashboard-widgets">
210 [% block metrics_widget %]
211 <div id="metrics-widget" class="[[ style_class('widget', variant='primary') ]]"
212 [[ htmx_component('widget',
213 get='/api/dashboard/metrics',
214 target='#metrics-content',
215 trigger='every 30s',
216 indicator='#metrics-loading') ]]>
218 <div class="widget-header">
219 <h4>[[ heroicon('chart-bar', 'outline', size='20') ]] Metrics</h4>
220 <div id="metrics-loading" class="htmx-indicator">
221 [[ wa_icon('spinner', class='spinning') ]]
222 </div>
223 </div>
225 <div id="metrics-content" class="widget-content">
226 [% if metrics %]
227 <div class="metrics-grid">
228 [% for metric in metrics %]
229 <div class="metric-item">
230 [[ kelp_component('metric', metric.value,
231 label=metric.label,
232 trend=metric.trend,
233 variant=metric.variant) ]]
234 </div>
235 [% endfor %]
236 </div>
237 [% else %]
238 <p>Loading metrics...</p>
239 [% endif %]
240 </div>
241 </div>
242 [% endblock %]
244 [% block chart_widget %]
245 <div id="chart-widget" class="[[ style_class('widget', variant='secondary') ]]">
246 <div class="widget-header">
247 <h4>[[ phosphor_icon('chart-line', 'bold', size='20') ]] Analytics</h4>
249 [# Lazy loading chart widget #]
250 [[ htmx_lazy_load('/api/dashboard/chart', 'Loading chart...',
251 trigger='revealed once', target='#chart-content') ]]
252 </div>
254 <div id="chart-content" class="widget-content">
255 [# Chart will be loaded when widget becomes visible #]
256 <div class="chart-placeholder">
257 [[ remix_icon('bar-chart-line', size='48', class='placeholder-icon') ]]
258 <p>Chart will load when visible</p>
259 </div>
260 </div>
261 </div>
262 [% endblock %]
263</div>
264"""
266# Example activity feed with infinite scroll
267EXAMPLE_ACTIVITY_FEED = """
268[# _activity_feed.html - Infinite scroll activity feed #]
269<div class="activity-feed-container">
270 [% block activity_header %]
271 <div class="activity-header">
272 <h3>[[ material_icon('timeline', 'outlined', size='24') ]] Activity Feed</h3>
274 [# Real-time search #]
275 <div class="activity-search">
276 <input type="text" name="q" placeholder="Search activities..."
277 [[ htmx_search('/api/activities/search', 300,
278 target='#activity-list',
279 indicator='#search-loading') ]]>
280 <div id="search-loading" class="htmx-indicator">
281 [[ phosphor_icon('magnifying-glass', 'regular', size='16') ]]
282 </div>
283 </div>
284 </div>
285 [% endblock %]
287 [% block activity_list %]
288 <div id="activity-list" class="activity-list">
289 [% for activity in activities %]
290 <div class="activity-item [[ style_class('card', variant='flat') ]]">
291 <div class="activity-avatar">
292 [[ cf_image_url(activity.user.avatar, width=40, height=40,
293 quality=85, format='webp') | img_tag(activity.user.name) ]]
294 </div>
296 <div class="activity-content">
297 <div class="activity-header">
298 <strong>[[ activity.user.name ]]</strong>
299 <span class="activity-action">[[ activity.action ]]</span>
300 <time class="activity-time">[[ activity.created_at | timeago ]]</time>
301 </div>
303 <div class="activity-description">
304 [[ activity.description | safe ]]
305 </div>
307 [% if activity.image %]
308 <div class="activity-image">
309 [[ twicpics_image(activity.image, resize='300x200',
310 quality=80, class='activity-img') | img_tag('Activity Image') ]]
311 </div>
312 [% endif %]
314 <div class="activity-actions">
315 [# HTMX-powered like button #]
316 <button [[ htmx_icon_toggle('heart-filled', 'heart-outline',
317 post=f'/api/activities/{activity.id}/like',
318 target='this',
319 class='like-btn') ]]>
320 [[ phosphor_icon('heart', 'regular' if not activity.is_liked else 'fill',
321 size='16') ]]
322 [[ activity.likes_count ]]
323 </button>
325 <button class="comment-btn"
326 [[ htmx_modal(f'/activities/{activity.id}/comments',
327 target='#modal-container') ]]>
328 [[ heroicon('chat-bubble-left', 'outline', size='16') ]]
329 [[ activity.comments_count ]]
330 </button>
331 </div>
332 </div>
333 </div>
334 [% endfor %]
336 [# Infinite scroll sentinel #]
337 [% if has_more_activities %]
338 [[ htmx_infinite_scroll_sentinel(f'/api/activities?page={next_page}',
339 '#activity-list') ]]
340 [% endif %]
341 </div>
342 [% endblock %]
343</div>
344"""
346# Example form with validation
347EXAMPLE_VALIDATION_FORM = """
348[# _validation_form.html - Form with real-time validation #]
349<div class="form-container">
350 <form [[ htmx_form('/api/users/create', target='#form-container',
351 validation_target='#form-errors') ]]>
353 <div id="form-errors" [[ htmx_error_container('form-errors') ]]></div>
355 <div class="form-group">
356 <label for="username">[[ material_icon('person', 'outlined', size='20') ]] Username</label>
357 <input type="text" id="username" name="username" required
358 [[ htmx_validation_feedback('username',
359 validate_url='/api/validate/username') ]]>
360 <div id="username-feedback" class="validation-feedback"></div>
361 </div>
363 <div class="form-group">
364 <label for="email">[[ heroicon('envelope', 'outline', size='20') ]] Email</label>
365 <input type="email" id="email" name="email" required
366 [[ htmx_validation_feedback('email',
367 validate_url='/api/validate/email') ]]>
368 <div id="email-feedback" class="validation-feedback"></div>
369 </div>
371 <div class="form-group">
372 <label for="avatar">Profile Picture</label>
373 <input type="file" id="avatar" name="avatar" accept="image/*">
374 <div class="file-preview" id="avatar-preview">
375 [# Preview will be shown here #]
376 </div>
377 </div>
379 <div class="form-actions">
380 [[ kelp_component('button', 'Create User', type='submit',
381 variant='primary', class='submit-btn') ]]
383 <button type="button" class="cancel-btn">
384 [[ wa_icon('times', size='16') ]] Cancel
385 </button>
386 </div>
387 </form>
388</div>
390<script>
391 // File preview with image optimization
392 document.getElementById('avatar').addEventListener('change', function(e) {
393 const file = e.target.files[0];
394 if (file && file.type.startsWith('image/')) {
395 const preview = document.getElementById('avatar-preview');
396 const img = document.createElement('img');
397 img.src = URL.createObjectURL(file);
398 img.style.maxWidth = '200px';
399 img.style.maxHeight = '200px';
400 img.className = 'preview-image';
401 preview.innerHTML = '';
402 preview.appendChild(img);
403 }
404 });
405</script>
406"""
408# Template examples dictionary for easy access
409TEMPLATE_EXAMPLES = {
410 "main_layout": EXAMPLE_TEMPLATE_WITH_VALIDATION,
411 "user_profile_fragment": EXAMPLE_USER_PROFILE_FRAGMENT,
412 "dashboard_widgets": EXAMPLE_DASHBOARD_WIDGETS,
413 "activity_feed": EXAMPLE_ACTIVITY_FEED,
414 "validation_form": EXAMPLE_VALIDATION_FORM,
415}
417# Template validation examples
418VALIDATION_EXAMPLES = {
419 "valid_template": """
420 <div class="user-card">
421 <h3>[[ user.name | title ]]</h3>
422 <p>[[ user.email ]]</p>
423 [[ wa_icon('user', size='24') ]]
424 </div>
425 """,
426 "template_with_errors": """
427 <div class="user-card">
428 <h3>[[ undefined_variable | title ]]</h3>
429 <p>[[ user.email ]]</p>
430 [[ unknown_filter(user.name) ]]
431 [[ wa_icon('user' ]] [# Missing closing parenthesis #]
432 </div>
433 """,
434 "template_with_warnings": """
435 <div class="user-card">
436 <h3>[[ optional_user.name | default('Guest') ]]</h3>
437 <p>[[ user.email ]]</p>
438 [[ user.bio | safe ]] [# Should validate if safe is appropriate #]
439 </div>
440 """,
441}
443# Autocomplete examples
444AUTOCOMPLETE_EXAMPLES = {
445 "filter_completion": "[[ user.name | ",
446 "function_completion": "[[ wa_",
447 "variable_completion": "[[ us",
448 "async_completion": "[[ await async_",
449}
451# Block definition examples
452BLOCK_EXAMPLES = {
453 "polling_block": {
454 "name": "live_stats",
455 "template_name": "_live_stats.html",
456 "htmx_endpoint": "/api/stats/live",
457 "trigger": "polling",
458 "auto_refresh": 10,
459 "update_mode": "replace",
460 },
461 "lazy_block": {
462 "name": "user_comments",
463 "template_name": "_user_comments.html",
464 "htmx_endpoint": "/api/users/{user_id}/comments",
465 "trigger": "lazy",
466 "update_mode": "inner",
467 },
468 "form_block": {
469 "name": "edit_profile",
470 "template_name": "_edit_profile_form.html",
471 "htmx_endpoint": "/api/users/{user_id}/edit",
472 "trigger": "manual",
473 "update_mode": "outer",
474 },
475}