Progress Bar - Loop Through Array and Display Divs - javascript

Getting really stuck on this one... I'm trying to build a donation progress bar... In ReactJS - but I'm a beginner, so I want to get the code right first in Vanilla js...
What I'm trying to do, is loop through an array of numbers, (aka, donations already submitted via a form). EG:
[2, 5, 25] etc.
Everytime, a donation is submitted, it get's added to this array.
Want I want, is for my donation bar to increase/fill in colour, based on the donations already made in the array.
The bar would be full at 100%. Or £100.
Here's the snippet of JS I already have:
// FUNCTION TO CALCULATE TOTAL DONATIONS
const numbers = donated.map(Number);
function add(a, b) {
return a + b;
}
// SUM VALUE OF NUMBERS IN THE ARRAY
const sum = numbers.reduce(add, 0);
console.log('numbers', numbers);
//THE VALUE OF NUMBERS IN ARRAY, TURNED INTO A PERCENTAGE
const total = 100;
const percentage = (sum / total) * 100;
console.log('percentage', percentage);
// LOOP THROUGH EVERY NUMBER IN THE ARRAY, AND ADD A DIV WITH A MATCHING WIDTH
for (var i = 0; i < numbers; i++) {
if (i < 100) {
const div = document.createElement('div');
div.style.background = 'red';
div.style.width = numbers + 'px';
div.style.height = '50px';
div.style.float = 'left';
document.querySelector('.bar').appendChild(div);
}
}
The loop works, slightly. The first div in the array gets added. But as I add more donations, no more divs are added to the progress bar.
Eventually, I want to stop at 100...

Got it working! I needed to set numbers[i] in my style width:
for (var i = 0; i < numbers.length; i++) {
if (i < 100) {
div +1;
const div = document.createElement('div');
div.style.background = 'red';
**div.style.width = numbers[i] + 'px';**
div.style.height = '50px';
div.style.float = 'left';
document.querySelector('.bar').appendChild(div);
}
}

As i see, you styled your divs float left. That mean each div will appears over the previous on left. In chrome, right click on the div and left click on inspect. You will see that you have many divs generated in your Dom.

Related

Reducing the number of times a helper function is run

I am trying to create a word cloud. In order to render text to the screen I am generating a random position for each word. This works perfectly, however there are a lot of overlapping words. In order to solve this I am storing the position and size of the elements in an array and then I created a helper function that checks for collisions, generates a new position for the element if it finds one, and then calls it's self again to check again from the start of the array. When I run my code the first 2-3 words render just fine but then I get an error saying Maximum call stack size exceeded. I saw there was already a post on this same issue on stack overflow.
I saw that the other person was using a forEach function and so was I so I converted it into a for loop like the answer suggested but it did not do anything. I think the issue boils down to the fact that there are so many collisions but I am not sure how to best approach the issue. Is there another way that I can generate unique positions for elements while still avoiding collisions?
Code:
function calculatePosition(parent, child) {
return Math.random() * parent - (child / 2)
}
// needed for rendering position of span elements
var ranges = []
var totalWidthOfWords = 0
var totalHeightOfWords = 0
// reposition element if there is a collision
function checkForCollisions(element, height, width, wordCloud, injectedSpan) {
for(var i = 0; i < ranges.length; i++) {
let current = ranges[i]
if(element.left >= current.width[0] && element.left <= current.width[1]) {
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, width) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
if(element.top >= current.height[0] && element.top <= current.height[1]) {
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, height) + "px";
checkForCollisions(element, height, width, wordCloud, injectedSpan)
}
}
}
// Create content in DOM
const injectedContent = data.map(line => {
const injectedSpan = document.createElement("span")
const injectedWord = document.createElement("p")
const wordCloud = document.querySelector(".word-cloud")
// mod weight value to get more managable inputs
let weightValue = (line.weight * 100).toFixed(2)
// sets values of words and renders them to the screen
injectedWord.innerText = line.word
injectedSpan.appendChild(injectedWord)
wordCloud.appendChild(injectedSpan)
// sets style attribute based on weight value
injectedWord.setAttribute("style", `--i: ${weightValue}`)
// flips words
if(Math.random() > 0.75) {
injectedWord.style.writingMode = "vertical-rl";
}
// Entrance animation
let left = innerWidth * Math.random()
let top = innerHeight * Math.random()
if(Math.random() < 0.5) {
injectedWord.style.left = "-" + left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
} else {
injectedWord.style.left = left + "px";
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
if(Math.random() < 0.5) {
injectedWord.style.top = "-" + top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, injectedSpan.clientHeight) + "px";
} else {
injectedWord.style.top = top + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientWidth, injectedSpan.clientWidth) + "px";
}
// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect()
console.log(spanPosition)
if(spanPosition) {
checkForCollisions(spanPosition, spanPosition.height, spanPosition.width, wordCloud, injectedSpan)
}
totalWidthOfWords += spanPosition.width
totalHeightOfWords += spanPosition.height
ranges.push({width: [spanPosition.left, spanPosition.right], height: [spanPosition.top, spanPosition.bottom]})
})
Link: https://jsfiddle.net/amotor/mdg7rzL1/4/
There is still a lot of work to do to make sure that it works properly, especially to make sure that the code does not produce any errors!
The general idea would be to follow IllsuiveBrian's comment to make sure, that checkForCollision only does the work of checking if there is a collision and that another function takes care of recalculating the position if necessary and then reevaluating a potential collision.
function checkForCollisions(element, wordCloud, injectedSpan) {
for(var i = 0; i < ranges.length; i++) {
let current = ranges[i];
// return true if there is a collision (you probably have to update the code you are using here to truly avoid collisions!)
if (collision) { return true; }
}
return false; // return false otherwise
}
Finally this part would take care of recalculating position and and rechecking for collision:
ranges.forEach(function(injectedSpan) {
// Get position of span and change coordinites if there is a collision
let spanPosition = injectedSpan.getBoundingClientRect();
if (spanPosition) {
while (checkForCollisions(spanPosition, wordCloud, injectedSpan)) {
injectedSpan.style.left = calculatePosition(wordCloud.clientWidth, element.width) + "px";
injectedSpan.style.top = calculatePosition(wordCloud.clientHeight, element.height) + "px";
}
}
});
Here is a quick idea on how to go into this direction: https://jsfiddle.net/euvbax1r/4/

Find index of every n number in array

I have a big data which I load and show on the page in chunks. Initially I load the first 50 items and after that on button click I get the next 25.
So I have 50 items on initial page load. After button click I fetch +25 items and now I have 75.
On the second button click I get the next +25 items and now I have 100 etc.
I need to find always the number that is ten places before the last number in the list.
When my list size is 50 - I need to get the index of the 40-th element in the list.
When my list size is 75 I need to get the index of the 65-th element in the list,
When my list size is 100 I need to get the index of the 90-th element in the list.
** Simulation **
let arr = [];
for (let i = 0; i < 50; i++) {
arr.push(i + 1)
}
let btn = document.getElementById('btn');
btn.addEventListener('click', () => {
for (let i = 0; i < 25; i++) {
arr.push(i + 1);
}
console.log(arr);
})
<button id="btn">Add items</button>
How can be this done?
Is there better way than
let index = arr[arr.length - 11];
console.log(index);
You're referencing the element the right way, you can create an arrow function that fetches the 10th last element (or undefined in case the list is smaller then 10)
var smallArr = [1,2,3,4,5,6,7,8,9,10,11,12];
var bigArr = [1,2,3,4,5,6,7,8];
const tenthLastElement = (array) => array[array.length - 11];
console.log(tenthLastElement(smallArr));
console.log(tenthLastElement(bigArr));

How can I scroll so that a specific element becomes at the middle of visible region of a div?

I have a series of list items and I want to put every specific item in the middle of the visible region of their parent div. So the first element can scroll to the middle I added some dummy items before it. (and also to the end of list items). and this is my function:
function scrollToMiddleView(elem) {
if (elem) {
var main = $("#container");
m = main.scrollTop() + main.height() / 2;
t = main.offset().top + m;
main.animate({scrollTop: elem.offset().top - t}, 500);
}
}
I test it on a sequence of elements. It works for some elements and doesn't work for some others. I works when the scrollbar is at the top. I want each element precisely located in the middle.
This is the function that worked. the main.scrollTop() must be subtracted from main.offset().top, and then the result must be subtracted from elem.offset().top.
function scrollToMiddleView(elem) {
if (elem) {
var main = $("#container");
m = main.height() / 2;
t = main.offset().top - main.scrollTop() + m;
q = elem.offset().top - t;
main.animate({scrollTop: q}, 500);
}
}

Calculate Javascript pixel widths for list of elements

I have a list with 4 numbers. If I divide 100 with the list's length, I get 25. I want to set width of four elements to multiples of this number, eg. for the first, it would be 25px, for the second 50px and so on.
This is the (pseudo)code I've written so far:
list{1,2,3,4}
var array = list.split(',');
var width = 100 / array.length;
for (var n = 0; n < array.length; n++) {
if(array[n]==1) {
width = width; //here I want width as 25;
<div style="width:"+Width +"></div>
}
if(array[n]==2){
width = width+width;//here I want width as 50
<div style="width:"+Width +"></div>
}
if(array[n]==3) ){
width = width+width;//here I want width as 75
<div style="width:"+Width +"></div>
}
if(array[n]==4 ){
width = width+width;//here I want width as 100
<div style="width:"+Width +"></div>
}
}
It seems like you mix up html and javascript syntax. Html are those <tag> things.
First of all, I recommend you this course or some other, just to get started with JavaScript.
To create an element in JS, you can either use document.write, which is probably much easier but may be used only before the document loads. You can use it like this:
width = 42; //or whatever
document.write('<div style="width: '+width+'px">adsf</div>');
Or the more difficult, but also more flexible way – to use the DOM. You would do it this way:
var div = document.createElement('div'); //create new element
div.style.width = 42; //set its width to whatever you want
div.textContent = "some text"; //add some text into the div
someElement.appendChild(div); //insert the element into another one
The someElement here is either an element you get by calling document.getElementById (or a similar function) or, if you want them directly inside the body, just write document.body.
.
With respect to #Marc_B's answer, the final code would look something like this:
var list = [1,2,3,4];
var div;
for (var n = 0; n < array.length; n++) {
div = document.createElement('div');
div.style.width = 25*n;
document.body.appendChild(div);
}
You do NOT need all those if() tests:
for (var n = 0; n < array.length; n++) {
width = 25 * (n + 1);
...
}
s0
n = 0 -> width = 25 * (0 + 1) -> 25 * 1 -> 25
n = 1 -> width = 25 * (1 + 1) -> 25 * 2 -> 50
n = 2 -> width = 25 * (2 + 1) -> 25 * 3 -> 75
etc...
Not 100% sure what your asking but I think you just want to times width by 2,3,4 depending on what n is.
(Go to Marc B's answer)
Edit: BTW when you declare width you by mistake (I think) use a capital W
var Width
Should be:
var width

Assign index # according to current section

Say I have a total width of 585px. And I wanted to divide the space into equal sections and assign each an index value within position. I could do something like this if I had lets say 6 sections: (assigned by total width / number of sections)
//Set up elements with variables
this.sliderContent = config.sliderContent;
this.sectionsWrap = config.sectionsWrap;
//Selects <a>
this.sectionsLinks = this.sectionsWrap.children().children();
//Create drag handle
this.sectionsWrap.parent().append($(document.createElement("div")).addClass("handle-containment")
.append($(document.createElement("a")).addClass("handle ui-corner-all").text("DRAG")));
//Select handle
this.sliderHandle = $(".handle");
var left = ui.position.left,
position = [];
var position = ((left >= 0 && left <= 80) ? [0, 1] :
((left >= 81 && left <= 198) ? [117, 2] :
((left >= 199 && left <= 315) ? [234, 3] :
((left >= 316 && left <= 430) ? [351, 4] :
((left >= 431 && left <= 548) ? [468, 5] :
((left >= 549) ? [585, 6] : [] ) ) ) ) ) );
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
But what if I had an x number of sections. These sections are just elements like
<li><a></a></li>
<li><a></a></li>
<li><a></a></li>
Or
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
<div><a></a></div>
How would I divide the total of 585px and classify the index in position according to the current left value of the .handle element? I can know where the drag handle is by using ui.position.left, what I want is to be able to set an index for each element and be able to animate handle depending on where the handle is within the indexed elements. Since each element is indexed I later call a transition method and pass in the current index # to be displayed. The code I show above works, but isn't really efficient. I also need to account for the width of the handle to fit the section width. http://jsfiddle.net/yfqhV/1/
Ok, there is a slight inconsistency in the difference between the range figures in the question, which makes it hard to algorithmise [ my made-up-word de jour =) ] this exactly:
81 to 199 = 118
199 to 316 = 117
316 to 431 = 115
431 to 518 = 118
If you can adjust for that, I have a solution - it's not especially clever JavaScript, so there may well be better ways to do this (SO JS people, feel free to educate me!) but it works.
First we need a function to find the index of an array range, a given value falls within (this replaces your nested if-else shorthands), then we have a function to set up the positional arrays, and finally we can do a range search and return the corresponding array of values.
This solution should dynamically deal with a varying number of sections, as long as this line:
var len = $("#sectionContainer").children().length;
is adjusted accordingly. The only other values that may need adjusting are:
var totalWidth = 585;
var xPos = 81;
although you could set them if you have elements you can draw the values from, making it even more of a dynamic solution.
/**
* function to find the index of an array element where a given value falls
* between the range of values defined by array[index] and array[index+1]
*/
function findInRangeArray(arr, val){
for (var n = 0; n < arr.length-1; n++){
if ((val >= arr[n]) && (val < (arr[n+1]))) {
break;
}
}
return n;
}
/**
* function to set up arrays containing positional values
*/
function initPositionArrays() {
posArray = [];
leftPosArray = [];
var totalWidth = 585;
var xPos = 81;
var len = $("#sectionContainer").children().length;
var unit = totalWidth/(len - 1);
for (var i=1; i<=len; i++) {
pos = unit*(i-1);
posArray.push([Math.round(pos), i]);
xMin = (i >= 2 ? (i==2 ? xPos : leftPosArray[i-2] + posArray[1][0]) : 0);
leftPosArray.push(Math.round(xMin));
}
}
var left = ui.position.left;
initPositionArrays();
// find which index of "leftPosArray" range that "left" falls within
foundPos = findInRangeArray(leftPosArray, left);
var position = posArray[foundPos];
if (position.length) {
$(".handle").animate({
left : position[0]
}, 400);
Slider.contentTransitions(position);
}
I've set up a jsFiddle to illustrate.
Enjoy!
Edit
I've looked at #JonnySooter s own answer, and whilst it calculates the positioning correctly, it won't deal with a variable number of sections.
To get it to work with any number of sections, the handleContainment div (that is created on-the-fly) needs to have it's width set dynamically (via inline styling).
This is calculated by multiplying the number of sections by the width of each section (which is actually the same as the width of the slider).
This is all done after creating the handle so that the width can be extracted from the "handle" css class, meaning a change to the width of the handle will cascade into the routine when applied at the css level.
See this jsFiddle where the number of sections can be altered and the slider behaves properly.
var numSections = // ...;
var totalWidth = // ...;
var sectionWidth = totalWidth / numSections;
var index = Math.floor($(".handle").position().left / sectionWidth);
var leftPosition = index * sectionWidth;
var rightPosition = leftPosition + sectionWidth - 1;
UPDATE:
I worked on trying to find a solution myself and this is what I came up with:
function( event, ui ) {
var left = ui.position.left, //Get the current position of the handle
self = Slider, //Set to the Slider object cus func is a callback
position = 1;
sections_count = self.sectionsLinks.length, //Count the sections
section_position = Math.floor(self.sectionsWrap.width() / sections_count); //Set width of each section according to total width and section count
left = Math.round(left / section_position); //Set the index
position = (left * section_position); //Set the left ammount
if(position < section_position){ //If handle is dropped in the first section
position = 0.1; //Set the distance to animate
left = 0; //Set index to first section
}
if (position.length) {
$(this).animate({
left : position //Animate according to distance
}, 200);
left = left += 1; //Add one to the index so that I can use the nth() child selector later.
self.contentTransitions(left);
}
}

Categories

Resources