Let me explain what I want to achieve: first of all, I'm building a React Application, but I think this is a JavaScript-related "problem".
Basically, what I want is to prevent the user to change page when he has made some changes on a form and he hasn't saved them. For this, let's just pretend we have a variable formIsEdited that is set to true if there are unsaved changes, false otherwise.
I know something similar to this can be achieve with the beforeUnload event, or with The <Prompt> component belonging To React Router, but both don't allow me to have a custom modal: what I want to show it's a div with my own style, my own button, etc, ...
So, I came up with an idea: I put a eventListener on the click event and, if the click is on a link and formIsEdited is true:
I prevent the default behavior;
I save in a variable lastClick the element clicked;
I show my modal;
If the user chooses to procede with the change page, i set formIsEdited to false, I pretend a click on the element saved in lastClick that will lead to the change page.
Then, it may happens that a whole div is inside an anchor element, so I also need to find the closest anchor element of the div on which the click has been made: if the research of the link doesn't return null, it means that the click would lead me to a link. So, I guessed that e.stopPropagation() would achieve what I wanted, but... it doesn't!
If you look at the snippet code.. I thought that, if you click on the blue box, $(e.target).closest('a'); would find the link to stackOverflow and then, since it's not null, would stop the propagation, therefore would avoid the change page.. But it doesn't work.
Any idea why? Or any idea to achieve what I wanted in the first place?
EDIT: I know there are several other question about preventing the changePage, but this question is more about the reason why stopPropagation does not avoid the click on the anchor element.
One more info: if I would take document.anchors and foreach element add a listener in which I do the action I itemize before, I can prevent the change page, with my own modal and my own logic!
But, unfortunately, since I'm using React, some anchor item are not in the DOM when I can call the function that adds the listener just mentioned.
EDIT2: I tried Luca's solution and, as I remembered, it doesn't work.. But the behavior of React is "strange".
Let me explain: if I use the following code:
let anchors = document.getElementsByTagName("a");
for (let i = 0; i < anchors.length; i++) {
anchors[i].addEventListener("click", (e) => {
clickListenerAnchorUnsavedChanges.call(this, e, anchors[i]);
}
}
function clickListenerAnchorUnsavedChanges(this: any, e: any, anchor: any) {
// if Edit has been made
console.log("Inside function1");
e.preventDefault();
}
The things works. Actually, let's pretend I click on the anchor which links to the /Account page: the write inside function1 is printed before the write I've put inside the constructor of the Component Account.
Instead, with the following function:
function test1(this: any) {
window.addEventListener("click", (e) => {
console.log("inside function 2");
// if edits have been made
let closestLink = $(e.target).closest('a');
if (closestLink != null) e.preventDefault();
}
}
It appears that the first thing to be printed is the console.log inside the constructor of Account component, and then inside function 2.
I don't know, it seems that, if I modify directly the behavior of an anchor, I can actually redirect/change the flow of the actions performed by a a click on a link. Instead, by modify the behavior on a click event, I cant.. But it seems that now this fact is more React-related, because in the snippet here the e.preventDefault() works.
window.addEventListener("click", (e) => {
var closestLink = $(e.target).closest('a');
if (closestLink!= null) {
e.preventDefault();
e.stopPropagation();
}
});
#span-1, #span-2 {
display: inline-block;
width: 200px;
height: 100px;
}
#span-1 {
background-color: blue;
}
#span-2 {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<a href="https://stackoverflow.com/">
<span id="span-1" />
</a>
<span id="span-2">
</span>
</div>
I would suggest using preventDefault() instead of stopPropagation(), please read What's the difference between event.stopPropagation and event.preventDefault? for more information on the topic.
window.addEventListener("click", (e) => {
var closestLink = $(e.target).closest('a');
if (closestLink!= null) {
e.preventDefault();
}
});
#span-1, #span-2 {
display: inline-block;
width: 200px;
height: 100px;
}
#span-1 {
background-color: blue;
}
#span-2 {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<a href="https://stackoverflow.com/">
<span id="span-1"></span>
</a>
<span id="span-2">
</span>
</div>
This still only works for links in the DOM, you can't control the user moving away by any other action, e.g. clicking on a link in the bookmarks-bar or an element that changes or replaces the location using a click handler.
Related
I'm making a website which requires pictures to be clickable. If you click on the image it should enlarge and show in the middle of the screen. If you then click again it should go smaller again and back on its place.
$(document).ready(function() {
$("#header").load("header.html .header");
$("#footer").load("footer.html .footer");
$("body").on('click', function(){
if(!$(".img1, .img2").hasClass('enlarged')){
$(".img1, .img2").on('click',function(){
$(this).addClass('enlarged');
});
}else{
$("body").on('click', '.enlarged', function(){
$(this).removeClass('enlarged');
});
}
});
});
.enlarged{
position:absolute;
z-index:2;
width:500px;
height:600px;
top:-10%;
left:300px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container" class="container">
<aside class="aside"><img src="fotos/foto1.JPG" id="img1" class="img1" style="transform:rotate(90deg);"/><img src="fotos/foto2.JPG" class="img2" style="transform:rotate(90deg);"/></aside>
<div class="box"></div>
</div>
My current script works but is very wonky. It only enlarges once and you have to triple-click.
I already made a question about it before, but after I updated nobody answered.
Also I'm not sure how to add images on Stack Overflow, otherwise I would've made a snippet.
Your click handler isn't actually performing the logic you want, it's just assigning other click handlers. Then upon further clicks those are performing the logic you want (sort of), but also further assigning more click handlers. After a couple clicks, this is going to get entirely weird.
You just want one click handler for your target elements:
$("body").on('click', '.img1, .img2', function(){
});
This handler would be invoked for any .img1 or .img2 on the page. Inside this handler, conduct your logic:
if (!$(this).hasClass('enlarged')) {
$(this).addClass('enlarged');
} else {
$(this).removeClass('enlarged');
}
Or, even simpler:
$(this).toggleClass('enlarged');
A lot of things are happening in your code:
You first only add a click handler on the body element. Which means the 1st time you click on the body it enters your conditional. In your first if you add another click handler, but now on .img1, .img2. The opposite (removing the enlarged class) is again on the body element and not on the .img1, .img2 elements.
Some advice:
Use only 1 class to toggle the enlarged state
Personally I'm a big fan of BEM, maybe not relevant for now.
Split up your JS concerns; I would create a plugin for this behaviour
A bit of code to point you in the right direction:
var $elements = $('.enlarge-img');
// in this case not very relevant, but a good habit to have
// a method to instantiate
function initialize() {
addEventListeners();
}
// adding event listeners
function addEventListeners() {
$elements.on('click.namespace', handleClick);
$('body').click('click.namespace', handleClickBody);
}
// You might want to handle clicks on the <body> to remove
// the enlarged state of any image
function handleClickBody(event) {
$elements.removeClass('enlarge-img--enlarged');
}
// If you click an image, you want to toggle the enlarged state class
function handleClick(event) {
event.preventDefault(); // not necessary on a <img/> but needed if you use a link
$(event.target).toggleClass('enlarge-img--enlarged`);
}
initialize();
Hopefully this helps you in the right direction of creating re-usable code. Happy coding!
I read this article saying a popular way to close popups by clicking anywhere outside of the popup is a bad practice
https://css-tricks.com/dangers-stopping-event-propagation/
He gives an alternative solution in the article
$(document).on('click', function(event) {
if (!$(event.target).closest('#menucontainer').length) {
// Hide the menus.
}
});
Explanation of the above code: "The above handler listens for clicks on the document and checks to see if the event target is #menucontainer or has #menucontainer as a parent. If it doesn't, you know the click originated from outside of #menucontainer, and thus you can hide the menus if they're visible."
I don't use jQuery so am trying to implement something similar in vanilla.js.
One thing I tried is to stop pointer events with CSS, but there might be things inside the inner-popup, like a button, that I do want to be clickable
If it confuses matters - My popup is a black overlay with a lowered opacity with then another div inside to be the actual popup.
I tried this but it didn't work - I'm attaching the listener only after showing the popup:
function showPopup(popup) {
popup.style.display = "flex";
popup.addEventListener("click", function(event) {
if (!event.currentTarget.contains(target)) {
closePopup(event.currentTarget);
}
})
EDIT: this works
function clickOutsidePopup(e, popup) {
console.log(e.target)
if (e.target.querySelector(".inner")) {
closePopup(popup);
}
}
Dont stop the pointer events, write a function that works instead, something like this :
document.addEventListener('click', function(e) {
var el = e.target.closest('#menucontainer');
if (el) {
// click inside the popup
} else {
// click outside the popup
}
});
Remember to remove the event handler when the popup is closed, otherwise you'll have multiple event handlers cancelling each other out.
Note that element.closest() need a polyfill to work in IE.
Approach a simpler way:
Create an overlay which covers the whole body (positition absolute; top: 0; left: 0; width 100%; height: 100%; z-index: 2). Attach an onclick event which triggers the modal.close();
Put your modal over that overlay. When the overlay is clicked, just remove it and close the modal. (position: absolute; z-index: 3);
I cannot figure out how to use intro.js on dropdown elements.
I found a similar question with no answer there: IntroJS Bootstrap Menu doesnt work
If you want to reproduce the error, follow these steps:
http://recherche.utilitaire.melard.fr/#/carto
You have to click on "Aide" (The green button on the top right), the problem occurs for the second step. on the change event, I do:
$scope.ChangeEvent = function (e) {
if (e.id === 'step2') {
document.getElementById('step1').click();
}
console.log("Change Event called");
};
When debugging, everything is working like a charm until that function end: _showElement
After that, I get lost in JQuery events, and the dropdown is closed...
If you want to reproduce, just add a breakpoint at the end of the _showElement function and you will understand what I mean...
Here a clearer solution
function startIntro(){
var intro = introJs();
intro.setOptions({
steps: [
{
element: "#mydropdown",
intro: "This is a dropdown"
},
{
element: '#mydropdownoption',
intro: "This is an option within a dropdown.",
position: 'bottom'
},
]
});
intro.onbeforechange(function(element) {
if (this._currentStep === 1) {
setTimeout(function() {
$("#mydropdown").addClass("open");
});
}
});
intro.start();
};
$(document).ready(function() {
setTimeout(startIntro,1500);
});
Note the setTimeout with no second argument (milliseconds) allows you to queue the function on event loop and run it after all events were processed (including the click closing the dropdown), also, it is better to add the class open to the dropdown if you want to set it to open state
In your template i.e. in
/views/nav/body-create.html
You have a code where ng-disabled is based on !currentPath.name. And the value of currentPath.name is null. Try inspecting the value on the following element. You will see that it is null.
<button class="dropdown-toggle btn btn-sm btn-default no-radius" ng-disabled="!currentPath.name">
Niveau{{currentPath.level?": "+currentPath.level:""}} <strong><b class="caret"></b>
</strong>
</button>
Make sure you have proper name or use different condition - I am not sure what you are trying to do with the condition.
Proof: Inspect the above element in chrome debugger at your recherche.utilitaire.melard.fr/#/carto link. Type the following in console and hit enter.
$scope.currentPath.name='Hello You';
And your "Niveau" menu starts working.
I found a workaround, it's quite ugly, but does the job:
$scope.ChangeEvent = function (e) {
if (e.id === 'step2') {
document.getElementById('step1').click();
setTimeout(function () {
document.getElementById('step2').style.display = 'block';
}, 500);
}
console.log("Change Event called");
};
I set the display attribute to block just after the click event I added in the change event
When clicking executing the click on the element 1, I noticed that jquery set the state of the style.display of the element 2 to '', so I wait a bit after clicking in order to set it back to 'block', I know it's ugly, but I didn't find anything better at the time
This happens because of the fixed position..
By using CSS, change the position for the parent item from fixed to absolute and it works perfectly.
For example:
position of .sidebar is fixed, leave it like that.
change position for .sidebar.introjs-fixParent to absolute.
Hope it helps you
I have number of drop down menu's which gets called with a onClick event right below those specific div's,and i also have a window.onclick set inorder to retract those dropdowns(which are active) when a user clicks elsewhere.
Now,when i click on a div(which has a dropDown) the drop down is getting activated(menu gets created) and simultaneously retracting (because of the window.click )
The menu's are getting created and getting destroyed at the same time!,Any work arounds to perform the window.onclick iff the clicked element is not on of the div's with a dropdown??
Silly question,from a beginner..
thanks in advance!!
here's a sample code!!.. the onclick on the div is supposed to bring out the menu -- and the window.onclick is supposed to retract the menu(all he menu's which are expanded)
<html>
<style type="text/css">
.bar{
width: 30px;
height: 30px;
background-color: black;
display: none;
}
</style>
<script type="text/javascript" >
function fire(){
document.getElementById("bar").style.display="block";
}
window.onclick=unfire;
function unfire(){
if(document.getElementById("bar").style.display== "block")
document.getElementById("bar").style.display="none";
}
</script>
<input type="button" value="foo" onclick="fire()">
<div id="bar" class="bar"></div>
</html>
Edit:added the code sample
this will happen because of a mechanism called "Event bubbling", which will likely not just firing your event for the specific DOM element, but will also continue to raise your event from your child DOM element(s), until it reach the very beginning of your page.
using jquery library for example, provides us with a method to prevent this default action from happening (event.stopPropagation() ).
I don't know exactly if native JS code can achieve the same behavior, but let me know if you can use jQuery, and I will provide you with code example.
thanks,
function doSomething() {
if (this.id == "myId") return;
// else do Something
}
You can call e.stopPropagation() (browsers) or e.cancelBubble = true (IE) to prevent the event from propagating up the tree.
You want to trigger onmousedown on the divs instead.
Html
<div class='item_container'>
[...bunch of links and pictures...]
<a class='item_owner'>John Doe</a>
</div>
Javascript
/**
Bind the onclick only if you hover on the item since we got a lot
of items and several events and plugins to setup on them.
*/
$('.item_container').live('mouseenter', function(e){
$this = $(this);
if (!$this.data('isSetup')) {
$this.click(function(e){
// do magic
return false;
});
[... a lot of other stuff]
$this.data({'isSetup': true});
}
});
Of course when I click anywhere in the div, it performs 'do magic'. Thanks to return false, if I click on any link in the div, it still performs 'do magic' and doesn't change the page, which is the expected behavior.
But there is one link that is suppose to actually change the page, the owner link. Trouble is, with my current set up, I prevent it from working.
You need to stop the propagation of the event from the links to the parent .item_container.
Add this block to the code:
$('.item_container a.item_owner').live('click', function(e){
e.stopPropagation();
});