I have the following function, that is called once in my program. When there is no user interaction it starts over again and again by recursion when the array is printed out. But when a user clicks on a link the function shall be stopped first (no more output from the array) and then be started again with new parameters.
function getText(mode){
var para = mode;
var url = "getText.php?mode=" + para;
var arr = new Array();
//get array via ajax-request
var $div = $('#text_wrapper');
$.each(arr, function(index, value){
eachID = setTimeout(function(){
$div.html(value).stop(true, true).fadeIn().delay(5000).fadeOut();
if(index == (arr.length-1)){
clearTimeout(eachID);
getText(para);
}
}, 6000 * index);
});
}
My code doesn't really stop the function but calling it once more. And that eventuates in multiple outputs and overlaying each other. How can I ensure that the function stops and runs just one at a time?
$(document).ready(function() {
$("#div1, #div2").click(function(e){
e.preventDefault();
var select = $(this).attr("id");
clearTimeout(eachID);
getText(select);
});
});
You keep overwriting eachID with the latest setTimeout, so when you clearTimeout(eachID) you are only stopping the last one.
Personally, I like to do things like this:
id = 0;
timer = setInterval(function() {
// do stuff with arr[id]
id++;
if( id >= arr.length) clearInterval(timer);
},6000);
This way, you only have one timer running, and you can clear it at any time.
Related
I have function:
function addModel() {
var values = new Array();
var $input = $('input[type=\'text\']');
var error = 0;
$input.each(function() {
$(this).removeClass('double-error');
var that = this;
if (that.value!='') {
values[that.value] = 0;
$('input[type=\'text\']').each(function() {
if (this.value == that.value) {
values[that.value]++;
}
});
}
});
$input.each(function(key) {
if (values[this.value]>1) {
//error++;
var name = this.value;
var product_model = $(this).parent().parent().find('.product-model').text();
var m = product_model.toLowerCase().areplace(search,replace);
$(this).parent().find('input[type=\'text\']').val(name + '-' + m);
}
});
return error <= 0; //return error > 0 ? false : true;
}
where are a lot of inputs to recheck... up to 50000. Usually are about 5000 to 20000 inputs. Of course browsers are freezing... How to move this function to web-worker and call it to get data back and fill form type="text"
thank you in advance.
Web workers don't have direct access to the DOM. So to do this, you'd need to gather the values of all 5-50k inputs into an array or similar, and then send that to the web worker (via postMessage) for processing, and have the web worker do the relevant processing and post the result back, and then use that result to update the inputs. See any web worker tutorial for the details of launching the worker and passing messages back and forth (and/or see my answer here).
Even just gathering up the values of the inputs and posting them to the web worker is going to take significant time on the main UI thread, though, as is accepting the result from the worker and updating the inputs; even 5k inputs is just far, far too many for a web page.
If maintaining browser responsiveness is the issue, then releasing the main thread periodically will allow the browser to process user input and DOM events. The key to this approach is to find ways to process the inputs in smaller batches. For example:
var INTERVAL = 10; // ms
var intervalId = setInterval((function () {
var values = [];
var $input = $('input[type=\'text\']');
var index;
return function () {
var $i = $input[index];
var el = $i.get();
$i.removeClass('double-error');
if (el.value != '') {
values[el.value] = 0;
$input.each(function() {
if (this.value == el.value) {
values[el.value]++;
}
});
}
if (index++ > $input.length) {
clearInterval(intervalId);
index = 0;
// Now set elements
intervalId = setInterval(function () {
var $i = $input[index];
var el = $i.get();
if (values[el.value] > 1) {
var name = el.value;
var product_model = $i.parent().parent().find('.product-model').text();
var m = product_model.toLowerCase().areplace(search,replace);
$i.parent().find('input[type=\'text\']').val(name + '-' + m);
}
if (index++ > $input.length) {
clearInterval(intervalId);
}
}, INTERVAL);
}
};
}()), INTERVAL);
Here, we do just a little bit of work, then use setInterval to release the main thread so that other work can be performed. After INTERVAL we will do some more work, until we finish and call clearInterval
In the below example we read from a JSON data and store it in a variable.
Then we loop through it to print out the value of each of its iteration.
I have "cartList.innerHTML" which will list out "Edit" 4 times as that is the number of the objects in the array. Like below
Edit
Edit
Edit
Edit
Once you click on the first Edit a modal should open and display the name of the first object, on clicking the second edit the name of the second and so on.
But for some reason the value remains to be the name of the last object for each Edit. How do I get it to print the correct name for each edit.
// Fetch JSON data
function loadJSON(file, callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', file, true); // Refers to JSON file
xobj.onreadystatechange = function() {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
}
}
xobj.send(null);
}
function load() {
loadJSON("assets/cart.json", function(response) {
var actual_JSON = JSON.parse(response);
console.log(actual_JSON);
var cartList = document.getElementById("cart-products");
var itemObj = actual_JSON.productsInCart;
var itemLength = actual_JSON.productsInCart.length;
// Loop through JSON data
for (var i = 0; i < itemLength; i++) {
(function(i) {
/* Output Link Element with Edit text & on click
display a modal containing current value of iteration */
cartList.innerHTML += '<div>'+
'Edit'+
'</div>';
var editCartModal = document.getElementById("edit-cart");
editCartModal.innerHTML = itemObj[i].p_name;// Name of the current object
})(i);
// for loop ends here
}
})
}
load(); // Loads function on page load
This happens because every function inside the for loop begins to execute after the loop ends. It's the same behavior as for callbacks. When you call each (function(i) {})(i) they all go to the end of function call stack, and only after last iteration of the loop they start executing (with the last i value).
I'm curious why do you need an internal inline function at all? Why don't just put this code directly to the loop body?
It's because though you are running the following code in an IIFE:
var editCartModal = document.getElementById("edit-cart");
editCartModal.innerHTML = itemObj[i].p_name;// Name of the current object
You're just modifying the same modal (getElementById()) four times.
A simple answer to fix this would be adding a data attribute:
for (var i = 0; i < itemLength; i++) {
(function(i) {
/* Output Link Element with Edit text & on click
display a modal containing current value of iteration */
cartList.innerHTML += '<div>'+
'<a href="javascript: void(0);"' +
'class="btn btn-outline button-edit" data-toggle="modal"' +
'data-target=".bs-example-modal-lg" data-custom-value="' + i +
'">Edit</a>'+
'</div>';
})(i);
// for loop ends here
}
/* ES5 way*/
/*
var list = document.getElementsByClassName('button-edit');
list.forEach(function(){
...
})
*/
[...document.getElementsByClassName('button-edit')].map(function(element){
element.addEventListener('click', function(e){
var chosenIdx = this.dataset.customValue;
var editCartModal = document.getElementById("edit-cart");
editCartModal.innerHTML = itemObj[chosenIdx].p_name;// Name of the current object
}, false);
});
I'm creating video objects via jquery from an array of urls that point to mp4 files.
I want to have the first video autoplay then once its finished pick up on the "ended" function and play the next video in the sequence.
$(document).ready(function () {
var urls = ["https://s3-us-west-1.amazonaws.com/linkto.mp4", "https://s3-us-west-1.amazonaws.com/linktoother.mp4"]
var videos = [];
for (var i = 0; i < urls.length; i++) {
var path = urls[i];
var video = $('<video></video>');
var source = $('<source></source>');
source.attr('src', path);
source.appendTo(video);
if (i === 0) {
video.attr('autoplay', 'true');
}
video.attr('id', 'video_' + i);
video.attr('preload', 'true');
video.attr('width', '100%');
videos.push(video);
video.on('ended', function () {
console.log('Video has ended!' + i);
// playNext(video);
});
$('#movie').append(video);
}
I can see the movies generated and the first one plays fine. The problem is when 'ended' function the variable i == 2 because thats how many videos are in the array and the for loop increased i. I've been away from javascript for a while. I can think of workarounds like adding an index attribute to the video element or something but basically how do I get the correct index in the "ended" function.
The problem here is the wrong use of a closure variable in a loop.
In your case you can create a local closure by using $.each() to iterate over the array
$(document).ready(function () {
var urls = ["https://s3-us-west-1.amazonaws.com/linkto.mp4", "https://s3-us-west-1.amazonaws.com/linktoother.mp4"]
var videos = [];
$.each(urls, function (i, path) {
var video = $('<video></video>');
var source = $('<source></source>');
source.attr('src', path);
source.appendTo(video);
if (i === 0) {
video.attr('autoplay', 'true');
}
video.attr('id', 'video_' + i);
video.attr('preload', 'true');
video.attr('width', '100%');
videos.push(video);
video.on('ended', function () {
console.log(i)
});
$('#movie').append(video);
})
})
You really don't want to have the index, since all the video elements are siblings you can just find the next sibling of the currently ended video element
video.on('ended', function () {
console.log('Video has ended!' + $(this).index());
var $current = $(this),
$next = $this.next();
console.log($next.find('source').attr('src'))
//playNext($(this));
});
Creating closures in loops: A common mistake
JavaScript closure inside loops – simple practical example
I was trying to show a text gradually on the screen (like marquee). e.g. H.. He.. Hell.. Hello. when I'm tracing it in debug in VS2010 it's working! but when it's actually running it shows the whole sentence at once.
I made a certain "delay" for about 3 seconds between each letter so it would suppose to take a while, but in reality it's shows everything immediately.
Who's the genius to solve this mystery? (please don't give me advices how to create the marquee effect, it's not the issue anymore. now it's just a WAR between me and javascript!) I'm assuming that it has to do with synchronization when calling function from function?
Thanks to whomever will help me get my sanity back.
you can download the code from here (VS project):
http://pcgroup.co.il/downloads/misc/function_from_function.zip
or view it here:
<body>
<script type="text/javascript">
//trying to display this source sentence letter by letter:
var source = "hi javascript why are you being such a pain";
var target = "";
var pos = 0;
var mayGoOn = false;
//this function calls another function which suppose to "build" the sentence increasing index using the global var pos (it's even working when following it in debug)
function textticker() {
if (pos < source.length) {
flash();
if (mayGoOn == true) {
pos++;
mayGoOn = false;
document.write(target);
textticker();
}
}
}
function flash() {
//I tried to put returns everywhere assuming that this may solve it probably one of them in not necessary but it doesn't solve it
if (mayGoOn == true) { return; }
while (true) {
var d = new Date();
if (d.getSeconds() % 3 == 0) {
//alert('this suppose to happen only in about every 3 seconds');
target = source.substring(0, pos);
mayGoOn = true;
return;
}
}
}
textticker();
</script>
You're obviously doing it wrong. Take a look at this.
var message = "Hello World!";
function print(msg, idx) {
if(!idx) {
idx = 0;
}
$('#hello').html(msg.substring(0, idx));
if(idx < msg.length) {
setTimeout(function() { print(msg, idx + 1) }, 200);
}
}
print(message);
Demo: http://jsbin.com/evehus
My code sends requests to Twitter for search data gets responses in the from of JSON. After getting the JSON, it stores the count of responses that match a certain condition in an array.
Here is the code that makes the call to the function that queries Twitter.
$(document).ready(function() {
...
graph = new HighCharts.chart({
chart: {
events: {
load: function() {
console.log("events.load");
var that = this;
var update = function() {
if (polling) {
console.log("update()");
// The least index series will be nearest the x-axis
var stackNumber = 0;
var numTweets = updateValues();
console.log(numTweets + "");
for (var i = 0, currentSeries = that.series; i < currentSeries.length; i++) {
var x = (new Date()).getTime(), // current time
y = numTweets[i];
stackNumber += y;
currentSeries[i].addPoint([x, y], true, true);
}
}
}
// set up the updating of the chart each second
var series = this.series[0];
setInterval(update, 1000);
}
}
(I'm probably missing some brace somewhere in the code-paste here, but I know for sure that my problem isn't related to a missing brace)
And here is the function that actually queries Twitter using a series of jQuery calls. The updateValues() function (which is outside the document-ready section) goes as follows:
function updateValues() {
var url = "http://search.twitter.com/search.json?callback=?&q=";
var cls = "Penn_CIS240"
var query = "%23" + cls;
var voteCount = [0,0,0,0];
// create an array of zeros
//for (var i = 0; i < 4; i++)
// voteCount.push(0);
$.getJSON(url + query, function(json){
console.log(json);
$.each(json.results, function(i, tweet) {
var user = tweet.from_user_id;
if (user % 2 == 0) {
voteCount[0] += 1;
}
else if (user % 3 == 0) {
voteCount[1] += 1;
}
else if (user % 5 == 0) {
voteCount[2] += 1;
}
else {
voteCount[3] += 1;
}
console.log("updateValues() -> getJSON -> each -> voteCount = " + voteCount);
});
console.log("updateValues() -> getJSON -> voteCount = " + voteCount);
});
console.log("updateValues() -> voteCount = " + voteCount);
return voteCount;
}
What is happening is that the variable voteCount is getting incremented properly inside the jQuery calls. However, outside of the calls, it is getting reset. So the log outputs look something like this:
updateValues() -> getJSON -> each -> voteCount = [1,0,0,0]
updateValues() -> getJSON -> voteCount = [1,0,0,0]
updateValues() -> voteCount = [0,0,0,0]
Does this problem have to do with jQuery's asynchronous calls, and I'm having interesting variable modification conflicts? Or is it something else?
When you use asynchronous callbacks in JavaScript, they execute later... asynchronously. So if you have:
var x = 5;
console.log("before getJSON", 5);
$.getJSON("/some/url", function (json) {
x = 10;
console.log("inside callback", x);
});
console.log("after getJSON", x);
the output will be
before getJSON 5
after getJSON 5
inside callback 10
Any code you want to execute after the request returns must be inside the callback; putting it physically "after" the $.getJSON call will not suffice. You should think of $.getJSON as "firing off" the JSON-getting process, then immediately returning to you; only later, when your script is done executing normal code and the server has responded, will the JavaScript event loop say "hey I got a response and am idle; time to call that callback that was waiting on the response!"
Because of this, your updateValues function will need to accept a callback of its own in order to notify its own caller that the values have been updated; just calling updateValues will only fire off the value-updating process, and the values won't be updated later until that idle time. Something like:
function updateValues(onUpdated) {
var url = "http://search.twitter.com/search.json?callback=?&q=";
var cls = "Penn_CIS240"
var query = "%23" + cls;
var voteCount = [0,0,0,0];
$.getJSON(url + query, function (json) {
// use of json to update voteCount ellided
onUpdated(voteCount);
});
}
Then calling code uses it as
updateValues(function (voteCount) {
// use the updated vote counts inside here.
});