I already found a solution to this problem, but I only know that it works. I don't know how or why and that's the purpose of this question.
I have a Vanilla JavaScript file that controls a dependent drop-down list of countries-regions-cities triplets.
Here is the code:
{% load i18n %}
{% load static %}
<script>
/* This function retrieve the csrf token from the cookies */
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
/* This function just sorts the list of regions or cities */
function sortList(dataList){
return dataList.sort(function(a, b){
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
return 0;
});
}
/**
Creates a XmlReq request and send it.
@param function func_name - it's the function in the server that will be called asynchronously.
@param string token - it's the csrf token necessary to validate django forms.
@param data json object - it's the data to send to the server
@param callback callback - it's the callback function to be called on success.
**/
function createXmlReq(func_name, token, data, callback){
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", callback);
xhr.open("POST", func_name);
xhr.setRequestHeader("X-CSRFToken", token);
/* The following two setRequestHeaders are not mandatory, the code works without them. */
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
}
document.getElementById("divProvinces").style.display="none";
document.getElementById("divCities").style.display="none";
/* Fetch the regions by the country when a country is selected from the drop-down list */
document.getElementById("id_country").onchange = function(){
var country_name = this.value;
var token = getCookie('csrftoken');
/* if country changes reset city list, no need to hide id_province because it will change anyway */
document.getElementById("id_city").innerHTML = "";
document.getElementById("divCities").style.display="none";
var data = {'country': country_name};
var xhr = createXmlReq("get-province", token, data, transferCompleteProvinces);
function transferCompleteProvinces(event){
data = JSON.parse(event.srcElement.response);
data.provinces = sortList(data.provinces);
let html_data = '';
if (data.provinces.length !== 0) {
html_data = '<option value="-----">{% trans "-- Select State --" %}</option>';
data.provinces.forEach(function(data){
html_data += `<option value="${data[0]}">${data[1]}</option>`
});
}else{
html_data = '<option value="-----">{% trans "No states available for this country" %}</option>';
}
document.getElementById("divProvinces").style.display="block";
document.getElementById("id_province").innerHTML=html_data
}
}
/* Fetch the cities by the region when a region is selected from the drop-down list */
document.getElementById("id_province").onchange = function(){
var province_name = this.value;
var token = getCookie('csrftoken');
var data = {'province': province_name};
var xhr = createXmlReq("get-cities", token, data, transferCompleteCities);
function transferCompleteCities(event){
data = JSON.parse(event.srcElement.response);
data.cities = sortList(data.cities);
let html_data = '';
if (data.cities.length !== 0) {
html_data = '<option value="-----">{% trans "-- Select City --" %}</option>';
data.cities.forEach(function(data){
html_data += `<option value="${data[0]}">${data[1]}</option>`
});
}else{
html_data = '<option value="-----">{% trans "No cities available for this region" %}</option>';
}
document.getElementById("divCities").style.display="block";
document.getElementById("id_city").innerHTML=html_data;
}
}
</script>
This code will send a request to functions defined in views.py, I'll only show the first one because there is no need to show both to understand the question. Here's the code:
# views.py
def getProvince(request):
# country = request.POST.get('country')
#from django.http import HttpResponse
#print(HttpResponse(request.POST.items()))
country = json.load(request)['country']
af = AddressForm()
provinces = af.get_state_by_country(country, get_language())
return JsonResponse({'provinces': provinces})
If I use request.POST.get('country')
, I receive nothing (None if I make a print statement to country).
It will only work with
country = json.load(request)['country']
And my question is: WHY? But that's not all. If I write the same code in JQuery, then request.POST.get('country')
will work!
Here is the JQuery:
{% load i18n %}
{% load static %}
<script src="{% static 'js/jquery/jquery-3.3.1.min.js' %}"></script>
<script>
$("#divProvinces").hide();
$("#divCities").hide();
$("#id_country").change(function(){
var countryId = $(this).val();
/* if country changes reset city list */
/* no need to hide id_province because it will be changed anyway */
$("#id_city").html("");
$("#divCities").hide();
$.ajax({
type: "POST",
url: "{% url 'get-province' %}",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'country': countryId
},
success: function (data){
console.log(data.provinces);
data.provinces = data.provinces.sort(function(a, b){
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
return 0;
});
let html_data = '';
if (data.provinces.length !== 0) {
html_data = '<option value="-----">{% trans "-- Select State --" %}</option>';
data.provinces.forEach(function(data){
html_data += `<option value="${data[0]}">${data[1]}</option>`
});
}else{
html_data = '<option value="-----">{% trans "No states available for this country" %}</option>';
}
$("#divProvinces").show();
$("#id_province").html(html_data);
}
});
});
$("#id_province").change(function(){
var provinceId = $(this).val();
$.ajax({
type: "POST",
url: "{% url 'get-cities' %}",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'province': provinceId
},
success: function (data){
console.log(data.cities);
data.cities = data.cities.sort(function(a, b){
if (a[1] < b[1]) return -1;
if (a[1] > b[1]) return 1;
return 0;
});
let html_data = '';
if (data.cities.length !== 0) {
html_data = '<option value="-----">{% trans "-- Select City --" %}</option>';
data.cities.forEach(function(data){
html_data += `<option value="${data[0]}">${data[1]}</option>`
});
}else{
html_data = '<option value="-----">{% trans "No city available for this region" %}</option>';
}
$("#divCities").show();
$("#id_city").html(html_data);
}
});
});
</script>
So the real question is: why request.POST.get('country')
works with JQuery (despite I'm sending a stringified JSon object), but it doesn't with Vanilla JavaScript? Why does the View only accept json.load(request)['country']
, and even if I send a string directly it doesn't work the other way?
Sure I could just go with the fact that the code works this way and just be happy with it. But I'm not satisfied. I would like to know why the same thing does work in JQuery but not in Vanilla JavaScript and why I have to use another mechanism in normal JS in order to make it work.