Why Javascript event running multiple times per click? - javascript

I wrote a js-class which handles the Ajax-calls when a user clicks on a button. So the user can simply click on open/close and can open/close a entity in my database.
All works fine. The button-icon changes and the popover title and text changes also. The user can click on the buttons again and again and the Ajax does the work and jQuery changes the look - BUT -
When I watch in my debug bar - I see that with every click a additional Ajax call is running. When I click the button 3 times - the same Ajax-call will run 4 times for changing the entity to open/close.
I inspected the handlers. Every time it is only one handler and one #close-course element.
I really don't understand why this Ajax calls get more and more with every click!
Here my js-class:
'use strict';
import $ from "jquery";
(function (window, $) {
class CourseApp {
constructor($wrapper) {
this.$wrapper = $wrapper.find('.js-wrapper');
this.$body = $wrapper
this.iconBtn = this.$wrapper.find('a').blur();
this.$submitCount = 0;
this.$wrapper.on(
'click',
'a',
this.handleOpenCloseAction.bind(this)
);
}
handleOpenCloseAction(e) {
let popover = this.$body.find('.popover')
// prevent popover to close - when open-button is hitted
popover
.on('mousedown', '#open-course', function (e) {
e.preventDefault()
});
// prevent popover to close - when open-button is hitted
popover
.on('mousedown', '#close-course', function (e) {
e.preventDefault()
});
// since open-course-link is clicked - open the course and
// do some action
popover
.on(
'click',
'#open-course',
function (e) {
const $btn = $(e.currentTarget);
let $icon = $btn.find('.icon-lock-open');
if (this.$submitCount === 0) {
this.$submitCount++;
$.ajax({
url: $btn.data('open-course-url'),
method: 'POST',
}).then(function (data) {
$('[data-toggle="popover"]').popover('hide');
this.iconBtn.blur();
this.iconBtn.attr('data-content', data);
this.iconBtn.attr('data-original-title', 'Kurs schließen');
$('.icon-lock').removeClass('icon-lock').addClass('icon-lock-open');
}.bind(this))
this.$submitCount = 0;
}
}.bind(this)
)
// since close-course-link is clicked - close the course and
// do some action
popover
.on(
'click',
'#close-course',
function (e) {
const $btn = $(e.currentTarget);
if (this.$submitCount === 0) {
this.$submitCount++;
$.ajax({
url: $btn.data('close-course-url'),
method: 'POST',
}).then(function (data) {
$('[data-toggle="popover"]').popover('hide');
this.iconBtn.blur();
this.iconBtn.attr('data-content', data);
this.iconBtn.attr('data-original-title', 'Kurs öffnen');
$('.icon-lock-open').removeClass('icon-lock-open').addClass('icon-lock');
}.bind(this))
this.$submitCount = 0;
}
}.bind(this)
)
}
}
window.CourseApp = CourseApp;
})(window, jQuery);
My html:
<td class="col-1 js-wrapper">
<a href="#"
class="btn btn-primary pop"
data-content="{% if entity.open %} {{ popoverContentClose }} {% else %} {{ popoverContentOpen }} {% endif %}" data-toggle="popover" tabindex="0" data-trigger="focus"
title=""
data-original-title="{% if entity.open %} Kurs schließen? {% else %} Kurs öffnen? {% endif %}"
data-placement="left">
{% if entity.open %}
<i class="icon icon-lock-open"></i>
{% else %}
<i class="icon icon-lock"></i>
{% endif %}
</a>
</td>
The popover (is in a twig block):
Möchtest du diesen Kurs wirklich schließen? Die Teilnehmer dieses Kurses müssen somit jeden Kurszugang bestätigen.
<br>
<table class='table popover-table mb-0'>
<tr>
<td class='pl-0 border-top-0'>
<a href='#'
class='btn btn-danger-outline col-md-12 ml-0' id='close-course' data-close-course-url='{{ path('close_course', {'id' : entity.id}) }}'><i class='icon icon-lock'></i>Schließen</a>
</td>
<td class='pr-0 border-top-0'>
<button class='btn btn-primary col-md-12' class='close'
data-dismiss='alert'>{{ 'common.close'|trans }}</button>
</td>
</tr>
</table>

Related

jQuery to close menu when form submitted / prevent function on error + update list with htmx

So what I have is a simple form with couple of fields that is located inside a menu, that slides in when I click button 'btn-openmenu'. I am trying to use htmx to dynamically update the page to show results instantly and also Javascript to control the menu where the form is located.
What I am trying to accomplish is that when the submit button on the form is pushed, it will submit the form and close the menu.
What happens now is that the menu closes okay when the form is submitted. What also can happen is that if the form is not valid and a error pops up, but the menu still closes and the form is never submitted.
Here is the html where form is located form (/items/new-item/):
{% load crispy_forms_tags %}
<form id="createnewitem"
hx-post="/items/new-item/">
{% csrf_token %}
{{ newcoreprocess|crispy }}
<div style="margin-top: 10px;">
<button type="submit" class="btn submit-new">Create item</button>
<a class="btn" id="clear">Clear</button>
</div>
</form>
<script>
$(document).ready(() => {
$('#clear').on('click', function () {
var form = $("#createnewitem");
form[0].reset();
})
})
</script>
<script>
$(document).ready(() => {
$('.submit-new').click(function () {
var pagecontent = $("#content");
var menu = $(".menu");
var buttonSpan = $("#btn_openmenu").find('span');
pagecontent.css('marginLeft', '0');
menu.css('width', '0');
buttonSpan.text('Close');
})
})
</script>
Here is the html where the menu is opened and updated list would be rendered (/items/):
<div class="items-content" id="itemcontent">
<div class="container-fluid">
<div class="row" id="items-list">
{% include 'items/items-list.html' %}
</div>
</div>
<button class="btn" id="btn_openmenu"
hx-get="/items/new-item/"
hx-target="#createitemmenu"
hx-swap="innerHTML">
<i class="fas fa-plus-circle" id="add-item-icon"></i><span class="spanicon" id="add-item-span">Create item</span>
</button>
<div class="menu">
<div class="container-fluid" id="createitemmenu">
</div>
</div>
</div>
<script>
$(document).ready(() => {
$('#btn_openmenu').on('click', function () {
var thisElement = $(this);
var buttonSpan = thisElement.find('span');
var pagecontent = $("#itemcontent");
var menu = $(".menu");
if(buttonSpan.text() === "Close"){
buttonSpan.text('Create item');
menu.css('width', '0');
pagecontent.css('marginLeft', '0');
}
else {
if(buttonSpan.text() === "Create item"){
buttonSpan.text('Close');
pagecontent.css('marginLeft', '20%');
menu.css('width', '20%');
}
}
})
})
</script>
And here is the list (/items/item-list/)
{% if items %}
<nav class="nav processnav flex-column" style="padding-top: 10px;">
{% for item in items %}
<a class="nav-link" href="#">{{ item.order }} {{ item.name }}</a>
{% endfor %}
</nav>
{% else %}
<div class="container-fluid mt-3">
<span>No created items</span>
</div>
{% endif %}
What is working:
menu opens correctly
htmx renders the form in the menu
form can be submitted and if valid, the menu will close
What is not working:
if errors on submit, it still closes the menu
I have tried to edit the function submit-new.click, but whatever I change in that, it stops working at all and this is only function I got to work with valid submit
htmx doesn't render the updated list where new item is created
I have tried to edit the hx-post in the form, but whatever I edit the new item submit stops working and it renders some other content to the place, where the form is located.
I think there are just little things I am missing, but I cannot seem to get it function the way I want. Any help for the issue?

Send data to another django template/view after specific process using ajax

I have a page where I load 2 files. After a click to the load button, this page reload to display a major part of the data, allowing the user to modified it. After a click to lunch button, I want to launch the process and I want to send to another page the path of results files to allow the user to download it.
My problem is after clicking on lunch button and send data to the other page. I have 2 versions :
The first one, send data to the result page, but I do not find a way to take back in the view the data modified by the user, the ajax seems to be ignored because of the type "summit" for the button launch :
<body>
<section class="bg-light py-5 border-bottom">
<div class="container px-5 my-5 px-5">
<div>
<h1 class="display-5 fw-bolder mb-2"> Convert to Dose </h1>
<br>
</div>
<form id="formCTD" action="{% url 'json:convertToDose' %}" method="POST" enctype="multipart/form-data">
{% csrf_token %}
##here some fields to modified one of them following
<div class="row gx-5 justify-content-center">
<div class="col-lg-4 col-xl-4">
{% if factors %}
<input name="img_path" readonly
class="form-control" {{ pathAnalyseForm.img_path }}>
{% else %}
<input id="btn-submit-form-jsonfile"
class="btn btn-lg" {{ formImgFile.imgFile }}>
{% endif %}
</div>
</div>
<div class="text-center">
<div class="d-grid">
{% if factors %}
<button class="btn btn-primary btn-lg" id="launchButton" type="submit">
Launch
</button>
{% else %}
<button class="btn btn-primary btn-lg" id="submitButton" type="submit">
Load
</button>
{% endif %}
/div>
</div>
</form>
</div>
</section>
</body>
And the js block :
<script type="text/javascript">
$(document).ready(function () {
$("#launchButton").on('submit', function(e) {
e.preventDefault();
var form = $("#formCTD").get(0) //recup en html
// Est-ce que le formulaire est valide ?
console.log("valid? ")
if (form.checkValidity()) {
console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ")
nam = $("input[name='img_path']").val()
json = $("input[name='json_path']").val()
console.log(nam)
data = {"img_path" : nam,
"json_path": json}
console.log("bef ajax")
$.ajax({
url: "/filmchromique/convertToDose/",
type: "POST",
data: data,
beforeSend: function (xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", $('input[name="csrfmiddlewaretoken"]').val());
},
success: function (response) {
console.log("ok")
},
error: function(xhr, status, error) {
alert(xhr.responseText);
}
})
console.log("after ajax")
}
});
});
</script>
And the view :
def launchCtd(request):
if request.method == 'POST':
#2 after click load
if bool(request.FILES.get('calibrationFile', False)) == True and bool(request.FILES.get('imgFile', False)) == True :
#do some test ok
context = {
'factors': True,
'factorsCalib': factorsCalib,
'formCtd': formCtd,
'formJsonFile': formJsonFile,
'formImgFile': formImgFile,
'pathAnalyseForm': pathAnalyseForm,
'doseRect': doseRect,
'ctrlRect': ctrlRect,
}
return render(request, 'convertToDose.html', context)
after click lunch
else:
if request.is_ajax:
print ("here")#check
img_path = request.POST.get("img_path")
doseRectStr = request.POST.getlist('form[]')
json_pactrlRectth = request.POST.get("json_path")
method = request.POST.get("analyse_type")
if method == 'rb':
#init var
if method == 'multi':
#init var
img_out_path, json_pactrlRectth = functionTOLaunch()
context = {
'filename': img_out_path,
'protocol_file': json_pactrlRectth,
}
return render(request, 'result.html', context)
#1 load init
else:
formCtd = CtdForm()
formJsonFile = JsonFileForm()
formImgFile = ImgFileForm()
context = {
'factors': False,
'formCtd': formCtd,
'formJsonFile': formJsonFile,
'formImgFile' : formImgFile,
}
return render(request, 'convertToDose.html', context)
and the result page is a basic TemplateView.
In this first case, console.log in the ajax are not printed, I do not understand why and I supposed the function is not called (and so the ajax part)
in the second version, views are identical but I modified this on the html :
<button class="btn btn-primary btn-lg" id="launchButton" type="button">
Launch</button>
and this in the js part :
$("#launchButton").on('click', function(e) {....}
Data are sended to the view, I can read it but when I do the render to display the result page with out process data, nothing append... I supposed I have to implement something in the succes ajax part, but I do not understand what I have to do. I supposed I have to implement a new ajax request, to send the new context to the result page but how I take back the context sended by the render in the success ajax ... I'am totaly new and lost on this tech
Thanks for reading :)

Jquery favorite function call api with ajax

I'm trying to create a favorite function in my django template that let user click on the icon and it will call to an api i made to send add and remove that favorite item from user data.
Currently how it work is if user click on the icon(favorite or unfavorite) it will call to an api that handle add favorite or remove favorite, and also it will replace the DOM of that tag with the opposite (for example if i click on favorite then the entire tag of it will be replace with tag content that has unfavorite icon and onclick function)
here my html for the tag, it display base on if it is favorite or not( {% %} tag is django handling it ):
<div class="article-category">{{ property.get_type_display }}
<div class="bullet"></div> {{ property.publish_date|date:'d-m-Y' }}
{% if not request.user|is_favorite:property.id %}
<a id="mylink" href="javascript:onclickFunction()" value="{{ property.id }}">
<i class="far fa-heart fa-lg" style="color: red" title="Add to favorite"></i>
</a>
{% else %}
<a id="mylink2" href="javascript:onclickFunction()" value="{{ property.id }}">
<i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite"></i>
</a>
{% endif %}
</div>
And here is my jquery script:
<script>
$(document).ready(function () {
$('#mylink').on('click', function (event) {
event.preventDefault();
property_id = $(this).attr("value")
$.ajax({
type: "POST",
url: "http://localhost:9999/api/add_favorite/" + property_id + "/",
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {{ refresh_token }}');
},
success: function (data) {
if (data.code == 200) {
alert('ok');
replace_part_1 = '<a id="mylink2" href="javascript:onclickFunction()" value="' + property_id +'"><i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite></i></a>'
$("#mylink").replaceWith(replace_part_1);
}
}
});
return false;
});
$('#mylink2').on('click', function (event) {
event.preventDefault();
property_id = $(this).attr("value")
$.ajax({
type: "DELETE",
url: "http://localhost:9999/api/remove_favorite/" + property_id + "/",
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {{ refresh_token }}');
},
success: function (data) {
if (data.code == 200) {
alert('ok');
replace_part_2 = '<a id="mylink" href="javascript:onclickFunction()" value="' + property_id +'"><i class="far fa-heart fa-lg" style="color: red" title="Add to favorite"></i></a>'
$("#mylink2").replaceWith(replace_part_2);
}
}
});
return false;
});
});
</script>
Now the first time i click on favorite or unfavorite it sent to the api and work, the replace html part for "#mylink2" onclick event but the "#mylink" replace doesn't include the tag which contain the icon.
After this any clicking event after that won't work and i have to refresh the page to click it again to work. Any click event after that also return this error:
Uncaught ReferenceError: onclickFunction is not defined
at :1:1
I'm super noob at jquery so i can't seem to find what is wrong, hope someone can help me
EDIT:
i changed my script to this by replace with value attribute of anchor tag:
<script>
$(document).ready(function () {
$('.article-details').on('click', '#mylink', function(event) {
event.preventDefault();
property_id = $(this).attr("value")
$.ajax({
type: "POST",
url: "http://localhost:9999/api/add_favorite/" + property_id + "/",
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {{ refresh_token }}');
},
success: function (data) {
if (data.code == 200) {
alert('ok');
replace_part_1 = '<a id="mylink2" href="#" value="' + property_id +'"><i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite></i></a>'
$("a[value='" + property_id + "']").replaceWith(replace_part_1);
}
}
});
return false;
});
$('.article-details').on('click', '#mylink2', function(event) {
event.preventDefault();
property_id = $(this).attr("value")
$.ajax({
type: "DELETE",
url: "http://localhost:9999/api/remove_favorite/" + property_id + "/",
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer {{ refresh_token }}');
},
success: function (data) {
if (data.code == 200) {
alert('ok');
replace_part_2 = '<a id="mylink" href="#" value="' + property_id +'"><i class="far fa-heart fa-lg" style="color: red" title="Add to favorite"></i></a>'
$("a[value='" + property_id + "']").replaceWith(replace_part_2);
}
}
});
return false;
});
});
</script>
and my html to:
<div class="article-category">{{ property.get_type_display }}
<div class="bullet"></div> {{ property.publish_date|date:'d-m-Y' }}
{% if not request.user|is_favorite:property.id %}
<a id="mylink" href="#" value="{{ property.id }}">
<i class="far fa-heart fa-lg" style="color: red" title="Add to favorite"></i>
</a>
{% else %}
<a id="mylink2" href="#" value="{{ property.id }}">
<i class="fas fa-heart fa-lg" style="color: red" title="Remove from favorite"></i>
</a>
{% endif %}
</div>
it worked but the onclick of #mylink when changing the html the i tag inside still disappear(the #mylink2 onclick worked it changed the html white the tag)
It looks like you are facing an event delegation problem. That is, the moment your jQuery code runs, $('#mylink2') does not exist in the document and hence the event doesnt get binded. Use event delegation instead:
$('.article-category').on('click', '#mylink2', function(event) {
event.preventDefault();
// your original click handler
});
#myParentWrapper should be an element that is consistent in your markup, preferably not body or document for performance reasons.
You can then remove the onclick attribute from your anchor - or - actually somewhere define the function that is referenced there if it should do anything other than what your jQuery code does already.

How do I close each instance of the same div class one a time using Javascript

What I'm trying to do is very straightforward: close every instance of a div each time the close button is clicked. What I'm getting instead is only the first clicked-on instance closes but the remaining ones can't.
I must say I come from Python and I'm not very familiar with Javascript. I think there is someway using ID instead of class? Here is my html (with Jinja) code for dynamically creating the objects I would want to close when clicked on:
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages%}
<div class="notification {{ category }} is-bold">
<button class="delete"></button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
And here is my Javascript code:
// notification remover
document.addEventListener('DOMContentLoaded', () => {
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
$notification = $delete.parentNode;
$delete.addEventListener('click', () => {
$notification.parentNode.removeChild($notification);
});
});
});
Example of generated html:
<div class="notification is-info is-bold">
<button class="delete"></button>
<div class="notification is-info is-bold">
<button class="delete"></button>
<div class="notification is-info is-bold">
<button class="delete"></button>
Try this instead:
document.addEventListener('DOMContentLoaded', () => {
(document.querySelectorAll('.notification .delete') || []).forEach(($delete) => {
$delete.addEventListener('click', (event) => {
event.target.parentNode.remove();
});
});
});
The problem with your code is that you are setting $notification with the last one, so when the eventListener triggers $notification is always the last.

change js confirm to a twitter bootstrap modal

I have some JavaScript that displays a confirm dialog box to the user to confirm their action with passed in variables.
However, I must change the plain confirm dialog to a twitter-bootstrap modal.
I have tried several times, read a lot of SO posts and read the twitter bootstrap docs, but I cannot get it to work as my knowledge of js is not good enough. I am getting stuck on the display of the variables in the twitter bootstrap modal.
I am hoping that some one can help me out by giving me some pointers.
Here is my current js dialog code:
function checkboxUpdated(checkbox, count, label, id) {
if (checkbox.checked) {
$('#menu_entry_' + id).show();
} else {
if (count > 0) {
if (! confirm('{% trans "You have '+ count +' saved '+ label +'.\n\nIf you leave this option un-checked your saved '+ label +' will be deleted only after you update this page.\n\nAre you sure you want to delete your ' + count + ' saved ' + label +'?" %}')) {
checkbox.checked = true;
return;
}
}
$('#menu_entry_' + id).hide();
}
}
EDIT: ADDED CODE OF #menu_entry_ as requested in comment:
{% for id, menu_entry, selected, item_count in menu_entries %}
<li id="menu_entry_{{ id }}" {% if not selected %}style="display:none;"{% endif %}>
<a
{% if id == 4 %}
href="{% url summary_details %}"
{% elif id == 8 %}
href="{% url objective_details %}"
{% elif id == 12 %}
href="{% url leading_employment_details %}"
{% elif id == 16 %}
href="{% url desired_occupation_details %}"
....
{% elif id == 112 %}
href="/"
{% else %}
href="/"
{% endif %}
onclick="showProgressAnimation();">
Note that I need to transform the following js confirm code to twitter bootstrap modal code:
if (! confirm('{% trans "You have '+ count +' saved '+ label +'.\n\nIf you leave this option un-checked your saved '+ label +' will be deleted only after you update this page.\n\nAre you sure you want to delete your ' + count + ' saved ' + label +'?" %}')) {
You can write your own jQuery plugin. First, add the Bootsrap's modal component to your document.
<div class="modal fade" id="confirm" tabindex="-1" role="dialog" aria-labelledby="confirm-label" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="confirm-label"></h4>
</div>
<div class="modal-body">
<p class="message"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default dismiss" data-dismiss="modal"></button>
<button type="button" class="btn btn-primary confirm" data-dismiss="modal"></button>
</div>
</div>
</div>
</div>
Basically, the plugin should display the modal when invoked and trigger a confirm event if the confirmation button is pressed or dismiss otherwise.
$.fn.confirm = function (options) {
var settings = $.extend({}, $.fn.confirm.defaults, options);
return this.each(function () {
var element = this;
$('.modal-title', this).html(settings.title);
$('.message', this).html(settings.message);
$('.confirm', this).html(settings.confirm);
$('.dismiss', this).html(settings.dismiss);
$(this).on('click', '.confirm', function (event) {
$(element).data('confirm', true);
});
$(this).on('hide.bs.modal', function (event) {
if ($(this).data('confirm')) {
$(this).trigger('confirm', event);
$(this).removeData('confirm');
} else {
$(this).trigger('dismiss', event);
}
$(this).off('confirm dismiss');
});
$(this).modal('show');
});
};
An improvement we can make to the code above is to expose the default plugin settings.
$.fn.confirm.defaults = {
title: 'Modal title',
message: 'One fine body…',
confirm: 'OK',
dismiss: 'Cancel'
};
Usage example:
$('#confirm').confirm().on({
confirm: function () {
console.log('confirm');
},
dismiss: function () {
console.log('dismiss');
}
});
See live example here: http://jsfiddle.net/cdog/4q9t9pk5/.
If you don't want to write your own solution you can try existing projects like: https://github.com/nakupanda/bootstrap3-dialog. It looks like what you were looking for. Docs and demos available here: http://nakupanda.github.io/bootstrap3-dialog/.
DEMO
You want to change your function as shown below. Then there's modal markup to add to the page (see demo) and the event listeners shown below:
function checkboxUpdated(checkbox, count, label, id) {
if (checkbox.checked) {
$('#menu_entry_' + id).show();
} else {
if (count > 0) {
//save data to use later
$('#message_p').text( '<the-message-to-show-on-the-modal>' )
.data('elem', '#menu_entry_' + id)
.data('chkbox', checkbox);
//set modal title
$('#title_h4').text( '<Modal Title>' );
//show modal
$('#confirm_modal').modal('show');
}
}
}
$(document).ready(function() {
$('.button-yes').on('click',function() {
var checkbox = $('#message_p').data('chkbox');
checkbox.checked = true;
$('#confirm_modal').modal('hide');
});
$('#confirm_modal').on('hidden.bs.modal', function() {
var elem = $('#message_p').data('elem');
$(elem).hide();
});
});
NOTE: Let us know if you have any questions.

Categories

Resources