Set dynamic values from a dictionary for select values inside a

ghz 11hours ago ⋅ 1 views

Set dynamic values from a dictionary for select values inside a Django template using Javascript or other method

I have three consecutive select options that their values change according to the previous select. The purpose is to categorize products using these select options. First option is to either categorize products with their usage or model value. If usage is selected as the first select option, then the second select that is populated with usages list which is all objects of the Usage model, is shown, and if model is selected, then the select populated with all objects of MainModel model is shown and the other select tag gets hidden with visually-hidden class.

To this point, my codes are as below:

views.py:

def get_common_queryset():
    usage_queryset = Usage.objects.all()
    main_model_queryset = MainModel.objects.all()
    sub_usage_queryset = SubUsage.objects.all()
    pump_type_queryset = PumpType.objects.all()
    queryset_dictionary = {
        "usage_queryset": usage_queryset,
        "main_model_queryset": main_model_queryset,
        "sub_usage_queryset": sub_usage_queryset,
        "pump_type_queryset": pump_type_queryset,
    }
    return queryset_dictionary

def solution_main(request):
    context  = get_common_queryset()
    return render(request, "solutions/solution_main.html", context) 

my template:

<div class="col-md-3 mx-md-5">
    <h2 class="h5 nm-text-color fw-bold mb-4">Choose usage or model:</h2>
    <select required aria-label="Select usage or model"
            id="usage_model_select" class="form-select" onchange="set_usage_or_model_dic()">
        <option>----</option>
        <option value="usage">Usage</option>
        <option value="model">Model</option>
    </select>
</div>

{#  usage or model select div #}
<div class="col-md-3 mx-md-5">

{#  usage select div #}
    <div class="usage visually-hidden" id="usage_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select usage:</h2>
        <select required aria-label="Select usage" class="form-select"
                name="usage_select" onchange="set_sub_usage_list()" id="usage_select_id">
            <option selected>----</option>
            {% for usage in usage_queryset %}
                <option value="{{ usage.id }}">{{ usage.usage_name_fa }}</option>
            {% endfor %}
        </select>
    </div>

{#  model select div #}
    <div class="model visually-hidden" id="model_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select model:</h2>
        <select required aria-label="Select model" class="form-select"
                name="model_select" onchange="set_pump_type_list()" id="model_select_id">
            <option selected>----</option>
            {% for model in main_model_queryset %}
                <option value="{{ model.id }}">{{ model.model_name_fa }}</option>
            {% endfor %}
        </select>
    </div>
</div>

{# select sub_usage or pump_type div #}
<div class="col-md-3 mx-md-5">

{#  sub_usage select div #}
    <div class="sub_usage visually-hidden" id="sub_usage_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select sub-usage:</h2>
        <select required aria-label="Select sub_usage" class="form-select" name="sub_usage_select">
            <option selected>All sub-usages from this usage</option>
            {% for sub_usage in sub_usage_queryset %}
                <option value="{{ sub_usage.id }}">{{ sub_usage.sub_usage_name_fa }}</option>
            {% endfor %}
        </select>
    </div>

{#  model select div #}
    <div class="pump-type visually-hidden" id="pump_type_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select pump type:</h2>
        <select aria-label="Select pump_type" class="form-select" name="pump_type_select">
            <option selected>All pump types of this model</option>
            {% for pump_type in pump_type_queryset %}
                <option value="{{ pump_type.id }}">{{ pump_type.type_name }}</option>
            {% endfor %}
        </select>
    </div>
</div>
<div>
    <input type="submit" value="Next" id="submit" class="btn btn-primary">
</div>

(I'm using JS to show/hide specific divs with removing/adding visually-hidden class)

But, I don't want to use sub_usage_queryset = SubUsage.obects.all(), I want to see which usage (or model) is selected in the previous stage and populate the next select according to this choice.

The solution that I have in mind is that since there are few usages and main_models, I can create a dictionary for each that contains different usages or main_models as keys, and their sub_usages or pump_types as a list for their value. As an example for usages:

sub_usage_list = {}
for usage in Usage.objects.all():
    usage_sub_usage_list = SubUsage.objects.filter(usage=usage)
    sub_usage_list[usage] = usage_sub_usage_list

So sub_usage_list will contain each usage as a key, and that usage's sub_usages as a list for that key's value.

I don't know if this approach is correct, and even if it is, I don't know how to use specific sub_usage list from this dictionary accoring to the selected value in the relating select option. I'm not very familiar with JS, So I'd very much appreciate your help and tips.

NOTE: As you can see, I'm using server side rendering, so I don't know how to do this without refreshing the page.

Answer

You're on the right track with the idea of creating a dictionary on the server side to dynamically populate the second select dropdown based on the first one. To implement this efficiently, you can leverage AJAX (Asynchronous JavaScript and XML) to make requests to the server and get the relevant data for the next select options, without refreshing the page.

Here’s how you can achieve this step by step:

1. Create the Server-Side View for Handling AJAX Requests

You need a new view that handles AJAX requests. This view will return the filtered options based on the user’s selection. For example, when a user selects a usage, the server will respond with the list of related sub_usage values.

from django.http import JsonResponse
from .models import Usage, SubUsage, MainModel, PumpType

def get_sub_usage_or_pump_type(request):
    # Get the selected option (either usage or model)
    selected_value = request.GET.get('selected_value')  # Can be 'usage' or 'model'
    selected_id = request.GET.get('selected_id')  # ID of the selected value

    if selected_value == 'usage':
        sub_usages = SubUsage.objects.filter(usage_id=selected_id)
        sub_usage_data = [{'id': sub_usage.id, 'name': sub_usage.sub_usage_name_fa} for sub_usage in sub_usages]
        return JsonResponse({'options': sub_usage_data})

    elif selected_value == 'model':
        pump_types = PumpType.objects.filter(main_model_id=selected_id)
        pump_type_data = [{'id': pump_type.id, 'name': pump_type.type_name} for pump_type in pump_types]
        return JsonResponse({'options': pump_type_data})

    return JsonResponse({'options': []})  # If no valid option, return an empty list

2. Update the Template to Include AJAX Logic

You’ll need to add JavaScript to handle the dynamic population of the second select box. When the first select box (usage_model_select) changes, the JavaScript should send an AJAX request to fetch the relevant options and populate the second dropdown accordingly.

Here’s the updated template with JavaScript:

<div class="col-md-3 mx-md-5">
    <h2 class="h5 nm-text-color fw-bold mb-4">Choose usage or model:</h2>
    <select required aria-label="Select usage or model"
            id="usage_model_select" class="form-select" onchange="set_usage_or_model_dic()">
        <option>----</option>
        <option value="usage">Usage</option>
        <option value="model">Model</option>
    </select>
</div>

{# usage or model select div #}
<div class="col-md-3 mx-md-5">
    {# usage select div #}
    <div class="usage visually-hidden" id="usage_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select usage:</h2>
        <select required aria-label="Select usage" class="form-select"
                name="usage_select" onchange="set_sub_usage_list()" id="usage_select_id">
            <option selected>----</option>
            {% for usage in usage_queryset %}
                <option value="{{ usage.id }}">{{ usage.usage_name_fa }}</option>
            {% endfor %}
        </select>
    </div>

    {# model select div #}
    <div class="model visually-hidden" id="model_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select model:</h2>
        <select required aria-label="Select model" class="form-select"
                name="model_select" onchange="set_pump_type_list()" id="model_select_id">
            <option selected>----</option>
            {% for model in main_model_queryset %}
                <option value="{{ model.id }}">{{ model.model_name_fa }}</option>
            {% endfor %}
        </select>
    </div>
</div>

{# select sub_usage or pump_type div #}
<div class="col-md-3 mx-md-5">
    {# sub_usage select div #}
    <div class="sub_usage visually-hidden" id="sub_usage_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select sub-usage:</h2>
        <select required aria-label="Select sub_usage" class="form-select" name="sub_usage_select">
            <option selected>All sub-usages from this usage</option>
            <!-- The options will be populated dynamically by AJAX -->
        </select>
    </div>

    {# pump-type select div #}
    <div class="pump-type visually-hidden" id="pump_type_div">
        <h2 class="h5 nm-text-color fw-bold mb-4">Select pump type:</h2>
        <select aria-label="Select pump_type" class="form-select" name="pump_type_select">
            <option selected>All pump types of this model</option>
            <!-- The options will be populated dynamically by AJAX -->
        </select>
    </div>
</div>

<div>
    <input type="submit" value="Next" id="submit" class="btn btn-primary">
</div>

<script>
    function set_usage_or_model_dic() {
        var selectedOption = document.getElementById('usage_model_select').value;
        
        // Show/hide the appropriate select boxes
        if (selectedOption === 'usage') {
            document.getElementById('usage_div').classList.remove('visually-hidden');
            document.getElementById('model_div').classList.add('visually-hidden');
            document.getElementById('sub_usage_div').classList.add('visually-hidden');
            document.getElementById('pump_type_div').classList.add('visually-hidden');
        } else if (selectedOption === 'model') {
            document.getElementById('model_div').classList.remove('visually-hidden');
            document.getElementById('usage_div').classList.add('visually-hidden');
            document.getElementById('sub_usage_div').classList.add('visually-hidden');
            document.getElementById('pump_type_div').classList.add('visually-hidden');
        }
    }

    function set_sub_usage_list() {
        var usageId = document.getElementById('usage_select_id').value;
        if (usageId) {
            fetch(`/get_sub_usage_or_pump_type/?selected_value=usage&selected_id=${usageId}`)
                .then(response => response.json())
                .then(data => {
                    var subUsageSelect = document.querySelector('select[name="sub_usage_select"]');
                    subUsageSelect.innerHTML = "<option selected>All sub-usages from this usage</option>";  // Reset options
                    data.options.forEach(function(subUsage) {
                        var option = document.createElement('option');
                        option.value = subUsage.id;
                        option.text = subUsage.name;
                        subUsageSelect.appendChild(option);
                    });
                    document.getElementById('sub_usage_div').classList.remove('visually-hidden');
                });
        }
    }

    function set_pump_type_list() {
        var modelId = document.getElementById('model_select_id').value;
        if (modelId) {
            fetch(`/get_sub_usage_or_pump_type/?selected_value=model&selected_id=${modelId}`)
                .then(response => response.json())
                .then(data => {
                    var pumpTypeSelect = document.querySelector('select[name="pump_type_select"]');
                    pumpTypeSelect.innerHTML = "<option selected>All pump types of this model</option>";  // Reset options
                    data.options.forEach(function(pumpType) {
                        var option = document.createElement('option');
                        option.value = pumpType.id;
                        option.text = pumpType.name;
                        pumpTypeSelect.appendChild(option);
                    });
                    document.getElementById('pump_type_div').classList.remove('visually-hidden');
                });
        }
    }
</script>

3. Explanation of the Changes:

  1. AJAX Call for Dynamic Data:

    • When the user selects a usage or model, an AJAX request is sent to the /get_sub_usage_or_pump_type/ view, which returns the filtered options in JSON format.
    • The set_sub_usage_list() function is triggered when the usage_select dropdown changes, and the set_pump_type_list() function is triggered when the model_select dropdown changes.
    • The server responds with a list of sub_usage or pump_type objects, which are used to populate the corresponding dropdown.
  2. Displaying and Hiding Selects Dynamically:

    • The function set_usage_or_model_dic() controls the visibility of the usage/model-related dropdowns, showing or hiding them based on the user's selection.
  3. Populating Select Options:

    • The JavaScript dynamically creates <option> elements and appends them to the corresponding select elements based on the response from the server.

4. Update the URLs

In urls.py, make sure you add the new view for AJAX requests:

from django.urls import path
from . import views

urlpatterns = [
    path('get_sub_usage_or_pump_type/', views.get_sub_usage_or_pump_type, name='get_sub_usage_or_pump_type'),
    # other URLs
]

This solution allows you to dynamically update the second and third select dropdowns based on the first one, without refreshing the page, using AJAX to handle the communication between the front-end and back-end.