Skip to content

Initialization of the external JS #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
rez0n opened this issue Mar 27, 2021 · 4 comments
Closed

Initialization of the external JS #158

rez0n opened this issue Mar 27, 2021 · 4 comments
Assignees
Labels
Q&A Questions and answers

Comments

@rez0n
Copy link

rez0n commented Mar 27, 2021

Hi, using django-bootstrap-modal-forms I faced the issue to unable correctly include external JS libraries in the modal form.
On the first modal open, I have errors in JS console, but on second modal open it works correctly.
I already had a discussion in the django-select2 repository: codingjoe/django-select2#47 (you can take a look at logs and also I posted a demo app)

But now I faced with the same issue on including django-bootstrap-datepicker-plus
image
Looks like jquery runs before the modal DOM is ready.
Are you can advise some ideas on how to overcome that?
Probably related to #142 and #141

Form code, not sure it is helpful, because too common, but anyway.

from bootstrap_modal_forms.forms import BSModalModelForm
from bootstrap_datepicker_plus import DatePickerInput
class MyModelDateForm(BSModalModelForm):

    date = forms.DateField(
        label='Date',
        widget=DatePickerInput(
            format='%d/%m/%Y', attrs={'class': 'form-control form-control'}),
        input_formats=('%d/%m/%Y', )
    )

    class Meta:
        model = MyModel
        fields = ['date']

<form method="post" action="">
  {% csrf_token %}

 <div class="modal-header">
    <h5 class="modal-title">{{title}}</h5>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>

  <div class="modal-body">
    {% for field in form %}
      <div class="form-group{% if field.errors %} is-invalid{% endif %}">
        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
        {{ field }}
        {% for error in field.errors %}
          <p class="help-block">{{ error }}</p>
        {% endfor %}
      </div>
    {% endfor %}
  </div>

  <div class="modal-footer">
    <button type="button" class="btn btn-light" data-dismiss="modal">Close</button>
    <button type="submit" class="submit-btn btn btn-dark">Save</button>
  </div>

</form>
{{ form.media }}
@rez0n
Copy link
Author

rez0n commented Mar 27, 2021

btw, I got it works using: monim67/django-bootstrap-datepicker-plus#47 (comment)

@trco
Copy link
Owner

trco commented Mar 27, 2021

@rez0n The form in the modal is loaded from form-url and as such the form on that url should already have fields instantiated with desired js. Any js that is needed for the form itself should be included in the template for the form. See script tag below. Once the form is loaded in the modal it can't use the js loaded in the base html page. See also comments in #25 and #34.

The solution you got more or less describes practically the same thing, to load the js used in the form before it's rendered in the modal.

To say it once more but differently, the form defined on your form-url should already be fully functional on that form-url, which you can visit and check, and the package will then just take that form and load it into the opened modal.

// form living on form-url
<form method="post" action="">
  {% csrf_token %}

  <div class="modal-header">
    <h3 class="modal-title">Create Book</h3>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>

  <div class="modal-body">

    <div class="{% if form.non_field_errors %}invalid{% endif %} mb-2">
      {% for error in form.non_field_errors %}
        {{ error }}
      {% endfor %}
    </div>

    {% for field in form %}
      <div class="form-group">
        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
        {% render_field field class="form-control" placeholder=field.label %}
        <div class="{% if field.errors %} invalid{% endif %}">
          {% for error in field.errors %}
            <p class="help-block">{{ error }}</p>
          {% endfor %}
        </div>
      </div>
    {% endfor %}
  </div>

  <div class="modal-footer">
    <button type="submit" class="btn btn-primary">Create</button>
  </div>

</form>

<script>
  // load your js here and the form fields above will be able to use it
</script>

@trco trco added the Q&A Questions and answers label Mar 27, 2021
@rez0n
Copy link
Author

rez0n commented Mar 27, 2021

@trco thank your for the answer, I saw #25 yesterday and it was help me to call select2 again after modal was opened, but I need to add timeout ot get it works. This is obv not good solution because I still have initial errors in the console on modal open.

{{ form.media }}
<script>
$('#modal').ready(function() {
  setTimeout(() => {
    $('.django-select2').djangoSelect2({
        dropdownParent: $('#modal'),
        width: '100%'
    });
     }, 100);

});
</script>

Describing workflow.
You open the page, press button to open modal, modal opens but JS can't initialize modules
Close the modal (but not refresh the page) and open modal again - it works.
I tried to include JS files using {{ form.media }} and using <script> </script>.
Looks like JS executes before modal HTML is ready.

If you have a time and want to understand issue you can take a look at the simple demo app: https://github.com/rez0n/django-select2-bootstrap-modal

For your convenience I give an command list to start the app.

git clone [email protected]:rez0n/django-select2-bootstrap-modal.git
cd django-select2-bootstrap-modal
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python manage.py runserver 127.0.0.1:7009

The demo app contains Category and Post objects. The database contains a single post and few categories.
Go to the page: http://localhost:7009/1/ and you will see "Select Category" button that opens the modal with django-select2.

In general, I not sure it is django-bootstrap-modal-forms issue at all but it affects any included JS added to the modal content and if you can fix this it will be helpful for new users.

@trco
Copy link
Owner

trco commented Sep 2, 2023

@rez0n I was considering to improve package and add option to pass external JS to modalForm when instantiating it. I researched your case with the great example repo you shared.

I found out that $().select2() can be triggered via console after modal opens, but the error in console said that .select2 is not a function. Of course the error implicates that select2 is called on initialised form field before js from django_select2 is loaded and added to jQuery.

Based on django_select2 code:

  1. the package should load select2.js from CDN. https://github.com/applegrew/django-select2/blob/master/django_select2/conf.py#L59
  2. the package allows to also define your own local static select2.js https://github.com/applegrew/django-select2/blob/master/django_select2/conf.py#L68, which should be added to the form's media as seen in https://github.com/applegrew/django-select2/blob/master/django_select2/forms.py#L120.

I didn't research the first option, but I checked the second one.

I copied select2.min.js from CDN to your app/static folder and set SELECT_JS = 'select2.min.js' in setting.py of your app. This didn't work, however next step was a breakthrough...

I added select2.min.js directly to the Media class of your form and voila. The error was gone and form field was instantiated as select2 field. See the PR I opened on your repo.

The conclusion is that modalForm doesn't need functionality you're describing, since Django allows to pass assets such as external JS to the forms via Media class. See docs for the examples https://docs.djangoproject.com/en/4.2/topics/forms/media/#assets-as-a-static-definition. This doc says that everything you can use same Media class on Form as on Widgets, which are described.

I won't explore what's the reason behind SELECT_JS = 'select2.min.js' option from django_select2 not working as it would be expected, however I'll link this comment to the issue you opened with them.

My action item is to add the info around passing assets to the forms through the Media class on the forms to the docs of my package.

The code...

forms.py

NOTE: select2.min.js should be present in static files.

from .models import Post
from bootstrap_modal_forms.forms import BSModalModelForm
from django_select2 import forms as s2forms

class CategoryPickWidget(s2forms.ModelSelect2Widget):
    search_fields = [
        "name__icontains",
    ]

class PostCategoryForm(BSModalModelForm):
    **class Media:
        js = [
            'select2.min.js'
        ]**

    class Meta:
        model = Post
        fields = ('category',)
        widgets = {
            "category": CategoryPickWidget(select2_options={'width': '100%', }),
        }

modal template

NOTE: {{ form.media }} should be present at the top.

{{ form.media }}

<form method="post" action="">
  {% csrf_token %}

  <div class="modal-header">
    <h5 class="modal-title">{{title}}</h5>
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>

  <div class="modal-body">
    {% for field in form %}
    <div class="form-group{% if field.errors %} is-invalid{% endif %}">
      <label for="{{ field.id_for_label }}">{{ field.label }}</label>
      {{ field }} {% for error in field.errors %}
      <p class="help-block">{{ error }}</p>
      {% endfor %}
    </div>
    {% endfor %}
  </div>

  <div class="modal-footer">
    <button type="button" class="btn btn-light" data-dismiss="modal">
      Close
    </button>
    <button type="submit" class="submit-btn btn btn-dark">Save</button>
  </div>
</form>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Q&A Questions and answers
Projects
None yet
Development

No branches or pull requests

2 participants