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

1"""Advanced Template Examples for FastBlocks Week 7-8. 

2 

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 

9 

10Author: lesleslie <les@wedgwoodwebworks.com> 

11Created: 2025-01-12 

12""" 

13 

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> 

22 

23 [# Enhanced font loading with optimization #] 

24 [[ await async_optimized_font_loading(['Inter', 'Roboto Mono'], critical=True) ]] 

25 

26 [# Stylesheet links from all adapters #] 

27 [[ stylesheet_links() ]] 

28 

29 [# WebAwesome icon library #] 

30 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6/css/all.min.css"> 

31 

32 <style> 

33 [[ font_face_declaration('CustomFont', { 

34 'woff2': '/fonts/custom.woff2', 

35 'woff': '/fonts/custom.woff' 

36 }, weight='400', style='normal') ]] 

37 

38 body { 

39 font-family: [[ font_family('primary') ]]; 

40 margin: 0; 

41 padding: 0; 

42 } 

43 

44 .htmx-container { 

45 min-height: 200px; 

46 border: 1px solid #ddd; 

47 border-radius: 4px; 

48 padding: 20px; 

49 } 

50 

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> 

65 

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 %] 

80 

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') ]] 

91 

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> 

97 

98 [# KelpUI button component #] 

99 [[ kelp_component('button', 'Get Started', variant='primary', size='large', 

100 class='hero-btn') ]] 

101 </div> 

102 </div> 

103 

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 %] 

113 

114 [% block footer %] 

115 <footer class="[[ style_class('footer') ]]"> 

116 <div class="footer-content"> 

117 <p>&copy; 2025 FastBlocks. Built with advanced template management.</p> 

118 

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 %] 

128 

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; 

135 

136 // Custom event handlers 

137 document.addEventListener('htmx:beforeRequest', function(evt) { 

138 evt.detail.elt.classList.add('loading'); 

139 }); 

140 

141 document.addEventListener('htmx:afterRequest', function(evt) { 

142 evt.detail.elt.classList.remove('loading'); 

143 }); 

144 </script> 

145</body> 

146</html> 

147""" 

148 

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') ]]> 

155 

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> 

162 

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 %] 

171 

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') ]] 

179 

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> 

189 

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""" 

205 

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') ]]> 

217 

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> 

224 

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 %] 

243 

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> 

248 

249 [# Lazy loading chart widget #] 

250 [[ htmx_lazy_load('/api/dashboard/chart', 'Loading chart...', 

251 trigger='revealed once', target='#chart-content') ]] 

252 </div> 

253 

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""" 

265 

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> 

273 

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 %] 

286 

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> 

295 

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> 

302 

303 <div class="activity-description"> 

304 [[ activity.description | safe ]] 

305 </div> 

306 

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 %] 

313 

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> 

324 

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 %] 

335 

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""" 

345 

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') ]]> 

352 

353 <div id="form-errors" [[ htmx_error_container('form-errors') ]]></div> 

354 

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> 

362 

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> 

370 

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> 

378 

379 <div class="form-actions"> 

380 [[ kelp_component('button', 'Create User', type='submit', 

381 variant='primary', class='submit-btn') ]] 

382 

383 <button type="button" class="cancel-btn"> 

384 [[ wa_icon('times', size='16') ]] Cancel 

385 </button> 

386 </div> 

387 </form> 

388</div> 

389 

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""" 

407 

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} 

416 

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} 

442 

443# Autocomplete examples 

444AUTOCOMPLETE_EXAMPLES = { 

445 "filter_completion": "[[ user.name | ", 

446 "function_completion": "[[ wa_", 

447 "variable_completion": "[[ us", 

448 "async_completion": "[[ await async_", 

449} 

450 

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}