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

1"""Template integration examples demonstrating FastBlocks adapter filters. 

2 

3This module provides complete examples showing how to use FastBlocks adapter 

4filters in Jinja2 templates with both sync and async patterns. 

5""" 

6 

7# Example 1: Basic Image Integration 

8BASIC_IMAGE_TEMPLATE = """ 

9[% extends "base.html" %] 

10 

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

15 

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

23 

24# Example 2: Responsive Image Integration 

25RESPONSIVE_IMAGE_TEMPLATE = """ 

26[% extends "base.html" %] 

27 

28[% block content %] 

29<article class="blog-post"> 

30 <h1>Article Title</h1> 

31 

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

38 

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

47 

48# Example 3: Style Framework Integration 

49STYLE_FRAMEWORK_TEMPLATE = """ 

50[% extends "base.html" %] 

51 

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

72 

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> 

81 

82 <!-- Critical font loading (async) --> 

83 [[ await async_critical_css_fonts(['Inter', 'Playfair Display']) ]] 

84 

85 <!-- Complete font stack (async) --> 

86 [[ await async_optimized_font_stack() ]] 

87 

88 <!-- Fallback font imports (sync) --> 

89 [[ font_import() ]] 

90 

91 <style> 

92 body { 

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

94 line-height: 1.6; 

95 } 

96 

97 h1, h2, h3 { 

98 font-family: [[ font_family('heading') ]]; 

99 } 

100 

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

113 

114# Example 5: HTMX Integration with Adapters 

115HTMX_INTEGRATION_TEMPLATE = """ 

116[% extends "base.html" %] 

117 

118[% block content %] 

119<div class="interactive-gallery"> 

120 <h2>Interactive Image Gallery</h2> 

121 

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

132 

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> 

141 

142 <!-- Modal container --> 

143 <div id="modal-container" [[ htmx_error_container('gallery-errors') ]]></div> 

144 

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

153 

154# Example 6: Form Integration with Validation 

155FORM_INTEGRATION_TEMPLATE = """ 

156[% extends "base.html" %] 

157 

158[% block content %] 

159<div [[ style_class('form-container') ]]> 

160 <h2>User Registration</h2> 

161 

162 <form [[ htmx_form('/users/register', target='#form-container', 

163 validation_target='#form-errors') ]]> 

164 

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> 

175 

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> 

186 

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> 

200 

201 <!-- Submit button --> 

202 <button type="submit" [[ style_class('button', variant='primary', size='large') ]]> 

203 [[ icon_with_text('user-plus', 'Create Account') ]] 

204 </button> 

205 

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> 

211 

212 <!-- Error container --> 

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

214</div> 

215[% endblock %] 

216""" 

217 

218# Example 7: Dashboard with WebSocket Integration 

219DASHBOARD_TEMPLATE = """ 

220[% extends "base.html" %] 

221 

222[% block content %] 

223<div class="dashboard" [[ htmx_ws_connect('/ws/dashboard', listen='data-update') ]]> 

224 

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> 

232 

233 <!-- Real-time notifications --> 

234 <div id="notifications" [[ style_class('notifications') ]]></div> 

235 </header> 

236 

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> 

251 

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> 

261 

262 <div id="chart-container" [[ style_class('chart-container') ]]> 

263 <!-- Chart will be loaded here --> 

264 </div> 

265 

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

273 

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> 

282 

283 <!-- Critical resources first --> 

284 [[ await async_critical_css_fonts() ]] 

285 

286 <!-- All stylesheets --> 

287 [[ stylesheet_links() ]] 

288 

289 <!-- Complete font stack --> 

290 [[ await async_optimized_font_stack() ]] 

291 

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 } 

299 

300 body { 

301 font-family: var(--font-primary); 

302 margin: 0; 

303 padding: 0; 

304 } 

305 

306 .loading-indicator { 

307 display: flex; 

308 align-items: center; 

309 gap: 0.5rem; 

310 } 

311 

312 .animate-spin { 

313 animation: spin 1s linear infinite; 

314 } 

315 

316 .animate-pulse { 

317 animation: pulse 2s ease-in-out infinite; 

318 } 

319 

320 @keyframes spin { 

321 from { transform: rotate(0deg); } 

322 to { transform: rotate(360deg); } 

323 } 

324 

325 @keyframes pulse { 

326 0%, 100% { opacity: 1; } 

327 50% { opacity: 0.5; } 

328 } 

329 </style> 

330 

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> 

339 

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> 

351 

352 <!-- Main content --> 

353 <main [[ style_class('main-content') ]]> 

354 [% block content %][% endblock %] 

355 </main> 

356 

357 <!-- Footer --> 

358 <footer [[ style_class('footer') ]]> 

359 [% block footer %] 

360 <p>&copy; 2024 FastBlocks App. All rights reserved.</p> 

361 [% endblock %] 

362 </footer> 

363 

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> 

367 

368 [% block scripts %][% endblock %] 

369</body> 

370</html> 

371""" 

372 

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} 

425 

426 

427def get_example_template(template_name: str) -> str: 

428 """Get an example template by name. 

429 

430 Args: 

431 template_name: Name of the template example 

432 

433 Returns: 

434 Template content string 

435 

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 } 

456 

457 return templates.get(template_name, "") 

458 

459 

460def get_adapter_config_example(adapter_name: str) -> dict[str, str]: 

461 """Get configuration example for an adapter. 

462 

463 Args: 

464 adapter_name: Name of the adapter (cloudinary, google_fonts, bulma, fontawesome) 

465 

466 Returns: 

467 Dictionary with settings_file and example_config 

468 """ 

469 return ADAPTER_CONFIGURATIONS.get(adapter_name, {}) 

470 

471 

472# Template usage patterns documentation 

473USAGE_PATTERNS = """ 

474# FastBlocks Template Integration Patterns 

475 

476## 1. Sync vs Async Filter Usage 

477 

478### Sync Filters (Basic functionality) 

479```jinja2 

480<!-- Basic image tag --> 

481[[ img_tag('photo.jpg', 'Description', width=300) ]] 

482 

483<!-- Basic font family --> 

484<style> 

485 body { font-family: [[ font_family('primary') ]]; } 

486</style> 

487 

488<!-- Style classes --> 

489<button [[ style_class('button', variant='primary') ]]>Click Me</button> 

490``` 

491 

492### Async Filters (Full functionality) 

493```jinja2 

494<!-- Image with transformations --> 

495[[ await async_image_url('photo.jpg', width=300, quality=80, format='webp') ]] 

496 

497<!-- Complete font import --> 

498[[ await async_font_import() ]] 

499 

500<!-- Responsive images --> 

501[[ await async_responsive_image('hero.jpg', 'Hero', { 

502 'mobile': {'width': 400}, 

503 'desktop': {'width': 1200} 

504}) ]] 

505``` 

506 

507## 2. HTMX Integration Patterns 

508 

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

515 

516### Component Integration 

517```jinja2 

518<div [[ htmx_component('card', get='/api/card-data', target='this') ]]> 

519 Card content 

520</div> 

521``` 

522 

523### Form Handling 

524```jinja2 

525<form [[ htmx_form('/submit', target='#result', validation_target='#errors') ]]> 

526 <!-- form fields --> 

527</form> 

528``` 

529 

530## 3. Error Handling 

531 

532### Error Containers 

533```jinja2 

534<div [[ htmx_error_container('form-errors') ]]></div> 

535``` 

536 

537### Retry Mechanisms 

538```jinja2 

539<div [[ htmx_retry_trigger(3, 'exponential') ]]> 

540 Content with retry logic 

541</div> 

542``` 

543 

544## 4. Performance Optimization 

545 

546### Lazy Loading 

547```jinja2 

548[[ await async_lazy_image('large-image.jpg', 'Description', loading='lazy') ]] 

549``` 

550 

551### Critical Resource Loading 

552```jinja2 

553[[ await async_critical_css_fonts(['Inter']) ]] 

554``` 

555 

556### Image Placeholders 

557```jinja2 

558[[ await async_image_placeholder(400, 300, 'Loading...') ]] 

559``` 

560 

561## 5. Template Organization 

562 

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 

569 

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 

574 

575### Layout Templates 

576- Extend base template 

577- Define navigation and footer blocks 

578- Include adapter-specific configurations 

579"""