I'll be short with words, here's the situation:
for (var _im = 0; _im < slideshow._preloadbulks[slideshow._preloadCurrentbulk].length; _im++) {
var tmpSlideIndex = (slideshow._preloadCurrentbulk*slideshow._preloadMaxbulkSize)+_im;
slideshow._preloadSlides[tmpSlideIndex] = document.createElement('video');
slideshow._preloadSlides[tmpSlideIndex].autoplay = false;
slideshow._preloadSlides[tmpSlideIndex].loop = false;
slideshow._preloadSlides[tmpSlideIndex].addEventListener('canplaythrough', slideshow.slideLoaded, false);
slideshow._preloadSlides[tmpSlideIndex].src = slideshow._slides[tmpSlideIndex][slideshow.image_size+"_video_url"];
slideshow._preloadSlides[tmpSlideIndex].addEventListener('error', function(){
console.log(tmpSlideIndex);
slideshow._preloadSlides.splice(tmpSlideIndex,1);
slideshow._slides.splice(tmpSlideIndex,1);
slideshow.slideLoaded();
}, true);
}
As you can see, I have a video array and I'm loading each element src to the DOM to pre-load it.
It works just fine, but I have to deal with a situation when one resource is n/a, then I need to remove it from the existing arrays.
The addEventListener('error', works just fine, it detects the unavailable resource but when I'm logging tmpSlideIndex into the console I get a different value rather than the original slide index (because the loop continues).
I've tried setting the useCapture flag as you can see to the error handler, thinking that will do the trick but it won't.
Any tricks?
Thanks!
The issue is that when you are creating a closure over the tmpSlideIndex variable, it allows you to reference that variable inside the children function, but it's not creating a brand new variable, and since the loop continues and your error handler function executes asynchronously, the value of tmpSlideIndex will always be the last index of the loop. To keep the original value, we can create a self-executing function to wich we will pass the value of tmpSlideIndex. That self-executing function will effectively create a new scope and we will finally return a function that will create a closure over the slideIndex variable that lives in it's parent function scope.
slideshow._preloadSlides[tmpSlideIndex].addEventListener('error', (function(slideIndex) {
return function () {
console.log(slideIndex);
slideshow._preloadSlides.splice(slideIndex,1);
slideshow._slides.splice(slideIndex,1);
slideshow.slideLoaded();
};
})(slideIndex), true);
Related
I have the following javascript function:
function actionFunction() {
let lookup_table = {
'imageurl1': "imageurl2",
};
for (let image of document.getElementsByTagName("img")) {
for (let query in lookup_table) {
if (image.src == query) {
image.src = lookup_table[query];
}
}
}
}
I want it to work even after a page is fully loaded (in other words, work with dynamically generated html elements that appeared post-load by the page's js).
It could either be by running the function every x seconds or when a certain element xpath is detected within the page, or every time a certain image url is loaded within the browser (which is my main goal here).
What can I do to achieve this using javascript + greasemonkey?
Thank you.
Have you tried running your code in the browser's terminal to see if it works without greasemonkey involved?
As to your question - you could either use setInterval to run given code every x amount of time or you could use the MutationObserver to monitor changes to the webpage's dom. In my opinion setInterval is good enough for the job, you can try learning how the MutationObserver works in the future.
So rewriting your code:
// arrow function - doesn't lose this value and execution context
// setInterval executes in a different context than the enclosing scope which makes functions lose this reference
// which results in being unable to access the document object
// and also window as they all descend from global scope which is lost
// you also wouldn't be able to use console object
// fortunately we have arrow functions
// because arrow functions establish this based on the scope the arrow function is defined within
// which in most cases is global scope
// so we have access to all objects and their methods
const doImageLookup = () => {
const lookup_table = {
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png': 'https://www.google.com/logos/doodles/2022/gama-pehlwans-144th-birthday-6753651837109412-2x.png',
};
const imgElementsCollection = document.getElementsByTagName('img');
[...imgElementsCollection].forEach((imgElement) => {
Object.entries(lookup_table).forEach(([key, value]) => {
const query = key; // key and value describe object's properties
const replacement = value; // here object is used in an unusual way, i would advise to use array of {query, replacement} objects instead
if (imgElement.src === query) {
imgElement.src = replacement;
}
});
});
};
const FIVE_MINUTES = 300000; // ms
setInterval(doImageLookup, FIVE_MINUTES);
You could make more complex version by tracking the img count and only doing the imageLookop if their number increases. This would be a big optimization and would allow you to run the query more frequently (though 5 minutes is pretty long interval, adjust as required).
I am setting an interval in the global scope so it can be cleared and restarted within functions. However, when I call it from within a function, it seems to be undefined. My understanding (and I've just done a ton of reading) is that Javascript variables defined outside of functions (var this = '' OR var this;) are global and accessible from anywhere else in the code. Every time I check my (allegedly) global variable from within a function, it is inaccessible.
As far as defining my global variable, I have tried a few options and none have worked. I have tried setting the variable but not giving it a value:
var mainTimeout;
I have tried setting it AND giving it an initial value:
var mainTimeout='x';
I have tried setting it as the actual time interval that it will eventually be:
var mainTimeout = setInterval(function(){setTimeClckState('default');}, 15000);
Regardless of which option I choose, I try calling it later from within a function like this:
$(".timeClckControls input[type=button]").click(function(){
if(mainTimeout){clearInterval(mainTimeout)}else{console.log('no timeout var set');};
});
Without fail, I always get console logged "no timeout var set". So it is never being recognized from within the function, even though I defined my variable with global scope. The result of this is that my time interval just stacks and it constantly triggers after the function is triggered a few times. Please help, I am out of ideas!
Full Code:
var mainTimeout = setInterval(function(){setTimeClckState('default');}, 15000);
$(".timeClckControls input[type=button]").click(function(){
if(!($(this).val()=='IN')&&!($(this).val()=='OUT')){
// IF CLEAR BUTTON, SET TIME CLOCK TO DEFAULT
if($(this).val()=="CL"){
setTimeClckState('default');
// IF BUTTON IS 4TH CHARACTER
}else if($('#empIDTxt').val().length==3){
$('#empIDTxt').val($('#empIDTxt').val()+$(this).val());
$('.timeClckControls .btn-primary').prop('disabled',true);
getEmpData();
}else{
$('#empIDTxt').val($('#empIDTxt').val()+$(this).val());
}
}
if(mainTimeout){clearInterval(mainTimeout)}else{console.log('no timeout var set');};
var mainTimeout = setInterval(function(){setTimeClckState('default');}, 15000);
});
function setTimeClckState(state){
switch(state){
case "default":
$('.timeClckControls input[type=text]').val('');
$('.timeClckControls input[type=button]').prop('disabled',false);
$('#statusDiv .alert').removeClass('alert-warning');
$('#statusDiv .alert').removeClass('alert-success');
$('#statusDiv .alert').addClass('alert-secondary');
$('#statusDiv .alert').html(' ');
$('#clockInOutBtn').removeClass('btn-warning');
$('#clockInOutBtn').removeClass('btn-success');
$('#clockInOutBtn').addClass('btn-secondary');
$('#clockInOutBtn').prop('disabled',true);
$('#clockInOutBtn').val('CLK');
$('#clearBtn').removeClass('btn-warning');
$('#clearBtn').removeClass('btn-success');
$('#clearBtn').addClass('btn-secondary');
$('#clearBtn').prop('disabled',true);
break;
case 'clockedIn':
$('#clearBtn').prop('disabled',false);
$('#clockInOutBtn').prop('disabled',false);
$('#clockInOutBtn').removeClass('btn-success');
$('#clockInOutBtn').addClass('btn-warning');
$('#clockInOutBtn').val('OUT');
break;
case 'clockedOut':
$('#clearBtn').prop('disabled',false);
$('#clockInOutBtn').prop('disabled',false);
$('#clockInOutBtn').addClass('btn-success');
$('#clockInOutBtn').removeClass('btn-warning');
$('#clockInOutBtn').val('IN');
break;
}
}
Okay I figured it out. Thanks to #Mischa, helped me realize what was happening. Problem was that I'm resetting the variable from within a function each time. So the variable actually ends up not having global scope. Googled "how to define global variable from within function". Turns out the best way is to set "window.myVar" from inside function. Ended up just making one function to stop and restart the interval:
function stopStartClearInt(){
if(window.mainTimeout){clearInterval(window.mainTimeout)}else{console.log('no timeout var set');};
window.mainTimeout = setInterval(function(){
setTimeClckState('default');
console.log('Interval check');
}, 5000);
}
I can run this from literally anywhere in my script and it will work!
Your code should work fine:
function setTimeClckState(str) {
console.log(str);
}
var mainTimeout = setInterval(function(){setTimeClckState('default');}, 500);
$(".timeClckControls input[type=button]").click(function(){
if(mainTimeout){
clearInterval(mainTimeout)
} else {
/**
* The return value of setInterval is just a unique id you use to pass back to
* clearInterval. It's not a structured object with any additional information,
* nor does it get set to null when you call clearTimeout. So even its cleared
* it will never be falsy
*/
console.log('no timeout var set');
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="timeClckControls">
<input type="button" value="click me" />
</div>
Trickshot #29 shows how to define touch events in jQuery. I've reworked it to my style of rogue writing in this fiddle.
What the author does is define a touchmove listener whenever a touchstart event is fired.
request.dom.ball.on('mousedown touchstart',myTouchStart);
function myTouchStart(myEvent){
request.dom.ball.on('mousemove.myNameSpace touchmove.myNameSpace',myTouchMove);
function myTouchMove(myEvent) {
What I'd like to do is put myTouchMove outside of myTouchStart because my rogue style of JavaScript writing is to try to keep it as flat as possible, and not have functions inside of functions, if I can help it.
That might seem strange since I already wrap everything inside of:
(function() {
})();
to begin with, but I really don't want to have functions inside of functions inside of functions if I can help it.
One way to do this is to return the inner function from another function (called a closure) that will pass any variables that you were originally referencing to your original function.
As far as I can tell in your case that's only one variable - local - but if you ever add new variables, just add a new argument to the function definition and the function call.
Updated fiddle: http://jsfiddle.net/ynUHb/1/
The affected code:
request.dom.ball.on('mousedown touchstart',myTouchStart);
function myTouchStart(myEvent){
var local = {};
//...
request.dom.ball.on('mousemove.myNameSpace touchmove.myNameSpace',myTouchMove(local));
//...
};
function myTouchMove(local) {
return function(myEvent) {
var myCss = {};
myEvent = (myEvent.originalEvent.touches) ? myEvent.originalEvent.touches[0] : myEvent;
myCss.top = local.elementPosition.y + myEvent.pageY - local.startPosition.y;
request.dom.top.text(myCss.top);
myCss.left = local.elementPosition.x + myEvent.pageX - local.startPosition.x;
request.dom.left.text(myCss.left);
request.dom.ball.css(myCss);
};
};
See:
Forming Closures
I'm trying to send data to a processing script. But for some reason the variable pjs below binds to the canvas "competence1" and enters the first if statement, but then the bindJavascript(this)-call returns error, but only in firefox. (works perfectly in chrome):
[pjs.bindJavascript is not a function]
var bound = false;
function initProcessing(){
var pjs = Processing.getInstanceById('competence1');
if (pjs != null) {
// Calling the processing code method
pjs.bindJavascript(this);
bound = true;
//Do some work
}
if(!bound) setTimeout(initProcessing, 250);
}
Environment: Mac OS X - Lion;
OBS! The bindJavascript(this)- method exists in the pde script loaded in the canvas-tag
By wrapping up all my script in a varable-map and by using the second way for setTimeout to be called i can follow each state and control the result.
So wrap it up-->
var ex = {
init : function(canId){
var canId = canId;
// check the if bound
// bind in this closure
// set new timer
}
}
setTimeout-->
setTimeout('ex.init("'+canId+'")', 2000);
and ofcourse add the parameter in so it can hold that value during it's own execution. So processing works just fine and i should use closure more often, that's the solution.
I had the same problem. I was using almost identical JS to you (which I got from the Pomax tutorial), and it was working fine. However, when I added the following preload directive (to load a backdrop), then suddenly my initProcessing function stopped working.
/* #pjs preload="metal_background.jpg"; */
The error message was the same: pjs.bindJavascript is not a function
On debugging, I could see that the pjs object did indeed not have a bindJavaScript function exposed, even though there is one declared in my PDE file.
It turns out this was purely down to timing... the preload had slowed down the initialisation of the processing object, so the second time round the 250ms loop, the pjs object existed, but didn't yet have its bindJavaScript function.
I am not 100% sure how Processing.js does this object construction, but in this case, a simple solution was just to check whether bindJavaScript actually was defined! I changed my code to the following:
var bound = false;
function initProcessing() {
var pjs = Processing.getInstanceById('mySketchId');
if(pjs != null) {
if(typeof(pjs.bindJavaScript) == "function") {
pjs.bindJavaScript(this);
bound = true;
}
}
if(!bound) setTimeout(initProcessing, 250);
}
After this it worked fine!
So I have a group of events like this:
$('#slider-1').click(function(event){
switchBanners(1, true);
});
$('#slider-2').click(function(event){
switchBanners(2, true);
});
$('#slider-3').click(function(event){
switchBanners(3, true);
});
$('#slider-4').click(function(event){
switchBanners(4, true);
});
$('#slider-5').click(function(event){
switchBanners(5, true);
});
And I wanted to run them through a loop I am already running something like this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners(i, true);
});
}
In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...
What am I missing?
This is a very common issue people encounter.
JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.
To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.
In the code below, we reference it with the function parameter j.
// Invoke generate_handler() during the loop. It will return a function that
// has access to its vars/params.
function generate_handler( j ) {
return function(event) {
switchBanners(j, true);
};
}
for(var i = 1; i <= totalBanners; i++){
$('#slider-' + i).click( generate_handler( i ) );
}
Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).
The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.
UPDATE: Added var before i to be sure it is declared properly.
Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".
Some links:
http://davidwalsh.name/event-delegate
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://lab.distilldesign.com/event-delegation/
Study this. It is a quite important thing to learn about event management in javascript.
[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]
$(function() {
$('.someAncestor').on('click', '.slider', function(e) {
// code to do stuff on clicking the slider. 'e' passed in is the event
});
});
Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.
Original answer maintained for more thorough explanation and legacy sample code:
I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:
$(function() {
$('body').delegate('.slider', 'click', function() {
var sliderSplit = this.id.split('-'); // split the string at the hyphen
switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
});
});
Liddle Fiddle: http://jsfiddle.net/2KrEk/
I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.
It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).
Try this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners($(this).attr('id').replace('slider-', ''), true);
});
}
That way you're getting the number from the id of the element that's actually been clicked.
Use jQuery $.each
$.each(bannersArray, function(index, element) {
index += 1; // start from 0
$('#slider-' + index).click(function(event){
switchBanners(index, true);
});
});
You can study JavaScript Clousure, hope it helps