Javascript ajax calls don't work inside of dynamically loaded HTML - javascript

I'm trying to write a generic javascript script to facilitate clicking through <a href> links while only replacing the inner HTML instead of reloading the whole page. The strange thing is, it works, except on any link inside of freshly loaded HTML.
<script src="{{ asset('bundles/app/js/jquery-2.2.0.min.js') }}"></script>
<script src="{{ asset('bundles/app/js/jquery.menu-aim.js') }}"></script>
<script src="{{ asset('bundles/app/js/main.js') }}"></script>
<script type="text/javascript">
$(document).ready(function() {
$("a").on("click", function(){
event.preventDefault();
$.ajax({
'url': $(this).attr('href'),
type: "post",
success: function(response, status) {
document.getElementById("content").innerHTML = response;
},
error: function() {
console.log('failure');
}
});
});
});
</script>
When I place the exact same URL from the loaded HTML directly in the sidebar menu containing the initial links, it loads fine. According to the documentation, the .on function should attach itself to any elements added later. I've also tried .delegate and the deprecated .live as suggested by older answers but then even the menu sidebar stopped working.
What am I missing here?

Here I assume your link container is "content" by ID, if not fix that with the correct container ID OR even wrap them IN one:
$(document).ready(function() {
$('#content').on('click', 'a', function() {
event.preventDefault();
$.ajax({
'url': $(this).attr('href'),
type: "post"
}).done(function(response, status) {
document.getElementById("content").innerHTML = response;
}).fail(function() {
console.log('failure');
});
});
});
Example markup:
<div id="content">
clickme
</div>
This is NOT as desirable, (placing it on the document) place the handler on the container
if you can
$(document).ready(function() {
$(document).on('click', 'a', function() {
event.preventDefault();
$.ajax({
'url': $(this).attr('href'),
type: "post"
}).done(function(response, status) {
document.getElementById("content").innerHTML = response;
}).fail(function() {
console.log('failure');
});
});
});
As to WHY this is not desirable (the document); you want to place your event handler hooks as close to the element as possible; when you attach to the document as here, if forces the code to go through the entire document for the event handler to find the a links and look for clicks on those.
Note the the documentation says
"A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element."
Thus for the a selector in your case. SO it places the event handler on EVERYTHING in the document, THEN filters on the a selector for yours. SO if you place it on a smaller container, it has to filter less on every event (click) that is executed.

You have to apply the behaviour to the loaded HTML as well, like in this example (not tested):
$(document).ready(function() {
function addBehaviour() {
// .off first so existing behaviour will be removed and not applied multiple times
$("a").off("click").on("click", function(){
event.preventDefault();
$.ajax({
'url': $(this).attr('href'),
type: "post",
success: function(response, status) {
document.getElementById("content").innerHTML = response;
addBehaviour(); // add the behaviour
},
error: function() {
console.log('failure');
}
});
});
}
addBehaviour();
});

Related

jQuery parents() selector failing

I have an anchor, and I have attached an onClick callback to it, so once it is clicked, an AJAX request is fired which calls a view that deletes the image from the database. It should also remove <div class="image-preview"> altogether, too, however that is not happening for some reason.
When I tested div removal code in JSFiddle, it works. The image is successfully getting removed from the database and delete_view is involved in the process. I have also tried to console.log from inside the success callback and I can see a debug message. console.log($(this).parents('.image-preview')); returns Object { length: 0, prevObject: Object(1) }, thus I think the selector is failing.
What could be the reason?
HTML:
<div id="information">
<div class="image-previews">
<div class="image-preview">
<img src="/media/tmp/None/IMG_20190507_144128.jpg" width="80" height="54">
<p><a id="115" class="delete-temp-image-link">delete</a></p>
<label><input type="radio" name="main" value="IMG_20190507_144128.jpg">main</label>
</div>
</div>
<div id="div0">
<div>Name: IMG_20190507_144128.jpg</div>
<div>Size: 3.03MB</div>
<div>Type: image/jpeg</div>
<div class="progressNumber">100%</div>
</div>
</div>
jQuery:
var $deleteClicked = function(event) {
var url = Urls.deleteTempImage(event.target.id);
$.ajax({
url: url,
data: {
'id': event.target.id
},
success: function (data) {
console.log('spam');
$(this).parents('.image-preview').remove();
}
});
}
$(document).on('click', '.delete-temp-image-link', $deleteClicked);
view:
def delete_view(request, id):
img = get_object_or_404(TemporaryImage, id=id)
img.delete()
return HttpResponse('successfull')
$(this) isn't available to your named click callback function. One way to make your code more explicit would be to store $(this), as others have said - or, simply use the id that you're already passing anyway. For example:
var $deleteClicked = function(event) {
var url = Urls.deleteTempImage(event.target.id);
$.ajax({
url: url,
data: {
'id': event.target.id
},
success: function (data) {
console.log('spam');
$("#"+event.target.id).closest('.image-preview').remove();
}
});
}
$(document).on('click', '.delete-temp-image-link', $deleteClicked);
Also, note that I used jQuery .closest() instead of .parents(). From the jQuery docs, .closest() does the following:
For each element in the set, get the first element that matches the
selector by testing the element itself and traversing up through its
ancestors in the DOM tree.
Check out the docs page for a description of the differences between .closest() and .parents(). The main difference is that .closest() only traverses up the DOM tree until it finds a match, rather than traversing all the way up to the root element. I doubt there are huge performance implications, but since you're selecting only one <div>, it's slightly more precise code.
You have to store $(this) before run $.ajax because you are using it in a wrong context.
var $deleteClicked = function(event) {
var url = Urls.deleteTempImage(event.target.id);
var storedThis = $(this);
$.ajax({
url: url,
data: {
'id': event.target.id
},
success: function (data) {
console.log('spam');
storedThis.parents('.image-preview').remove();
}
});
}
$(document).on('click', '.delete-temp-image-link', $deleteClicked);
This should work as expected.
Try this code for jQuery.
var $deleteClicked = function(event) {
var url = Urls.deleteTempImage($(this).attr('id'));
$.ajax({
url: url,
data: {
'id': $(this).attr('id')
},
success: function (data) {
console.log('spam');
$(this).parents('.image-preview').remove();
}
});
}
$('.delete-temp-image-link').on('click', $deleteClicked);
Same problem occur to me as well.
Actually '$(this)' is tricky as it may seem.
'$(this)' cannot be used in success function as it lost it scope in success function.
Try to define '$(this)' outside the success(ie, before ajax),eg
const element = $(this)
And then in your success function:
element.parents('.image-preview').remove();
This should definitely solve your problem.
Hope this helps!

jQuery function doesn't work after after loading dynamic content

I'm using below code. This is bootstrap 3 delete conformation message.
$(document).ready(function(){
$('a.btnDelete').on('click', function (e) {
e.preventDefault();
var id = $(this).closest('div').data('id');
$('#myModal').data('id', id).modal('show');
});
$('#btnDelteYes').click(function () {
var id = $('#myModal').data('id');
var dataString = 'id='+ id ;
$('[data-id=' + id + ']').parent().remove();
$('#myModal').modal('hide');
//ajax
$.ajax({
type: "POST",
url: "delete.php",
data: dataString,
cache: false,
success: function(html)
{
//$(".fav-count").html(html);
$("#output").html(html);
}
});
//ajax ends
});
});
This is the trigger element that I'm using
<div data-id="MYID"><a class="btnDelete" href="#">Delete</a></div>
And I'm using the same HTML element dynamically to trigger delete and it doesn't work.
Can someone point me the correct way to do it?
You have to use event delegation
$(document).on("click" , '#btnDelteYes' ,function () {
Pretty much: bind the click higher up to something that exists when the script is run, and when that something is clicked, tell it to pass the click event to the #btnDelteYes element instead
I cant understand what exactly you are doing on your code due to missing information, but the answer is: you should use event delegation on the dynamically inserted content
you can try
$('[data-id=MYID]').on('click','.btnDelteYes',function({
e.preventDefault();
var id = $(this).closest('div').data('id');
$('#myModal').data('id', id).modal('show');
});
here <div data-id="MYID"> should be a hard coded html content and The idea is to delegate the events to that wrapper, instead of binding handlers directly on the dynamic elements.

check all images loaded after .ajax request?

I need to know when all images are loaded from appended html source to perform another function. How can I check that?
$(document).ready(function () {
$('a.load-more').click(function (e) {
e.preventDefault();
$.ajax({
type: "GET",
url: $(this).attr('href'),
data: {
page: last_id
},
dataType: "script",
success: function () {
$('.load-more').show();
}
});
});
});
I'm appending html source like this:
$('.container').append('<%= escape_javascript(render(:partial => #items)) %>');
It gives html source successfully, but I can't find out when all images from that source is loaded
.container updates every time link is clicked Load more
and the .container source looks like this:
<ul class="container">
<%= render #items %>
</ul>
On all browsers supporting event capturing phase, you can capture onload event for all new added images:
document.addEventListener(
'load',
function(event){
var elm = event.target;
if( elm.nodeName.toLowerCase() === 'img' && $(elm).closest('.container').length && !$(elm).hasClass('loaded')){ // or any other filtering condition
console.log('image loaded');
$(elm).addClass('loaded');
if($('.container img.loaded').length === $('.container img').length) {
// do some stuff
console.log("All images loaded!")
}
}
},
true // Capture event
);
To support e.g IE8 which doesn't handle capturing phase, you should set onload event to specific images once they are added in the DOM, setting it in script onload event.

JS script doesn't work when i click back on site

i create a nav on my site. Navigation works good but when i back on the main site all script from js file doesn't work. Script for my nav:
var default_content="";
$(document).ready(function(){
//Ajax navigate
checkURL();
$('footer a').click(function(){
checkURL(this.hash);
});
default_content = $('#login_box').html();
setInterval("checkURL()",250);
.........
Methods:
var lasturl="";
function checkURL(hash)
{
if(!hash) hash=window.location.hash;
if(hash != lasturl)
{
lasturl=hash;
if(hash=="")
$('#login_box').html(default_content);
else
loadPage(hash);
}
}
function loadPage(url)
{
url=url.replace('#page','');
$.ajax({
type: "POST",
url: "include/help4meApi.php",
data: 'page='+url+'&tag=navigation',
dataType: "html",
success: function(msg){
if(parseInt(msg)!=0)
{
$('#login_box').html(msg);
}
}
});
}
Problem is when i back to the main site scripts doesn't work.
the issue comes from here $('#login_box').html(default_content);. When u calle the .html() the event binded within the element will be cleaned.
When you backspace, back to the main page, the event handler is already not there, so you have to register it again
//For show and hide register values
$("#registerInputsBtn").click(function(){
$(".register_information").hide(1000);
$(".register_values").show(1000);
$("#firstname").focus();
});
register again on the piece of code that you assign the content to #login_box. For example the quick fix:
if(hash==""){
$('#login_box').html(default_content);
$("#registerInputsBtn").click(function(){
$(".register_information").hide(1000);
$(".register_values").show(1000);
$("#firstname").focus();
});
}
but here the codes are not DRY :( rearrange the flow on how the page load and the register the event in the correct flow is recommended.

Trying to delete specific id

$('.icn-trash').on('click', function(e) {
e.preventDefault();
deleteBookmark($('.del').data('bookmarkid'));
});
function deleteBookmark(bookmarkID) {
$.ajax({
url: '/BookmarkApi/delete/' + bookmarkID,
type: 'POST',
success: function(response) {
$('.icn-trash').closest('.del').remove();
console.log('removed');
},
error: function(error) {
}
});
}
HTML - it will be generated dynamically for every bookmark folder.
<a href="#" class="del" data-bookmarkid="xxx">
<span class="actions" style="z-index:300">
<i class="icn-trash"></i>
</span>
</a>
Everytime, I try to delete the specific id of the bookmark folder, multiple folders are removed. It should only remove one specific folder when clicking trash icon. When I clicked trash icon on 6th folder, the 1st, 2nd, 3rd, 4th, 5th folders will disappear and i refreshed again only to find 1st folder actually being removed from database.
I want to have 6th folder disappear and removed from database.
Help appreciated.
Try this way
$(document).on('click', '.icn-trash',function(evt) {
evt.preventDefault();
var b_id=$(this).closest('.del').data('bookmarkid')
$.ajax({
url: '/BookmarkApi/delete/' + b_id,
type: 'POST',
success: function(response) {
$(this).closest('.del').remove();
console.log('removed');
},
error: function(error) {
}
});
});
I would bind the event on the a tag:
$('.del').on('click', function(e) {
e.preventDefault();
deleteBookmark(this);
});
function deleteBookmark(ele) {
$.ajax({
url: '/BookmarkApi/delete/' + $(ele).data('bookmarkid'),
type: 'POST',
success: function(response) {
$(ele).remove();
console.log('removed');
},
error: function(error) {
}
});
}
Or if you still want to bind on .icn-trash:
$('.icn-trash').on('click', function(e) {
e.preventDefault();
deleteBookmark($(this).closest('.del'));
});
function deleteBookmark(ele) {
$.ajax({
url: '/BookmarkApi/delete/' + ele.data('bookmarkid'),
type: 'POST',
success: function(response) {
ele.remove();
console.log('removed');
},
error: function(error) {
}
});
}
You are removing content using class attribute that return collection of all the links and remove the links.
$('.icn-trash').on('click', function (e) {
debugger
event.preventDefault()
deleteBookmark(this);
});
function deleteBookmark(obj) {
debugger
bookmarkID = $('.del').data('bookmarkid');
$(obj).remove();
$.ajax({
url: '/BookmarkApi/delete ,
data: { bookmarkID: bookmarkID},
type: 'POST',
success: function (response) {
console.log('removed');
},
error: function (error) {
}
});
}
From the jQuery Documentation of closest API
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
Now what is happening in the code $('.icn-trash').closest('.del').remove();
$(.icn-trash') is returning the set of all element elements having the class icn-trash. And then you are using this set to find the closest element with class .del which will return a set of all anchor tags (in your html example). Finally you are removing the entire set. Hence multiple bookmarks are getting deleted.
I am assuming you need to delete the anchor tag which has the data-bookmarkid as bookmarkID
$('.icn-trash').on('click', function(e) {
e.preventDefault();
deleteBookmark($('.del').data('bookmarkid'));
});
function deleteBookmark(bookmarkID) {
$.ajax({
url: '/BookmarkApi/delete/' + bookmarkID,
type: 'POST',
success: function(response) {
// get the element with data-bookmarkid = bookmarkID and remove it
$("[data-bookmarkid='" + bookmarkID + "']").remove()
console.log('removed');
},
error: function(error) {
}
});
}
EDIT: I didn't read the last part of your question
When I clicked trash icon on 6th folder, the 1st, 2nd, 3rd, 4th, 5th folders will disappear and i refreshed again only to find 1st folder actually being removed from database.
Can you make sure that the bookmarkIds are correct in the data-bookmarkid attribute? Since clicking on 6th trash icon is removing the 1st folder from the database, there seems to be a problem either with your server code or the data-bookmarkid attribute is incorrect in the 6th trash icon
EDIT2: Why you are getting the wrong bookmarkId (Didn't investigat enough thoroughly earlier, got involved in something else)
Again quoting from jQuery documentation for data API
Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute.
So analyzing your code deleteBookMark($('.del').data('bookmarkid')): $('.del') will contain all the elements with class name del (in your example all the anchor tags). Calling .data('bookmarkid') with the entire set will return the bookmark id of the first anchor tag. The best way to do this is to first obtain the element which was clicked. This can be done by modifying your code
Again from jQuery Documentation for Event Listener
When jQuery calls a handler, the this keyword is a reference to the element where the event is being delivered;...
To create a jQuery object from the element so that it can be used with jQuery methods, use $( this ).
This means that
$('.icn-trash').on('click', function(e) {
e.preventDefault();
deleteBookmark($('.del').data('bookmarkid'));
});
In the listener you have attached, this will refer to the .icn-trash element you clicked on. So now you know which element was clicked, find the closest .del element and then get its data-bookmarkid attribute. i.e.
$('.icn-trash').on('click', function(e) {
e.preventDefault();
var curIconTrash = $(this),
closestAnchor = curIconTrash.closest('.del'),
bookMarkId = closestAnchor.data('bookmarkid')
deleteBookmark(bookmarkId);
});
Or taking the advantage of jQuery chaining
$('.icn-trash').on('click', function(e) {
e.preventDefault();
deleteBookmark($(this).closest('.del').data('bookmarkid'));
});
Note: This was also suggested by #sreedhar-r
Hope this helps! Cheers :-)

Categories

Resources