with one click
glue-fetch
GlueFetchHelper patterns for making AJAX calls in templates
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
GlueFetchHelper patterns for making AJAX calls in templates
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Best practices for writing Django tests (updated template)
Advanced seeding patterns for linking specific foreign keys
Apply DjangoSpire app CSS variables in templates instead of inline styles
Django URL configuration patterns, namespaces, and nested structure conventions
Best practices for AI bots, prompts, and intel in scope_of_work
Examples on how we build our form views.
| name | glue-fetch |
| description | GlueFetchHelper patterns for making AJAX calls in templates |
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:
tryGlueFetch MethodGlueFetchHelper 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);
}
| Property | Type | Description |
|---|---|---|
success | boolean | true if request succeeded, false on error |
response | object | Parsed response data from the backend |
| 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) |
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());
GlueFetch automatically includes the CSRF token — no additional configuration needed.
let {success, response} = await GlueFetchHelper.tryGlueFetch(url, {
payload: { is_active: !this.isActive },
})
if (success) {
this.isActive = !this.isActive
}
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 = ''
}
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()
}
Use ViewGlue to fetch and render template fragments dynamically. This pattern is ideal for:
| 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.) |
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()
}
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()
}
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)
}
}
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')
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')
| 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 |
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
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."}
@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))
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'),
]
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
@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'
)
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'),
]
| 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)) |
Django Spire includes a notification system that displays toast messages. Use the dispatch helpers to show user feedback:
// Available helpers from django_spire notification.js
dispatchSuccess(message) // Shows success toast
dispatchError(message) // Shows error toast
dispatchWarning(message) // Shows warning toast
dispatchInfo(message) // Shows info toast
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);
}
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);
}
}
Use Alpine's $dispatch for cross-component communication. This allows child components (like modals) to notify parent components (like pages) to refresh data.
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);
}
In the parent component, use Alpine's .window modifier to listen for events that bubble up:
<div x-data="{ items: [] }" @item-saved.window="refreshItems()">
<!-- content -->
</div>
Note: Without
.window, the event won't reach sibling components due to event bubbling. Always use@event-name.windowfor cross-component communication.
When you need to pass dynamic data (like arrays of objects) from Django to Alpine.js, use JSON.parse with the |escapejs filter.
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:
JSON.parse() — never inject raw JSON into x-data|escapejs filter — handles special characters and prevents syntax errorsthis.items inside methodsx-for with :key for dynamic lists<!-- ❌ Broken - will break if JSON contains single quotes -->
<div x-data="{
items: {{ items_json|safe }},
}">
Use x-ref instead of document.querySelector:
<input x-ref="searchInput" type="text">
<button @click="$nextTick(() => { $refs.searchInput?.focus(); })">Focus</button>
// Inside x-data
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 |
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 |
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()">
<!-- Actions section -->
<button @click="dispatch_modal_view('...')">Complete Pallet</button>
<!-- Table section -->
<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
# urls/json_urls.py
urlpatterns = [
path('complete-pallet/', json_views.complete_pallet_view, name='complete_pallet'),
]
# urls/template_urls.py
urlpatterns = [
path('<int:pk>/finished-goods/table/', template_views.finished_goods_table_view, name='finished_goods_table'),
]
JSON.parse('{{ data|escapejs }}') for passing Django data to Alpinex-ref + $refs instead of document.querySelector()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.
let {success, response} = await destructuring{% url %} template tag for all endpointsawait inside async functionsif (success) checks before accessing responseresponse (not nested in data.message)toggleLoadingOverlay() called before and after async operationsdispatchSuccess() and dispatchError() for user feedbackthis.$dispatch('event-name', data) for cross-component events@event-name.window="handler()" with .window modifiertoggleLoadingOverlay() called before and after render operationsnew ViewGlue(url) created with URLrender_inner, render_outer, render_insert_adjacent)await used with render methodsx-ref directive and this.$refs to access DOM elementssuccess_json_response / error_json_responseTemplateResponse for template rendering@permission_required or @login_requiredprocess_request_body for JSON payloadsform_error_as_str(form) for validation errorsWSGIRequest, dict for responses)