Calculate Javascript pixel widths for list of elements - javascript

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

Related

Progress Bar - Loop Through Array and Display Divs

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.

Dynamically generate a grid of images to fit a predefined area

I need to create a grid of instagram images and fit them in a predefined area. For example: I have a div of 600x400px and 50 square images (there will always be an even number of images). I need to arrange the images in rows and columns to fill as much space of the containing div as possible.
You can see a fiddle of what I am trying to achieve here. This is the end result:
This is actually quite a good result. Things get a little more out of whack when you change the number of images, to 44 for example.
This is how I am working out the grid rows and columns:
//container size:
var width = 600;
var height = 400;
var items = 44;
var rows;
var cols;
var rows = Math.floor(Math.sqrt(items));
while (items % rows != 0) {
rows = rows - 1
}
cols = (items / rows);
if (rows > cols) { //make it landscape
//swap values
rows = [cols, cols = rows][0];
}
The output of this for 44 images for example is 4 rows of 11 columns. The combined height of the 4 rows is 232px, quite a lot less than 400px. So how do I change this to fill more space in the container?
The math's a bit tricky due to wrapping. An alternative is to build the images string without any dimensions, then grow them iteratively until they overflow the container.
var width = 400,
height = 300,
items = 44,
grid_str = '',
$grid = $('#grid'),
i;
function dimensions(x) {
$('#grid div').css({
width: x,
height: x
});
};
$grid.width(width).height(height);
for(i = 0 ; i < items ; i++) {
grid_str+= '<div class="col"></div>';
}
$grid.html(grid_str);
for(i = 1 ; i < 1000 ; i++) {
dimensions(i);
if($grid[0].scrollHeight > $grid.height()) {
break;
}
}
dimensions(i-1);
In this fiddle, change width, height, and items, and you'll see that the maximum size is always chosen for div squares.

How to find selected elements within a javascript marquee selection box without using a loop?

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];
}
}

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);
}
}

Javascript - dynamically changing width of an element

There are a few similar questions on Stack but I think mine is the result of some syntax error on my part. Anyway, I'm building a lightbox in javascript and want to dynamically set the absolute position (left % css) to be half of the width of the element.
This is a snippet from the code. Full code is here http://jsfiddle.net/Yab3Q/4/
var modal = document.getElementById(btnId+"-modal");
var modalChildren = modal.childNodes;
for (var x = 0; x<modalChildren.length; x++){
if (!(modalChildren[x].className === "lightBox")){
var childWidth = modalChildren[x].offsetWidth;
var halfWidth = childWidth / 2;
modalChildren[x].style.left = halfWidth + "px";
}
}
The line modalChildren[x].css.left = halfWidth + "px"; is returning "Uncaught TypeError: Cannot set property 'left' of undefined". However, modalChildren[x].className and modalChildren[x].offsetWidth; both return the expected values, so I'm unsure why I can view but not update the css here.
You try to alter the style properties of the following elements:
0: <div>
1: #text
2: <img>
3: #text
You can see this list for yourself by typing
console.log(modalChildren)
since a textNode does not have a style property, an error is thrown
text node has no style property:
log and check:
for (var x = 0; x<modalChildren.length; x++){
console.log(modalChildren[x]);
console.log(modalChildren[x].style);
if (!(modalChildren[x].className === "lightBox")){
var childWidth = modalChildren[x].offsetWidth;
var halfWidth = childWidth / 2;
modalChildren[x].style.left = halfWidth + "px";
}
}
Take a look at node types: http://www.javascriptkit.com/domref/nodetype.shtml

Categories

Resources