| name | glue-fetch |
| description | GlueFetchHelper patterns for making AJAX calls in templates |
GlueFetch
Overview
GlueFetchHelper is a JavaScript utility from Django Spire that provides a consistent interface for making AJAX calls from templates using Alpine.js. It wraps fetch with built-in CSRF token handling, error parsing, and standardized response handling.
Use GlueFetch when you need to:
- Trigger backend actions from interactive UI elements
- Fetch data without a full page reload
- Handle form submissions via AJAX
- Implement reactive UI patterns that communicate with the server
Core Concepts
The tryGlueFetch Method
GlueFetchHelper returns a promise that resolves to { success, response }:
let {success, response} = await GlueFetchHelper.tryGlueFetch(url, {
payload: { key: 'value' },
});
if (success) {
this.field = response.deliverables;
} else {
dispatchError(response);
}
Return Value
| Property | Type | Description |
|---|
success | boolean | true if request succeeded, false on error |
response | object | Parsed response data from the backend |
Options
| Option | Type | Description |
|---|
url | string | The endpoint URL (typically built with Django's {% url %}) |
payload | object | JSON body sent with the request |
method | string | HTTP method (POST by default when payload provided) |
URL Construction
Always use Django's {% url %} template tag for URL generation:
let url = '{% url 'app:view:json:action' pk=object.pk %}';
For URLs with query parameters, build them inline:
let url = '{% url 'app:view:json:lookup' %}' + '?barcode=' + encodeURIComponent(input.toUpperCase());
CSRF Protection
GlueFetch automatically includes the CSRF token — no additional configuration needed.
Common Patterns
Toggle State
let {success, response} = await GlueFetchHelper.tryGlueFetch(url, {
payload: { is_active: !this.isActive },
})
if (success) {
this.isActive = !this.isActive
}
Dynamic URL with Query Params
let url = '{% url 'app:view:json:lookup' %}' + '?barcode=' + encodeURIComponent(input.toUpperCase())
let {success, response} = await GlueFetchHelper.tryGlueFetch(url)
if (success) {
this.items.push({ pk: response.pk, name: response.barcode })
this.scanInput = ''
} else {
this.scanError = 'Not found.'
this.scanInput = ''
}
URL Building with Parameters
let params = new URLSearchParams()
if (this.filterType === 'active') {
params.set('status', 'active')
} else {
params.set('bin', this.selectedBinPk)
}
let {success, response} = await GlueFetchHelper.tryGlueFetch(url + '?' + params.toString())
if (success) {
this.refresh()
}
Template Responses (ViewGlue)
Use ViewGlue to fetch and render template fragments dynamically. This pattern is ideal for:
- Adding new items to a list
- Replacing individual rows or cards
- Lazy-loading complex components
- Dynamic form sections
ViewGlue Methods
| Method | Description |
|---|
render_inner(element) | Replaces element's innerHTML with rendered template |
render_outer(element) | Replaces element and its contents with rendered template |
render_insert_adjacent(element, data, position) | Inserts HTML at position ('afterbegin', 'beforeend', etc.) |
Adding Items to a List
async add_line_item(pk = 0) {
let base = '{% url 'partner:scope_of_work:line_item:template:form' pk=0 %}'
const url = base.replace('0', pk)
let glue_view = new ViewGlue(url)
toggleLoadingOverlay()
await glue_view.render_insert_adjacent(this.$refs.line_items)
toggleLoadingOverlay()
}
Replacing a Component
async edit_item(pk) {
let url = '{% url 'app:model:template:form' pk=0 %}'.replace('0', pk)
let view = new ViewGlue(url)
toggleLoadingOverlay()
await view.render_inner(this.$refs.itemContainer)
toggleLoadingOverlay()
}
Deleting and Re-rendering
async delete_item(pk, element) {
let {success} = await GlueFetchHelper.tryGlueFetch('{% url 'app:model:json:delete' pk=pk %}', {
payload: {},
})
if (success) {
let url = '{% url 'app:model:template:detail' pk=pk %}'
let view = new ViewGlue(url)
await view.render_outer(element)
}
}
Two Response Patterns
Pattern 1: JSON Response (GlueFetch)
Use when the response is simple data (PK, status, message).
let {success, response} = await GlueFetchHelper.tryGlueFetch(url, {
payload: { field: this.field },
})
if (success) {
this.refresh()
}
Backend returns:
return success_json_response(pk=result.pk, message='Created')
Pattern 2: Template Response (ViewGlue)
Use when you need to render HTML dynamically.
let view = new ViewGlue('{% url 'app:model:template:form' pk=pk %}')
toggleLoadingOverlay()
await view.render_insert_adjacent(this.$refs.listContainer)
toggleLoadingOverlay()
Backend returns:
return TemplateResponse(request, context, template='app/model/form/form.html')
When to Use Which
| Use Case | Pattern |
|---|
| Simple actions (toggle, delete) | JSON Response |
| Status updates | JSON Response |
| Adding new items | Template Response |
| Replacing dynamic content | Template Response |
| Complex nested forms | Template Response |
| Real-time validation | Template Response |
Backend JSON Views
Imports
from __future__ import annotations
from typing import TYPE_CHECKING
from django.contrib.auth.decorators import permission_required
from django.http import JsonResponse
from django_spire.contrib.form.utils import form_error_as_str
from django_spire.contrib.responses.json_response import error_json_response, success_json_response
from django_spire.core.shortcuts import process_request_body
if TYPE_CHECKING:
from django.core.handlers.wsgi import WSGIRequest
Response Helpers
Success Response:
return success_json_response(
message='Preview Generated!',
deliverables=deliverables.to_string(),
hosting=hosting.to_string(),
)
Returns:
{"success": true, "message": "Preview Generated!", "deliverables": "..."}
Error Response:
return error_json_response(form_error_as_str(form))
Returns:
{"success": false, "message": "Form validation failed: field is required."}
View Pattern
@permission_required('app_model.change_model')
def action_json_view(request: WSGIRequest) -> JsonResponse:
data = process_request_body(request, key='')
form = ActionForm(data)
if form.is_valid():
try:
result = Model.objects.create(**form.cleaned_data)
return success_json_response(
message='Created successfully',
pk=result.pk
)
except Exception as e:
return error_json_response(str(e))
return error_json_response(form_error_as_str(form))
URL Patterns
from django.urls import path
from app.module.views import json_views
app_name = 'json'
urlpatterns = [
path('ai-preview/', json_views.ai_preview_json_view, name='ai_preview'),
path('generate/', json_views.generate_json_view, name='generate'),
]
Backend Template Views
Imports
from __future__ import annotations
from typing import TYPE_CHECKING
from django.contrib.auth.decorators import permission_required
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from app.model.models import Model
if TYPE_CHECKING:
from django.core.handlers.wsgi import WSGIRequest
View Pattern
@permission_required('app_model.view_model')
def form_template_view(request: WSGIRequest, pk: int) -> TemplateResponse:
obj = get_object_or_404(Model, pk=pk)
context = {
'object': obj,
}
return TemplateResponse(
request,
context=context,
template='app/model/form/form.html'
)
@permission_required('app_model.add_model')
def create_template_view(request: WSGIRequest) -> TemplateResponse:
obj = Model()
context = {
'object': obj,
}
return TemplateResponse(
request,
context=context,
template='app/model/component/item.html'
)
URL Patterns
from django.urls import path
from app.module.views import template_views
app_name = 'template'
urlpatterns = [
path('form/<int:pk>/', template_views.form_template_view, name='form'),
path('create/', template_views.create_template_view, name='create'),
path('detail/<int:pk>/', template_views.detail_template_view, name='detail'),
]
Common Response Patterns
| Pattern | Example |
|---|
| Return message | success_json_response(message='Done') |
| Return multiple fields | success_json_response(name=value, status=status) |
| Return new object PK | success_json_response(pk=result.pk, message='Created') |
| Form validation errors | error_json_response(form_error_as_str(form)) |
| Exception errors | error_json_response(str(e)) |
Notifications
Django Spire includes a notification system that displays toast messages. Use the dispatch helpers to show user feedback:
dispatchSuccess(message)
dispatchError(message)
dispatchWarning(message)
dispatchInfo(message)
Notification Pattern
After a successful action, dispatch a notification and handle the response:
let {success, response} = await GlueFetchHelper.tryGlueFetch(url, {
payload: { field: this.field },
});
if (success) {
dispatchSuccess(response.message || 'Action completed successfully.');
this.refresh();
} else {
dispatchError(response.message);
}
Full Example
async saveItem() {
let {success, response} = await GlueFetchHelper.tryGlueFetch('{% url 'app:model:json:save' pk=object.pk %}', {
payload: {
name: this.name,
description: this.description,
},
});
if (success) {
dispatchSuccess(response.message || 'Item saved successfully.');
close_modal();
} else {
dispatchError(response.message);
}
}
Alpine.js Event Dispatching
Use Alpine's $dispatch for cross-component communication. This allows child components (like modals) to notify parent components (like pages) to refresh data.
Dispatching Events
From a child component (modal, form, etc.):
if (success) {
dispatchSuccess(response.message);
this.$dispatch('item-saved', { id: response.pk });
close_modal();
} else {
dispatchError(response.message);
}
Listening for Events
In the parent component, use Alpine's .window modifier to listen for events that bubble up:
<div x-data="{ items: [] }" @item-saved.window="refreshItems()">
</div>
Note: Without .window, the event won't reach sibling components due to event bubbling. Always use @event-name.window for cross-component communication.
Passing JSON Data to Alpine.js
When you need to pass dynamic data (like arrays of objects) from Django to Alpine.js, use JSON.parse with the |escapejs filter.
Proper JSON Parsing Pattern
Django template:
<div x-data="{
items: JSON.parse('{{ items_json|escapejs }}'),
}">
<template x-for="item in items" :key="item.pk">
<option :value="item.pk" x-text="item.name"></option>
</template>
</div>
Key points:
- Wrap JSON in
JSON.parse() — never inject raw JSON into x-data
- Use
|escapejs filter — handles special characters and prevents syntax errors
- Access data with
this.items inside methods
- Use
x-for with :key for dynamic lists
Anti-Pattern (Don't Do This)
<div x-data="{
items: {{ items_json|safe }},
}">
Finding DOM Elements
Use x-ref instead of document.querySelector:
<input x-ref="searchInput" type="text">
<button @click="$nextTick(() => { $refs.searchInput?.focus(); })">Focus</button>
this.$nextTick(() => {
this.$refs.searchInput?.focus();
});
| Pattern | Use Case |
|---|
x-ref="name" | Mark element for Alpine access |
this.$refs.name | Access in Alpine methods |
$refs.name?.focus() | Focus, click, scroll, etc. |
$nextTick() | Wait for DOM update before accessing |
$refs for DOM Access
Use x-ref to mark elements and $refs to access them:
<div x-data="{
async refreshTable() {
let view = new ViewGlue('{% url 'app:model:template:table' pk=object.pk %}');
await view.render_inner(this.$refs.tableContainer);
}
}">
<div x-ref="tableContainer">
{% include 'app/model/component/table.html' %}
</div>
</div>
| $ref Pattern | Description |
|---|
x-ref="name" | Mark element for reference |
this.$refs.name | Access the DOM element |
render_inner() | Replace element's contents |
render_outer() | Replace entire element |
Complete Workflow: Form → Notification → Table Refresh
This pattern shows a complete flow: submit form, show notification, refresh parent table.
1. Parent Component (Page with Table)
<div x-data="{
async refreshFinishedGoods() {
let url = '{% url 'app:model:template:finished_goods_table' pk=object.pk %}';
let view = new ViewGlue(url);
toggleLoadingOverlay();
await view.render_inner(this.$refs.finishedGoodsTable);
toggleLoadingOverlay();
}
}" @pallet-completed.window="refreshFinishedGoods()">
<button @click="dispatch_modal_view('...')">Complete Pallet</button>
<div x-ref="finishedGoodsTable">
{% include 'app/model/component/finished_goods_table.html' %}
</div>
</div>
2. Child Component (Modal Form)
async confirmPallet() {
let {success, response} = await GlueFetchHelper.tryGlueFetch(
'{% url 'production:line:station:json:complete_pallet' %}',
{ payload: { station: this.station, case_count: this.cases } }
);
if (success) {
dispatchSuccess(response.message);
this.$dispatch('pallet-completed', { finished_good: this.product });
close_modal();
} else {
dispatchError(response.message);
}
}
3. Backend View (returns JSON)
@login_required
def complete_pallet_view(request: WSGIRequest) -> dict:
data = process_request_body(request, key='')
form = CompletePalletForm(data)
if not form.is_valid():
return error_json_response(form.errors.as_text())
record = create_pallet(form.cleaned_data)
return success_json_response(
message=f'{record.name} pallet completed.',
record_id=record.pk,
)
4. Backend Template View (returns HTML)
@login_required
def finished_goods_table_view(request: WSGIRequest, pk: int) -> TemplateResponse:
line = get_object_or_404(ProductionLine, pk=pk)
finished_goods = line.inventory_records.filter(
inventory__type=InventoryTypeChoices.FINISHED_GOOD
).select_related('inventory')[:20]
return TemplateResponse(
request,
context={'finished_goods': finished_goods},
template='app/model/component/finished_goods_table.html'
)
5. Table Template
{% if finished_goods %}
<table class="table">
{% for record in finished_goods %}
<tr>
<td>{{ record.barcode }}</td>
<td>{{ record.inventory.name }}</td>
<td>{{ record.quantity }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<p class="text-muted">No records yet.</p>
{% endif %}
6. URL Configuration
urlpatterns = [
path('complete-pallet/', json_views.complete_pallet_view, name='complete_pallet'),
]
urlpatterns = [
path('<int:pk>/finished-goods/table/', template_views.finished_goods_table_view, name='finished_goods_table'),
]
Frontend (Alpine.js Data)
- JSON Parsing: Uses
JSON.parse('{{ data|escapejs }}') for passing Django data to Alpine
- DOM Access: Uses
x-ref + $refs instead of document.querySelector()
Backend: Passing JSON to Templates
When views need to pass data arrays to Alpine.js, generate JSON in the view:
import json
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from app.production.line.output.models import ProductionLineOutput
from app.production.line.output.choices import ProductionLineOutputTypeChoices
@permission_required('production.view_production')
def complete_pallet_modal_view(request: WSGIRequest, pk: int) -> TemplateResponse:
station = get_object_or_404(models.ProductionLineStation, pk=pk)
finished_goods = ProductionLineOutput.objects.filter(
production_line=station.production_line,
type=ProductionLineOutputTypeChoices.FINISHED_GOOD,
).select_related('inventory')
return TemplateResponse(
request,
template='production/line/station/modal/content/complete_pallet.html',
context={
'station': station,
'finished_goods_json': json.dumps([
{'pk': fg.inventory.pk, 'name': fg.inventory.name}
for fg in finished_goods
]),
},
)
Note: Always select_related() on foreign keys used in the JSON to avoid N+1 queries.
Code Review Checklist
Frontend (GlueFetch)
- Destructuring Pattern: Uses
let {success, response} = await destructuring
- URL Generation: Uses
{% url %} template tag for all endpoints
- Async/Await: Calls are prefixed with
await inside async functions
- Error Handling:
if (success) checks before accessing response
- Response Access: Access fields directly from
response (not nested in data.message)
- Payload Format: JSON payload keys match backend form field names
- Loading Overlays:
toggleLoadingOverlay() called before and after async operations
- Notifications: Uses
dispatchSuccess() and dispatchError() for user feedback
- Alpine Dispatch: Uses
this.$dispatch('event-name', data) for cross-component events
- Event Listener: Parent uses
@event-name.window="handler()" with .window modifier
Frontend (ViewGlue)
- Loading Overlays:
toggleLoadingOverlay() called before and after render operations
- ViewGlue Instantiation:
new ViewGlue(url) created with URL
- Render Method: Correct method used (
render_inner, render_outer, render_insert_adjacent)
- Await Usage:
await used with render methods
- $refs: Uses
x-ref directive and this.$refs to access DOM elements
Backend
- JSON Response: Uses
success_json_response / error_json_response
- Template Response: Uses
TemplateResponse for template rendering
- Permissions: Decorated with
@permission_required or @login_required
- Request Parsing: Uses
process_request_body for JSON payloads
- Form Validation: Validates forms before processing
- Error Messages: Uses
form_error_as_str(form) for validation errors
- Type Hints: Views have proper type hints (
WSGIRequest, dict for responses)
Related Skills