I have Bootstrap modal window for updating data everytime I close it, I face the so-called double-submission problem. That is, if I press F5, a message pops up telling me that I'm going to submit the same data for the second time and it inserts records into my table if I press ok. Moreover, if I try to open the modal window for the second time, it is rendered incorrectly ! Does it have anything to do with Django somehow ?
The modal window:
<form class="modal fade" id="openTaskWindow" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" method="post">{% csrf_token %}
<div class="modal-dialog modal-lg form-horizontal" role="form">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="exampleModalLabel">Новая задача</h4>
</div>
<div class="modal-body" style="font-size: smaller">
{{ createTask_form.as_p}}
</div>
<div class="modal-footer">
<input class="btn btn-default btn-sm" type="submit" value="Сохранить"/>
</div>
</div>
</div>
</form>
The button that calls the window:
<div><button type="button" data-toggle="modal" data-target="#openTaskWindow" data-backdrop="static" data-keyboard="false">Launch my modal</button></div>
The view (that is called when submit button of the modal window is pressed):
def createTask(request):
taskTable = Tasks.objects.all()
if request.method == 'POST':
task_form = TaskForm(request.POST)
if task_form.is_valid():
temp_form = task_form.save(commit=False)
temp_form.is_important = 0
temp_form.save()
return render_to_response('task_management/task_list.html',{'createTask_form':temp_form, 'taskTable': taskTable},context_instance=RequestContext(request))
else:
task_form = TaskForm()
return render_to_response('task_management/task_list.html',{'createTask_form':task_form, 'taskTable': taskTable, 'task_id':''},context_instance=RequestContext(request))
After reading discussions of the community, I tried to do this:
$('#openTaskWindow').submit(function() {
location.href = location.href;
});
but it did not help. Any ideas?
Well, this has not much to do with Django. The browser cache the post request for re-submission. The easiest way to prevent this is to return HttpResponseRedirect when for is valid.
So instead
if task_form.is_valid():
temp_form = task_form.save(commit=False)
temp_form.is_important = 0
temp_form.save()
return render_to_response('task_management/task_list.html',{'createTask_form':temp_form, 'taskTable': taskTable},context_instance=RequestContext(request))
Try to do
if task_form.is_valid():
temp_form = task_form.save(commit=False)
temp_form.is_important = 0
temp_form.save()
return HttpResponseRedirect('your_url')
And if you need to pass some extra arguments to the view you can accomplish that through query string or session based cache. And you don't need your Javascript hack.
Related
I am trying to build some search functionality within a modal. When a user clicks a button it will open a modal with a search box. I then want the results of the search to be shown within the same modal?
<div class="modal" id="select2modal">
<div class="modal-dialog" role="document">
<div class="modal-content modal-content-demo">
<div class="modal-header">
<h6 class="modal-title">Search for product to add</h6><button aria-label="Close" class="close" data-dismiss="modal" type="button"><span aria-hidden="true">×</span></button>
</div>
<div class="modal-body">
<h6>product Search</h6>
<div class="card-body pb-2">
<form method=POST action="{% url 'searchproduct' %}">
{% csrf_token %}
<div class="input-group mb-2">
<input type="text" class="form-control border-right-0 pl-3" name="search" placeholder="Enter product Name" value="">
<span class="input-group-append">
<button class="btn ripple btn-primary" type="submit"><i class="fa fa-search"></i></button>
</span>
</div>
</form>
</div>
</div>
<div class="modal-footer">
<button class="btn ripple btn-primary" type="button">Add product</button>
<button class="btn ripple btn-secondary" data-dismiss="modal" type="button">Close</button>
</div>
</div>
</div>
</div>
view
def search_product(request):
searched = request.POST['searched']
return render(request, 'index.html',{'searched':searched})
I don't think i should be doing return render(request, 'index.html',{'searched':searched}) think I should just be returning searched
The problem is this relies on the page being refreshed that closes the modal.
So what is the correct way - Do I need to use JavaScript or something.
UPDATE:
I have added jquery now
<script type="text/javascript">
$(document).on('submit','#post-form', function(e){
e.preventDefault();
$.ajax({
type:'POST',
url: '/searchproduct',
data:{
searched:$('#search').val(),
csrfmiddlewaretoken:$('input[name=csrfmiddlewaretoken]').val(),
},
success: function (){
}
});
});
</script>
Which seems is doing a POST and returning 200 but the data returned data isn't being shown in the modal.
def search_product(request):
searched = request.POST['searched']
returned_products = Product.objects.filter(product_name__contains=searched)
return {'searched':searched,'returned_products':returned_products}
and then within my modal i am using:
{% for t in returned_products %}
{{t.product_name}}
{% endfor %}
UPDATE 17/02
I think i have made some progress, but i still not getting the results back within the modal, but my search is returning a 200 rather than a error now.
In my AJAX:
<script type="text/javascript">
$(document).on('submit','#post-form', function(e){
e.preventDefault();
$.ajax({
type:'POST',
url: '/searchproducts',
data:{
searched:$('#search').val(),
csrfmiddlewaretoken:$('input[name=csrfmiddlewaretoken]').val(),
},
success: function (data){
$('jresponse').html(returned_products);
}
});
});
I'm returning returned_products and within my modal I have
<div id="jresponse">
{{ returned_products }}
</div>
```
My view
```
def products(request):
searched = request.POST['searched']
returned_products = Product.objects.filter(product_name__contains=searched).values()
return JsonResponse({"returned_products":list(returned_products)})
```
I think the best way to approach this is to use JavaScript and Ajax calls in your frontend/template and HTTP responses with JSON data in your view/backend. You would not do a form submit but instead POST HTTP calls via Ajax to your view (which most likely would be triggered by the button you already have in place). Your view would then respond with a HTTP response which has to be interpreted and incorporated again by JavaScript in your frontend. A good way to structure your response is using JSON format, which can be easily interpreted using JavaScript. Django Rest Framework can make this easier for bigger projects, but it might be good enough to build the JSON responses more or less manually at small projects.
Let's consider the following page in a client side blazor app:
#page "/test"
<div id="modalDialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<EditForm Model="#model" OnSubmit="#SubmitHandler">
<div class="form-group d-flex justify-content-between">
<label class="col-form-label col-3" for="editDT">Time</label>
<InputText bind-Value="#model" id="editDT" Class="form-control" />
</div>
<button type="submit" class="btn btn-primary" #*data-dismiss="modal"*#>Submit</button>
</EditForm>
</div>
</div>
</div>
</div>
<button data-toggle="modal" data-target="#modalDialog" class="btn btn-primary">Open</button>
#functions {
private string model { get; set; } = "Model";
private void SubmitHandler()
{
Console.WriteLine("Submit");
}
}
When I click the Open button, the modal appears as expected. Then clicking on the Submit button in the modal, "Submit" gets printed in the browser console, again as expected.
But I need also to close the modal when I click Submit so I uncomment the data-dismiss attibute.
Now, the modal closes as expected, but the Submit handler is not called anymore (browser console remains empty).
1) Is this the expected behaviour?
2) If yes, is there a way to close the modal in the Submit handler without javascript interop?
3) If not, is there another way to both close the modal and have the Submit handler called when clicking on the Submit button, again without js interop?
Your biggest issue is using bootstrap as is. BS uses it’s own JS to manipulate the DOM, this won’t work with Blazor as Blazor needs to control the DOM. Same as Angular, React or Vue. If something else modifies the DOM then odd things can happen, as you’re finding.
I would suggest swapping to one of the Blazor-fied bootstrap libraries such as BlazorStrap. Or if you’re just after a modal I’ve written one called Blazored.Modal
To make the form submit inside the bootstrap modal do the following:
Give your form an id
<EditForm id="my-form-ref">
Then add attribute form to the submit button with the value of the form id
<button type="submit" class="btn btn-danger" form="my-form-ref">Submit Form</button>
I guess that dismiss="modal" is viable only if you use <button type="button"></button>, but this would not enable "submission of the form". To really solve this issue, I'd suggest you use the <form> tag
and <button type="button"> tag instead.
But a better solution is to follow what Chris Sainty suggested in his answer.
I may add that it doesn't seem to me a good practice to embed the Bootstrap dialog box in Blazor, when such object can easily implemented in Blazor...
Hence, I would suggest that you yourself create a dialog-box component, a templated one, perhaps based on the Bootstrap dialog box...After all, I guess that like all of us, you are in the learning phase of Blazor. So it can be a good exercise.
Hope this helps...
All good suggestions. However, for the sake of completeness, I found a way to achieve what I wanted, even if it is a not very elegant workaround:
#page "/test"
#using System.ComponentModel.DataAnnotations;
<div id="modalDialog" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-body">
<EditForm EditContext="#EC">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group d-flex justify-content-between">
<label class="col-form-label col-3" for="editDT">Time</label>
<InputText bind-Value="#model.Name" id="editDT" Class="form-control" />
</div>
#if (EC.Validate())
{
<button type="button" onclick="#SubmitHandler" class="btn btn-primary" data-dismiss="modal">Submit</button>
}
else
{
<button type="button" onclick="#SubmitHandler" class="btn btn-primary">Submit</button>
}
</EditForm>
</div>
</div>
</div>
</div>
<button data-toggle="modal" data-target="#modalDialog" class="btn btn-primary">Open</button>
#functions {
public class ModelClass
{
[Required]
public string Name { get; set; }
}
private ModelClass model { get; set; } = new ModelClass { Name = "My Name" };
private EditContext EC { get; set; }
private void SubmitHandler()
{
Console.WriteLine("Submit");
}
protected override void OnInit()
{
EC = new EditContext(model);
base.OnInit();
}
}
I have this form which contains a button with some dynamic value, when I click on it it should add the product to the checkout page.
Here is the html form :
{%for p in product %}
<div class="single-product">
<div class="product-f-image">
<img src="data:image/png;base64,{{p.image_medium}}" alt="">
<div class="">
{% if not user.is_authenticated %}
<form action="/login/">
<button class="add-to-cart-link" type="submit"> Add to cart</button>
</form>
{%else%}
<form id="form-id" action="" method="POST">
{% csrf_token %}
<button class="add-to-cart-link" type="submit" name="product" value="{{p.id}}" >
<input type="hidden" name="product_name" value="{{p.name}}">
<input type="hidden" name="product_price" value="{{p.lst_price}}">
Add to cart</button>
</form>
{%endif%}
<i class="fa fa-link"></i> See details
</div>
</div>
<h2>{{p.id}}</h2>
<div class="product-carousel-price">
<ins>{{p.lst_price}} €</ins>
</div>
</div>
{%endfor%}
And here is my what I am doing with selenium:
bon_commande = self.selenium.find_element_by_xpath("//button[#name='product' and #value='37']/parent::form")
bon_commande.submit()
And thanks for the help !
You don't need to locate Submit button to submit a form - use any element inside form or form element itself:
self.selenium.find_element_by_id("form-id").submit()
self.selenium.find_element_by_class_name("add-to-cart-link").submit()
Update
Try to wait until django variable "{{p.id}}" is replaced with generated value:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(self.selenium, 10).until(EC.presence_of_element_located((By.XPATH, "//button[#name='product' and #value='37']"))).submit()
Change to click the submit button:
// add some sleep to wait the JS files of page
// load completely to register click event to the submit button
// otherwise nothing to response to the click
// (because the `action` of the form is empty.)
self.selenium.sleep(15); // sleep 15 seconds
self.selenium.find_element_by_xpath("//button[#name='product' and #value='37']").click()
To click on the button with text as Add to cart you can use the following line of code :
self.selenium.find_element_by_xpath("//form[#id='form-id']/button[#class='add-to-cart-link' and #name='product']").submit()
#or
self.selenium.find_element_by_xpath("//form[#id='form-id']/button[#class='add-to-cart-link' and #name='product']").click()
I have a template structure similar to this:
#X_List.html
<div class="row">
{% include './X_List_Table.html' %}
</div>
<div id="confirm" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="testmodal" aria-hidden="true">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content"></div>
</div>
</div>
#X_List_Table.html
<table>
<thead>
<tr>
<th>Desc</th>
<th>Activate</th>
</tr>
</thead>
<tbody>
{% for item in x_list %}
<tr>
<td>{{ item.id }}</td>
<td><a data-toggle="modal" href="{% url 'x:x_quantity' item.id %}" data-target="#confirm">Click me</a></td>
</tr>
{% endfor %}
</tbody>
</table>
My view is defined as:
#views.py
def x_quantity(request, id):
return render(request, 'modal.html', {'quantity': X.objects.filter(pk=id).count()}
and the modal.html:
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h3>Attention</h3>
</div>
<div class="modal-body">
<p>The number of elements is {{ quantity }}</p>
</div>
<div class="modal-footer"></div>
The problem is:
Supposing that I have 2 elements in the table, their urls would be:
'x/x_quantity/1'
'x/x_quantity/2'
Consider that for these elements:
One returns a QuerySet with atleast 1 element
One returns an empty QuerySet
When I click on the link, it should run the view, get the quantity based on the id of the element, return it as a context variable to the modal so it can be displayed.
The problem is:
When I click on a link, the view is being called with the id of the element, which can be confirmed by looking at the server shell [06/Apr/2018 17:00:23] "GET /x/x_quantity/1 HTTP/1.1" 200 898.
If I click on the other element, THE VIEW IS NOT BEING CALLED, there is no request going out.
What I intend to do is to display a modal with the quantity of the element clicked.
Is this a confusion on my part regarding how the {% url 'app:app_view' var %} should behave on a href or I'm not supposed to do this and should, instead, use AJAX?
Perhaps this is related with "refreshing" context variables as well?
The explanation for the behavior you are seeing can be found in the Bootstap documentation:
If a remote URL is provided, content will be loaded one time via
jQuery's load method and injected into the .modal-content div. If
you're using the data-api, you may alternatively use the href
attribute to specify the remote source. An example of this is shown
below:
If you want to use the same modal to load different content, you have to use Ajax.
A (quite ugly) workaround would be to render a modal for each item in x_list. Just be aware that the value doesn't get updated if you open the same modal twice.
Let me first clarify that before this example I have never used Bootstrap. I found your question interesting so I played a little bit with Bootstrap CDN. Also I do not use a lot of Javascript so everyone feel free to correct any bad practices.
I think what you want is doable using AJAX so here is my solution:
I changed a link to a button because all the modal examples had buttons not links :)
#X_List.html
<table>
<thead>
<tr>
<th>Desc</th>
<th>Activate</th>
</tr>
</thead>
<tbody>
{% for x in x_list %}
<tr>
<td>{{ x.id }}</td>
<td><button id="{{ x.id }}" type="button" class="btn btn-info modal-btn">Click me</button></td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Modal -->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
<button type="button" class="close" data-dismiss="modal">× </button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Javascript (you will need js-cookie - I used CDN) passes the id to the server side and then shows received quantity at the end in a modal.
$(document).ready(function() {
$(".modal-btn").click(function(event) {
var x_id = $(event.target).attr('id');
$.ajax({
url : "/ajaxquantity/",
type : "POST",
dataType: "json",
data : {
x_id : x_id,
csrfmiddlewaretoken: Cookies.get('csrftoken')
},
success : function(json) {
var quantity = json.quantity;
$(".modal-title").text('Id: ' + x_id);
$(".modal-body").text('Quantity: ' + quantity);
$('#myModal').modal('show');
},
error : function(xhr,errmsg,err) {
alert(xhr.status + ": " + xhr.responseText);
}
});
return false;
});
});
Then you need to add this line into urlpatterns list:
url(r'^ajaxquantity/$', views.ajaxquantity, name='ajaxquantity')
And finally the server side. I do not know how your model looks like so here I used your query from the question.
# views.py
def ajaxquantity(request):
if "x_id" in request.POST:
response_dict = {}
x_id = request.POST.get('x_id')
quantity = X.objects.filter(pk=id).count()
response_dict.update({'quantity': quantity})
return JsonResponse(response_dict)
else:
return render(request, 'yourapp/X_List.html')
So this worked for me (with a little different QuerySet). It is very important that Jquery is only defined once!!!
Keep in mind that this is a minimal working example.
I am not an expert in Bootstrap 3. It was my first time to use the widget "datepicker". I have two date pickers that need to be displayed inline to each other. When I try a normal input it works but if it is a date picker it goes a little far at the bottom. I tried the position relative however it did not resolve the issue. See screen shot attached.Date picker goes at the bottom
Hope some one can give me some light. Below is my code.`
<div class="horiz-search-bar-wrapper">
<form class="form-inline">
<div class="form-group">
<label><strong>Search</strong></label>
</div>
<div class="input-daterange">
<div class="form-group rel-position-date">
<label>Check-In:</label>
<div class="input-group input-append date" id="from">
<input type="text" class="form-control">
<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</div>
<div class="form-group rel-position-date">
<label>Check-Out</label>
<div class="input-group input-append date" id="to">
<input type="text" class="form-control">
<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</div>
</div>
<div class="form-group mar-left">
<button type="button" class="btn btn-primary">
Submit
</button>
</div>
</form>
</div>
`
Just an update to this, we were able to fixed the issue by replacing the <label></label> tag to <span></span>. The bootstrap datepicker adds top pixels when it sees more than 1 label.
Hope this could help the others.
I was also seeing issues with the Datepicker appearing too low. Whether the targeted element was toward the top or bottom of the viewport and the Datepicker was respectively orienting itself above or below the element, I was seeing a Datepicker appear "too-low."
Stumbled on to a fix with some JavaScript hackery bound to the DP's "show" event/hook.
Note the use of the native Boostrap CSS utility classes datepicker-orient-top and datepicker-orient-bottom.
$('#DateOfBirthForEditing').datepicker().on("show", function (e) {
var refDatePicker = $('div.datepicker.datepicker-dropdown');
var refDatePickerField = $('#DateOfBirthForEditing');
var fieldPositionTop = refDatePickerField.offset().top;
var dpModalTopOffset = 0;
if (refDatePicker.hasClass('datepicker-orient-top')) {
dpModalTopOffset = fieldPositionTop - 255;
} else if (refDatePicker.hasClass('datepicker-orient-bottom')) {
dpModalTopOffset = fieldPositionTop + 30;
}
refDatePicker.css({ "top": dpModalTopOffset });
});