In a Rails project I need to keep a link_to_remote from getting double-clicked. It looks like :before and :after are my only choices - they get prepended/appended to the onclick Ajax call, respectively. But if I try something like:
:before => "self.stopObserving()"
the Ajax is never run. If I try it for :after the Ajax is run but the link never stops observing.
The solutions I've seen rely on creating a variable and blocking the whole form, but there are multiple link_to_remote rows on this page and it is valid to click more than one of them at a time - just not the same one twice. One variable per row declared outside of link_to_remote seems very kludgey...
Instead of using Prototype I originally tried plain Javascript first for this proof of concept - but it fails too:
click
just puts up an alert when clicked - the lambda here does nothing? This next one is more like the desired goal and should only alert the first time. But instead it alerts every time:
click
All ideas appreciated!
I got this to work using jQuery:
link.click(function() {
alert('foo');
$(this).unbind('click');
$(this).click(function() {
return false;
});
return false;
})
So surely you can accomplish the same with plain old javascript.
The benefit of doing this with jQuery is that it would be easy to add a class to all links that you want to behave this way and in your application.js you could assign this behavior to all links of that class.
Of course, you can do all that with plain old JavaScript too... jQuery just makes it easier.
Related
I'm using two simple addEventListener mouseenter and mouseleave functions respectively to play and stop animations (Bodymovin/SVG animations, though I suspect that fact is irrelevant).
So, the following works fine:
document.getElementById('animationDiv').addEventListener('mouseenter', function(){
animation.play();
})
(The HTML couldn't be simpler: The relevant part is just an empty div placeholder filled by script - i.e., <div id="animationDiv"></div>.
I can place that in the same file as the one that operationalizes the animation code, or I can place it in a separate "trigger" file, with both files (and other others necessary to processing) loaded in the site footer.
The problem arises when I need to be able to set triggers for any of multiple similar animations that may or may not appear on a given page.
If only one of two animatable elements are present on a page, then one of two sets of triggers will throw an error. If the first of two such triggers is not present, then the second one will not be processed, meaning that the animation will fail. Or at least that's what it looks like to me is happening.
So, just to be clear, if I add the following two triggers for the same page, and the first of the following two elements is present, then the animation will play on mouseenter. If only the second is present, its animation won't be triggered, apparently because of the error thrown on the first.
document.getElementById('firstAnimationDiv').addEventListener('mouseenter', function(){
firstAnimation.play();
})
document.getElementById('secondAnimationDiv').addEventListener('mouseenter', function(){
secondAnimation.play();
})
At present I can work around the problem by creating multiple trigger files, one for each animation, and setting them to load only when I know that the animatable element will be present, but this approach would get increasingly inefficient when I am using multiple animations per page, on pages whose content may be altered.
I've looked at try/catch approaches and also at event delegation approaches, but so far they seem a bit complicated for handling this simple problem, if appropriate at all.
Is there an efficient and flexible standard method for preventing or properly handling an error for an element not found, in such a way that subsequent functions can still be processed? Or am I missing something else or somehow misreading the error and the function failure I've been encountering?
WHY I PICKED THE ANSWER THAT I DID (PLUS WORKING CODE)
I was easily able to make the simple, directly responsive answer by Baoo work.
I was unable to make the answers below by Patrick Roberts and Crazy Train work, though no doubt my undeveloped js skills are entirely at fault. When I have the time, or when the issue next comes up for me in a more complex implementation (possibly soon!), I'll take another look at their solutions, and see if I can either make them work or if I can formulate a better question with fully fledged coding examples to be worked through.
Finally, just to make things clear for people who might be looking for an answer on Bodymovin animations, and whose js is even weaker than mine, the following is working code, all added to the same single file in which a larger set of Bodymovin animations are constructed, relieving me of any need to create separate trigger files, and preventing TypeErrors and impaired functionality.
//There are three "lets_talk" animations that can play - "home," "snug," and "fixed"
//and three types of buttons needing enter and leave play and stop triggers
let home = document.getElementById('myBtn_bm_home');
if (home) home.addEventListener('mouseenter', function() {
lets_talk_home.play();
});
if (home) home.addEventListener('mouseleave', function() {
lets_talk_home.stop();
});
let snug = document.getElementById('myBtn_bm_snug');
if (snug) snug.addEventListener('mouseenter', function() {
lets_talk_snug.play();
});
if (snug) snug.addEventListener('mouseleave', function() {
lets_talk_snug.stop();
});
let fixed = document.getElementById('myBtn_bm_fixed');
if (fixed) fixed.addEventListener('mouseenter', function() {
lets_talk_fixed.play();
});
if (fixed) fixed.addEventListener('mouseleave', function() {
lets_talk_fixed.stop();
});
At typical piece of underlying HTML (it's generated by a PHP function taking into account other conditions, so not identical for each button), looks like this at the moment - although I'll be paring away the data-attribute and class, since I'm not currently using either. I provide it on the off-chance that someone sees something significant or useful there.
<div id="letsTalk" class="lets-talk">
<a id="myBtn" href="#"><!-- a default-prevented link to a pop-up modal -->
<div class="bm-button" id="myBtn_bm_snug" data-animation="snug"></div><!-- "snug" (vs "fixed" or "home" is in both instances added by PHP -->
</a>
</div>
Obviously, a more parsimonious and flexible answer could be - and probably should be - written. On that note, correctly combining both the play and stop listeners within a single conditional would be an obvious first step, but I'm too much of a js plodder even to get that right on a first or second try. Maybe later/next time!
Thanks again to everyone who provided an answer. I won't ask you to try to squeeze the working solution into your suggested framework - but I won't ask you not to either...
Just write your code so that it won't throw an error if the element isn't present, by simply checking if the element exists.
let first = document.getElementById('firstAnimationDiv');
if (first) first.addEventListener('mouseenter', function() {firstAnimation.play();});
You could approach this slightly differently using delegated event handling. mouseover, unlike mouseenter, bubbles to its ancestor elements, so you could add a single event listener to an ancestor element where every #animationDiv is contained, and switch on event.target.id to call the correct play() method:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
switch (event.target.id) {
case 'firstAnimationDiv':
return firstAnimation.play();
case 'secondAnimationDiv':
return secondAnimation.play();
// and so on
}
});
You could also avoid using id and use a more semantically correct attribute like data-animation as a compromise between this approach and #CrazyTrain's:
document.getElementById('animationDivContainer').addEventListener('mouseover', function (event) {
// assuming <div data-animation="...">
// instead of <div id="...">
switch (event.target.dataset.animation) {
case 'first':
return firstAnimation.play();
case 'second':
return secondAnimation.play();
// and so on
}
});
First, refactor your HTML to add a common class to all of the placeholder divs instead of using unique IDs. Also add a data-animation attribute to reference the desired animation.
<div class="animation" data-animation="first"></div>
<div class="animation" data-animation="second"></div>
The data- attribute should have a value that targets the appropriate animation.
(As #PatrickRobers noted, the DOM selection can be based on the data-animation attribute, so the class isn't really needed.)
Since your animations are held as global variables, you can use the value of data-animation to look up that variable. However, it would be better if they weren't global, but were rather in a common object.
const animations = {
first: null, // your first animation
second: null, // your second animation
};
Then select the placeholder elements by class, and use the data attribute to see if the animation exists, and if so, play it.
const divs = document.querySelectorAll("div.animation");
divs.forEach(div => {
const anim = animations[div.dataset.animation];
if (anim) {
anim.play(); // Found the animation for this div, so play it
}
});
This way you're guaranteed only to work with placeholder divs that exist and animations that exist.
(And as noted above, selection using the data attribute can be done const divs = document.querySelectorAll("div[data-animation]"); so the class becomes unnecessary.)
So, here's a script that I've written to make some inputs dependent on an affirmative answer from another input. In this case, the 'parent' input is a radio button.
You can see that it hides parent divs of inputs when the document is ready, and then waits for the pertinent option to be changed before firing the logic.
If you'll look at the comment near the bottom of the javascript, you'll see what's been stumping me. If I remove the if statement, the change function does not fire. If I set the variable so that there is not an error logged in the console, then the change event does not fire.
If I change the jquery selector to $('select').change... the event fires, but obviously won't work on a radio button. Changing it to $('input').change... also fails.
<script type="text/javascript"><!--
$(function(ready){
$('#input-option247').parent().hide();
$('#input-option248').parent().hide();
$('#input-option249').parent().hide();
$('#input-option250').parent().hide();
$('#input-quantity').attr('type', 'hidden');
$('#input-quantity').parent().hide();
$('input[name="option\\[230\\]"]').change(function() {
if (this.value == '21') { //If yes, display dependent options
$('#input-option247').parent().show().addClass('required');
$('#input-option248').parent().show().addClass('required');
$('#input-option249').parent().show().addClass('required');
$('#input-option250').parent().show().addClass('required');
} else if (this.value == '22') { //If no, hide dependent options
$('#input-option247').parent().hide().removeClass('required');
$('#input-option248').parent().hide().removeClass('required');
$('#input-option249').parent().hide().removeClass('required');
$('#input-option250').parent().hide().removeClass('required');
}
});
//I don't know why this is necessary, but the input.change() event WILL NOT FIRE unless it's present. If I set the variable, then it breaks the change function above. If it's not here, it breaks the change function above. I'm stumped.
if(poop){}
});//--></script>
I'm really hoping that someone will see something rather obvious that my tired brain won't see. This is such a simple script, and I'm pulling my hair out over what seems like a rather annoying bug.
If you selector has special characters you need to use \\ before those characters.
$('input[name="option[230]"]')
should be
$('input[name="option\\[230\\]"]')
See http://api.jquery.com/category/selectors/
This may or may not be a good answer, but I managed to get the problem solved. I have another script on the page that is firing on the change event using this selector: $('select[name^="option"], input[name^="option"]').change(function() {
My best guess is that both functions cannot fire using a single change event from the same element. I moved the functional part of the code above to be within the second script, and it seems to be working as expected. If anyone wishes to contribute an answer that explains this behavior, I will accept it.
I have two instances that use the toggle(). So in the code there is this link:
Completely independent link
Followed by a more complex show hide toggle area:
Show All
And then the reverse of that:
Show Less
The problem I have is when I toggle the "Show all" link - it renders the "Completely Independent link" unusable. Sometimes it even opens shows/hides #div1?!?
Not really sure what is going on but the two instances are definitely getting muddled up.
I think what is happening is your click event is bubbling up the DOM and it hits other elements - but I can't be sure without seeing more of your HTML structure.
That being said - I would suggest putting everything in separate click observers, it removes the javascript from the HTML, and makes it easier to handle.
For example your 3 links
Completely independent link
Show All
Show Less
would change to
Completely independent link
Show All
Show Less
and add this in your head or js file or somewhere on your page
<script>
document.observe('dom:loaded', function()
$('link1').observe('click',function(){
$('div1').toggle();
});
$('showall').observe('click',function(){
$('showLessInfo').toggle();
$('showAllInfo').toggle();
$('showAllInfoLink').toggle();
});
$('showless').observe('click',function(){
$('showLessInfo').toggle();
$('showAllInfo').toggle();
$('showAllInfoLink').toggle();
});
});
</script>
Now for extra bonus, I changed the href="#" to href="javascript:void(0);" to avoid the need to stop the event.
Dude, your selectors are all messed up.
You can't just use $('div1'), because that won't return anything. If it's a class use $('.div1') and if it's an id use use $('#div1').
Also maybe avoid writing your handlers inline, it's much harder to read and notice possible errors. Just put all of that in a seperate javascript file.
So, I use jQuery quite extensively and I am well aware of the "right" way to do the below, but there are times where I want to solve it in a more generic way. I'll explain.
So, I may have a link, like this: <a href='menu' class='popup'>Show menu</a>. Now, I have a jQuery function that fires on click for all a.popup that takes the href-attribute and shows the <div id='menu'></div> item (in this case). It also handles URL's if it can't find a DOM item with that ID.
No problem here. But, there are times when I don't have the same control over the coe where I can create a selectable target that way. Either because the code isn't created by me or because it is created through a chain of function that would all need a huge ovrhaul which I won't do.
So, from time to time, I would like to have this code:
Show menu
This would be in a case where I can only submit the label and the HREF for a link. No class, no nothing.
Problem here is that the function popup() has no idea about what element invoked it, and in most cases that's not a problem for me, since I only need to know where the mouse cursor was upon invokation.
But in some cases, I use someone elses jQuery functions, like qTip or something else. so I still want to fire off qTip(); when clicking a link that runs this JS function, but what do I attach it to to make it show? I can't just runt $().qTip(); because that implies $(this) and "this" is undefined inside the function.
So how do I do it? Any ideas?
Is there anyway you change the javascript method to javascript:popup('menu', this);? I've used this method successfully many times.
Instead of referring to "this" try referring to $('a:focus') to refer to the link that was clicked.
Here's a quick and, as #Crescent Fresh would add, dirty (☺) sample:
<body>
<p>Show popup()</p>
<div id="menu" style="display:none">Today's menu</div>
<script type="text/javascript">
function popup(elm) {
$('#' + elm).show();
alert( $('a:focus').text() )
}
</script>
</body>
I tried just ":focus" but IE7 returned too much content. I tested this in FF 3.6.3, IE7, Chrome 4.1.249.1064 (all on Windows) and it seems OK, but I see now (when I was just about to hit "Post Your Answer") this relies on the browser's native support for querySelectorAll - see this jQuery Forum post ":focus selector filter?" and the jQuery.expr entry in the jQuery Source Viewer (where it appears Paul's idea was not implemented).
How about
Show menu
Once you get the event object you can virtually do anything to it.
i have a link_to_function that shows a hidden div. now i would like to hide this div if the user clicks out of this div(onBlur or onclick). when should i call this function and how? this is my function that shows the hidden div:
<%= link_to_function "ShowHorse", "$('horsePic').show();" :class =>"links_02"%>
shoud it be from inside this function? or should i call an external action with link to remote to look after events on the site? i would be able to use function onblur if it references a form element(text_field or sth). but i dont know how or when to put code for just div element. i was trying sth like:
:onclick=>"if($('loginContainer').onClick) {} else {$('loginContainer').hide}"
i dont know much javascript so i am kind of a lost here. was checking google but wasnt able to find anything useful. any help would be greatly appreciated!
It appears that you're using Prototype, so you can use the built-in JavaScript helpers in Rails to do this for you.
One thing to be careful with in JavaScript versus Ruby is that functions are not called unless the brackets are included. Without the brackets you get a reference to the function instead.
// Check to see if a function is defined
if ($('something').onClick)
true;
// Check to see if a defined function returns a true value by calling it
if ($('something').onClick())
true;
Typically you can just introduce functions in your link_to definition as required.
You should look at defining a click event on the whole body of the page. If a click isn't caught on the div you're watching, close the div. You might need to look at capturing and bubbling javascript events, to get the right click target.
http://www.quirksmode.org/js/events_order.html Very good resource for understanding how a click will move through your page.