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.
Because we can't set table-cell's max-height so I am using this loop to limit row height (equal to cell), by increasing the table width until all rows height < height I set in loop.
var i = 0,
row, table = document.getElementsByTagName('table')[0],
j = table.offsetWidth;
while (row = table.rows[i++]) {
while (row.offsetHeight > 200 && j < 3000) {
j += 250;
table.style.width = j + 'px';
}
}
But there is a problem in this script, if for some reason a specific row height can't be less than 400px, which is greater than max-height I set for rows in script, the table becomes unnecessary 3000px wide because of the condition in loop.
Is there any way to send loop to next row if a row height is same before and after table width increment by loop. I tried web searching but nothing found to handle this, if anyone here knows about this please help me
Example (no problem when no <br/> tag): http://jsfiddle.net/jm5cpqr4/5/
Example (problem when <br/> usage limiting least possible height of cell): http://jsfiddle.net/jm5cpqr4/6/
script47 correctly indicated a break statement is used in the resolution.
Readers may need to reduce the offset value tested in the "failing" case on jsfiddle to reproduce the problem in their browser - it did't immediately fail for me
A solution example:
function loaded()
{ var i = 0, row, table = document.getElementsByTagName('table')[0],
j = table.offsetWidth;
var testHeight = 100;
while (row = table.rows[i++])
{ var h0 = row.offsetHeight;
while (row.offsetHeight > testHeight && j < 3000)
{ j += testHeight;
table.style.width = j + 'px';
if(row.offsetHeight==h0)
{ table.style.width = j-testHeight + 'px'
break;
}
}
}
}
loaded();
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
I am writing my own drag and drop file manager. This includes a javascript marquee selection box which when active calculates the elements (files) that are intersected and selects them by adding a class to them.
I currently perform the check during a mousemove handler, loop through an array of element coordinates and determine which ones are intersected by the drag and drop selection box.
The function currently looks like this:
selectItems : function(voidindex){
var self = this;
var coords = self.cache.selectioncoords;
for(var i=0, len = self.cache.items.length; i<len; i++){
var item = self.cache.items[i];
var itemcoords = item.box_pos;
if(coords.topleft.x < (itemcoords.x+201) && coords.topright.x > itemcoords.x && coords.topleft.y < (itemcoords.y+221) && coords.bottomleft.y > itemcoords.y){
if(!item.selected){
item.selected = true;
item.html.addClass('selected').removeClass('activebutton');
self.cache.selecteditems.push(i);
self.setInfo();
}
}
else{
if(item.selected){
item.selected = false;
if(!voidindex || voidindex !== i){
item.html.removeClass('selected');
}
var removeindex = self.cache.selecteditems.indexOf(i);
self.cache.selecteditems.splice(removeindex, 1);
self.setInfo();
}
}
}
},
There is lots of dirty logic in the code above which ensures that the DOM is only manipulated when the selection changes. This is not relevant to the question and can be exluded. The important part is the intersection logic which checks the coordinates of the element versus the coordinates of the marquee selection box.
Also please note that the item dimensions are fixed at 201px width by 221px height.
I have tested this and all works perfectly, however I have the need to support potentially thousands of files which would mean that at some point we will start seeing UI performance decrease.
I would like to know if there is anyway to perform intersection detection without looping through the coordinates of each element.
The coordinates of the marquee box are defined as follows at any given time:
selectioncoords : {
topleft : {
x : 0,
y : 0
},
topright : {
x : 0,
y : 0
},
bottomleft : {
x : 0,
y : 0
},
bottomright : {
x : 0,
y : 0
},
width : 0,
height : 0
}
And the coordinates of each item, stored in the self.cache.items array are defined as follows:
item : {
box_pos : {
x : 0,
y : 0
},
grid_pos : {
row : 1,
column : 1
}
}
So the information available will always be the actual grid position (row/column) as well as the physical item position (left and top offsets in pixels within the grid).
So to summarize, the question is, is there anyway to detect item intersection from a set of marquee selection box coordinates as defined above without looping through the whole array of item coordinates every time the mousemove event fires?
Thanks in advance for any help.
The following depends upon a locked grid with the dimensions as described.
You are comparing a mouse-defined rectangle against a grid with static edge sizes. Thus, given an x coordinate or a y coordinate, you should be able to derive pretty easily which column or row (respectively) the coordinate falls into.
When the user starts the select box, grab that x and y, and find the row/column of the start. When the mouse moves while pulling the select box, you find (and then update) the row/column of the finish. anything that is both within the rows defined by that box and within the columns defined by that box (inclusive) is selected. If you then keep your selectable elements in a two-dimensional array according to rows and columns, you should be able to just grab the ones you want that way.
Mind, how much more (or less) efficient this is depends on the size of your expected selection boxes as compared to the total size, and the degree to which you expect the grid to be populated. Certainly, if the average use case is selecting half or so of the objects at a time, there's not a whole lot you can do to cut down efficiently on the number of objects you have to look at each time.
Also, though it is kludgy, you can have the mousemove handler not fire every time. Letting it pause a bit between updates will reduce the responsiveness of this particular function a fair bit, but it'll cut down significantly on the amount of resources that are used.
There are several ways you could approach this. Here's one. First you need the items in some kind of organized structure that you can look up quickly by row and column. You could use a two-dimensional array, or for simplicity I'm going to use a hash table. You could do this at the same time that you create the self.cache.items, or later, something like this:
var cacheLookup = {};
function initCacheLookup() {
var items = self.cache.items;
for( var i = 0, n = items.length; i < n; i++ ) {
var item = items[i];
var key = [ item.grid_pos.row, item.grid_pos.column ].join(',');
cacheLookup[key] = item;
}
}
Then when you want to get the items intersecting the rectangle, you could do something like this:
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth ) + 1;
var right = Math.floor( br.x / itemWidth ) + 1;
var top = Math.floor( tl.y / itemHeight ) + 1;
var bottom = Math.floor( br.y / itemHeight ) + 1;
var selecteditems = [];
for( var row = top; row <= bottom; row++ ) {
for( var col = left; col <= right; col++ ) {
var key = [ row, col ].join(',');
var item = cacheLookup[key];
if( item ) {
selecteditems.push( item );
}
}
}
// Now selecteditems has the items intersecting the rectangle
There's probably an off-by-one error or two here, but this should be close.
Well, as I said, that is one way to do it. And it has the possibly interesting property that it doesn't depend on the order of items in the self.cache.items array. But that cacheLookup hash table smells like it might not be the most efficient solution.
Let me take a guess: isn't that array already in the correct order by rows and columns (or vice versa)? For example, if your grid is four wide, then the top row would be array elements 0-3, the second row 4-7, the third row 8-11, etc. Or it could be a similar arrangement going down the columns.
Assuming it's in row-by-row order, then you don't need the hash table at all. That initCacheLookup() function goes away, and instead the search code looks like this:
var nCols = 4/*whatever*/; // defined somewhere else
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth );
var right = Math.floor( br.x / itemWidth );
var top = Math.floor( tl.y / itemHeight ) * nCols;
var bottom = Math.floor( br.y / itemHeight ) * nCols;
var items = self.cache.items;
var selecteditems = [];
for( var iRow = top; iRow <= bottom; iRow += nCols ) {
for( var col = left; col <= right; col++ ) {
var index = iRow + col;
if( index < items.length ) {
selecteditems.push( items[index] );
}
}
}
// Now selecteditems has the items intersecting the rectangle
This code will be a little faster, and it's simpler too. Also it doesn't depend at all on the item.box_pos and item.grid_pos. You may not need those data fields at all, because they are easily calculated from the item index, grid column count, and item height and width.
Some related notes:
Don't hard code 201 and 221 in the code. Store those in variables once, only, and then use those variables when you need the item height and width.
There is a lot of duplication in your data structures. I recommend that you ruthlessly eliminate all duplicated data unless there is a specific need for it. Specifically:
selectioncoords: {
topleft: {
x: 0,
y: 0
},
topright: {
x: 0,
y: 0
},
bottomleft: {
x: 0,
y: 0
},
bottomright: {
x: 0,
y: 0
},
width: 0,
height: 0
}
More than half the data here is duplicated or can be calculated. This is all you need:
selectioncoords: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
The reason I bring this up is that was a bit confusing when working on the code: "I want the left edge. Do I get that from topleft.x or bottomleft.x? Are they really the same like they seem? How do I pick?"
Also, as mentioned above, the item.box_pos and item.grid_pos may not be needed at all if the items are stored in a sequential array. If they are needed, you could store just one and calculate the other from it, since there's a direct relationship between the two:
box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
You can limit the scope of your checks by indexing each item in a grid, as often as necessary and no more often. You can use the grid to give you a list of elements near an X, Y coordinate or that might be in an X1, Y2, X1, Y2 range.
To get you started ...
var Grid = function(pixelWidth, pixelHeight, boxSize) {
this.cellsIn = function(x1, y1, x2, y2) {
var rv = [];
for (var x = x1; x < x2; x += boxSize) {
for (var y = y1; y < y2; y += boxSize) {
var gx = Math.ceil(x/boxSize);
var gy = Math.ceil(y/boxSize);
rv.push(this.cells[gx][gy]);
}
}
return rv;
} // cellsIn()
this.add = function(x1, y1, x2, y2, o) {
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
cells[i].push(o);
}
} // add()
this.get = function(x1, y1, x2, y2) {
var rv = [];
var rv_index = {};
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
var cell = cells[i];
for (var oi in cell) {
if (!rv_index[cell[oi]]) {
rv_index[cell[oi]] = 1;
rv.push(cell[oi]);
}
}
}
return rv;
} // get()
this.cells = [];
for (var x = 0; x < Math.ceil(pixelWidth/boxSize); x++) {
this.cells[x] = [];
for (var y = 0; y < Math.ceil(pixelHeight/boxSize); y++) {
this.cells[x][y] = [];
}
}
};
So, rather than iterating through all possible objects, whatever they may be, you iterate over all the objects that are near or potentially in the given coordinates.
This requires that you maintain/re-index the grid as item coordinates change. And you'll likely want to add some functionality to the above (or similar) Grid class to modify/move existing objects. But, to the best of my knowledge, an index of this sort is the best, if not only, way to index objects "in space."
Disclaimer: The code above isn't tested. But, I have similar code that is. See the DemoGrid function class here: http://www.thepointless.com/js/ascii_monsters.js
The functionality of my DemoGrid is similar (as far as I remember, it's been awhile), but accepts x, y, radius as parameters instead. Also notable, my mouse events don't touch the grid every time the event fires. Checks are rate-limited by a game/main loop.
If the system is set up such that
self.cache.items is ordered from left to right and top to bottom
(0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
There is an item in each space
GOOD - (0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
BAD - (0,0),(2,0)(1,2),(1,3),(2,1),(2,3)
We need to know the total number of columns.
So the code to get you started.
// Some 'constants' we'll need.
number_of_columns = 4;
item_width = 201;
item_height = 221;
// First off, we are dealing with a grid system,
// so that means that if given the starting x and y of the marquee,
// we can determine which element in the cache to start where we begin.
top_left_selected_index = Math.floor(selectioncoords.topleft.x / item_width) + (Math.floor(selectioncoords.topright.y / item_height) * number_of_columns );
// Now, because the array is in order, and there are no empty cache points,
// we know that the lower bound of the selected items is `top_left_selected_index`
// so all we have to do is walk the array to grab the other selected.
number_columns_selected = (selectioncoords.bottomright.x - selectioncoords.topleft.x) / item_width;
// if it it doesn't divide exactly it means there is an extra column selected
if((selectioncoords.bottomright.x - selectioncoords.topleft.x) % item_width > 0){
number_columns_selected += 1;
}
// if it it doesn't divide exactly it means there is an extra column selected
number_rows_selected = (selectioncoords.bottomright.y - selectioncoords.topleft.y) / item_height;
if((selectioncoords.bottomright.y - selectioncoords.topleft.y) % item_height > 0){
number_rows_selected += 1;
}
// Outer loop handles the moving the pointer in terms of the row, so it
// increments by the number of columns.
// EX: Given my simple example array, To get from (1,0) to (1,1)
// requires an index increase of 3
for(i=0; i < number_rows_selected; i++){
// Inner loop marches through the the columns, so it is just one at a time.
// Added j < number_of_columns in case your marquee stretches well past your content
for(j=0; j < number_columns_selected && j < number_of_columns; j++){
// Do stuff to the selected items.
self.cache.items[top_left_selected_index + (i * number_of_columns) + j];
}
}