I have an exercise for a web development school, which tells me : "how to clone a square 7 times, and augmenting his number by 1 ( from 1 to 8 then).
I think I need a "while" cycle, with incrementation until i = 8, but I'm not sure. Also, can I create a variable with an html element inside, and then try to duplicate it?
// create the first square and insert it into doc
const square = document.createElement('div');
square.style.width = '50px';
square.style.height = '50px';
square.style.margin = '10px';
square.style.backgroundColor = 'blue';
square.innerText = 1;
document.body.appendChild(square);
// simple cycle where i begins from needed number
for (let i = 2; i < 9; i++) {
const clone = square.cloneNode();
clone.innerText = i;
document.body.appendChild(clone);
}
Hopefully this will make clear how to select elements on the page, use a loop to make more, and change what is inside each element.
// Identifies existing HTML elements so JavaScript can interact with them
const container = document.getElementById("container");
const original = document.querySelector(".square");
// Identifies a constant and a variable for our loop
const total = 8;
let number = 1;
// Runs a loop as long as the condition is true
// (Note: It won't run once number == 8, but the time before that will make square #8)
while(number < total){
// Changes a value that will effect whether the condition is true
number++;
// Prints some information about what this iteration of the loop is doing
console.log("making square number " + number);
// Makes a copy of an HTML element
const copy = original.cloneNode();
// Changes the copy to be different from the original
copy.innerHTML = number;
// Adds the copy to the page by appending it to the `container` element
container.appendChild(copy);
}
.square{
height: 16px; /* How tall the element will be, not including padding */
width: 16px;
padding: 16px; /* How much extra (blue) space the element will include */
margin: 16px; /* How much extra (white) space the element will have around it */
font-size: 16px;
text-align: center
}
.blue{
background-color: lightskyblue;
}
<div id="container">
<div class="blue square">1</div>
</div>
Related
I've tried this code that was posted but I can't get it to work the way I need. I can get it to create both fields but I can't make them look the same as the format of the first fields. How can I Add the entire section and make it look the same? and control the maximum number of activities inserted.
TIA
This is what I'm trying to acomplish
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn{color:red;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>
First, the problems:
I can get it to create both fields but I can't make them look the same as the format of the first fields...
This is likely because in your original .activityGroup there are a number of <br> elements between the <input> and the <textarea> elements – incorrectly – used for spacing which you haven't inserted into the copy.
Also, two of the attributes of the original <textarea> (rows="5", and cols="50") are omitted from the one you create within the function, not to mention the line-breaks and other white-space.
This can be rectified by simply creating, and appending, those other elements:
// first, some utility functions to simplify life:
const D = document,
get = (sel, ctx = D) => ctx.querySelector(sel),
getAll = (sel, ctx = D) => [...ctx.querySelectorAll(sel)],
create = (tag, props) => Object.assign(D.createElement(tag), props),
// renamed variables for consistency between JavaScript
// and the HTML upon which it acts:
addBtn = get("#addBtn"),
activity = get("#activity"),
// creating a generator function to use as a counter;
// fundamentally this is little different from the
// original i = 0 and then incrementing in the function
// body, but the generator can't be affected by an
// accidental decrement:
iterator = function*() {
// initial value:
let count = 1;
// a deliberate infinite loop, but the
// generator function exits/pauses at
// every yield so while this is an 'infinite
// function' it doesn't cause any blocking:
while (true) {
// yields value and then increments it:
yield count++
}
},
// getting a reference to the generator function:
counter = iterator(),
remove = (e) => e.currentTarget.closest('div').remove();
addBtn.addEventListener("click", function() {
// getting the current count of all elements matching
// the selector within the document:
const currentCount = getAll('.activityGroup').length,
// retrieving the next value from the
// generator function:
i = counter.next().value;
// if the currentCount is greater than 4 (again, using
// a Yoda condition):
if (4 < currentCount) {
// exit the function:
return false;
}
// creating a new <div>:
const activityGroup = create('div', {
// assigning the class-name of 'activityGroup':
className: 'activityGroup'
}),
// creating a new <button>:
removeButton = create('button', {
// assigning the class-name of 'delBtn',
className: 'delBtn',
// and the text-content of 'X':
textContent: 'X'
}),
input = create('input', {
type: 'text',
id: `activity_${i}`,
placeholder: `Activity ${i}`
}),
textarea = create('textarea', {
id: `activity_description_${i}`,
placeholder: `Activity description ${i}`
});
// binding the remove() function as the'click' event-handler
// on the removeButton:
removeButton.addEventListener('click', remove);
// using the HTMLElement.dataset API to set the
// custom data-count attribute to the current
// value of the i variable (for use in the CSS):
activityGroup.dataset.count = i;
// using Element.append() to append multiple elements
// in one call:
activityGroup.append(input, textarea, removeButton);
activity.appendChild(activityGroup);
});
/* caching common values (useful for theming and
maintenance): */
:root {
--bg-primary: hsl(0 0% 0% / 0.3);
--columns: 2;
--fs: 16px;
--spacing: 0.5em;
}
/* a simple CSS reset to remove default
margins and padding, and to have all
elements sized in a way that includes
their padding and border-widths in the
assigned size: */
*,::before,::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-size: var(--fs);
}
#activity {
/* using CSS Grid for layout: */
display: grid;
/* setting a space - held in the custom
property - between adjacent elements: */
gap: var(--spacing);
/* creating a number of columns (equal to the value held
in the --columns property) of equal width, each of one
fraction of the remaining space) */
grid-template-columns: repeat(var(--columns), 1fr);
padding: var(--spacing);
}
.activityGroup {
/* aligning an element in the grid to the start of that
grid (alignment is on the cross-axis of the inline-
axis, the inline-axis being the writing direction
of the language of the user's browser): */
align-self: start;
border: 0.2em solid var(--bg-primary);
display: grid;
gap: inherit;
padding: var(--spacing);
}
.activityGroup::before {
background-color: var(--bg-primary);
/* setting the content of the ::before pseudo element to the
string of 'Activity ' concatenated with the attribute-value
of the data-count attribute: */
content: "Activity " attr(data-count);
display: block;
/* spanning all three grid-columns: */
grid-column: span 3;
/* applying padding to the start of the inline axis: */
padding-inline-start: var(--spacing);
/* moving the element outwards with negative margins,
to place the content against the outer edges of the
element, despite the spacing on that element: */
margin-block-start: calc(-1 * var(--spacing));
margin-inline: calc(-1 * var(--spacing));
}
input,
textarea {
grid-column: span 3;
padding: 0.5em;
}
textarea {
min-block-size: 5em;
resize: vertical;
}
.delBtn {
color: red;
grid-column: 3;
}
/* using a simple media query to modify the values of the CSS Custom
properties as required to improve the experience on different
devices: */
#media screen and (max-width: 700px) {
:root {
--columns: 1;
--fs: 18px;
}
}
<!-- we don't appear to be using jQuery, so I removed that <script> element -->
<!-- switching id and classes to use camelCase rather than
having arbitrary mixed-case format; choose a style you
prefer, but remember that consistency matters more than
the choice you make: -->
<div id="activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<!-- removed the <br> elements, as well as the "rows" and "cols"
attributes from the <textarea>: -->
<textarea placeHolder="Type your description"></textarea>
<button class="delBtn">X</button>
</div>
<!-- Remove button -->
</div>
<!-- changed the id from "add" to "addBtn," purely for consistency
but, again, make your own choice on that: -->
<button id="addBtn">Add Activity</button>
As to your question:
How can I [add an] entire section and...control the maximum number of activities inserted[?]
Controlling the number of total activities/sections on the page is relatively easy and simply requires you to check the number of existing sections before adding a new one:
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
// using document.querySelectorAll() to retrieve a NodeList of all
// elements matching the supplied CSS selector, and then retrieving
// the length of that NodeList:
let currentCount = document.querySelectorAll('.activityGroup').length;
// using a "Yoda condition" to see if the currentCount is greater than
// 4 (this approach guards against the most likely error of an assessment,
// that of accidentally assigning a value instead of comparing); if it is:
if (4 < currentCount) {
// we exit the function here:
return false;
}
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
// creating a <br> element:
const br = document.createElement('br');
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
// appending a clone of the <br> element:
newspan.appendChild(br.cloneNode());
// appending the <br> element:
newspan.appendChild(br);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
// setting the rows and cols attributes:
txtarea.setAttribute('rows', 5);
txtarea.setAttribute('cols', 50);
// appending white-space (approximating what's in the HTML):
newspan.append(document.createTextNode('\n '));
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>
Now, I'd like to improve the above functions as follows with explanatory comments in the code itself:
const addActivity = document.getElementById("add");
var i = 0;
const activityDiv = document.getElementById("Activity");
addActivity.addEventListener("click", function() {
// using document.querySelectorAll() to retrieve a NodeList of all
// elements matching the supplied CSS selector, and then retrieving
// the length of that NodeList:
let currentCount = document.querySelectorAll('.activityGroup').length;
// using a "Yoda condition" to see if the currentCount is greater than
// 4 (this approach guards against the most likely error of an assessment,
// that of accidentally assigning a value instead of comparing); if it is:
if (4 < currentCount) {
// we exit the function here:
return false;
}
i++;
const newspan = document.createElement('div');
newspan.className = "activityGroup";
// creating a <br> element:
const br = document.createElement('br');
const removeButton = document.createElement('button');
removeButton.addEventListener('click', function(e) {
e.target.closest(".activityGroup").remove();
});
removeButton.className = "delbtn";
removeButton.innerHTML = "X";
//
const txtfield = document.createElement('input');
const txtarea = document.createElement('textarea');
//
txtfield.id = 'activity_' + i;
txtfield.placeholder = "Activity " + i;
newspan.appendChild(txtfield);
// appending a clone of the <br> element:
newspan.appendChild(br.cloneNode());
// appending the <br> element:
newspan.appendChild(br);
//
newspan.appendChild(txtarea);
txtarea.id = 'activity_description_' + i;
txtarea.placeholder = "Activity Description " + i;
// setting the rows and cols attributes:
txtarea.setAttribute('rows', 5);
txtarea.setAttribute('cols', 50);
// appending white-space (approximating what's in the HTML):
newspan.append(document.createTextNode('\n '));
newspan.appendChild(removeButton);
activityDiv.appendChild(newspan);
});
.delbtn {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="Activity">
<div class="activityGroup">
<input placeHolder="Type your activity" />
<br>
<br>
<textarea rows="5" cols="50" placeHolder="Type your descvription"></textarea>
<button class="delbtn">X</button>
</div>
<!-- Remove button -->
</div>
<button id="add">Add Activity</button>
JS Fiddle demo.
References:
CSS:
align-self.
box-sizing.
content.
CSS custom properties.
CSS logical properties.
display.
gap.
grid-column.
grid-template-columns.
grid-template-rows.
inline-size.
margin.
margin-block.
margin-inline.
min-block-size.
padding.
padding-block.
padding-inline.
repeat().
var().
JavaScript:
document.createElement().
document.querySelector().
document.querySelectorAll().
Element.append().
Element.querySelector().
Element.querySelectorAll().
Element.remove().
Event.target.
Event.currentTarget.
EventTarget.addEventListener().
Generator functions.
HTMLElement.dataset.
Node.appendChild().
Object.assign().
yield.
Do you mean "If I have Activity 1, 2, 3, 4, 5 and Delete 3 I want to be able to get Activities 1, 2, 4, 5?"
From what I've gathered, you want a button that adds and deletes activities but you only want it to work when previous activity isn't empty.
In the javascript, when you handle the buttons click event check previous activity, if it's empty, return, if it isn't empty, execute the add/delete code.
I want to display an array of numbers, after that if he users wishes to change any of the numbers in array then he/she should write the desired value in its place and click submit button the array gets updated and display the updated array.
I have tried this till now, and gone through numerous articles to find a solution but i couldn't find one which fits here.
This is what my code looks like, it is a bit long but I dont know how much code is adequate so this is it.. please help me get this.
making grid editable
<style>
#container{
display:grid;
grid-template-columns: repeat(9, 8%);
margin-left: 35%;
margin-right:25%;
margin-top:10%;
}
</style>
<body style="background-color:grey">
<div id='container'>
</div>
<input id="someInput" type="text" value=<p id="output"></p>>
<input type="button" value="submit" onClick="doStuff()">
</body>
<script>
var myContainer = document.getElementById('container')
grid=[0,1,4,6,3,0,9,3,2];
for(var i=0;i<9;i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id= `${i}`
var num=grid[i];
myInput.value=num;
myContainer.appendChild(myInput)
}
var x = document.createElement("BUTTON");
function doStuff(){
var nameElement = document.getElementById("someInput");
a = nameElement.value
b = nameElement.id
new_grid=[];
for(var j=0;j<9;j++){
if(j==b){
new_grid[j]=a;
}
else{
new_grid[j]=grid[j];
}
}
for(var i=0;i<9;i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id= `${i}`
var num=new_grid[i];
myInput.value=num;
myContainer.appendChild(myInput)
}
}
window.onload = function() {
//when the document is finished loading, replace everything
//between the <a ...> </a> tags with the value of splitText
document.getElementById("output").innerHTML=grid[4];
}
</script>
'''
then I tried to add a input box so that i can show the array numbers in value attribute of it and when users updates it and clicks submit button it gets updated but then i wasn't able to get array numbers in value attribute of input tag.
This will update your grid array values with the user inputted values on button click.
var myContainer = document.getElementById('container');
grid = [0,1,4,6,3,0,9,3,2];
for(var i=0; i < grid.length; i++){
var myInput = document.createElement('input')
//we want to add position of an input box as id
myInput.id = `${i}`
myInput.value = grid[i];
myContainer.appendChild(myInput);
}
var x = document.createElement("BUTTON");
x.textContent = "UPDATE";
myContainer.appendChild(x);
x.addEventListener("click", UpdateGridFromUserInput, false);
function UpdateGridFromUserInput() {
var allinputs = document.getElementById('container').getElementsByTagName('input');
for(var j = 0; j < allinputs.length; j++) {
grid[j] = allinputs[j].value;
}
console.log(grid);
}
<div id="container">
</div>
Here's an example. I just use input number fields. You can set the min max and add an onChange event if you want to change the value of the nums array.
const nums = [1, 2, 3, 4, 5];
const container = document.querySelector(".container");
nums.forEach(num => {
const numElm = document.createElement("input");
numElm.classList.add("number");
numElm.type = "number";
numElm.min = 1;
numElm.max = 9;
numElm.value = num;
container.appendChild(numElm);
});
.container {
display: flex;
justify-content: space-between;
}
.number {
padding: .5rem;
width: 2rem;
text-align: center;
border: 2px solid grey;
border-radius: 10px;
}
<div>
<p>Selected numbers</p>
<div class="container" />
</div>
I think you have got relevant solutions already, but I would like to show you "little another way" which save a few rows of code and it is more optimal with larger arrays.
Instead of the iterating array when you want to change a single number, you can add an event listener to it. You save an ID to the element by the index of the array, so it is easy to approach the actual position in the array.
First thing first, I would like to comment on a few things in your code, which could be better (we all learn!)
for(var i=0;i<9;i++){var myInput = document.createElement('input') ...
You want to create an input for each element in the array. As a programmer, you know that there will be 9 elements, but (as a programmer :)) you don't always want to remember and count how many elements the array has. It is better to use grid.length which returns the actual length of the array.
function doStuff(){...}
Always name your functions as what they are really doing. It really helps!
var nameElement = document.getElementById("someInput");
a = nameElement.value
b = nameElement.id
Always name your properties as what they actually mean. You could use "input", "inputValue", "inputId". Helps too :)
In doStuff() function, you are creating other inputs again. I think it is not really necessary when you have already created them.
Second thing second let's look at how it looks like as I described before.
Here is the code (use and learn ES6, it's better, trust me):
let array = [0, 1, 4, 6, 3, 0, 9, 3, 2];
let container = document.getElementById("container");
let output = document.getElementById("arrayResult");
output.innerHTML = array;
//create inputs for the array
for (let i = 0; i < array.length; i++) {
let inputElement = document.createElement("input");
inputElement.type = "number";
inputElement.id = i;
inputElement.value = array[I];
//if an user change the value, update the array at its position
inputElement.addEventListener("input", () => {
let id = inputElement.id;
array[id] = inputElement.value;
})
container.appendChild(inputElement);
}
//create submit button
let submit = document.createElement("input");
submit.type = "submit";
submit.value = "Change";
container.appendChild(submit);
//update output
submit.addEventListener("click", () => {
output.innerHTML = array;
});
<div id="container">
</div>
<div>
<label>Result:</label>
<label id="arrayResult"></label>
</div>
I have element:
<div class="show">Lorem ipsum dolor sit .</div>
css:
.show {
height: 300px;
width: 600px;
}
so before this div is rendered, is there a way to know how much chars count this div can hold. I have multiple line and my div contains multiple elements inside it ex:
https://jsfiddle.net/pvyk3etw/
I have tried so far this :
count how many characters fit in a DIV
and also used line-clamp
but because my goal is not just to add ellipsis, it also needs some text next to it- like ...more simple css overflow property does not work.
in simple words: I have
height = 300;
width= 600;
based on above numbers can we in anyways count how character count that div container can hold?
please do let me know for any resourses I may missed to check out.
thx
In continuation from comments:
"You could render the div hidden or otherwise off the visible view. Fill it with chars that you know will overflow. Then condition when the relative Y goes past the bounds, while keeping track of the count chars. Hence a loop. When the relative Y goes past the bounds. break the loop. Just an idea for an algorithm."
The following code (runnable) might address the issues, or even if needing improvement, will address the concept in the above comment. It's an "iterative" solution, so the length of the content to be inserted, is 1:1 on time increase on execution:
function determineMaxCharsBeforeOverflow(el, boundedHeight) {
// where el is the DOM element of the "span" within the div
// where boundedHeight is the height of the container "div"
let i;
let n; // char count
let m = 100000000; // arbitrary large number
for (i = 0; i < m; i++) {
el.innerHTML += "m"; // `m` is used since thats what an em is, i think
let h = el.offsetHeight; // or use whatever cross-browser equivalent
// console.log("h: " + h);
if (h > boundedHeight) {
return i; // can return i safely, i think, since we want a Base 1 result before overflow, even though i is Base 0. Might need tweaking in practice.
}
}
return -1; // returns -1 on failure, meaning m needs to be increased for the bound
}
function testFunc() {
let el = document.getElementById("test_data");
let height = 300; // hard coding here, you can replace with dynamic pixel height
console.log( determineMaxCharsBeforeOverflow( el, height ) );
}
.clip {
overflow:hidden;
overflow-wrap: anywhere; /* or its cross-browser-equivalent */
}
.show {
border: 1px solid red; /* removable */
height: 300px;
width: 600px;
}
.show .hidden {
left:-300px; /* matches width of .show */
}
<div id="actual" class="show"></div>
<div id="test" class="show clip _hidden"><span id="test_data"></span></div> <!-- remove underscore on hidden class for production -->
<button type="button" onclick="testFunc()">Click</button>
All other explanation is in the code comments.
Note: Some padding / margin issues may need to applied separately from this solution.
Is there a way in Vanilla JS to get each line of a text element when the page loads or when the window is resized?
Lets say for the element <h2>This is a beautiful example text</h2>.
On mobile (with less space it breaks into several lines) would be displayed as:
This is a
beautiful
example text
and i would like to get an array like:
["This is a", "beautiful", "example text"]
if we resize the window, maybe on desktop, than the text would have a little bit more space so it needs to break in to less lines, maybe:
This is a beautiful
example text
and then i would like to get:
["This is a beautiful", "example text"]
i have tried something like:
let text = document.querySelector('.text').textContent;
let lines = text.split(/\r|\r\n|\n/);
but i always end up getting the whole string together like ["This is a beautiful text"].
I am trying to do this, because i need to style each line independently (yeap, crazy client wishes!), i cannot change the html and it needs to work when i resize the window. So i think i need to do it with JS, and somehow see how CSS is affecting the text and breaking it into several lines...
so, to clarify, what i am trying to get with JS with the text of each line, when the text breaks because of the container getting smaller. So the Element and the text are the same, but due to less space it breaks into different amount of lines.
Any ideas?
** === UPDATE === **
I can get the number of lines using .getClientRects(). But what i am trying to do is get the text of each of the lines of the element
I've put together a mock up JS solution.
Overview:
Get an array of words
Append each word one at a time to a hidden element with the same font-sizing styles as the title
Check if the element is larger than the title, if so, add the current line (before we added the last word to it) to an array
function calcLines() {
// Build an array of each word used in the original title
var allWords = document.getElementById("title").innerText.match(/\S+/g) || [];
// The array we will fill with each line
var lines = [];
// The current line we are working on building
var currentLine = "";
// Work through the words until we're filling the correct amount of space
for (var i = 0; i < allWords.length; i++) {
// Build a new line and check if it is now too large for the container
var newLine = currentLine + allWords[i] + " ";
document.getElementById("fontSizeTester").innerText = newLine;
if (
document.getElementById("fontSizeTester").clientWidth >
document.getElementById("title").clientWidth
) {
// If the line is now larger, use the previous line (without the last added word) and reset the current line to just the last word
lines.push(currentLine.trim());
currentLine = allWords[i] + " ";
} else {
// If it's not long enough yet, just keep adding words
currentLine = newLine;
}
}
// Push any unfinshed final line to the array
lines.push(currentLine.trim());
console.log(lines);
}
// Run on load and on resize
calcLines();
window.addEventListener("resize", calcLines);
h2 {
font-size: 30px;
font-weight: normal;
font-family: arial;
}
#fontSizeTester {
position: absolute;
visibility: hidden;
height: auto;
width: auto;
white-space: nowrap;
}
<h2 id="title">This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text This is a beautiful example text</h2>
<h2 id="fontSizeTester"></h2>
You can create a flex container in your body , and attach your array there. As result all items in your array will be filling the width of the screen.
https://codepen.io/Vlasenko/pen/vYLwMvE - check it here
const cont = document.querySelector(".container");
const arr = ["This is a", "beautiful", "example text"];
arr.forEach((item) => {
const div = document.createElement("div");
div.className = "item";
div.innerText = item;
cont.appendChild(div);
});
<style>
.container {
display: flex;
flex-wrap: wrap;
}
.item {
padding-right: 5px;
}
</style>
<body>
<div class="container"></div>
</body>
I'm creating a script that takes two input dimensions, width, and height, and creates a scaled grid which is representative of how many blocks could fit in a box with the given dimensions with the following function:
function makeRow() {
for (var i = 1; i <= blocksTall; i++) {
var mb = document.createElement("div");
mb.setAttribute("class", "matrix-block mb-off");
mb.setAttribute("onClick", "select_mb('" + j + "," + i + "');");
placeBlocks.appendChild(mb);
if (i = blocksWide) {
placeBlocks.appendChild('br');
}
}
}
This function works fine to display the first row of blocks, and then inserts a break tag after the row has finished being rendered, which is exactly what I want to do. The problem is I need to generate 17 more rows, with the same number of blocks, each one under the previous row, so my first thought was, I'll just wrap another for loop around this first for loop and since there is a break there, it will render the new row below the previous one:
for (var j = 1; j <= blocksTall; j++) { // Vertical for loop.
for (var i = 1; i <= blocksWide; i++) { // Horizontal for loop.
var mb = document.createElement("div");
//mb.setAttribute("id", "matblock-" + i + "-" + j);
mb.setAttribute("class", "matrix-block mb-off");
mb.setAttribute("onClick", "select_mb('" + i + "," + j + "');");
placeBlocks.appendChild(mb);
}
if (j = blocksWide) {
placeBlocks.appendChild(brk);
}
}
Where blocksWide = 17. Here is a fiddle with the complete script. When I log the value for j in the console, it does in fact increment (which tells me that the for loop is working). What seems to be happening though is that it is for some reason rendering the row, and then either rendering the new row on top of it (seems unlikely since the break tag is rendered after each row completes) or, for some reason the children are destroyed each time a new "horizontal" for loop is run.
Does anybody know why this might be happening and how to properly get each row to be appended under the last row so it produces a grid of blocks instead of just one row?
Thanks in advance, any help is greatly appreciated.
So, I'm a bit confused about some aspects of your script, but I think you have two major issues.
Firstly, you only ever call document.createElement("br") once, which means you only ever create a single line-break; and a single line-break can only appear in one place in the DOM. This:
placeBlocks.appendChild(brk);
removes brk from its current position in the DOM and then puts it at the end of placeBlocks. You should change it to this:
placeBlocks.appendChild(document.createElement("br"));
Secondly, I don't think that if (j = blocksWide) { makes sense. Note that it's equivalent to this:
j = blocksWide;
if (blocksWide != 0) {
which means that it interferes with your for-loop by manipulating the value of j. I think the fix for that issue is simply to remove the whole if-check, and to perform its body unconditionally.
I really don't understand what you were trying to do with the remainder operators and the dividing, but blocksWide resolved to infinity causing an infinite loop, and blocksHigh was just 17. All of the other variables besides full weren't used.
You don't actually need two loops, although it is ok to do that. If you want to use just one loop you basically just need to know if i is a multiple of dispW.
So you divide i by dispW then you want to know if it is an integer, to find this you use the remainder operator for 1 and if it resolves to 0 it is an interger. It looks like this...
if ((i / dispW) % 1 === 0)
// if ( dispW=3 && ( i=3 || i=6 || i=9 || ... ) ) true;
This in a loop would look like
totalWidth = dispW * dispH; // total number of blocks
for (var i = 1; i <= totalWidth; i++) {
// do stuff;
if((i / dispW) % 1 === 0) {
// insert new line break;
}
}
The method you used for selecting the blocks was a round about way of doing it. First you shouldn't use inline javascript, second you shouldn't use javascript to embed inline javascript in a dynamically created element. Use element.onclick = function; instead.
Notice there is no braces after the function. This is because you are actually passing the function reference and not the returned value of the function.
element.onclick passes an event object to the function reference. You can use this to select the block that was clicked on like so.
for ( ... ) {
...
var element = document.createElement('div');
element.onclick = myFunction;
...
}
function myFunction(e) {
var clicked = e.target // this is the element that was clicked on
}
Also, you were creating one <br> element outside of the loop. Because appendChild moves elements and does not create elements it will just keep moving the line break until the loop finishes. It should look like this.
placeBox.appendChild(document.createElement('br'))
// append a newly created line break;
Then even if all the logic worked as intended and you create a new line break every time, floated blocks means no line breaks use display: inline-block; instead.
So in the end what you get is...
(Full difference)
window.onload = function () {
renderGrid();
};
function renderGrid() {
var blocksTall = document.getElementById('height-in').value;
var blocksWide = document.getElementById('width-in').value;
var blocksTotal = blocksWide * blocksTall;
var placeBlocks = document.getElementById('matrix-shell');
while (placeBlocks.firstChild) {
placeBlocks.firstChild.remove();
}
console.log(blocksWide + "/" + blocksTall);
for (var i = 1; i <= blocksTotal; i++) {
var mb = document.createElement("div");
mb.className = 'matrix-block mb-off';
mb.onclick = select_mb;
placeBlocks.appendChild(mb);
if (((i / blocksWide) % 1) === 0) {
var brk = document.createElement("br");
placeBlocks.appendChild(brk);
}
}
}
function select_mb(e) {
var cur_mb = e.target;
if (cur_mb.className == "matrix-block mb-off") {
// Turn cell on.
cur_mb.style.backgroundColor = "#00FF00";
cur_mb.className = "matrix-block mb-on";
} else {
//Turn cell off.
cur_mb.style.backgroundColor = "#000";
cur_mb.className = "matrix-block mb-off";
}
}
.matrix-block {
height: 10px;
width: 10px;
border: 1px solid #fff;
display: inline-block;
background-color: black;
}
.mb-off {
background-color: black;
}
#matrix-shell {
font-size: 0;
display: inline-block;
border: 1px solid red;
white-space: nowrap;}
<table>
<tr>
<td>Width:</td>
<td>
<input id="width-in" name="width-in" type="text" />
</td>
</tr>
<tr>
<td>Height:</td>
<td>
<input id="height-in" name="height-in" type="text" />
</td>
</tr>
<tr>
<td colspan="2">
<button onClick="renderGrid()">Compute</button>
</td>
</tr>
</table>
<br/>
<div id="matrix-shell"></div>