I'm puzzled; I have a window.onscroll = function() { .. } which works fine, except for some cases. In these cases, the <html> element has one of two specific classes. So that seems easy enough; don't run the onscroll if either of those classes exist. But, how?
Roughly, I have this set up;
if (!html.classList.contains('firstClass') && !html.classList.contains('secondClass')) {
window.onscroll = function() { ... }
}
But the onscroll function runs every time. I've also tried to put the if statement inside the onscroll, but no luck.
Now I'm unsure if the markup of the if statement is correct. So I've also tried in in a simpler fashion, where the statement is always true or false;
if (html.classList.contains('thirdclass') { }
In this example, the code either runs or it doesn't, no matter if the class gets added or removed. It might have to do with how the DOM works, and the html element maybe not getting picked up?
I'm unsure if the markup in the if statement is even correct; all sources I find on this use jQuery, and none of them clearly state how to run if (element has one of either two class classes) {}.
Who can help me get on the right track?
edit:
Well slap me silly. I set up a jsfiddle to demonstrate, but in there it does work as expected(!). This leaves me to think that it has to do with the ecosystem this code is running in (Magento 2 frontend). Maybe how the js is initialized? (Yes, I do need to call jquery sadly);
define([
"jquery"
],
function($) {
"use strict";
// code here
});
Could that be the case?
Based on your description, I guess your html tag initially dont have the classes firstClass and secondClass.
So, here the problem is that you are registering an event listener to onscroll event only if the html element don't have the classes firstClass and secondClass. As initially it is true (because initially the html element doesn't have the class required classes), it will register the event listener and it will always fire whenever you scroll.
Also your code will never fire the event listener if initially the classes are firstClass and secondClass.
Now, what you need to change to achieve your goal is to add the if statement inside the onclick listener instead of outside.
Here's the code:
window.onscroll = function () {
if (!html.classList.contains('firstClass') && !html.classList.contains('secondClass')) {
// Do stuff
}
};
What the above code does is that it first registers the event listener no matter what the class of the html element. But it will execute the code inside the if block only if the html element don't have both the classes. So, your goal is achieved here.
Hope this helps :)
Related
I've the following html structure
<body data-page="first">
<div class="start">Test</div>
</body>
and the following js
$('body[data-page="first"] .start').on('click',function (){
body.attr('data-page','second');
});
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
I would expect, that after the second click on .start, the console would show "Test", but it doesn't...
Can you tell me what I'm doing wrong?
Thanks in advance!
While you have your answer, I don't think the essential point has been made in any of the answers so far, and that is that the binding of an event handler must happen after the target element exists.
When you try to bind an event handler to a particular element in the DOM, the element must exist at the time. If it does not exist, the handler has nothing to bind to, and so the binding fails. If you later create the element, it's too late, unless you re-run the binding statement.
It will soon become second nature to call appropriate event handler binding statements after you create a new element (by modifying the HTML using javascript) that needs a handler.
For instance, in my current project I regularly make AJAX calls to a server to replace blocks of HTML as things happen on the page. Even if some of the new elements are exactly the same as the ones being replaced, they will not inherit any bindings from the replaced elements. Whenever I update the HTML I call a function that contains necessary statements to bind my event handlers to the new copy of the active elements.
Your code would work if you made the following change:
$('body[data-page="first"] .start').on('click',function ()
{
body.attr('data-page','second');
$('body[data-page="second"] .start').on('click',function (){
console.log('Test');
});
})
A couple of other (off-topic, but related) points:
It's possible to bind a handler to an element multiple times. The trick to avoiding this is to include the .off() method in the chain before binding (noting though that .off("click") will unbind all click handlers bound to that element, not just yours) e.g.
$("#mybutton").off("click").click(function(){myHandler()});
"the arrow function doesn’t have its own 'this' value" () so don't use arrow functions in event handlers if you plan to reference any of the element's properties via 'this'. e.g.
$("#mybutton").off("click").click(() => {console.log(${this.id})}); // >> "undefined"
The issue is that the page is rendered with the data-page set to first, and when you click again on it, that part of javascript still see "first", since is not rerendered, so you need a dynamic function, the read all the intereaction with that button, and than check wich value that attribute has. Like this you can make infinite cases, and still go on.
$('body .start').on('click',function (){
const attr = $('body').attr('data-page');
if(attr === 'first') {
$('body').attr('data-page','second');
} else {
console.log('second');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body data-page="first">
<div class="start">Test</div>
</body>
And if you don't like the fact that is targetting all the "body" wich is weird, becouse you should have only 1 body, you can use an ID to target the right one
PS: is never a good idea to duplicate your function, if you can set everything in a dynamic function, that reads everything, is easier to debug in the feature, and is lighter and more clean to work on
$('body[data-page="first"] .start').click(function (){
var body = $('body[data-page="first"] .start');
body.attr('data-page','second');
});
This method can help :
var timesClicked = 0;
$('.start').on('click',function (){
timesClicked++;
if (timesClicked>1) {
console.log('Test');
}
});
I have a series of spans (togglers) and a series of divs (toggled). I created a make_toggle function that receives the toggler and its corresponding toggled as arguments.
Everything seems to work kind of ok up to the point where I try to implement a "toggle on click out". What I've tried is to attach to the html click event a function that checks whether the target of the click is contained within the toggled element. On toggle "back", I would then detach the handler so I am only checking when I need.
var check_if_clickingout = function(e) {
if (!toggled[0].contains(e.target)) {
toggle();
}
};
See fiddle: https://jsfiddle.net/andinse/65o211nc/11/
It doesn't even seem to work anymore but when it used to, it was triggering many more times than necessary (which was the reason for me to come here ask for help).
What am I doing wrong? What is the most effective way to go about this kind of situation where I am giving functionality to a series of independent DOM elements?
Just putting this out here that this seems to do the same thing.
$("span").click(function() {
$(this).siblings("div").toggleClass("blue");
});
Maybe I am missing something more that I am not seeing in your example.
See more: http://api.jquery.com/toggleclass/
I have a fiddle which creates a viewer for a set of data. If you are looking at the javascript, it will look at 3 lines, if you search for SEARCH_HERE
$("body").append("TEMPLATE<hr />Maintaining Object").append($maintence);
//$("body").html($maintence);
//$("body").html($_table);
The fiddle is located at: http://jsfiddle.net/fallenreaper/wFGW6/1/
The first one will show the TEMPLATE on the page and then adding new ITEMS will all have working events when doing
var $data = $_table.clone(true,true);
in the addBlock() function.
If you only uncomment the second line, it will JUST show the maintainer item.
When you add items [+], you will show the form, but the events would not be there.
I was thinking that since $_table is removed from the page, the events are not there any longer. The 3rd line, pretty much reappends $_table to the document, and the events are not there.
IS this suppose to be like this? Should i instead just create a wrapper function which is executed inside of addBlock() to attach all the handlers accordingly?
This is rather odd.
EDIT:
One answer, pointed to delegated events, which seems like it could work. There is an issue though that seems to set $(this) to a new object, the body tag, instead of the selected element.
inside of a click event would be redefined as:
$("body").on("click", $expander, function(){...});
//instead of:
//$expander.click(function(){...});
I was thinking to just do something like left-hand assignment, something like:
$(this) = $expander;
but according to a website, left-hand assignment doesnt work. (http://hungred.com/how-to/tutorial-override-this-object-javascript/). They did point me in a direction which would be VERY useful.
function example(eventHandler){
ALL MY CODE.
}
$("body").on("click", $expander, function(event){
example.call($expander, event);
});
Does this look feasible, or should i be planning another route?
You can use jQuery's .on() to do some event delegation. Your code is too long for me to read and edit, but in a nutshell rather than having $add.click(function() { ... }) you bind the event listener to the parent or body $("body").on("click", ".addNew", function() { ... })
I have a sample code:
<input width="50" type="text" value="" name="application_id" id="text_input">
Test Name
And javascript
function addSelect(id, title) {
document.getElementById('text_input').value = title;
}
When I run code, result error is addSelect is not defined ? demo here , how to fit it ?
Your script has been defined to run onLoad, which means your function is not available in the global scope like you expect. It will be defined in a local scope of some onLoad method (whichever jsFiddle uses). With this setting, I think jsFiddle puts your code into this or something similar to:
window.onload = function () {
// Your code
};
(which is similar to onDomReady option)
This is so you don't have to worry about binding the right event and you can just test your script (making sure the page has loaded).
When you try to call the function, which you expect to be in the global scope, it won't work. Just change the setting on the left to no wrap (head) (or no wrap (body))
http://jsfiddle.net/TmLut/3/
And as mplungjan has pointed out, and I somehow didn't realize at all, when using the onclick of the anchor element, you'd probably want to prevent default behavior of the link (even if it's just to go to "#"), and can be achieved in several ways, but one is:
Text
Although at the same time, one might argue you shouldn't have inline handlers at all, and would want to be binding the event with Javascript completely. Depending on that case, you have options to prevent the default behavior still. In any case, you can still grab ahold of the event object (normalized per browsers...which jQuery does, by the way) and call event.preventDefault(); in the method.
Here its http://jsfiddle.net/TmLut/4/
I changed onload to head on the left side select box
I'm having trouble with some jquery code.
in my HTML page I use ajax to get some info, and then I'm changing an HTML element
with $("#id").html(...).
The problem is, I also have a $(document).ready code which I wanna call only once
when the page is done loading, but after each change in the html with the $("#id").html(...)
the code is called once again.
How can I run the $(document).ready code only once?
Here is an example:
$(document).ready(function(){
// this code will run not only once...
}
function f(){
$("#id").html(...);
}
Try:
var run = false;
$(document).ready(function() {
if(!run) {
...
run = true;
}
});
...or...
$(window).load(function() {
...
});
The first one will make sure it is only run once; the 2nd one is run when the entire page is finished loading (useful if you need to resize things once images have finished loading).
From the comments on the .ready documentation:
Looks like .ready() fires not only when page initially has settled the
DOM, but apparently also after changes to the DOM. This is an issue if
your ready handler changes the DOM. That will result in ready() firing
more than once. It may result in an endless loop if each invocation
adds yet more to the DOM. Firefox and IE behave differently to this,
including different error messages, and leaving the page display in
different states. So, if ready() modifies the DOM, then it would be
wise to have a way to check whether ready has already been fired.
Replying to self: Well it appears that part of the problem is not that
the ready function fires again (though that is possible aparently),
but that changing the DOM causes the script that creates the ready
function to fire again, adding an additional ready function, etc etc.
This seems to happen if the javascript is embedded in the html at a
point beyond (or contained in) the part of the DOM that the ready
handler modifies. (Obviously would be better to put script that
creates a ready function in the document head, but in this case that's
not an option.) Problem fixed by checking a global flag variable to be
undefined before executing jQuery(document).ready(...).
If this might be your problem, you can adopt the same solution:
var onLoadFired = false;
$(document).ready(function() {
/* Ensure this function only runs once */
if (onLoadFired) {
return;
}
else {
onLoadFired = true;
}
/* Business logic */
// .. your code here ..
});
Or, better, move your handler into a separate script file that's included by a script tag in your page's head element.
Try this:
$(window).bind("load", function() {
...
});