So I have a confirm button:
<confirm-button class-name="btn-primary"
text="Save"
body="A customer with the same Last Name is already associated with this Company. Do you want to create this customer anyway?"
action="vm.save(false)"
place="bottom"></confirm-button>
It's a custom directive:
.directive('confirmButton', function () {
return {
restrict: 'E',
replace: true,
scope: {
text: '#',
className: '#',
action: '&'
},
controller: function ($scope, $element, $attrs) {
var content = $attrs.body ? '<p>' + $attrs.body + '</p>' : '';
var place = $attrs.place ? $attrs.place : 'top';
var rndEleId = 'BtnId' + Math.random().toString(36).slice(2);
var popoverOpts = {
title: $attrs.title || 'Are you sure?',
placement: place,
html: true,
content: content +
'<div class="centered">' +
'<button id="yes' + rndEleId + '" name="yes' + rndEleId + '" type="button" class="btn btn-primary btn-small" data-result="yes"><i class="icon-ok"></i> Yes</button>' +
' ' +
'<button id="no' + rndEleId + '" name="no' + rndEleId + '" type="button" class="btn btn-warning btn-small" data-result="no"><i class="icon-ban-circle"></i> No</button>' +
'</div>'
};
$element.popover(popoverOpts)
.parent()
.delegate('button',
'click',
function(e) {
e.preventDefault();
var el = $(e.currentTarget);
var result = el.data('result');
$element.popover('hide');
if (result === 'yes') {
$scope.$apply($scope.action);
}
}
);
},
template: '{{text}}'
};
})
I have this button on the page twice: once at the top of the page, and once at the bottom. Regardless of which one you click, when the Yes or No buttons are clicked, they fire twice. This means the Save method in the button Action property is called twice. If you're updating a record, that's not a horrible thing, but if you're creating a record... It's created twice.
I used Chrome's Developer Tools and placed a breakpoint on the e.preventDefault(); line in the directive, and sure enough, it gets hit twice.
But this ONLY happens if there is more than one of these directives used on the page. If I have just one button (at top or bottom of the page), the e.preventDefault(); breakpoint only gets hit once. Further, it seems like it's multi-threaded. I put a counter variable in at one point, with the old school theory of "set it to 0, then set it to ++, and check to see if it's now 1. If it's greater than 1, don't do the action. If it equals 1, do the action." And the variable... was created new each time, and thus never incremented past 1.
I'm stymied.
You haven't really asked a direct question but it appears that you want to know why this is happening.
It's happening because you are adding a click event listener to the parent of the directive every time the directive loads. So when you had 2 of these directives on the same page, you clicked your button and the event was fired twice because there are 2 event listeners registered. Heres where you are registering your event listener using the delegate method:
$element.popover(popoverOpts)
.parent()
.delegate('button',
'click',
function(e) {
...
First of all you need to make sure that you clean up after yourself and unbind your event listener when your directive get's destroyed (check out life-cycle hooks in the documentation). Here is a quick example of how you can unbind your event listener when your directive get's destroyed:
// ...
// register your popover here
$element.popover(popoverOpts);
// reference your parent
var parent = $element.parent();
// reference your click function
var handleClick = function(e) {
e.preventDefault();
var el = $(e.currentTarget);
var result = el.data('result');
$element.popover('hide');
if (result === 'yes') {
$scope.$apply($scope.action);
}
}
// register your event listener
parent.on('click', handleClick);
// when your directive get's destroyed you need to unbind the `handleClick` function
$scope.$on('$destroy', function(e) {
parent.off('click', handleClick);
});
// ...
To solve your issue you will need to make sure that you are registering your event listeners to suit your needs. You can read more about event listeners here.
Although to be honest, looking at your code, you should give ui-bootstrap a try.
Related
I have a button that when I click it more than once it is adding elements from the previous click. It works fine the first time through. We are using jQuery 1.11.1.
$(function() {
$('a[name="generateReport"]').bind('click', function() {
$(this).attr('href', $(this).attr('href') + '?est=' + est.value + '&prodcd=' + prodcd.value + '&startDate=' + startDate.value + '&endDate=' + endDate.value + '&tracking=' + tracking.value);
})
})
What I am seeing is that the URL past to the server is adding the fields from the prior click. This of course causes issues when it gets to the server. Does this need to be cleared out after each click?
This is the code that calls it from our Grails app(2.4.3)
<g:link class="btn btn-primary" name="generateReport" controller="generateTTLReport" action="renderOutput">
<i class="icon-print icon-white"></i>
Generate Report
</g:link>
Thanks,
Tom
Split the current href at the "?" to remove the query string parameters. Also, let jQuery build your new query string parameter string.
$(function() {
$('a[name="generateReport"]').on('click', function() {
var $this = $(this),
path = $this.attr('href').split('?')[0],
params = {
est: est.value,
prodcd: prodcd.value,
startDate: startDate.value,
endDate: endDate.value,
tracking: tracking.value
},
href = [path, $.param(params)].join('?');
$this.attr('href', href);
});
});
The problem with your code is, every time you click on that button, bind callback is invoked. So the first time you clicked, it got the href attribute added some parameters and replaced it. Again when you clicked on that for the second time, it does the same thing. It gets the href attribute which now contains parameters from previous update and then replace the existing. If you keep the href as it is, and only update the query parameters, you can define that as a global variable and use that in your event handler
You can hard code that link as a variable within in your script like this
$(function() {
// define your variable within document.ready but outside your event handler
var reportURL = '/LSRTIS/generateTTLReport/renderOutput';
$('a[name="generateReport"]').bind('click', function() {
var urlWithParams = reportURL + '?est=' + est.value + '&prodcd=' + prodcd.value + '&startDate=' + startDate.value + '&endDate=' + endDate.value + '&tracking=' + tracking.value;
$(this).attr('href', urlWithParams );
});
});
Hope this helps :)
I have added a function to jQuery prototype as below. What I want to do is when this method is invoked, generate an html form based on the arguments passed to the method and show it in a colorbox.
(function($) {
$.fn.myFunction = function(data){
var form = $('<form name="people"></form>');
var index;
for (index = 0; index < data.length; index++) {
var match = data[index];
$('<input type="radio" name="person">' + match['name'] + ' [' + match['uri'] + ']<br> ')
.attr("value", match['uri'])
.appendTo(form);
}
$('<input type="button" id="a_button" value="Add"/><br>')
.appendTo(form);
var list = $('<div>').append(form).html();
$('#a_button').click(
function(){
console.log('message from event handler');
}
);
$.colorbox({ innerWidth:420, innerHeight:315, html: list });
};
})(jQuery);
As you can see, form has a button called Add using which I hope to make an ajax request. But unfortunately click event handler attached to this button doesn't seem to be invoked.
Does anyone have any idea about what's wrong here? myFunction is actually invoked by a drupal ajax command in case if that's helpful.
You are appending the form to the DOM after attaching the event handler.
$('#a_button') searches the DOM at that specific point in time, but the form is not added to the DOM until after your call to colorbox with list as a parameter.
Try a permanent delegated event handler instead (or simply add the click handler after the colorbox line).
e.g.
$(document).on("click", "#a_button", function(){
console.log('message from event handler');
});
or
$.colorbox({ innerWidth:420, innerHeight:315, html: list });
$('#a_button').click(
function(){
console.log('message from event handler');
}
);
When I add a comment, and hit the click-able text "Edit" the alert box doesn't pop up. First when I add the second comment, I'm able to hit the "edit" on the first one comment, and the alert box pop up.
Why that??
Live Demo
function addComment(name1) {
var container = $('#divComments');
var inputs = container.find('label');
var id = inputs.length + 1;
var div = $('<div />', {
class: 'CommentStyle'
});
$('<label />', {
id: 'comment' + id,
text: name1
}).appendTo(div);
var d = new Date();
var $fulaDate = $('<div class="floatleft">' + d.getFullYear() + "-" + monthNames[d.getMonth()] + "-" + d.getDate() + "T" + d.getHours() + ":" + d.getMinutes() + '</div>').appendTo(div);
var $edit = $('<p />', { class: 'edit', text: 'Edit' }).addClass('edit').appendTo(div);
$('.edit').click(function () {
alert('Hallo');
});
div.appendTo(container);
}
You need to use event delegation for dynamically created elements:
$('#divComments').on('click','.edit',function () {
alert('Hallo');
});
Also, as suggested by #Archer, you need to move the click handler outside of your function to avoid nested click events from firing multiple times.
Updated Fiddle
Problem with your implementation is that when you are attaching event like
var $edit = $('<p />', { class: 'edit', text: 'Edit' }).addClass('edit').appendTo(div);
$('.edit').click(function () {
alert('Hallo');
});
Edit element which you created just now is not added to DOM it is appeded to div only, which is not added to DOM. thus in short it doesn't exists in DOM, thus event is not binded with the button.
So to fix the issue instead of binding event to $('.edit') you need to bind event with $edit.
var $edit = $('<p />', { class: 'edit', text: 'Edit' }).appendTo(div);
$edit.click(function () {
alert('Hallo');
});
DEMO
However I would recommend you to use Event Delegation as
Event delegation allows us to attach a single event listener, to a parent element, that will fire for all descendants matching a selector, whether those descendants exist now or are added in the future.
Code
function addComment(name1) {
}
$('#divComments').on('click', '.edit', function () {
alert('Hallo');
});
DEMO with event delegation
I'm trying to log the click on an anchor that's being generated asynchronously.
The asynchronous call - which works perfectly fine - looks like this:
$("#txt_search").keyup(function() {
var search = $("#txt_search").val();
if (search.length > 0)
{
$.ajax({
type: "post",
url: "<?php echo site_url ('members/searchmember') ;?>",
data:'search=' + search,
success: function(msg){
$('#search_results').html("");
var obj = JSON.parse(msg);
if (obj.length > 0)
{
try
{
var items=[];
$.each(obj, function(i,val){
items.push($('<li class="search_result" />').html(
'<img src="<?php echo base_url(); ?>' + val.userImage + ' " /><a class="user_name" href="" rel="' + val.userId + '">'
+ val.userFirstName + ' ' + val.userLastName
+ ' (' + val.userEmail + ')</a>'
)
);
});
$('#search_results').append.apply($('#search_results'), items);
}
catch(e)
{
alert(e);
}
}
else
{
$('#search_results').html($('<li/>').text('This user does not have an account yet'));
}
},
error: function(){
alert('The connection is lost');
}
});
}
});
The anchor I want to get to is <a class="user_name" href="" rel="' + val.userId + '">' + val.userFirstName + ' ' + val.userLastName + ' (' + val.userEmail + ')</a>'
I detect the click on these anchors with this function:
// click op search results
$("a.user_name").on('click', function(e) {
e.preventDefault();
});
The problem seems to be that the preventDefault is not doing anything... I've looked at most of the questions involving this problem on Stackoverflow and checked jQuery's own documentation on the topic, but I can't seem to find what's wrong. I've tried adding a async: false statement to the AJAX-call, because perhaps the asynchronous call might be the problem, but that didn't fix it.
Event does not bind with dynamically added element unless you delegate it to parent element or document using on(). You have to use different form of on for event delegation.
$(document).on('click', 'a.user_name', function(e) {
e.preventDefault();
});
delegated events
Event handlers are bound only to the currently selected elements; they
must exist on the page at the time your code makes the call to .on().
To ensure the elements are present and can be selected, perform event
binding inside a document ready handler for elements that are in the
HTML markup on the page. If new HTML is being injected into the page,
select the elements and attach event handlers after the new HTML is
placed into the page. Or, use delegated events to attach an event
handler, as described next.
Delegated events have the advantage that they can process events from
descendant elements that are added to the document at a later time. By
picking an element that is guaranteed to be present at the time the
delegated event handler is attached, you can use delegated events to avoid the need to
frequently attach and remove event handlers, Reference
The .on() syntax you showed will only bind handlers to elements that match the selector at that moment - not to elements added in the future. Try this instead:
$("#search_results").on("click", "a.user_name", function(e) {
e.preventDefault();
});
This binds a handler to the parent element, and then when a click occurs jQuery only calls your callback function if the actual target element matches the selector in .on()'s second parameter at the time of the click. So it works for dynamically added elements (as long as the parent exists at the time the above runs).
This should work for you -
$('.search_result').on('click', 'a.user_name', function(){
// your code here
// code
return false;
});
try this
$("a.user_name").on('click', function(e) {
return false;
});
or
$(document).on('click', 'a.user_name', function(e) {
e.preventDefault();
});
Difference between .on() functions calls
May be your a href linked with other listeners too. Check with event.preventDefault
event.stopImmediatePropagation();
together.
You can check this site for more info
https://codeplanet.io/preventdefault-vs-stoppropagation-vs-stopimmediatepropagation/
I had this problem too, and it turned out my selector was wrong.
I am trying to assign a series of objects stored in an array to jquery click event handlers.
The problem is , when the event fires, I only ever references the last object in the array.
I have put together a simple example to show the problem:
function dothis() {
this.btns = new Array('#button1', '#button2');
}
// Add click handler to each button in array:
dothis.prototype.ClickEvents = function () {
//get each item in array:
for (var i in this.btns) {
var btn = this.btns[i];
console.debug('Adding click handler to button: ' + btn);
$(btn).click(function () {
alert('You clicked : ' + btn);
return false;
});
}
}
var doit = new dothis();
doit.ClickEvents();
The HTML form contains a couple of buttons:
<input type="submit" name="button1" value="Button1" id="button1" />
<input type="submit" name="button2" value="Button2" id="button2" />
When button1 is clicked, it says "You clicked #Button2"
It seems that both button click handlers are pointing to the same object inside var btn.
Considering the variable is inside the for loop, I cannot understand why.
Any ideas?
You need a function factory to close the loop variable, such as this:
//get each item in array:
for (var i=0; i<this.btns.length; i++) {
$(this.btns[i]).click(function(item) {
return function () {
alert('You clicked : ' + item);
return false;
}
}(this.btns[i]));
}
Another good option is to let jquery help you. Use jQuery.each(). The variable btn here is local to the handler function, and so isn't reused between iterations. This allows you to close it and have it keep its value.
$.each(this.btns, function() {
var btn = this;
$(this).click(function () {
alert('You clicked : ' + btn);
return false;
}
});
within an event handler, 'this' usually refers to the element firing the event, in this case, it would be your button
so the solution to your problem is fairly easy, instead of referencing the btn variable, which lives in a higher scope and gets mutated long before the event handler fires, we simply reference the element that fired the event and grab its ID
$(btn).click(function () {
alert('You clicked : #' + this.id);
return false;
});
Note: if your array contains other selectors that just the ID, this will obviously not reflect that and simply continue to show the ID
Lucky, the click handler (and all other event handlers afaik) take an extra parameter for eventData, useful like so:
$(btn).click(btn, function (event) {
alert('You clicked : #' + event.data);
return false;
});
User an array if you're passing multiple things:
$(btn).click(['foo', 'bar'], function (event) {
alert('this should be "foo": ' + event.data[0]);
alert('this should be "bar": ' + event.data[1]);
return false;
});
you have to use closures for this.
i'm not sure if i remember the correct syntax but you could try this:
$(btn).click(function () {
return function() {
alert('You clicked : ' + btn);
return false;
}
});
maybe you need to change just the click binding:
$(btn).click(function () {
alert('You clicked : ' + $(this).attr('id'));
return false;
});
Your problem is here:
alert('You clicked : ' + btn);
btn retains the value from the last time it was called in the loop. Read the value from the button in the event.
$(btn).data('selector', btn).click(function () {
alert('You clicked : ' + $(this).data('selector'));
return false;
});
http://jsfiddle.net/Mc9Jr/1/