Django formset.js : Can't delete a from - javascript

We're using formset.js in our django project to add or delete forms in a form.
I can't make the delete button work.
I see in the formset.js (available here https://pypi.python.org/pypi/django-formset-js/0.4.0 )
this code :
Formset.prototype.bindForm = function($form, index) {
var prefix = this.formsetPrefix + '-' + index;
$form.data(pluginName + '__formPrefix', prefix);
var $delete = $form.find('[name=' + prefix + '-DELETE]');
// Trigger `formAdded` / `formDeleted` events when delete checkbox value changes
$delete.change(function(event) {
if ($delete.is(':checked')) {
$form.attr('data-formset-form-deleted', '');
$form.trigger('formDeleted');
} else {
$form.removeAttr('data-formset-form-deleted');
$form.trigger('formAdded');
}
}).trigger('change');
var $deleteButton = $form.find(this.opts.deleteButton);
$deleteButton.bind('click', function() {
$delete.attr('checked', true).change();
});
};
My problem is that i don't find any checkbox in the code. The template shows this :
<div data-formset-body>
<!-- New forms will be inserted in here -->
{% for form in formset %}
<div data-formset-form>
{{ form.as_p }}
<!-- ajout YCO pour progresser dans le delete
<input type="checkbox" name="form-{{ forloop.counter0 }}-DELETE">
-->
<div class="hidden">{{ form.DELETE }}</div>
<a data-formset-delete-button >{% trans "Delete form" %}</a>
<!-- onclick ="$(this).parent().remove();" -->
</div>
{% endfor %}
</div>
Could someone tell me :
Is there a checkbox needed somewhere or if that property is added by the script to the delete button ?
Where should i write the code <input type="checkbox" name="form-0-DELETED"> ?
Is there a working sample of a working delete button available somewhere ?

When using yourform.DELETE in template, must use with can_delete formset parameter as below.
from django import forms
from django.forms.formsets import formset_factory
class YourForm(forms.Form):
date = forms.DateField()
YourFormSet = formset_factory(YourForm, can_delete = True)
Django document "Formsets"

Related

Django: request.is_ajax() returning False

I'm trying to make the search of 'patient' Dynamic with ajax. Every thing in my code is working well but I don't know wy request.is_ajax() always returns false. I search about it but I didn't find the solution yet, right now my code do the search but with changing of the url and that mean the js dosen't work. I don't know how work well with javascript in Django so please help me.
This is my views.py:
def index(request):
ctx = {}
url_parameter = request.GET.get("q")
if url_parameter:
queryset = Patient.objects.annotate(fullname=Concat('first_name', Value(' '), 'last_name'))
patients = queryset.filter(fullname__icontains=url_parameter)
#patients = Patient.objects.filter(name__icontains=url_parameter)
else:
patients = Patient.objects.all()
ctx["patients"] = patients
print(request.is_ajax())
if request.is_ajax():
html = render_to_string(template_name="patient/patients-results-partial.html",context={"patients": patients})
data_dict = {"html_from_view": html}
return JsonResponse(data=data_dict, safe=False)
return render(request, "patient/index.html", context=ctx)
index.html:
{% extends 'base.html' %}
{% block content %}
<div class="col-md-6 offset-md-4">
{# part added for dynamic search#}
{# icon and search-box #}
<form class="form-inline">
<i id="search-icon" class="fas fa-search" aria-hidden="true"></i>
<input id="patient-input" class="form-control form-control-sm ml-3 w-75" type="text" placeholder="Search" aria-label="Search" name="q">
</form>
{# artist-list section #}
<div id="replaceable-content" class="col-6">
{% include 'patient/patients-results-partial.html' %}
</div>
</div>
<div class="col-md-6 offset-md-4">
Ajouter un nouveau patient
</div>
<div class="col-md-6 offset-md-4">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>Prénom</th>
<th>Nom</th>
<th>Téléphone</th>
<th>Sexe</th>
<th>Date de naissance</th>
<th>Docteur</th>
</tr>
</thead>
<tbody>
{% for field in patients %}
<tr>
<td>{{field.first_name}}</td>
<td>{{field.last_name}}</td>
<td>{{field.phone}}</td>
<td>{{field.sex}}</td>
<td>{{field.birth_date}}</td>
<td>{{field.doctor}}</td>
<td>Details</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock content %}
and this is my js code:
const patient_input = $("#patient-input");
const search_icon = $('#search-icon');
const patients_div = $('#replaceable-content');
const endpoint = '/patient/';
const delay_by_in_ms = 700;
let scheduled_function = false;
let ajax_call = function (endpoint, request_parameters) {
$.getJSON(endpoint, request_parameters)
.done(response => {
// fade out the patients_div, then:
patients_div.fadeTo('slow', 0).promise().then(() => {
// replace the HTML contents
patients_div.html(response['html_from_view'])
// fade-in the div with new contents
patients_div.fadeTo('slow', 1)
// stop animating search icon
search_icon.removeClass('blink')
})
})
}
patient_input.on('keyup', function () {
const request_parameters = {
q: $(this).val() // value of patient_input: the HTML element with ID patient-input
}
// start animating the search icon with the CSS class
search_icon.addClass('blink')
// if scheduled_function is NOT false, cancel the execution of the function
if (scheduled_function) {
clearTimeout(scheduled_function)
}
// setTimeout returns the ID of the function to be executed
scheduled_function = setTimeout(ajax_call, delay_by_in_ms, endpoint, request_parameters)
})
I think this kind of AJAX call, is clean and easier than what you have in your code.
This model AJAX is working for me.
Maybe this js code helps you to fix your bug.
$('#form').on('submit', function (event) {
event.preventDefault();
const form = $(this);
const submit_btn = form.find('#submit-button');
$.ajax({
url: 'your URL for send ajax call',
type: 'POST', // Method of call, such as POST or GET
data: 'data as JSON', // You can use 'form.serialize()' to
beforeSend: function () {
// This line calls before that AJAX is called.
},
success: function (data) {
// If the back-end returns a JSON response with 200 status code, js will run this line.
},
error: function (data) {
// If the back-end returns a JSON response with 400 status code, js will run this line.
},
complete: function () {
// After success or error is called , this function will be called
},
})
})

Javascript - Cannot read property 'style' of null - Problem when using onclick to post text to a table

When I click on a list item 'Add' button, I'm using button.onclick to send that item's text to fill out a table on the same html page. However, I keep getting a "Cannot read property 'style' of null" in the console whenever I click an item. This was working fine until recently. Something changed...somewhere, but I can't figure out why it's returning this error suddenly.
Here's the Javascript
function showPage(page) {
document.querySelectorAll('div').forEach(div => {
div.style.display = 'none';
})
document.querySelector(`#${page}`).style.display = 'block';
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('button').forEach(button => {
button.onclick = function() {
showPage(this.dataset.page);
}
});
});
function myFunction(txt) {
var myTxt = txt;
console.log(myTxt)
if (txt.includes('QB')) {
document.getElementById("id_QB").value = myTxt;
}
else if (txt.includes('RB')) {
document.getElementById("id_RB1").value = myTxt;
}
else if (txt.includes('WR')) {
document.getElementById("id_WR").value = myTxt;
}
else if (txt.includes('TE')) {
document.getElementById("id_TE").value = myTxt;
}
else if (txt.includes('K')) {
document.getElementById("id_K").value = myTxt;
}
}
</script>
Here's the html page which is displaying a django template (I've deleted the on-page script tags containing the Javascript above).
{% load static %}
{% block body %}
<form method="POST">
{% csrf_token %}
<table id="playerName">
{{ form.as_table }}
</table>
<input type="submit" value="Submit" >
</form>
<br>
{% for player_data in player_data %}
<li><p>Player ID: {{ player_data.player_id }}: {{ player_data.player_name }}, {{ player_data.team }}, {{ player_data.position }}</p></li> <button onclick="myFunction('{{ player_data.player_name }} {{ player_data.position }}')">Add</button>
{% endfor %}
{% endblock %} ```

Dynamically added Django FormSet data not being posted

I am modifying a FormSet using JavaScript/jQuery by dynamically adding a form to a Django FormSet. For example, I start with one form asking about a User's education. The User can then press an add button to add an identical form to input information about secondary schooling (e.g. grad school). The form gets added in the browser and I can input data, but when I POST the data, it only shows one form in the FormSet with the information from the second form in the browser.
POST DATA
edu-0-degree u'Doctorate'
first_name u'User'
last_name u'One'
Submit u'Submit'
edu-0-date_started u'01/01/12'
edu-MIN_NUM_FORMS u'0'
edu-0-school u'School Two'
edu-INITIAL_FORMS u'0'
edu-MAX_NUM_FORMS u'1000'
edu-0-date_finished u'01/01/16'
edu-0-id u''
edu-TOTAL_FORMS u'2'
csrfmiddlewaretoken u'qgD2supjYURWoKArWOmkiVRoBPF6Shw0'
I'm then getting an error saying:
ValidationError: [u'ManagementForm data is missing or has been tampered with'].
Here are the relevant pieces of code:
views.py
def build_profile(request):
EducationFormset = modelformset_factory(EducationModel, AddEducationForm, extra=1)
if request.method == "POST":
education_formset = EducationFormset(request.POST, prefix='edu')
for form in education_formset:
if form.is_valid() and form.has_changed():
education = EducationModel(
school = form.cleaned_data['school'],
date_started = form.cleaned_data['date_started'],
date_finished = form.cleaned_data['date_finished'],
degree = form.cleaned_data['degree'],
user = current_user
)
education.save()
return HttpResponseRedirect(reverse('private', args=[current_user.username]))
context = {
'edu_formset' : forms['education'],
}
return render(request, "build_profile.html", context)
(Here I've tried with and without the form.has_changed() piece with the same result.)
Template build_profile.html
<h2>Education</h2>
{{ edu_formset.management_form }}
{% for form in edu_formset.forms %}
<div id="{{ form.prefix }}-row" class="dynamic-form">
{{ form|crispy }}
<div {% if forloop.first %} class="hidden" {% endif %}>
<button type="button" class="btn btn-default btn-sm delete-row">
<span class="glyphicon glyphicon-minus" aria-hidden="true"></span>
</button>
</div>
</div>
{% endfor %}
<div class="btn-group btn-group-xs" role="group" aria-label="...">
<button type="button" class="btn btn-default add-row">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
</button>
</div>
build_profile.js (The code to dynamically add forms to the FormSet)
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function addForm(btn, prefix) {
var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
var row = $('.dynamic-form:first').clone(true).get(0);
$(row).removeAttr('id').insertAfter($('.dynamic-form:last')).children('.hidden').removeClass('hidden');
$(row).children().not(':last').children().each(function() {
updateElementIndex(this, prefix, formCount);
$(this).val('');
});
$(row).find('.delete-row').click(function() {
deleteForm(this, prefix);
});
$('#id_' + prefix + '-TOTAL_FORMS').val(formCount + 1);
return false;
}
function deleteForm(btn, prefix) {
$(btn).parents('.dynamic-form').remove();
var forms = $('.dynamic-form');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).children().not(':last').children().each(function() {
updateElementIndex(this, prefix, i);
});
}
return false;
}
$(document).ready( function () {
$('.add-row').click( function () {
return addForm(this, 'edu')
});
$('.delete-row').click( function () {
return deleteForm(this, 'edu')
});
});
What am I doing wrong?
You're getting the ValidationError because edu_TOTAL-FORMS = 2 and only 1 form from the formset is in your post args. View source in the browser and make sure that the names of your forms are prefixed properly. It looks like both forms have the edu-0 prefix and when you submit only the last one on the form is posted.

Form in modal with symfony2 and jquery is not being submitted with data

I have a list of Schools displayed in my list.html.twig. For each school I need to insert some data which is filled in a form inside a modal. I need that once the form is filled, the modal is submitted and closes, showing again the background page. Normally the submit action of the modal causes page refresh, and I want to avoid that obviously.
The inspiration for the code was this tutorial, specifically I followed the creation of the form...
//school controller
$school = new School();
$form = $this->createForm(
new SchoolFormType($param),
$school,
array(
'action' => $this->generateUrl("school_modal_vp", array(
'param' => $param,
)),
'method' => 'POST'
));
if($request->isMethod('POST')) {
$form->handleRequest($request);
if($form->isValid()) {
$data = $form->getData();
$em->persist($data);
$em->flush();
$response = new Response(json_encode([
'success' => true,
]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
... and the function which "replaces" the submit action of the modal with a AJAX call with form data, storing it to database and closing modal.
<script>
var param_id = '{{ param.id }}';
function sendForm(form, callback) {
// Get all form values
var values = {};
$.each( form[0].elements, function(i, field) {
if (field.type != 'checkbox' || (field.type == 'checkbox' && field.checked)) {
values[field.name] = field.value;
}
});
// Post form
console.log(values);
$.ajax({
type : form.attr( 'method' ),
url : form.attr( 'action' ),
data : values,
success : function(result) { callback( result ); }
});
}
$(function() {
$("#school_"+param_id+"_save").on("click", function( e ) {
e.preventDefault();
sendForm($("#myModalSchool_" + param_id).find('form'), function (response) {
$("#myModalSchool_" + param_id).modal('hide');
});
});
});
</script>
However, this works only for the last modal created while listing the schools. Any help is appreciated, and please if you need ask for details.
EDIT 1:
This is the template as requested
<div class="modal fade" data-backdrop="static" id="myModalSchool_{{ param.id }}">
<div class="modal-dialog modal-lg">
<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>
<h3 class="modal-title">
School
</h3>
</div>
<div class="modal-body">
<form id="school_{{ param.id }}" name="school_{{ param.id }}" method="post" action="{{ path('school_modal_vp', {param_id: param.id, }) }}" class="form-horizontal">
{{ form_errors(form) }}
{{ form_rest(form) }}
{{ form_end(form) }}
</div>
</div>
</div>
I think the main problem is the var param_id = '{{ param.id }}'; which is defined manually in your javascript.
First, I advise you to add a class on all your save button (e.g modal-submit) and a data-id on each button.
Example:
<button type="button" class="btn btn-primary modal-submit" data-id="{{myData.id}}">Submit</button>
Then in your javascript when you click on a save button (with modal-submit), you retrieve the id from the data-id and execute the sendForm($("#myModalSchool_" + param_id).find('form'),....
Example:
$(function() {
$(".modal-submit").on("click", function( e ) {
e.preventDefault();
var param_id = $(this).attr('data-id');
sendForm($("#myModalSchool_" + param_id).find('form'), function (response) {
$("#myModalSchool_" + param_id).modal('hide');
});
});
});
EDIT:
Saved multiple times issue ?
Moreover, i think you defined the javascript above in each modal. That's why the save is called multiple times. You need to have only one instance of this javascript (so it can't be placed in your modal view). Try to put the javascript in your global layout.
Hope it will help

How do I correctly load a specific jQuery script

I've been given a script by my e-commerce provider that will allow me to dynamically change the stock levels based on the product option selected by the user from a simple select element.
While the code seems strait forward, it will not run properly. As I am fairly new to jQuery, I'm assuming this is due to me not properly initializing the script within my html. The code itself is a default code that my e-commerce provider hands out, but does not support it in any way.
Here is the script
<script>
// <![CDATA[
var selectCallback = function(variant, selector) {
if (variant) {
if (variant.available) {
// Selected a valid variant that is available.
$('#add-to-cart').removeClass('disabled').removeAttr('disabled').val('Add to Cart').fadeTo(200,1);
} else {
// Variant is sold out.
$('#add-to-cart').val('Sold Out').addClass('disabled').attr('disabled', 'disabled').fadeTo(200,0.5);
}
// Whether the variant is in stock or not, we can update the price and compare at price.
if ( variant.compare_at_price > variant.price ) {
$('#product_price').html('<span class="product-price on-sale">'+ Shopify.formatMoney(variant.price, "") +'</span>'+' <s class="product-compare-price">'+Shopify.formatMoney(variant.compare_at_price, "")+ '</s>');
} else {
$('#product_price').html('<span class="product-price">'+ Shopify.formatMoney(variant.price, "") + '</span>' );
}
} else {
// variant doesn't exist.
$('#add-to-cart').val('Unavailable').addClass('disabled').attr('disabled', 'disabled').fadeTo(200,0.5);
}
}
// initialize multi selector for product
jQuery(function($) {
new Shopify.OptionSelectors("product-select", { product: , onVariantSelected: selectCallback });
});
// ]]>
</script>
Any ideas on why this might not be working? You can see this script live on my site:
http://www.yandasmusic.com/products/fender-american-standard-stratocaster?variant=1178632565
EDIT:
Upon further inspection, I have found a second piece of code that also calls up the product option selection box. I can tell that this code also serves the function of changing the currently shown product image based on the current option selected:
<script>
var selectCallback = function(variant, selector) {
if (variant && variant.available) {
jQuery('#add-to-cart').removeAttr('disabled').removeClass('disabled'); // remove unavailable class from add-to-cart button, and re-enable button
if(variant.price < variant.compare_at_price){
jQuery('#product_price .price').html('<span class="money">' + Shopify.formatMoney(variant.price, "{{ shop.money_format }}") + '</span><span class="money compare-at-price">' + Shopify.formatMoney(variant.compare_at_price, "{{ shop.money_format }}") + '</span>');
} else {
jQuery('#product_price .price').html('<span class="money">' + Shopify.formatMoney(variant.price, "{{ shop.money_format }}") + '</span>');
}
} else {
jQuery('#add-to-cart').addClass('disabled').attr('disabled', 'disabled'); // set add-to-cart button to unavailable class and disable button
var message = variant ? "Sold Out" : "Unavailable";
jQuery('#product_price .price').text(message);
}
// if (variant && variant.featured_image) {
// var originalImage = $(".zoomWrapper img");
// var newImage = variant.featured_image;
// var element = originalImage[0];
// Shopify.Image.switchImage(newImage, element, function (newImageSizedSrc, newImage, element) {
// $(element).parents('a').attr('href', newImageSizedSrc);
// $(element).attr('src', newImageSizedSrc);
// });
// };
if (variant && variant.featured_image) {
var originalImage = $("#elevatezoom_big");
var newImage = variant.featured_image;
var element = originalImage[0];
Shopify.Image.switchImage(newImage, element, function (newImageSizedSrc, newImage, element) {
$(element).attr('src', newImageSizedSrc);
$("#elevatezoom_gallery a").each(function(){
if ( $(this).attr('data-zoom-image') == newImageSizedSrc ) {
$(this).trigger('click')
};
});
});
};
};
jQuery(document).ready(function($){
new Shopify.OptionSelectors("product-select", { product: {{ product | json }}, onVariantSelected: selectCallback, enableHistoryState: true });
// Add label if only one product option and it isn't 'Title'.
{% if product.options.size == 1 and product.options.first != 'Title' %}
$('.selector-wrapper:eq(0)').prepend('<label>{{ product.options.first }}</label>');
{% endif %}
// Auto-select first available variant on page load.
{% assign found_one_in_stock = false %}
{% for variant in product.variants %}
{% if variant.available and found_one_in_stock == false %}
{% assign found_one_in_stock = true %}
{% for option in product.options %}
$('.single-option-selector:eq({{ forloop.index0 }})').val({{ variant.options[forloop.index0] | json }}).trigger('change');
{% endfor %}
{% endif %}
{% endfor %}
});
</script>
If I run both scripts, the page shows two select boxes for the product options. Is it possible to combine them?
You have a JavaScript error on your page because of this line of code:
new Shopify.OptionSelectors("product-select", { product: , onVariantSelected: selectCallback });
According to this reference document: https://docs.shopify.com/manual/configuration/store-customization/advanced-navigation/linked-product-options, that line of code should actually look like this:
new Shopify.OptionSelectors("product-select", { product: {{ product | json }}, onVariantSelected: selectCallback });

Categories