React component onClick handler not working - javascript

I have a basic pop-up (an image) that appears when you click on a button. When you click outside of that pop-up, it should close but when you click inside of it, it should stay open and call the test function. Its initial state, isPopupVisible, is set to false. When you click on the button, I set the state to true which renders the pop-up window.
The problem is the test function isn't being called when you click on the image. I think it is because the state is set to false and the image element with the onClick function isn't initially rendered. Does anyone know how to solve this?
(written in ecmascript 6)
getInitialState () {
return {isPopupVisible: false}
},
onClick () {
if(!this.state.isPopupVisible){
this.setState({isPopupVisible: true});
document.body.addEventListener('click', this.onClickBody)
}
},
onClickBody () {
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', this.onClickBody)
},
test () {
console.log('the onClick from the image works!');
},
showPopup () {
if(this.state.isPopupVisible){
return <div>
<img src='img.png' onClick={this.test} />
</div>
}else{
return null;
}
},
render () {
return (
<span>
<button onClick={this.onClick}>
See Popup
</button>
{this.showPopup()}
</span>
);
}

This is a bit of a hack, but...
onClickBody () {
setTimeout(function(){
if (!this.clickedInBounds && this.isMounted()) {
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', this.onClickBody)
}
this.clickedInBounds = false;
}.bind(this), 0);
},
test () {
console.log('the onClick from the image works!');
this.clickedInBounds = true;
},
The setTimeout is just to make it run that code after test has a chance to run. isMounted is just for the small chance that the component was unmounted between the click on body, and the setTimeout coming around.
jsbin

Rather than adding a click listener on body, you can add an underlying transparent div before rendering the popup that'll close the popup when clicked.
For example, this is how this is implemented in react-bootstrap Modal component: https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Modal.jsx#L73
You can check out the demo here: http://react-bootstrap.github.io/components.html#modals

Your onClickBody handler is getting called before test is ever invoked, changing state and removing the image from the virtual DOM before the synthetic event ever fires.
There may be an elegant way around this, but because you are mixing native and React event APIs, my guess is that you'll have to change the signature of onClickBody to have the event as its first parameter and then wrap the body of the method in something like if (e.target.nodeName !== "IMG") {}.
JSFiddle showing this in action here: http://jsfiddle.net/reactjs/69z2wepo/

I know it has been a year for this Post but I know the right answer.
you have a lot of referencing dangers.
onClick () {
var self = this;
if(!this.state.isPopupVisible){
self .setState({isPopupVisible: true});
document.body.addEventListener('click', self .onClickBody)
}
},
onClickBody () {
var self = this;
this.setState({isPopupVisible: false});
document.body.removeEventListener('click', self .onClickBody)
},
test () {
console.log('the onClick from the image works!');
},
showPopup () {
var self = this;
if(this.state.isPopupVisible){
return <div>
<img src='img.png' onClick={self.test} />
</div>
}else{
return null;
}
},
this should for sure solve your issue

Related

Actually do default after preventing default

I want to prevent default behavior of a link (a) then do the default behavior, let's say open open a link in a new window.
Here is some HTML code:
<a href="somewhere" target="_blank" id="mylink">
And the JS code:
document.getElementById('mylink').addEventListener('click', function (e) {
e.preventDefault();
axios.post('options', new FormData(document.querySelector('#myform')))
.then(function(){
// Here I want to do what the link should have done!
});
});
I know I can do something like this:
window.open(e.target.href);
But it's not an option because the browser consider this as a popup. And I don't want to rewrite something in JS, just consider the link as usual: this link has to do its default behavior (which was prevented).
Is there a way to do this?
Here is some idea:
var openingPopup = false;
document.getElementById('mylink').addEventListener('click', function (e) {
if(!openingPopup){
e.preventDefault();
axios.post('options', new FormData(document.querySelector('#myform')))
.then(function(){
// make sure this would not run twice
openingPopup = true;
document.getElementById('mylink').click();
});
}else{
// skipping this only for one time
openingPopup = false;
}
});
This way,
you run the popup opener click handler once,
then prevent the others,
trigger the click event again manually,
this time do nothing, but allow others to run.
As per #Electrox-Qui-Mortem 's suggested link, after you complete your pre-process(es), you can remove the event listener and call the click again.
(relevant MDN link: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener )
const anchor = document.getElementById('anchor');
anchor.addEventListener('click', test);
function test(e){
e.preventDefault();
alert('test');
if( true ){
console.log( this );
this.removeEventListener('click', test);
this.click();
}
}
<a id="anchor" href="https://www.google.com">Test</a>
It's got wonky performance inside code testing tools (like JSfiddle and CodePen) -- you would have to test it in your actual application to make sure if it works appropriately for your use case.
I've only had good results with removeEventListener when referencing an outside function as the "listener" func. In this case test()
In a more "native" way you could create a new "click" event which is not cancelable and trigger it against the same element.
var anchor = document.querySelector('#target');
function triggerClick(target) {
var newClick = new MouseEvent('click', {
view: window,
bubbles: true,
cancelable: false
});
target.dispatchEvent(newClick);
}
anchor.addEventListener('click', function(e) {
e.preventDefault();
if(e.defaultPrevented) {
alert('Prevented!');
triggerClick(anchor);
}
else {
alert('Not prevented');
}
});
Click me!
Here the key is the cancelable: false in the new event created in the function triggerClick(target), which bypass the e.preventDefault().
In the embedded example on StackOverflow it doesn't work, but here's a JSFiddle!

Using onclick on an <a> element

I have an a element containing an href attribute. Clicking on it would delete data so I want the user to confirm that action. The href attribute refers to a php file with an id of the data that will be deleted in the GET parameter. I've added an onclick attribute, that should execute the following piece of JS (it shows a Semantic UI modal that asks for confirmation):
confirmmodal = function () {
beforeunload = function () {
$('.ui.basic.modal')
.modal({
closable: false,
onDeny: function () {
return false;
},
onApprove: function () {
return true;
}
})
.modal('show')
;
}
}
But when I run this it still goes to the page that would delete the data (although I haven't built it yet, so nothing is deleted). Would there be an option that gives the onclick attribute priority over the href attribute somehow?
You need to add event.preventDefault() at the end of your code.
Eg:
Delete
function showDialog(e) {
// custom code to show dialog here
e.preventDefault();
}
Okay, I got there with a few tweaks on the script, taking gavgrif's comment into account as well.
I made the <a> element a little different, so it won't contain an href attribute anymore:
<a title="Delete post" onclick="confirmmodal(this)" data-postid="'. $row['postnr'] .'"><i class="large delete middle aligned icon"></i></a>
Now, if the icon is clicked, the postid is available for the JS as well, so we can just refer to that in the GET parameter when the confirm button is clicked:
confirmmodal = function (a) {
$('.ui.basic.modal')
.modal({
closable: false,
onDeny: function () {
return true;
},
onApprove: function () {
window.location.href = "deletepost.php?id=" + a.dataset.postid
return true;
}
})
.modal('show')
;
}
Which is a semi-ugly fix, but it's not that many more lines, and I don't know s*** about JQuery :)
Thanks for all the help, I almost got there with preventDefault() but I couldn't continue if the action was confirmed, so this is an easier solution.

How to close div when div loses focus?

I made a simple plunkr here http://plnkr.co/edit/zNb65ErYH5HXgAQPOSM0?p=preview
I created a little datepicker I would like this to close itself when you focus out of it (focusout of datepicker) if I put blur on input I'm unable to use the datepicker, if I put focusout event on datepicker it doesn't works
I also tried:
angular.element(theCalendar).bind('blur', function () {
$scope.hideCalendar();
});
but it doesn't work.
Any clue?
this is because you are removing the item before you get a chance to do anything, here is a working example:
http://plnkr.co/edit/mDfV9NLAQCP4l7wHdlfi?p=preview
just add a timeout:
thisInput.bind('blur', function () {
$timeout(function(){
$scope.hideCalendar();
}, 200);
});
have you considered using existing datepickers? like angularUI or angular-strap: http://mgcrea.github.io/angular-strap/##datepickers
Update:
Not a complete solution, but should get you quite closer:
angular.element($document[0].body).bind('click', function(e){
console.log(angular.element(e.target), e.target.nodeName)
var classNamed = angular.element(e.target).attr('class');
var inThing = (classNamed.indexOf('datepicker-calendar') > -1);
if (inThing || e.target.nodeName === "INPUT") {
console.log('in');
} else {
console.log('out');
$timeout(function(){
$scope.hideCalendar();
}, 200);
}
});
http://plnkr.co/edit/EbQl5xsCnG837rAEhBZh?p=preview
What you want to do then is to listen for a click on the page, and if the click is outside of the calendar, then close it, otherwise do nothing. The above only takes into account that you are clicking on something that has a class name which includes datepicker-calendar, you will need to adjust it so that clicking within the calendar doesn't close it as well.
How about closing on mouseout?
You need to cancel the close if you move to another div in the calendar though:
//get the calendar as element
theCalendar = element[0].children[1];
// hide the calendar on mouseout
var closeCalendarTimeout = null;
angular.element(theCalendar).bind('mouseout', function () {
if ( closeCalendarTimeout !== null )
$timeout.cancel(closeCalendarTimeout);
closeCalendarTimeout = $timeout(function () {
$scope.hideCalendar();
},250)
});
angular.element(theCalendar).bind('mouseover', function () {
if ( closeCalendarTimeout === null ) return
$timeout.cancel(closeCalendarTimeout);
closeCalendarTimeout = null;
});
EDIT
Adding a tabindex attribute to a div causes it to fire focus and blur events.
, htmlTemplate = '<div class="datepicker-calendar" tabindex="0">' +
angular.element(theCalendar).bind('blur', function () {
$scope.hideCalendar();
});
So, i know it probably is not the best practice or the best way to do this, but at the end i fixed and got what i need using this:
thisInput.bind('focus click', function bindingFunction() {
isMouseOnInput = true;
$scope.showCalendar();
angular.element(theCalendar).triggerHandler('focus');
});
thisInput.bind('blur focusout', function bindingFunction() {
isMouseOnInput = false;
});
angular.element(theCalendar).bind('mouseenter', function () {
isMouseOn = true;
});
angular.element(theCalendar).bind('mouseleave', function () {
isMouseOn = false;
});
angular.element($window).bind('click', function () {
if (!isMouseOn && !isMouseOnInput) {
$scope.hideCalendar();
}
});
I setted up some boolean vars to check where mouse is when you click the page and it works like a charm if you have some better solution that works , please let me know, but this actually fixed all.
I accept this as the answer but i thank all the guys on this page!

Issue with event functions

I have a strange issue with jQuery.
I have a function, which gets executed on an event of a <a> tag.
link.click(someAction);
In the action, I modify another div-element, where I simply set a few CSS parameters and modify the classes.
This works as expected.
Now, I wanted to expand someAction with a bool parameter.
I figured that I could call the method now as followed:
link.click(function () {
someAction(true);
});
Unfortunately, this does not work. I have no idea why.
The method gets called and everything, but the CSS & classes simply do not change.
Then again by calling exactly the same method with link.click(someAction); it works.
Can anyone tell me why?
Here's some code
var openPopover = function( showOverlay ){
if (typeof showOverlay === "undefined" || showOverlay === null) showOverlay = true;
if (showOverlay) {
// Add transparent overlay
overlay.show();
}
// Select popover next to the clicked item
popover = $(this).next("div.popover");
// It positioned underneath the clicked item, with the spacing above
// Display the popover
popover.show();
// Reset classes
popover.removeClass("hide-animation");
popover.addClass("show-animation");
var animationEnd = function() {
$(overlay).off("webkitTransitionEnd");
$(overlay).off("oTransitionEnd");
$(overlay).off("transitionend");
};
// Add animation did end observer
$(overlay).on("webkitTransitionEnd", animationEnd);
$(overlay).on("oTransitionEnd", animationEnd);
$(overlay).on("transitionend", animationEnd);
// Run animations
popover.addClass("shown");
overlay.addClass("shown");
// If the browser doesn't support css3 animations, we call it manually
if (!supportsCSSAnimations) {
setTimeout(animationEnd, animationDuration);
}
};
selectButton.hover(openPopover); // Opens the popover correctly
selectButton.hover(function () {
openPopover(true); // Doesn't work
});
After your changes:
this in the following line, will point to window:
popover = $(this).next("div.popover");
whereas before, it pointed to selectButton. Try:
selectButton.hover(function () {
openPopover.call(this, true);
});
Make sure to preventDefault on the link once it has been clicked:
link.click(function (e) {
e.preventDefault();
someAction(true);
});

JavaScript - Hook in some check on all 'click' events

So I have a regular onclick event attached to a few buttons, each function that handles the onclick event does something different (so I can't reuse the same function for both events).
element1.onclick = function() {
if(this.classList.contains('disabled') {
return false;
}
// For example make an AJAX call
};
element2.onclick = function() {
if(this.classList.contains('disabled') {
return false;
}
// For example hide a div
};
I'm writing duplicate code for this 'disabled' class check, I want to eliminate this by hooking in some common onclick check then fire the regular onclick event if that check passes.
I know the below won't work but I think it will illustrate what I'm trying to do:
document.addEventListener('click', function() {
// 1. Do the disabled check here
// 2. If the check passes delegate the event to the proper element it was invoked on
// 3. Otherwise kill the event here
});
I'm not using any JavaScript library and I don't plan to, in case someone comes up with 'Just use jQuery' type answers.
EDIT: Had to pass boolean third argument to addEventListener as true and everything is fine.
Use event capturing, like so:
document.addEventListener('click', function(event) {
if (/* your disabled check here */) {
// Kill the event
event.preventDefault();
event.stopPropagation();
}
// Doing nothing in this method lets the event proceed as normal
},
true // Enable event capturing!
);
Sounds like you need to set the capture flag to true and then use .stopPropagation() on the event if a certain condition is met at the target, f.ex:
document.addEventListener('click', function(e) {
if ( condition ) {
e.stopPropagation();
// do soemthing else, the default onclick will never happen
}
}, true);​​​​​​​​​​​​​​​​​​​​​​
Here is a demo: http://jsfiddle.net/v9TEj/
You can create a generic function that receives a callback:
//check everything here
function handleOnclick(callback) {
if(this.classList.contains("disabled")) {
return false;
} else {
callback(); //callback here
}
}
//and now on every onclick, just pass the custom behavior
element1.onclick = function() {
handleOnClick(function() {
console.log('element1 onclick fire'); // For example hide a div
});
};
element2.onclick = function() {
handleOnClick(function() {
console.log('element2 onclick fire'); // For example ajax request
});
};
Edit
Based on your latest comment, let me know if this rewrite works for you... only one biding this time.
element1.customFunction = function() {
handleOnClick(function() {
console.log('element1 onclick fire'); // For example hide a div
});
};
element2.customFunction = function() {
handleOnClick(function() {
console.log('element2 onclick fire'); // For example ajax request
});
};
document.addEventListener('click', function() {
//1. grab the element
//2. check if it has the customFunction defined
//3. if it does, call it, the check will be done inside
};

Categories