Skip to content

Add a template tag (or other mechanism) for including DAL media globally in a base template #1406

@davmlaw

Description

@davmlaw

Summary

When multiple forms containing DAL autocomplete widgets appear on the same page,
each {{ form.media }} call independently emits the full DAL script bundle
(autocomplete_light.js, select2.js, etc.). Django's Media system only
deduplicates scripts when media objects are combined with + before rendering;
separate inline {{ form.media }} calls produce duplicate <script> tags.

The result is a console.error on every affected page:

autocomplete_light.js:17 The DAL function "select2" has already been registered.

This fires because select2.js is executed twice, and the second call to
yl.registerFunction('select2', ...) hits the already-registered guard added in
PR #982.

A secondary trigger exists even on pages with a single autocomplete form: the
common workaround of re-dispatching window.load to re-initialise widgets loaded
via Ajax (referenced in issue #1221) causes autocomplete_light.js's load
listener to fire a second dal-init-function event, which again triggers the
duplicate-registration error.

Steps to reproduce

  1. Create a page with two separate forms, each containing a ModelSelect2 widget.
  2. Render them independently in a template:
    {{ form_one.media }}
    {{ form_two.media }}
  3. Open the browser console — The DAL function "select2" has already been registered. appears.

Root cause

Widget.media is the only documented mechanism for including DAL's CSS/JS. On
pages with a single form this works fine. On pages with multiple forms rendered
independently (a very common pattern in Django projects), the same scripts are
injected multiple times because there is no way for the template author to say
"include DAL's assets once, globally".

History

In v1/v2, {% include 'autocomplete_light/static.html' %} could be placed in a
base template for exactly this purpose. It was removed in favour of form.media,
which works correctly only when a single form is on the page or when the
developer manually combines media objects in Python before rendering.

Suggested fix

Add a small template tag to dal (or dal_select2) that renders the library's
required CSS and JS, suitable for placement in a base template:

# dal/templatetags/dal_tags.py
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.simple_tag
def dal_media():
    """Render DAL CSS and JS for global inclusion in a base template."""
    from dal_select2.widgets import ModelSelect2
    return mark_safe(str(ModelSelect2(url='').media))

Usage in a base template:

{% load dal_tags %}
{% dal_media %}

Projects using this pattern would then strip DAL's scripts from per-widget
form.media to avoid the duplicate, e.g. by subclassing their widgets:

class ModelSelect2(autocomplete.ModelSelect2):
    @property
    def media(self):
        # DAL scripts loaded globally via {% dal_media %} in base template
        return forms.Media(js=["my_app/js/dal_reload.js"])

Where dal_reload.js re-initialises any autocomplete widgets inserted
dynamically (e.g. via Ajax), without re-dispatching window.load:

// Only runs when DAL is already initialised (i.e. in an Ajax-loaded context).
// Avoids re-dispatching window.load which would re-fire dal-init-function and
// trigger the "already registered" error.
$(document).ready(function() {
    if (window.__dal__initialize) {
        $('[data-autocomplete-light-function]').each(window.__dal__initialize);
    }
});

Note: DAL 3.9+ already uses a MutationObserver to catch dynamically-added
elements, so the reload script is mostly belt-and-suspenders for edge cases.

Alternatives considered

  • Combine media in every view — requires touching every view and template
    that has more than one autocomplete form. Error-prone and does not scale.
  • Use {% block media %} — only works if all forms are rendered in a single
    template context; breaks for reusable template tags/includes that each render
    their own form.
  • Rely on the __dal__initialize guard — prevents the critical double-init
    bug but still emits duplicate <script> tags and still logs console.error,
    which is noisy and misleading for developers.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions