I have an image map which has several divs on it as city points. And I wrote a class in css to animate those points' color, so I can add that class through jQuery, wait sometime and remove the class. The goal is to animate those points randomly (add class, wait, remove class at random), but currently I am stuck with waiting before removing the class. I tried different solutions, including those that are posted on this site, but no result. Hre is the code:
function builtCities() {
if ($('body.page-service-map').size()) {
var content = $('#region-content .content'),
cityDot = '<div class="city-dot"></div>',
cities = [
'moscow',
'saint-petersburg',
'krasnodar',
'rostov-na-donu',
'tyumen',
'omsk',
'irkutsk'
];
for (var i = 0; i < 7; i++) {
content.append(cityDot);
}
$('body.page-service-map .city-dot').each(function (index) {
$(this).addClass(cities[index]);
});
// animation
for (var j = 0; j < cities.length; j++) {
function partA(partB) {
$('.city-dot').eq(j).addClass('animate');
window.setTimeout(partB, 1000);
} partA(partB);
function partB() {
$('.city-dot').eq(j).removeClass('animate');
}
}
}
} builtCities();
It's not working because of closures. Do it like this:
for (var j = 0; j < cities.length; j++) {
$('.city-dot').eq(j).addClass('animate');
window.setTimeout((function (j) {
return function () {
$('.city-dot').eq(j).removeClass('animate');
};
}(j)), 1000);
}
Your current one doesn't work because your j variable will be persisted and will actually be equal to cities.length at the time you're calling partB. To get around this, the above calls a function passing in j which will return another function using a separate variable (the parameter j) that will use the correct index.
Related
currently I'm using a for loop to iterate over some tags, and when I try to alter the
tags .mouseover in JS, it returns null.
let htmlElements = document.getElementsByTagName("a");
for (let i = 0; i < 22; i++) {
for (let j = 0; j < 20; j++) {
htmlElements[i * 20 + j].onmouseover = onTileHovered(j, i, this);
}
}
If I console.log, it prints out null.
A solution for this is putting set it and make the function there.
let htmlElements = document.getElementsByTagName("a");`
for (let i = 0; i < 22; i++) {
for (let j = 0; j < 20; j++) {
htmlElements[i * 20 + j].onmouseover = function () {
// function stuff
};
}
}
This works, however, I'm worried that this would be making multiple copies of the function which would be pretty inefficient, and was wondering why the other way doesn't work.
What you're doing is perfectly fine. Adding 400 event listeners will have no visible impact at all on an application on modern devices, even on potato-level phones. Computers can handle repetitive tasks of much higher intensity.
If you were adding enough listeners that it was an issue to consider, then an approach would be to turn the elements into an array, and whenever a container of all those <a>s is moseovered, check the index of the element in the array.
const aContainer = document.querySelector('.container'); // replace this with appropriate selector
const anchors = [...aContainer.querySelectorAll('a')];
aContainer.addEventListener('mouseover', (e) => {
const { target } = e;
const a = target.closest('a');
if (!a) return;
const index = anchors.indexOf(a);
// do stuff with the clicked anchor and the index
// modulo can be used to find i and j, if needed
});
Im trying to simulate a Typewriter effect with javascript.
Theorically it should work with my code:
function TypeWriteToDoc(txt, id, x){
document.getElementById(id).innerHTML = document.getElementById(id).innerHTML + txt.charAt(x);
}
function TypeWrite(txt,id){
for (var i = 0; i < txt.length; i++){
setTimeout(function() {
TypeWriteToDoc(txt, id, i);
}, 1000*(i+1));
}
}
That should be it, when i call TypeWrite("example", "p_test"); it should write each character of "test" in the "p_test" html. I think the problem its not on my code since when i call the function without using setTimeout it works like in the code below:
function TypeWriteWithNoSettimeout(txt, id){
for (var i = 0; i < txt.lenght; i++){
TypeWriteToDoc(txt, id, i);}
}
This is a common issue with var in for-loops with callback functions.
The easiest solution? Just use let instead. let has support in all major browsers.
function TypeWrite(txt,id){
for (let i = 0; i < txt.length; i++){
setTimeout(function() {
TypeWriteToDoc(txt, id, i);
}, 1000*(i+1));
}
}
Similar to the previous response but rather than appending original text along with div.innerHtml, I adjusted it to be just the text char which simulates more of a typewriter feel. To increase the delay, I multiplied the index with 1000 rather than adding it since the larger increments are more visible.
function TypeWriteToDoc(txt, id, i) {
setTimeout(function() {
var div = document.getElementById(id)
div.innerHTML +=txt.charAt(i)
}, 1000 * (i))
}
function TypeWrite(txt,id){
for (var i = 0; i < txt.length; i++) {
TypeWriteToDoc(txt, id, i)
}
}
TypeWrite('example', 'p_test')
<div id="p_test"></div>
I'm trying to iterate through an array of RSS feeds as below:
var rssFeeds = [ ['http://www.huffingtonpost.com/tag/womens-rights/feed/', "Huffington Post"], ['http://abcnews.go.com/topics/urss?pageid=681900', "ABC News"], ['http://www.globalissues.org/news/topic/166/feed', "Global Issues"], ['http://topics.nytimes.com/top/reference/timestopics/subjects/f/feminist_movement/index.html?rss=1', "The New York Times"] ];
This array contains both the location of the feed and a string with the name of the feed. I iterate through the array with a for loop as below, the one with i as an iterator.
Once I receive the results, I iterate through the results in the callback function with the for loop j. I append each fetched result to another array entryArray, and after appending each result, I add a new attribute 'source' to that fetched result using the name for the RSS feed.
function loadEntries()
{
var feedr = new Array();
for( var i = 0; i < rssFeeds.length; i++ )
{
feedr[i] = new google.feeds.Feed( rssFeeds[i][0] );
feedr[i].setNumEntries(loadAmount);
feedr[i].load(function(result) {
if (!result.error) {
console.log(i);
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[i][1];
}
}
});
}
}
However, and this is where the problem arises, the iterator I use (i) to indicate the name to append is always equal to rssFeeds.length, because the callback functions for all four load commands occur after the initial for loop has already finished iterating. The console.log(i); you see always returns 4.
This worked when I copied and pasted the code for each item individually, but I'd rather not copy and paste because the RSSFeeds array will probably be much longer in the future. Is there any way I can accomplish this with a loop?
Consider the following JSFiddle example
The reason your code does not function as intended is because the value of the variable i, will be i = 4 at the end of the loop and not 0,1,2,3 as desired because this would already have happened been incremented by the for loop.
The trick is to use a recursive function and your load function will function similarly to:
feedr.load(function (result) {
if (!result.error) {
console.log(i);
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[i][1];
}
if (rssFeeds.length - 1 > i) {
loadEntries();
i++;
}
}
});
So first of all, I'd like to thank nd_macias for providing me with the links that helped me find this solution. Basically, I wrapped the load function in a function, and then called that function with the for loop as below:
function loadEntries()
{
var feedr = new Array();
for( var i = 0; i < rssFeeds.length; i++ )
{
feedr[i] = new google.feeds.Feed( rssFeeds[i][0] );
feedr[i].setNumEntries(loadAmount);
var f = function(n) {
feedr[n].load(function(result) {
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[n][1];
}
}
});
}
f(i);
}
}
Quick bit about my background:
-been learning for about 3 months;
-work in tech support for a small software company. 2 years exp.
-a lot of knowledge is secondhand and I am still learning the basics
I am trying to create an object every second. The object is created directly to the last position of an array that remembers a set quantity of objects created before the most recent one
function Fruit(name, position) {
this.name = name;
this.position = position;
}
var showXMostRecentFruits = 20;
var fruitCounter = 0;
function generateName() {
var name = 'Experimental Fruit' + fruitCounter;
return name;
}
var fruitsArray = [];
function shiftFruits() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i] = fruitsArray[i + 1];
}
function updateFruitPositions() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
}
var fruitTimer; //used for setting and clearing setTimeout
function createNewFruit() {
shiftFruits();
fruitsArray[showXMostRecentFruits - 1] = new Fruit(generateName());
updateFruitPositions();
fruitCounter += 1;
fruitTimer = setTimeout(function() {
createNewFruit();
}, 1000);
}
Say the function createNewFruit() is run once
createNewFruit();
Then I try to pull some meaning from the array
console.log(fruitsArray[19];
All I get is:
Fruit {}
undefined
This issue is when I want to run a loop (see updateFruitPositions()) that updates a propery of each object in the array, an error is returned that the objects are undefined. I get that they are undefined because they are not assigned to unique variables (at least not that I'm aware of). How can I identify the objects or how can I create unique containers for them so I access them in the array?
You need to test whether a given element is set to something before attempting to write to one of its properties.
Instead of this...
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
Use this:
for (i = 0; i < showXMostRecentFruits; i++) {
if (fruitsArray[i])
fruitsArray[i].position = i;
}
You fill the array from the end, staring with element 20. Without the if (fruitsArray[i]), you're attempting to set undefined.position = i for the first 19 elements.
You could replace the showFruits function with something much more efficient:
function shiftFruits() {
if (fruitsArray.length > showXMostRecentFruits) {
fruitsArray.shift();
}
}
and updateFruitPositions only needs to update members that exist, the length is controlled by shiftFruits:
function updateFruitPositions() {
for (i = 0; i < fruitsArray.length; i++) {
fruitsArray[i].position = i;
}
}
or where forEach is supported:
function updateFruitPositions() {
fruitsArray.forEach(function(fruit, i){fruit.position = i});
}
so it only visits members that exist. And the createNewFruit has:
fruitsArray.push(new Fruit(generateName());
I'm creating a matching game and I'm trying to add a class from an array to match against.
The code I have below creates the classes I need, then randomizes them.
My problem is in the randomizeDeck function. I'm trying to add each of the classes to the specified element twice. When I console.log the code the classes gets added to the first six elements but not the last six, which I need it to do so that I have the classes to match against in the matching game I'm creating.
var cardDeck = new Array();
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
}
}
createDeck();
var randDeck = cardDeck.sort(randOrd);
function randomizeDeck() {
card.each(function(i){
$(this).addClass(randDeck[i]);
});
}
randomizeDeck();
I think your createDeck function needs to create 12 classes instead of 6. Just push each one twice:
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
cardDeck.push("card-" + i);
}
}
Then you'll have an array of 12 classes (2 each of 6 unique classes), which will be randomized and assigned to the 12 cards.
I suggest a separate variable to keep track of the index, rather that the each index. Once you've gone through the pack once, it might be a good idea to shuffle the deck again so the order is different on the second pass. YMMV.
function sortCards(randOrd) {
randDeck = cardDeck.sort(randOrd);
}
function randomizeDeck() {
var count = 0;
cards.each(function(i) {
if (i === 6) { count = 0; sortCards(randOrd); }
$(this).addClass(randDeck[count]);
count++;
});
}
Your randomizeDeck() function can be rewritten to use the same array of class names twice:
function randomizeDeck() {
card.each(function(i){
if(i < 6)
$(this).addClass(randDeck[i])
else
$(this).addClass(randDeck[i-6]);
});
}
Note: I would rewrite the variable card as $cards so that you know it's a jQuery object and in this case a collection of them. Otherwise, its hard to tell it apart from any other javascript var.
Try something like this - it's tested now updated
SEE THIS FIDDLE
http://jsfiddle.net/8XBM2/1/
var cardDeck = new Array();
function createDeck() {
for (i = 1; i <= 6; i++) {
cardDeck.push("card-" + i);
}
}
createDeck();
var randDeck = cardDeck.sort();
alert(randDeck);
function randomizeDeck() {
var x = 0;
$('div').each(function(i){
if ( i > 5) {
$(this).addClass(randDeck[x]);
x++;
} else {
$(this).addClass(randDeck[i]);
}
});
}
randomizeDeck();