yourlabs / django-autocomplete-light

A fresh approach to autocomplete implementations, specially for Django. Status: v4 alpha, v3 stable, v2 & v1 deprecated.
https://django-autocomplete-light.readthedocs.io
MIT License
1.8k stars 467 forks source link

Extra class added with crispy-forms select that breaks it. #1185

Closed glennbach closed 3 years ago

glennbach commented 4 years ago

Description:

I'm using this with django-crispy-forms. If I just display the plain form with:

{{add_member_form}}

everything works perfectly. If, I use crispy forms:

{% crispy add_member_form %}

then an extra class select2 is added to the select, which breaks it. Where is that class being added? Can I remove it? Any suggestions? Thanks.

Code:

My form:

class AddMemberForm(forms.Form):

    member = UserAutocompleteChoiceField(
        choices=[],
        widget=autocomplete.Select2(
            url='core:user--autocomplete',
            attrs={
                'autofocus': 'autofocus',
                'data-placeholder': 'Enter a name search here ...',
                'data-minimum-input-length': 3,
                'dropdownParent': "#addMemberModal"
            }
        )
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_id = 'id-addmemberform'
        self.helper.form_class = 'form'
        self.helper.form_method = 'post'
        self.helper.form_action = reverse('core:group--create')
        self.helper.include_media = False
        self.helper.add_input(Submit('submit', 'Add'))

Actual HTML generated:

<form action="..." class="form" id="id-addmemberform" method="post" name="id-addmemberform">
    <input type="hidden" name="csrfmiddlewaretoken" value="...">
    <div id="div_id_member" class="form-group">
        <label for="id_member" class="requiredField">Member<span class="asteriskField">*</span></label>
        <div class="">
            <select name="member" autofocus="autofocus" data-placeholder="Enter a name search here ..." data-minimum-input-length="3" dropdownparent="#addMemberModal" class="select2 form-control" id="id_member" data-autocomplete-light-url="..." data-autocomplete-light-function="select2" data-autocomplete-light-language="en">
            </select>
        </div>
    </div>
    <div class="form-group">
        <div class="">
            <input type="submit" name="submit" value="Add" class="btn btn-primary" id="submit-id-submit">
        </div>
    </div>
</form>
jpic commented 4 years ago

Select2 visually replaces the HTML select in favor of its own input.

What do you mean "breaks it" ?

I don't see the JS in your form HTML, are you sure {{ form.media }} is being rendered ?

glennbach commented 4 years ago

I do include the media. What I meant by 'breaks it' (yeah, that wasn't terribly clear, was it), I mean that it no longer accepts any input at all. The input is inert and unresponsive. As I mentioned, if I just display the form by itself, it works fine. It is only when I have crispy display it that it includes the extra class 'select2' that causes the issue. The class is attached to the original select input, but one of the replacement spans inherits it from the select apparently. I can now cause it to fail by adding the class to the span in the browser inspector. First of all, I don't understand why the span fails simply because it is given a class of 'select2'. Second, I don't know where that class is coming from. Displaying the form alone doesn't have that class. It only shows up when crispy displays it, and they say that they aren't adding it.

The outer span:

<span role="combobox" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-disabled="false" aria-labelledby="select2-id_member-container" class="select2-selection select2-selection--single">
    <span class="select2-selection__rendered" id="select2-id_member-container" role="textbox" aria-readonly="true">
        <span class="select2-selection__placeholder">Enter a name search here ...</span>
    </span>
    <span class="select2-selection__arrow" role="presentation"><b role="presentation"></b>
    </span>
</span>

Has select2 added:

<span role="combobox" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-disabled="false" aria-labelledby="select2-id_member-container" class="select2-selection select2-selection--single select2">
glennbach commented 3 years ago

I figured it out, finally. In CrispyFieldNode from templatetags.crispy_forms_field, they set the class_name to:

class_name = widget.__class__.__name__.lower()

So, since the widget is Select2, the class is set to select2, which apparently screws up the javascript. To fix it, I just had to add the following to my settings:

CRISPY_CLASS_CONVERTERS = {'select2':''}

so it doesn't do that. I'm mentioning this just in case someone else might have this issue.

gamesbook commented 3 years ago

@glennbach It might be worth mentioning this as a crispy-forms issue as its their code that triggers the error. Perhaps they don't have to actually have the .lower() conversion for their code to work?

glennbach commented 3 years ago

I started with an issue in their repo, and they insisted that they didn't add the class (incorrectly). So, I have informed them. It looks like they don't want to do anything about it. At least, if anyone else sees this issue, they have a way to get around it. I wonder if there's any way in your Javascript to survive this situation. Anyway, at least there's a way forward... Thanks!

jpic commented 3 years ago

Grats for getting to the bottom of this !!

Perhaps changing something in DAL would help in any way ?

Maybe patching __name__ unless this would have other (unintended) side effects ?

As for next version: I believe we can prevent any such kind of conflicts with shadow dom enabled web components, so we're really hanging on until then but I will have time available soon to release that with support for both the next model (where your widget generates your view which is auto registered to urls and code is shared) and the current model (where you glue everything yourself).

Currently, only the next model was implemented, but a compatibility is possible for easier (find | xargs sed based) switch from select2 to the webcomponent in your project.

glennbach commented 3 years ago

i don't know enough about your code to have any suggestions, but with a workaround available, it seems that waiting for the next version is reasonable. thanks for a great module!

jpic commented 3 years ago

Yes indeed, and kudos to you for posting the solution ;)