How to create dynamic modals/popups in template

In my projects.html, I used for loop to create CSS cards for each of my project objects. I now intend to add a modal popup for each project in the sense that a user will be able to click a “more info” button on each card and this will display a popup containing more information about each project.

I have created the modal functionality with the help of JS but the problem is that it would only work if my projects were entered manually and so I could reference them one by one with JS. Here is my code.

Html code showing how my project objects are loaded and displayed

                 {% for project in projects %}

                        <div class="project-item">

                            <img src="{{ project.image.url }}" alt="project images">

                            <h1>{{ project.title}}</h1>

                            <p>{{project.description}}</p>

                            <a href="{{project.url}}" target="_blank" class="card-link">  <h2>{{ project.linktext }}</h2></a>

                            <a href="{{project.url}}" target="_blank" class="card-link popupbtn">  <h2> More Info </h2></a>

                        </div>

                    {% endfor %}

Skeleton html code for my modal popup


<div class="popup-wrapper">

            <div class="popup">

                <div class="popup-close">X</div>

                <div class="popup-content">

                    <h1>This is the popup</h1>

                    <p> We are going to add more info here soon</p>

                    <a href="#"> View Website</a>

                </div>

            </div>

        </div>

Modal popup JS

// get elements for popup

const popupbtn = document.querySelector('.popupbtn');

const popup = document.querySelector('.popup-wrapper');

const popupclose = document.querySelector('.popup-close');

// popup

popupbtn.addEventListener('click',() =>{

    popup.style.display = "block";

    console.log("opening modal");

});

popupclose.addEventListener('click', () => {

    popup.style.display = 'none';

});

popup.addEventListener('click', (e) => {

    if(e.target.className === 'popup-wrapper'){

      popup.style.display = 'none';

    }

});

So my question is how can we create modals with dynamic content ( eg. I add the popup html code under the project item for loop) that depends on which project the user wants more information on and how will I implement that functionality in JS so that the right modal is displayed. Thank you.

1 Like

You’ve got at least three different ways to approach this (not counting some minor variations).

  • Create a view that returns the html fragment that you want displayed in your pop-up. Clicking on something that is supposed to activate the pop-up does a GET to that view to retrieve the HTML, and then injects it into the appropriate div.

  • When the page is rendered, create the HTML for each selection and put those fragments within hidden divs on the page. In this case, the JS copies that hidden div into the modal for being displayed.

  • Render a complete pop-up for each selection, and have the button (or whatever) be associated with the pop-up it’s supposed to display.

Thank you for your response. In the end, I went with the second option and made use of the JavaScript parent and children features to get and display the right info on the modal

Hello,
Vicrost, could you share the solution code? I am not as pro as Ken and I do not know how exacly I could implement that solution.
I have a problem with popup window. When I push the button with new window, the form at main site, report the error because I did not fulfill all fields yet and immediatelly close new window :frowning:
Thank for help

Ken have you got any example code with your solutions?

Sorry for the late reply. I did this a couple months back so my memory about it is a little bit fuzzy. I knew from past experiences that it might be difficult for you if I just gave you a sample solution so I’ve tried to explain what I did as best as I could. If you have any questions, I’ll be happy to try my best to help.

So I had some project objects that I was displaying and I wanted each of them to have a “more info” button which users can click on to get more information about the respective project.

This is a summary of what the solution does:

  1. A hidden div is placed below each project card code and inside the for loop looping through the project objects. This div will contain all the project info that will be displayed on the popup.
  2. Another div that will be hidden by default is placed somewhere below the code dealing with loading the project cards onto the template. This div will contain the design of the popup and includes all the elements you’ll need for displaying the project information.
  3. Then, using javascript, we’ll continuously check if any of the “more info” button is clicked. If one of them is, get all the elements in the hidden div for that specific project. Then set the content of each - initially blank - popup elements to the element in the hidden div containing the info we need.Then, display the popup by displaying the popup div.
  4. Finally, using javascript again, check continuously if the user clicks on the popup close button. If they do, close the popup.

Okay, First off, below is the html code. some will have comments that explain the code.

Html Template (take note of the .popup-info class):

 <div class="project-box">
                    {% for project in projects %}  
                        <div class="project-item">  <! -- this just displays each project as a card -->
                            <img src="{{ project.image.url }}" alt="project images">
                            <h1>{{ project.title}}</h1>
                            <p>
                                {{project.description}}
                            </p>
                            <a href="{{project.url}}" target="_blank" class="card-link">  <h2>{{ project.linktext }}</h2></a>
                            <a href="#" class="card-link popupbtn">  <h2> More Info </h2></a>
                            
                            <div class="popup-info">   <!-- this div will be hidden by default and I use it to store all the info I'll need to display on the popup for each project -->
                                <p>{{project.image.url}}</p>
                                <h1>{{project.title}}</h1>
                                <p>{{project.description}}</p>
                                <p>{{project.linktext}}</p>
                                <p>{{project.url}}</p>
                            </div>
                        </div>
                        
                    {% endfor %}
                </div>
<div class="popup-wrapper">  <!-- holds the design of the popup. will be hidden initially -->
            <div class="popup">
                <header>
                    <span class="popup-title"></span>  <!-- title of the project -->
                    <div class="popup-close">x</div> 
                </header>
                <div class="popup-content">
                    <div class="popup-imgbox">
                        <img src="" alt="" class="popup-image"> <!-- an image of the project-->
                    </div>
                    <p class="popup-descrip"></p>   <!-- project description-->
                    <a href="#" target="__blank__" class="popup-link"></a> <!-- an external link unique to the project -->
                </div>
            </div>
        </div>

Now this is the javascript code. First is the just the whole code. Then I break down each section and explain it.
Javascript:

// get elements for popup
const wrapper = document.querySelector(".popup-wrapper");
const popupbtns = document.querySelectorAll('.popupbtn');
const popup = document.querySelector('.popup');
const popupclose = document.querySelector('.popup-close');
const popupContent = document.querySelector(".popup-content");

            // popup

// get project item popup info

popupbtns.forEach(btn => btn.addEventListener('click', () => {
const popupinfo = btn.parentNode.childNodes[11];
    popupContent.querySelector(".popup-image").src = popupinfo.children[0].textContent //project image
    popup.querySelector(".popup-title").textContent = popupinfo.children[1].textContent; // project title
    popupContent.querySelector(".popup-descrip").textContent  =  popupinfo.children[2].textContent; // project description
    popupContent.querySelector(".popup-link").textContent =  popupinfo.children[3].textContent; //project link text
    popupContent.querySelector(".popup-link").href = popupinfo.children[4].textContent;

    
    wrapper.style.display = "block";
    popup.style.display = "block";
}));


//this  block of code will continuously check if the user clicks the "X" button
 on the popup which will mean they want to close the popup and so it will close it if they do.
popupclose.addEventListener('click', () => {
    popup.style.display = 'none';
    wrapper.style.display = "none";

});

// this block of code continuously checks if the user clicks anywhere outside the popup box. 
If they do, it will close the popup. This is optional. You don't have to include this feature.
popup.addEventListener('click', (e) => {
    if(e.target.className === 'popup-wrapper'){
      popup.style.display = 'none';
      wrapper.style.display = "none";
    }
});

So as I alluded to earlier, the JavaScript is what will allow us to dynamically change the content shown on the popup depending on which project object I click "more info " on as well as make the popup open and close.

Basically, what I did was that I first got all the elements that I’ll need to make the popup work.

// get elements for popup
const wrapper = document.querySelector(".popup-wrapper");   // this gets the div representing the screen when the popup is showing
const popupbtns = document.querySelectorAll('.popupbtn');  // this gets the "more info" button on each of my project card
const popup = document.querySelector('.popup');   // this gets the popup containing the info itself (the box)
const popupclose = document.querySelector('.popup-close');  // this gets the "X" button that will allow the user to close the popup
const popupContent = document.querySelector(".popup-content");  // this gets the div holding all the popup content
// get project item popup info

popupbtns.forEach(btn => btn.addEventListener('click', () => {
    const popupinfo = btn.parentNode.childNodes[11];
    
    
    popupContent.querySelector(".popup-image").src = popupinfo.children[0].textContent //project image
    popup.querySelector(".popup-title").textContent = popupinfo.children[1].textContent; // project title
    popupContent.querySelector(".popup-descrip").textContent  =  popupinfo.children[2].textContent; // project description
    popupContent.querySelector(".popup-link").textContent =  popupinfo.children[3].textContent; //project link text
    popupContent.querySelector(".popup-link").href = popupinfo.children[4].textContent;

    
    wrapper.style.display = "block";
    popup.style.display = "block";
}));

Then what I did with this part of the code (the one above ) is :

  1. I used forEach to add an eventlistener to each of the “more info” buttons stored in the popupbtns variable
  2. This means that now there is an infinite loop continuously checking whether any of the “more info” buttons is clicked. If a user clicks on of them, this line of code const popupinfo = btn.parentNode.childNodes[11]; will then get the popupinfo for the project that the user clicked “more info” on
  3. Then I matched the elements in the popupinfo class with the elements in the popup. For example:
 popup.querySelector(".popup-title").textContent = popupinfo.children[1].textContent; // project title

This line of code sets the content of this <span class="popup-title"></span> to whatever is inside <h1>{{project.title}}</h1>.
3. After I set the content of the popup,

wrapper.style.display = "block";
popup.style.display = "block";

These two lines of code display the popup.

So that was the solution. I hope it helps. :grinning:

Dear Vicrost
thank you so much for reply!!!
I need few days to implement your solution and then I will come back with a comment.

THANK YOU !!!

Best regards
Wojciech

Dear Vicrost,
I read your answer carefully and I have the impression that this is not entirely the solution to my problem. When you log in to the Django admin site and enter the tab for adding an object to one of the models with the Foreign Key field, note that you are already in the form and open another form with the “+” button, even though the main form has not been fullfilled at all. This main form does not report an error, it is rather pause. After the new form is completed correctly in the popup, the object created in it is loaded into the Foreign Key field and is available for selection.
I am looking for a solution with two forms, one of which is in the popup window and refreshes the Foreign Key field of the main form.
Do you have any idea?



I’m trying to understand what you are trying to do so please correct me if I’m wrong.

  • You want to use a popup form to add new objects to the model

  • The popup will open when you click on “+” button

  • When you click on “+” , fill in the fields and save the form, the new object should be added to the model and the page should refresh.

  • The problem right now is that the when you click “save” after filling the fields in the pop up form, the main form on the admin fills the fields but does not save it as a new object of the model.

If that is your problem, kindly share the code related to this process so I can help. I am also by no means a django pro :sweat_smile: but I’ll try to help as much as I can.

You are right:

Please take a look at this website:

there is a nice solution of my problem.
Adding new object works very well, but I have a problem with Edit.
I have changed in urls.py url() to path():

from django.contrib import admin
from django.urls import path

from my_app.views import BookCreate, AuthorCreatePopup, AuthorEditPopup, get_author_id
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('book/$', TemplateView.as_view(template_name="book.html")),


    path('book/create/', BookCreate, name = "BookCreate"),
    path('author/create/', AuthorCreatePopup, name = "AuthorCreate"),
    path('author/(?P<pk>\d+)/edit/', AuthorEditPopup, name = "AuthorEdit"),
    path('author/ajax/get_author_id/', get_author_id, name = "get_author_id"),
    path('', TemplateView.as_view(template_name="home.html")),

I think the problem is here because I do not know what it means hehe: 'author/(?P\d+)/edit/

 path('author/(?P<pk>\d+)/edit/', AuthorEditPopup, name = "AuthorEdit"),

At terminal I can see the error like below:

AttributeError: 'WSGIRequest' object has no attribute 'is_ajax'
[11/Jan/2022 09:53:15] "GET /author/ajax/get_author_id/?author_name=Kto%C5%9B%20znany5 HTTP/1.1" 500 66207

Could you take a look and give me the advise? Should I uploud here my code?

Hey

Unfortunately I am not familiar with AJAX but I copied your error into google just to check what’s going on. I found this stackoverflow answer which might be helpful. It seems that the method is_ajax() is deprecated as of django 3.1 . I hope that helps.

Hello @vicrost,
I have a small question I did the same manipulation as you but it displays me each time the same data, here is my code in attachment, if you can help me, I really struggle.

My JS

My HTML (where I have my data)

My popup

Two notes:

First, this is an old thread that was marked as “solved” more than a year ago. The issue you’re trying to present here is likely going to get more visibility if you opened it as a new issue.

Second, please do not post images of code. They can’t be quoted in replies, searched for by others, and frequently are very difficult to read. Copy/paste your code into the body of your message, surrounded by lines of three backtick - ` characters. This means you’ll have a line of ```, then your code, then another line of ```. This forces the forum software to keep your code properly formatted, which is critical with Python (and useful with other formatted text as well).

okok I will recreate a post

@KenWhitesell you can help me it’s a project to be done quickly, I followed the tuto of @vicrost , but still for me it doesn’t work, it sends me back each time the same info in the popup.

And I also posted but I didn’t get an answer ^^’