django-autocomplete-light icon indicating copy to clipboard operation
django-autocomplete-light copied to clipboard

jQuery Imports and accessing $.fn.select2

Open dwasyl opened this issue 5 years ago • 18 comments

Hi there,

I've recently upgraded some systems to Django 2.0+ and in turn am using the latest DAL 3.3.2. However, I've run in to an issue I can't seem to get sorted. I'm trying to configure some default values for Select2 through $.fn.select2 however, I always get an error it doesn't exist.

I've gone through my code and worked out that the error is related to the loading of jQuery. To accommodate other code on my page, I load jQuery first in my scripts, and then others follow and are finally followed by any specific code. i.e.:

    <script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
    (other scripts)
    {% form.media %}

When this is the case, I can't access select2 through $.fn. at all. When I disable my original import of jQuery, I can access $.fn.select2 just fine - although this causes me a number of other problems on my page. The same is the case when I disable the importing of jQuery in the DAL widgets.py file.

Do I always need to subclass the widget to remove jQuery from the media file now? Or am I missing from the docs?

dwasyl avatar Dec 18 '18 16:12 dwasyl

For some further debugging, I'm also unable to listen to events on any select2 objects on the page. I'm using it combined with django-dynamic-formset as I have on other projects, but whenever additional forms are added to the page the select2 fields are never initialized, and I'm unable to do it manually.

This was working fine with DAL 3.2.7 & Django 1.11 and it appears to be connected to the way jQuery is initiated, but I can't sort it more than that.

dwasyl avatar Jan 05 '19 08:01 dwasyl

@dwasyl if the yl.jQuery hacketry gets in the way, do you think a patch to get rid of it and simplify (use global $) would fix your issue ?

jpic avatar Jan 10 '19 13:01 jpic

@jpic Good call - I'd been trying all sorts of things - based on your post I went and removed DAL's jQuery include and everything instantly worked (using $ as the jQuery selector). Not sure why yl.jQuery wouldn't hook onto the events, but that's all it took to get it working.

Maybe there could be someway to disabled DAL's jQuery entirely? Didn't need to change or simplify anything, just got rid of the extra include.

dwasyl avatar Jan 11 '19 06:01 dwasyl

Great job, contribution remain open to get rid of yl.jQuery.

jpic avatar Jan 11 '19 09:01 jpic

I had the same issue on Django 2.1, I think this is the same as #1053

Removing just the line from select2 widgets.py:

    'admin/js/vendor/jquery/jquery%s.js' % extra,

Fixes everything.

davmlaw avatar Feb 01 '19 13:02 davmlaw

I gave a try to DAL 3.3.4 which has just been released a couple of days ago and can still reproduce this issue. Thanks @davmlaw for the working tip!

Do you have any plan to make a PR for this fix? Thanks!

Phyks avatar Apr 12 '19 13:04 Phyks

I had the same issue on Django 2.1, I think this is the same as #1053

Removing just the line from select2 widgets.py:

    'admin/js/vendor/jquery/jquery%s.js' % extra,

Fixes everything.

Same issue, made the patch to:

/usr/local/lib/python3.6/dist-packages/dal_select2/widgets.py

and the error went away. Thanks. Time DAL was fixed!

bernd-wechner avatar Apr 25 '19 12:04 bernd-wechner

I had the same issue on Django 2.1, I think this is the same as #1053

Removing just the line from select2 widgets.py:

    'admin/js/vendor/jquery/jquery%s.js' % extra,

Fixes everything.

This worked for me, but now JQuery isn't working at all in the admin. Looks like it's loading '/static/autocomplete_light/jquery.init.js' before '/static/admin/js/jquery.init.js' which is screwing everything up.

I'm on Django 2.2

tracedaily avatar May 14 '19 18:05 tracedaily

Trey, do you want to try to make it work without the jquery.init.js and on Django 2.2 ? Feel free to remove all code you think is not necessary and please share your patch.

Besides, I understand your frustration, if I hadn't switched to webpack long ago for npm support, I would definitely try concepts borrowed from other web frameworks such as cubicweb that has a request.static object that allows static dependency injection all along the response generation process, that could make it compatible both with the admin and frontend ie. with a middleware that can inject missing dependencies. Thank you deeply for sharing your solutions which are always helpful to others struggling with the same issues as you.

jpic avatar May 14 '19 18:05 jpic

I had the same issue on Django 2.1, I think this is the same as #1053 Removing just the line from select2 widgets.py:

    'admin/js/vendor/jquery/jquery%s.js' % extra,

Fixes everything.

This worked for me, but now JQuery isn't working at all in the admin. Looks like it's loading '/static/autocomplete_light/jquery.init.js' before '/static/admin/js/jquery.init.js' which is screwing everything up.

I'm on Django 2.2

I got this working in django 2.2 by leaving a placeholder in the line in question. My widgets.py looks like this:


return forms.Media(
            js=(
                '',
                'autocomplete_light/jquery.init.js',
                'admin/js/vendor/select2/select2.full%s.js' % extra,
            ) + i18n_file + (
                'autocomplete_light/autocomplete.init.js',
                'autocomplete_light/forward.js',
                'autocomplete_light/select2.js',
                'autocomplete_light/jquery.post-setup.js',
            ),
            css={
                'screen': (
                    'admin/css/vendor/select2/select2%s.css' % extra,
                    'admin/css/autocomplete.css',
                    'autocomplete_light/select2.css',
                ),
            },
        )

tracedaily avatar May 21 '19 17:05 tracedaily

Trey, do you want to try to make it work without the jquery.init.js and on Django 2.2 ? Feel free to remove all code you think is not necessary and please share your patch. Besides, I understand your frustration, if I hadn't switched to webpack long ago for npm support, I would definitely try concepts borrowed from other web frameworks such as cubicweb that has a request.static object that allows static dependency injection all along the response generation process, that could make it compatible both with the admin and frontend ie. with a middleware that can inject missing dependencies. Thank you deeply for sharing your solutions which are always helpful to others struggling with the same issues as you.

This is probably a little outside of my wheelhouse, but I'll give it a go

tracedaily avatar May 21 '19 17:05 tracedaily

I got bad news! This was working fine with davmlaw's patch in 3.3.2.

Alas my webserver upgrade to 3.3.4 and now the change event handler is no longer called on my website.

The analogous patch isn't available because this (3.3.2):

   @property
    def media(self):
        extra = '' if settings.DEBUG else '.min'
        i18n_name = SELECT2_TRANSLATIONS.get(translation.get_language())
        i18n_file = ('admin/js/vendor/select2/i18n/%s.js' % i18n_name,) if i18n_name else ()
        return forms.Media(
            js=(
#                 'admin/js/vendor/jquery/jquery%s.js' % extra,
                'autocomplete_light/jquery.init.js',
                'admin/js/vendor/select2/select2.full%s.js' % extra,
            ) + i18n_file + (
                'autocomplete_light/autocomplete.init.js',
                'autocomplete_light/forward.js',
                'autocomplete_light/select2.js',
                'autocomplete_light/jquery.post-setup.js',
            ),
            css={
                'screen': (
                    'admin/css/vendor/select2/select2%s.css' % extra,
                    'admin/css/autocomplete.css',
                    'autocomplete_light/select2.css',
                ),
            },
        )

became this in 3.3.4:

    class Media:
        """Automatically include static files for the admin."""

        css = {
            'all': (
                'autocomplete_light/vendor/select2/dist/css/select2.css',
                'autocomplete_light/select2.css',
            )
        }
        js = (
            'autocomplete_light/jquery.init.js',
            'autocomplete_light/autocomplete.init.js',
            'autocomplete_light/vendor/select2/dist/js/select2.full.js',
            'autocomplete_light/select2.js',
        )

And worse still if I try and wind back to 3.3.2:

$ pip3 install -I django-autocomplete-light==3.3.2
Collecting django-autocomplete-light==3.3.2
  Downloading https://files.pythonhosted.org/packages/33/eb/1ef9265d2bbefcc1d8c7077302ab48edbfa0f3c89f8731f288a71542f3b8/django-autocomplete-light-3.3.2.tar.gz (179kB)
    100% |████████████████████████████████| 184kB 2.7MB/s 
Building wheels for collected packages: django-autocomplete-light
  Running setup.py bdist_wheel for django-autocomplete-light ... done
  Stored in directory: /home/weaver/.cache/pip/wheels/08/47/55/0c26dd5363ae3ba82e20d25227aee41227dbd4de8b14540601
Successfully built django-autocomplete-light
Installing collected packages: django-autocomplete-light
Successfully installed django-autocomplete-light-3.3.4

pip3 downloads 3.3.3.tar.gz but ends up installing 3.3.4 ... GRRRRRR.

Man I'd like this fixed. It's killed my website.

I'm on Django 2.1.7. Django 2.2 doesn't seem to be in the upgrade stream yet, or?

I'm happy to upgrade to Django 2,2 or whatever to have this work.

bernd-wechner avatar Jul 07 '19 02:07 bernd-wechner

OK, I'm upgraded both my dev box and server to 3.3.5. And egads this is a frustrating PITA but the change event binds fine on my dev box, and fails on my server! Grrrrr.

Here's how I include DAL for now in the HTML:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.css"/ >
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.full.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2-bootstrap-theme/0.1.0-beta.10/select2-bootstrap.min.css">

<!-- START DAL media -->
<link href="/static/admin/css/vendor/select2/select2.min.css" type="text/css" media="screen" rel="stylesheet">
<link href="/static/admin/css/autocomplete.css" type="text/css" media="screen" rel="stylesheet">
<link href="/static/autocomplete_light/select2.css" type="text/css" media="screen" rel="stylesheet">
<script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
<script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.min.js"></script>
<script type="text/javascript" src="/static/admin/js/vendor/select2/i18n/en.js"></script>
<script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
<script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
<script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>
<script type="text/javascript" src="/static/autocomplete_light/jquery.post-setup.js"></script>
<!-- END DAL media -->
<script>$.fn.select2.defaults.set( "theme", "bootstrap" );</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.css"/ >

Yet in Chromium's debugger I see on the loaded page on my dev box:

image

and on a loaded page form my server:

image

And this is how I bind the listener:

	// Add a listener to the game selector (so that the form can adapt to the properties of the newly selected game)
	const game_selector = $("#"+id_prefix+"game");
	game_selector.on("change", switchGame);

identical on both contexts of course, I publish all code from the dev box to server.

And yet you can see on my dev box, the change event has a handled inside of select2.full.js before my form_rankings.js and on the server it lacks that, instead it has a handled in jquery.min.js after my handler in form_rankings.js.

That aside, it is bound but somehow is not actually called when I change the selection. Or better said it is in the first context (served form manage.py /runserver) and not in the second context (served by my web server).

So I got the code delivered by each server and did a diff:

*** form_data_runserver.html	2019-07-07 15:23:50.574705270 +1000
--- form_data_webserver.html	2019-07-07 15:24:42.666448532 +1000
***************
*** 21,31 ****
  	<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2-bootstrap-theme/0.1.0-beta.10/select2-bootstrap.min.css">
  
  	<!-- START DAL media -->
! 	<link href="/static/admin/css/vendor/select2/select2.css" type="text/css" media="screen" rel="stylesheet">
  <link href="/static/admin/css/autocomplete.css" type="text/css" media="screen" rel="stylesheet">
  <link href="/static/autocomplete_light/select2.css" type="text/css" media="screen" rel="stylesheet">
  <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
! <script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>
--- 21,33 ----
  	<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2-bootstrap-theme/0.1.0-beta.10/select2-bootstrap.min.css">
  
  	<!-- START DAL media -->
! 	<link href="/static/admin/css/vendor/select2/select2.min.css" type="text/css" media="screen" rel="stylesheet">
  <link href="/static/admin/css/autocomplete.css" type="text/css" media="screen" rel="stylesheet">
  <link href="/static/autocomplete_light/select2.css" type="text/css" media="screen" rel="stylesheet">
+ <script type="text/javascript" src="/static/admin/js/vendor/jquery/jquery.min.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/jquery.init.js"></script>
! <script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.min.js"></script>
! <script type="text/javascript" src="/static/admin/js/vendor/select2/i18n/en.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/autocomplete.init.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/forward.js"></script>
  <script type="text/javascript" src="/static/autocomplete_light/select2.js"></script>

Subtle, but I wonder if this is the cause?

So I forced the web server to deliver the top block, and it works! Aaagh.

And there it is, by forcing my dev box line by line towards the webserves version it is this one include:

<script type="text/javascript" src="/static/admin/js/vendor/select2/select2.full.min.js"></script>

that DAL puts into the danged webserver version that breaks it!

And in the end it turns out, that the same workaround that davmlaw's patch still works in 3.3.5 and I had some confusions too between system dist-packages and local site-packages and had two dals installed. With way rather than patch dal and rely on that I found this restructure works:

{#  VERY SENSITIVE - If we load jquery, and dal_media, alas dal_media reloads jquery #}
{#  and this breaks the eventhandlers we bind to selct2 change events. It is a DAL bug#}
{#  and discussed here: https://github.com/yourlabs/django-autocomplete-light/issues/1079 #}
{#  DAL should not reload eithere of these two libraries if we have loaded them already!#}

{#	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>#}
{#	<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.full.js"></script>#}

	<!-- START DAL media -->
	{{ dal_media }}
	<!-- END DAL media -->

	{# Bootstrap theme for select2 widget #}
	<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2-bootstrap-theme/0.1.0-beta.10/select2-bootstrap.min.css">
	<script>$.fn.select2.defaults.set( "theme", "bootstrap" );</script>

and then the code works in both environments fine without need of a DAL patch. But this is a defintie bug in DAL right now. It should categorically not reload the jquery library if it's already loaded.

bernd-wechner avatar Jul 07 '19 05:07 bernd-wechner

@jpic If detecting an existing jQuery import and avoiding doing it again isn't possible, what about adding some type of setting switch to disable DAL jQuery import? Either globally, or on a per field/widget level?

dwasyl avatar Nov 10 '19 01:11 dwasyl

Maybe removing all jquery loading at all from DAL will work well nowadays ? as long as it's automatically loaded in the admin, we can expect frontend users to load it. Perhaps make a medium version number bump to warn maintainers

jpic avatar Nov 10 '19 08:11 jpic

Since the admin in 2+ seems to load it everywhere now, your idea solves that problem and not importing it would save a host of hassle. If you really wanted to be sure, I've seen some packages offer up jquery inclusion based on a setting, but that doesn't seem necessary.

dwasyl avatar Nov 10 '19 14:11 dwasyl

Otherwise just add an install step asking them to load jquery prior to loading form.media should work too

jpic avatar Nov 10 '19 15:11 jpic

That would work, any chance of it being pushed into an update release soon?

dwasyl avatar Nov 11 '19 22:11 dwasyl