I am trying to make an MVC for training purposes and I am following a tutorial for that which is rather old. The implementation in the tutorial was made using live() but I decided to use jQuery 2.1.1 and have to implement on() method. I made a small use case for clarification.
I can insert new elements on the page while adding them in the DB
I can delete preloaded elements which existed in the DB at page load both from the DB and DOM
I can not remove elements which are added live neither from the db nor the DOM.
this is my entire code regarding that.
$(function(){
$.get('dashboard/xhrGetListings', function(o){
for (var i = 0; i < o.length; i++ )
{
$('#listInserts').append('<div>' + o[i].text + '<a class="del" rel="' + o[i].id + '" href="#">x</a></div>');
}
$('.del').on("click", function() {
delItem = $(this);
var id = $(this).attr('rel');
$.post('dashboard/xhrDeleteListing', {'id': id}, function(o) {
delItem.parent().remove(); // THIS IS NOT EXECUTED AT ALL
}, 'json');
return false;
});
}, 'json');
//Not necesarly relevant, it just helps for code clarity
$('#randomInsert').on("submit", function() {
var url = $(this).attr('action');
var data = $(this).serialize();
console.log(data);
$.post(url, data, function(o) {
$('#listInserts').append('<div>' + o.text + ' <a class="del" rel="' + o.id + '" href="#">X</a></div>');
}, 'json');
return false;
});
});
Another issue that I'm not focussing on at this point is that if I delete the parent inside the $.post method (as shown in the code above) it's not deleted, only if I move that line outside of the post method. Any clarification on that would be also very appreciated.
Use event delegation and event.preventDefault() it stops the default action
$('#listInserts').on("click", '.del' , function(event) {
event.preventDefault();
// your code come here
});
Related
This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed last year.
I use SignalR to push notifications. To be simple, I have created some elements using the following jQuery code:
<script>
$(document).ready(function(){
var connection = new signalR.HubConnectionBuilder().withUrl("/signalRServer").withAutomaticReconnect().build();
connection.start();
connection.on("ReceiveNotification", function(sender, workCenter, serviceType, notificationId){
event.preventDefault();
if($('#notifi-badge').length){
var currentNotificationCount = parseInt($('#notifi-badge').text());
$('#notifi-badge').text(currentNotificationCount + 1);
$('.notification-items').prepend('<li><a class="dropdown-item notification-item" style="font-family: vazir !important" Id="'+ notificationId +'" href="#">New '+ serviceType +' for '+ workCenter +' sent.</a></li>');
}else{
$('#notifi-badge').text('1');
}
});
});
</script>
and I use the following code to detect those created items:
<script>
$(document).ready(function(){
$('.notification-item').on("click", function(){
var selectedId = $(this).attr('Id');
var notificationBadge = parseInt($('#notifi-badge').text());
var formData = new FormData();
alert(selectedId);
.
.
.
</script>
SignalR works fine and I can push notifications. When I click those created elements, I expect to alert but it does not.
Listen to the click event bubbling up from .notification-item to the nearest parent that is present at runtime: see event delegation using .on(). Your issue is due to .notification-item being dynamically added and is not available at runtime, so no click event handler is bound to it.
Without looking at your markup, is it not clear which is the nearest parent that is present at runtime: in this case I can suggest listening to the click event at the level of the document instead:
$(document).on('click', '.notification-item', function(){ ... })
If the element .notification-items (the parent that you're appending elements to) is indeed present at runtime, then you can do this:
$('.notification-items').on('click', '.notification-item', function(){ ... })
The click event is not binded to new attached items(.notification-item).
To do so, you have to rebind events when each new items added:
call the event binding function after new element prepended:
<script>
$(document).ready(function(){
var connection = new signalR.HubConnectionBuilder().withUrl("/signalRServer").withAutomaticReconnect().build();
connection.start();
connection.on("ReceiveNotification", function(sender, workCenter, serviceType, notificationId){
event.preventDefault();
if($('#notifi-badge').length){
var currentNotificationCount = parseInt($('#notifi-badge').text());
$('#notifi-badge').text(currentNotificationCount + 1);
$('.notification-items').prepend('<li><a class="dropdown-item notification-item" style="font-family: vazir !important" Id="'+ notificationId +'" href="#">New '+ serviceType +' for '+ workCenter +' sent.</a></li>');
bindEvent();
}else{
$('#notifi-badge').text('1');
}
});
});
</script>
and declare bindEvent function
function bindEvent(){
$('.notification-item').unbind("click").bind("click", function(){
var selectedId = $(this).attr('Id');
var notificationBadge = parseInt($('#notifi-badge').text());
var formData = new FormData();
formData.append("notificationId", selectedId);
$.ajax({
type: "POST",
url: "/Page/DeleteNotification",
contentType: false,
processData: false,
data: formData,
success: function(response){
if(response.success){
$('#'+ selectedId +'').remove();
if(notificationBadge >= 1){
$('#notifi-badge').text(notificationBadge - 1);
}else{
$('.badge-notification').remove();
}
}else{
console.log("Error! Removing process failed.");
}
}
});
});
}
I am getting an id that is not addressable by jquery ("#"+id).something .
At document start I have a :
var g_justClicked = '';
$.ajaxSetup({
beforeSend:function(event){
if(g_justClicked) {
console.log('g_justClicked='+g_justClicked+' tagName='+$('#'+g_justClicked).tagName);
};
var wOffset = $('#'+g_justClicked).offset();
$('#loading').show();
},
complete:function(){
$('#loading').hide();
}
});
At document end I have another script (all elements with class spinner should set the global variable 'g_justClicked'):
$(document).ready(function () {
$('.spinner').click(function() {
g_justClicked = $(this).attr('id');
console.log('.spinner.click: g_justClicked='+g_justClicked);
});
This works fine, the variable is set and displayed correctly in ajaxSetup.
BUT: referencing it in tagName= or in wOffset = with
$('#'+g_justClicked).
results in
"TypeError: wOffset/tagName is undefined"
Note: all ids start with several characters, t.e. "boxshow12345" is a typical id.
What am I doing wrong?
I think was able to reproduce your scenario here: https://jsfiddle.net/mrlew/qvvnjjxn/3/
The undefined in your console.log is because you're accessing an inexistent jQuery property: .tagName. This property is only available to native HTML Element.
To retrieve the tag name from a jQuery Object, you should use: .prop("tagName"), or access the property accessing the native element with $('#'+g_justClicked)[0].tagName
So, if you change
console.log('g_justClicked='+g_justClicked+' tagName='+$('#'+g_justClicked).tagName);
to:
console.log('g_justClicked='+g_justClicked+' tagName='+$('#'+g_justClicked).prop("tagName"));
Will successfully log: g_justClicked=boxshow12345 tagName=BUTTON, as expected.
Note: In order to your logic work, you have to click .spinner first.
Your problem is that your ajax setup runs regardless of whatever you do in the click handler, and it runs before you even setup that handler. The initial value for g_justClicked is empty string, and this is what it tries to access in $('#'+g_justClicked), hence the error.
If you want to click the spinner and then pass the id to the beforeSend, do it like this:
$(document).ready(function() {
$('.spinner').click(function() {
var g_justClicked = this.id; //simplify this a bit
console.log('.spinner.click: g_justClicked=' + g_justClicked);
// call ajax
_setupAjax( g_justClicked );
});
});
function _setupAjax(g_justClicked) {
$.ajaxSetup({
beforeSend: function(event) {
if (g_justClicked) {
console.log('g_justClicked=' + g_justClicked + ' tagName=' + $('#' + g_justClicked).tagName);
};
var wOffset = $('#' + g_justClicked).offset();
$('#loading').show();
},
complete: function() {
$('#loading').hide();
}
});
}
UPDATE
If you don't want a separate function, just move your ajax setup into the click handler:
$(document).ready(function() {
$('.spinner').click(function() {
var g_justClicked = this.id; //simplify this a bit
console.log('.spinner.click: g_justClicked=' + g_justClicked);
// call ajax setup
$.ajaxSetup({
beforeSend: function(event) {
if (g_justClicked) {
console.log('g_justClicked=' + g_justClicked + ' tagName=' + $('#' + g_justClicked).tagName);
};
var wOffset = $('#' + g_justClicked).offset();
$('#loading').show();
},
complete: function() {
$('#loading').hide();
}
});
});
});
OK #mrlew.
Answer: I tried your .prop appoach, but still got "undefined". Now back to the roots:
The goal is to get the id of any element that was clicked to modify the busy indicators position, while ajax is running. Newly I am back to my original approach, without global variable and parameter passing:
$(document).ready(function () {
$('.spinner').click(function() {
_setupAjax();
});
});
which works, and:
function _setupAjax() {
$.ajaxSetup({
beforeSend: function() {
$('#loading').show();
wJustClicked = $(this).attr('id'); /// <- that doesnt work!
console.log("_setupAjax wJustClicked="+wJustClicked);
console.log('_setupAjax tagName=' + $('#' + wJustClicked).prop("tagName"));
....defining css based on id (no problem)..
which yields "undefined" twice. I tried so many ways to get that f.... id.
#mrlew
thanks a lot for your help. Meanwhile I found the solution. All trouble came from a timing problem. Here is what works (for all DIV, SPAN and IMG of class=spinner and having an id:
$(document).ready(function () {
_setupAjax();
$('.spinner').click(function() {
wJustClicked = $(this).attr('id');
if(wJustClicked == null) alert('Id missing on item clicked');
console.log('.spinner.click! id='+wJustClicked);
var wOffset = $('#' + wJustClicked).offset();
var xPos = Math.round(wOffset.left) + 8;
var yPos = Math.round(wOffset.top) + 4;
console.log(wJustClicked+' offset left='+wOffset.left+' top='+wOffset.top+' xPos='+xPos+' yPos='+yPos);
wDiv = 'loading';
$('#'+wDiv).css('left',xPos);
$('#'+wDiv).css('top',yPos);
});
and the js function:
function _setupAjax() {
$.ajaxSetup({
beforeSend: function() {
$('#loading').show();
},
complete: function() {
$('#loading').hide();
}
});
}
A strange thing remained (I have firebug installed), which I have solved with Math.round: the x and y position come overdetailed like 170.5134577 and 434.8768664 ?!?
I can live with that. But where does this pseudo precision come from?
Again thanks a lot to keep my hope upright.
I am adding query string to urls using this jQuery
$( 'a' ).attr( 'href', function(index, value) {
return value + '?appp=3';
});
This works fine, but after the ajax load, the new href's created are not recognized by the above jQuery. Is there a way to reload the code so to affect the new posts URL's which are loaded via ajax. Super thanks.
Perfect, thanks #developer, that worked nicely. Here how I did it as you mentioned:
function apendquery(){
$( 'a' ).attr( 'href', function(index, value) {
return value + '?appp=3';
});
};
$(document).ready(function(){
apendquery();
});
$(document).ajaxComplete(function () {
apendquery();
});
Of course it wont work on new elements. You can create function which you shuold call each time when new 'a' elements appears in DOM. Also you will need to create some kind of flag which helps you identify and update the href only for the new elements.
var updateHref = function(){
$( 'a[data-updated!=1]' ).attr( 'data-updated',1).attr( 'href',
function(index, value) {
return value + '?appp=3';
});
};
...
$.ajax(...)
.done(function() {
...
// add new 'a' tags
....
// then call our function to update href
updateHref();
});
Actually here is my final code which is better version in case the URL already appended a query should anyone might find useful:
function apendquery() {
$('a').attr('href', function(index, value) {
return value + (value.indexOf('?') !== -1 ? "&" : "?") + 'appp=3';
});
};
$(document).ajaxComplete(function() {
apendquery();
});
This question already has answers here:
Events triggered by dynamically generated element are not captured by event handler
(5 answers)
Closed 8 years ago.
I have a simple front-end in jQuery/HTML5 (+ backend-generated code which does not bring the issue, so I will omit it). The currently-in-use jQuery version is 1.8.3 and no version collision exists (i.e. no other jQuery version is loaded - it happened many times in other systems here).
The front-end invokes the following routines:
detailAjaxCall("\/client\/orders\/detailsLoad\/id\/3");
$(".upLink").click(function(){
console.log("subiendo");
var id = $(this).closest("tr").data('detail-id');
var url = "\/client\/orders\/detailMoveUp" + "/id/" + id;
detailAjaxCall(url);
return false;
});
$(".downLink").click(function(){
console.log("bajando");
var id = $(this).closest("tr").data('detail-id');
var url = "\/client\/orders\/detailMoveDown" + "/id/" + id;
detailAjaxCall(url);
return false;
});
$(".delLink").click(function(){
console.log("borrando");
var id = $(this).closest("tr").data('detail-id');
var url = "\/client\/orders\/detailDelete" + "/id/" + id;
detailAjaxCall(url);
return false;
});
Note: the url string are not malformed. they are generated by a json exporter (this chunk of code was extracted from the view source option in Google Chrome browser). Evaluating any of them will return a string with no backslashes.
The detailAjaxCall("/client/orders/detailsLoad/id/<number>") actually works: it returns the expected json code when I hit the url, and renders the appropiate table items:
function detailAjaxCall(url)
{
$.get(
url,
{},
function(data, status, xhr) {
//todo refrescar(data);
var table = $("#detail-list");
table.empty();
if (data.length == 0) {
$("<tr></tr>").addClass("empty").append($("<td></td>").addClass("empty").text("No hay detalles para este pedido")).appendTo(table);
} else {
$.each(data, function(index, element) {
$("<tr></tr>")
.data('detail-id', element['id'])
.append(
$("<td></td>")
.append(
$("<span></span>").addClass("product-name").text(element['producto_nombre'])
)
.append("<br />")
.append(
$("<span></span>").addClass("product-dims").text(
"Ancho: " + element['ancho'] +
", Largo: " + element['largo'] +
", Calibre: " + element['calibre']
)
)
)
.append($("<td></td>").addClass("quantity").text(element['cantidad']))
.append($("<td></td>").addClass("price").text(element['precio']))
.append(
$("<td></td>")
.append(
$("<a></a>").addClass("upLink").text("subir").attr("href", "javascript: void 0")
).append(" ")
.append(
$("<a></a>").addClass("downLink").text("bajar").attr("href", "javascript: void 0")
).append(" ")
.append(
$("<a></a>").addClass("delLink").text("eliminar").attr("href", "javascript: void 0")
).append(" ")
)
.appendTo(table);
});
}
},
'json'
).fail(function(){
$("#ajaxDetailErrorDialog").dialog("open");
});
}
Pay attention to the generation of the "<a></a>" since my problem is with them. They all have classes like delLink, upLink and downLink.
My issue starts here: calling $(".delLink").click(callback), $(".upLink").click(callback), $(".downLink").click(callback) does not seem to bind the events to the newly created items (althought they are created inside the ajax call). Seeing the source code for the click method, passing parameters, is like a call to on.
So: what am I doing wrong to bind the event dynamically, so newly created elements trigger my events as well?
You need to dynamically delegate the click handlers because you assign your click handlers before the new elements are created.
For example, delegate to the document:
$(document).on('click', '.upLink', function(){
console.log("subiendo");
var id = $(this).closest("tr").data('detail-id');
var url = "\/client\/orders\/detailMoveUp" + "/id/" + id;
detailAjaxCall(url);
return false;
});
This works because all clicks on the document will be checked by this handler, to see if they match .upLink. Even if you create new elements after this is assigned, the clicks still pass through this event.
I am using getJSON to access Vimeo's Simple API, and any objects created on the page by the call, do not react to the rest of the javascript that is on the page. It is probably something simple that I am missing. Here is my getJSON code:
$.getJSON("http://vimeo.com/api/v2/album/1822727/videos.json", function(data){
$.each(data, function (index, value) {
var videoID = value.id;
var videoThm = value.thumbnail_large;
$('#galThms').prepend('<li id="thm' + videoID + '" style="background-image:url(' + videoThm + ');"></li>');
console.log(videoThm);
});
});
Here you go: http://jsfiddle.net/8t3Xq/1/
This demonstrates loading your <li> thumbs just as your question does, then I show how to easily change one of them. How to "change" them is endless, this is just a simple example of changing the content and background. So you must not have your selectors right.
This is just a snippet, see fiddle for everything...
$.getJSON("http://vimeo.com/api/v2/album/1822727/videos.json", function(data){
$.each(data, function (index, value) {
var videoID = value.id;
var videoThm = value.thumbnail_large;
$('#galThms').prepend('<li id="thm' + videoID + '" style="background-image:url(' + videoThm + ');"></li>');
console.log(videoThm);
});
});
window.changeIt=function()
{
$('li').first().html("I'm changed!");
$('li').first().css("background-image","");
}
Just make sure the <li>s are present first before your code that changes them is present. Would need to see more of you code to understand when/how that happens.
$.getJSON("http://vimeo.com/api/v2/album/1822727/videos.json", function(data){
$.each(data, function (index, value) {
var videoID = value.id;
var videoThm = value.thumbnail_large;
$('#galThms').append('<li id="thm' + videoID + '" style="background-image:url(' + videoThm + ');"></li>');
console.log(videoThm);
$( "#galThms li" ).click(function() {
$(this).hide();
});
});
});
try this
there is no way that my answer is so far removed from the problem statement. my guess is that either I somehow errantly posted this answer or the problem was edited. apologies
you could also use:
$(document).on('click','li .playVideo',function(){
//do something
});
i would probably change your #playVideo to a class, if you will have multiple li's