I have a HTML document containing a series of Questions and Answers.
Each Question is contained in a div. Each Answer is contained in a div. Each Answer is given a unique id.
The CSS style for each Answer is set to 'none' in order to initially hide the element.
When a Question is clicked on, the div passes the id of the corresponding Answer to this function:
function toggle(id) {
var state = document.getElementById(id).style.display;
if (state == 'block') {
document.getElementById(id).style.display = 'none';
} else {
document.getElementById(id).style.display = 'block';
}
}
(This toggles the appearance of the Answer div.)
The problem: This mechanism works well in Safari if the scrollbar is at the default (top) position. Otherwise, this will cause the scrollbar to reset to the default position, effectively ruining the usefulness of this mechanism on long pages.
Q. Is there any way to prevent the scrollbar position from being reset?
Thanks.
You've said that you're using
<a href='#'>...</a>
...for the question. That's the problem, that's a link telling the browser to go to the top of the page. You have a couple of options for correcting it:
Hook the click event on the link (or its parent, etc.) and cancel the default action. How you do that depends on how you hook the event. It sounds like you're already hooking the event, so this is probably the simplest solution. If you're using an onclick= style hook, return false; out of your handler function. If you're using addEventListener, use preventDefault on the event passed into it. (You will presumably already be aware that most versions of IE don't support addEventListener, but rather attachEvent; how you prevent the default is also different. This sort of thing is why I use a library like jQuery, Prototype, YUI, Closure, or any of several others to smooth over browser differences.)
Use the javascript: pseudo-protocol instead of #, e.g.:
<a href='javascript:;'>...</a>
The pseudo-protocol tells the browser to run the script code in the href element. In the above, the code is just a ; (statement terminator), which does nothing. You could, of course, have it trigger your toggle function:
<a href='javascript:toggle("foo");'>...</a>
Don't use an a at all. Whether it's appropriate to use one depends on whether it really is conceptually a link, which is totally your call.
Related
Question
I want to trigger a (middle) mouse click on a link, in a way that triggers the native browser behavior for this event.
E.g. in those browsers I work with, middle-click will cause the linked page to open in a new tab in the background.
But if other browsers have a different behavior, that should be triggered instead.
I know there is the element.click(); method, but how do I tell it which mouse button should be clicked?
Background
The background is that I want to make a div behave as much as possible like a regular link. The idea was that I would create a hidden link tag and trigger the native browser behavior.
Requirements for the div-as-a-link:
href can come from a data-href attribute.
Native browser behavior for left-click, middle-click and possibly right-click.
respect of the target attribute, which could come from a data-target attribute.
tabindex
activation with the keyboard?
possibility to select text snippets within the div. This means we cannot just use an overlay.
Why not use a native link tag? Because the div contains inner <a> tags. So making the div box an a-tag would cause nested a-tags which would cause some browsers to restructure the DOM tree.
Obviously I could just set document.location.href on click. But this is only 20% of the browser's native behavior.
I could also try to detect the mouse button, and use js to open the tab in the background, if it was the middle button.
But maybe some browsers have a different behavior for middle-click. I would rather let the browser do its thing, than trying to replicate a specific browser behavior with js.
The idea I had was to create a hidden <a> tag with the same href, and delegate click events from the <div> to this hidden <a> tag. For this, I need to trigger the event with the same mouse button. I am not sure how to do this.
js or jQuery?
jQuery is ok for my personal use case. But to make this useful to a wider audience, maybe also post how to do it without jQuery, if you know.
See also
There are some question which deal with this kind of problem, but each of them looks at a different angle or has different constraints.
This is what I come up with thanks to #JonUleis in the comments and #MikeWillis in https://stackoverflow.com/a/32868971/246724
$('.linkbox', context).once('linkbox').each(function(e){
var $linkbox = $(this);
var href = $linkbox.data('href');
if (!href) {
return;
}
var $hiddenLink = $('<a>').attr('href', href).hide().appendTo('body');
$linkbox.click(function(e){
$hiddenLink[0].dispatchEvent(new MouseEvent('click', {button: e.button, which: e.which}));
e.preventDefault();
});
});
Note that I put the hidden link outside of the clicked div, to prevent recursion.
The right-click menu does not give me "copy link url", but I imagine this would be really hard to replicate.
UPDATE: I found that I don't really need to append the link to anything, and it still works. But I only tested this in Chromium on Linux so far.
This is the code to achieve left click and middle click functionality.
tabindex works with any html element so you can use it not the div
$("#link").on('click', function(e) {
const hasTargetBlank = $(this).data('target') === '_blank';
const href = $(this).data('href');
switch(e.which) {
// left click
case 1: {
if (hasTargetBlank) {
window.open(href);
} else {
window.location = href;
}
break;
}
// middle click
case 2: {
window.open(href);
break;
}
// right click
case 3: {
// do what you want to do
}
}
});
I am trying to write a simple conditional that says if div1 is displayed, then change div2's display to none. However, I want this to be updated live. So anytime div1 display is 'grid', div2 disappears from sight.
<script>
if($('.div1').css("display") == "grid") {
$('div2').css({"display":"none"});
}
</script>
What am I doing wrong here?
A block of javascript code will not just magically run whenever convenient for you, unless you make it so that it is run in such a way. What you have written will just run once, and move on. Javascript will not by itself will look for when things change.
You need to track the change and run your code after that change. If you are writing the javascript for a site, you probably know when these changes occur, so you can execute your code block when they do occur. For example if div1 changes to grid when user clicks a button, then you can bind your function to its click event so handle the situation.
A more advanced method would be to watch for changes on DOM and run a function when they occur. You can do this with MutationObservers. You can do precisely what you want, if div changes to grid, run myFunction() for example.
Another method would be to have a function run on intervals but this is an obsolete technique which is prone to errors and crashes and is by no means recommended to be used in javascript.
The $.watch plugin can do this:
$('.div1').watch('display', function() {
var display = ($(this).css('display') === 'grid' ? 'none' : 'block');
$('.div2').css('display', display);
});
Unlike the setInterval method, here's what the library does (from the github page):
This plugin lets you listen for when a CSS property, or properties, changes on element. It utilizes Mutation Observers to mimic the DOMAttrModified (Mutation Events API) and propertychange (Internet Explorer) events.
Be aware that your original code uses $('div2') instead of $('.div2') and will only match elements that look like this: <div2>foo</div2>. I've changed it to a class in my example.
I have a <div> overlayed onto my page, and that tracks the mouse. However, on occasion the user is able to move the mouse at the appropriate speed to enter the tracking <div>. Also, this <div> will sometimes prevent the user from clicking something else, that is below/behind.
What is the correct way to visually show this <div>, without it 'blocking' or interfering with the underlying DOM, or any of them for that matter? In particular, to stop it interfering with mouse events.
Good answers to this already, one you can consider also is the css:
pointer-events: none;
Which effectively makes events act on the items below the div, not the div itself.
Support for this on IE isn't great, but it's a nice way if you either don't need IE support, or if you have the time to do it properly and include a conditional fallback.
Check out this for support info: http://caniuse.com/pointer-events
you can place above it (with higher z-index) an absolute positioned div element, with an opacity value of 0
OR
you can use jQuery:
$(function(){
$('#divId').click(function(e){
e.preventDefault();
e.stopImmediatePropagation();
e.stopPropagation();
});
});
i personally prefer the opacity approach. but that's me.
hope that helped
Yuo can try to capture the event and prevent its default behavior using
event.preventDefault();
here a pseudo code, it works without jquery
var el = document.getElementById("yourdiv");
el.addEventListener("click", action, false);
function action() {
event.preventDefault();
return false;
}
I wrote an alternative to the jQuery Accordion, as that didn't offer multiple open section support (any idea why they opted to not include support for that? What's the history there?). I did some research on StackOverflow, as well on Google to see what other options others came up. I needed something that could be used on the fly on multiple elements.
After seeing several solutions and experimenting with them, in the end, I wrote my own version (based on Kevin's solution from http://forum.jquery.com/topic/accordion-multiple-sections-open-at-once , but heavily modified).
jsFiddle can be found here: http://jsfiddle.net/3jacu/1/
Inline Code:
$(document).ready(function(){
$.fn.togglepanels = function(){
return this.each(function(){
h4handler = $(this).find("h4");
$(h4handler).prepend('<div class="accordionarrow">▼</div>');
$(h4handler).click(function() {
$(h4handler).toggle(
function() {
barclicked = $(this);
$(barclicked).find(".accordionarrow").html('►');
$(barclicked).next().slideUp('slow');
window.console && console.log('Closed.');
return false;
},
function() {
barclicked = $(this);
$(barclicked).find(".accordionarrow").html('▼');
$(barclicked).next().slideDown('slow');
window.console && console.log('Open.');
return false;
}
);
});
});
};
$("#grouplist").togglepanels(); }
Oddly, the accordion arrow at the right side stopped working once I pasted it in jsFiddle, while it works in my local copy.
In any case, the issue is that toggling isn't working as expected, and when it does, it fires duplicate toggle events which result in it closing, opening, then ultimately closing the section and it won't open from that point on (it toggles open then closes back). That's assuming it works! At first, it won't work as it doesn't respond. I think there's a logic error somewhere I'm missing.
From what I wrote/see in the code, it searches the given handle for the corresponding tag (in this case, h4), pops the handle into a variable. It then adds the arrow to the h4 tag while applying the accordionarrow class (which floats it to the right). It then adds a click event to it, which will toggle (using jQuery's toggle function) between two functions when h4 is clicked.
I suspect the problem here is that I may be mistakenly assuming jQuery's toggle function will work fine for toggling between two functions, that I'll have to implement my own toggle code. Correct me if I'm wrong though!
I'm trying to write the code so it'll be as efficient as possible, so feedback on that also would be appreciated.
Thanks in advance for your time, assistance, and consideration!
You have the toggle binding (which is deprecated by the way) inside of the click binding, so a new event handler is getting attached every time you click the header.
As a random aside you should also fire events within the plugin (where you have the console lines would make sense) so that external code can react to state changes.
I believe your issue is the $(h4handler).click(function() { you have wrapped around the toggle listener. Essentially what this was doing was making so every click of the tab was adding the toggle listener, which was then also firing an event. Removing the click listener will have the behaviour you expect.
You forgot to paste the trailing characters ); to close the function call to jQuery function ready. Fixed: http://jsfiddle.net/LeZuse/3jacu/2/
UPDATE: I've just realised I did not really answer your question.
You are duplicating the .toggle functionality with binding another .click handler.
The doc about .toggle says:
Description: Bind two or more handlers to the matched elements, to be executed on alternate clicks.
Which means the click event is already built in.
NOTE: You should use local variables instead of global, so your plugin won't pollute the window object. Use the var keyword for this:
var h4handler = $(this).find("h4");
On a dynamic page I wish to disable and later re-enable sections. Visually they are grayed out, but need to prevent users from playing with the section until later.
For elements using on-click triggers, would like to:
save the current on-click trigger in an attribute
remove the current on-click trigger
add trigger that no-defaults and no-propagates
to re-enable:
get rid of the no-default trigger
re-apply the trigger previously saved
clear attribute where it was saved
From replies so far:
conjecture: using pure JavaScript html5 level, without delegation or some other external mechanism, it is impossible to extract the on-click trigger from an element.
Solution
was very tempted by delegations - and was defeated by not being able to prevent memory leaks. ended up doing it anyway, with a simple gc to do the job.
extends (add|remove)EventListener with (add|push|pop|remove)PopableEventListener, making code change trivial. allows me to push and pop listeners to any level - wonderful for form context changes other than merely enable/disable.
source: http://code.google.com/p/chess-spider/source/browse/http/scripts/popable.js
doc: http://code.google.com/p/chess-spider/wiki/PopableEventListener?ts=1303738300&updated=PopableEventListener
Editorial
Contrary to most I have seen, the ability to access listeners in the dom would be a significant benefit; besides being able to sanely disable re-enable, the ability to coerce them into a different scope would be incredibly useful.
The best way to handle hundreds of triggers is to use event delegation.
You then examine each click for if it matches your trigger. If it is the child of a disabled section you discard the click.
A jQuery implementation would be something like as follows:
$('.trigger', document.body).live('click', function(e) {
var target = $(e.target);
if (target.parents('.disabled').length > 0) {
// handle click
}
});
Obviously you can use other frameworks or plain JavaScript as suits you best.
I presume you are talking about adding and removing listeners. You can do that a number of ways, the simplest if you only have one listener for an event is to add it as a property of the element. To remove it, just assign null:
function sayHi() {
alert('hi');
}
// Add a listener
var someElement = document.getElementById('someElementID');
someElement.onclick = sayHi;
// Remove it
someElement.onclick = null;
If you need more than one listener for an event, you can use other schemes, such as addEventListener and attachEvent
Of course you can just track the state of the elements (say in a class or object), then the listener can respond based on the state.
What about classes? Something like
function act(e) {
var target = e.currentTarget;
if (target.className === 'active') {
//element active, disable it
target.className = 'disabled';
//other stuff
}
else if (target.className === 'disabled') {
//element disabled, enable it
target.className = 'active';
e.preventDefault();
//other stuff
}
}
elem.onclick = act;
You can also be brave and use data-* attributes (link)
Why not just save each onclick's enabled state into an array, then check that array each time it is called, if the state is false, just return without running anything, this could also help keep track of what to grey out.
Here is my idea: http://jsfiddle.net/mazzzzz/MXEjv/1/
It is a bit messy, but the two top functions are the important ones. The first will toggle (based on class), and the second will say if the element's onclick is enabled (again by class). Just make sure the objects have the same class, and one will effect the other, and vise versa. Alternately, you could just pass in the id, instead of using the class (like I did).
Hope that helps a bit.