Changing div width with a button - javascript

i am trying to learn JS, React and Node.js. Now i am trying to change the width of a div with a button but i'm getting always the same error, that it "Cannot read property 'style' of null
Here is my JS
var sideNav = document.getElementById("side-nav");
var linksSideNavTop = document.getElementById("links-side-nav-top");
var linksSideNavBottom = document.getElementById("links-side-nav-bottom");
function openMenuTablet() {
if (sideNav.style.width === '70px') {
sideNav.style.width = '300px';
linksSideNavTop.style.display = "block";
linksSideNavBottom.style.display = "block";
} else {
sideNav.style.width = '70px';
linksSideNavTop.style.display = "none";
linksSideNavBottom.style.display = "none";
};
}
Here the CSS
#side-nav {
position: fixed;
background-color: #454545;
height: 100%;
width: 70px;
}

Have a look at this working example:
function changeWidth() {
document.getElementById('myContainer').style.width = '500px'
}
#myContainer {
width: 250px;
height: 50px;
background: #aaa;
}
<div id="myContainer"></div>
<button onclick="changeWidth()">Change width</button>

Above answers Are correct, also I can show my example how to do that by .classList.toggle()
const box = document.querySelector(".box")
const button = document.querySelector("button")
button.addEventListener("click", () => {
box.classList.toggle("toggle")
})
.box{
width: 100px;
height: 100px;
background: red;
margin-bottom: 10px;
transition: 0.5s
}
.box.toggle{
width: 200px;
}
<div class="box"></div>
<button>Toggle Class List</button>
if your class list has toggle your div width increases and and in Javascript .toggle method removes and adds your passed classList which is "toggle" in our case

Are you sure you have declared the items with the id´s of links-side-nav-top, links-side-nav-bottom and side-nav?
You can try to console.log(linksSideNavTop); to check existence of id.

The error "Cannot read property 'style' of null" means that the code to get the element (document.getElementById()) isn't retrieving (founding) an element with that particular ID.
I would suggest for you to double check that you don't have duplicated ID with the same exact name and also to move the document.getElementById() inside your function.
function openMenuTablet() {
var sideNav = document.getElementById("side-nav");
var linksSideNavTop = document.getElementById("links-side-nav-top");
var linksSideNavBottom = document.getElementById("links-side-nav-bottom");
if (sideNav.style.width === '70px') {
// ...
} else {
// ...
};
}
Also a good alternative to document.getElementById() would be document.querySelector():
document.querySelector("h1"); // Search html tag "h1"
document.querySelector(".rect"); // Search class name "rect"
document.querySelector("#square"); // Searcd id name "square"
For last, to know if you are really founding and retrieving the intended element you can for example console.log() the var element:
var sideNav = document.getElementById("side-nav");
console.log(sideNav);
// ...

Related

How to make this div scrollTop value match that of the textarea?

I'm using a div to format and display the text from a textarea of equal dimensions and I need them to be permanently in sync. However, I haven't been able to synchronize their respective scrollTops after the input text goes past the bottom of the textarea.
My process has been similar to the one described here, however I can't get his solution to work on my project.
Here's a demo and snippets of the minimum relevant code:
<section>
<div class="input-text__container">
<div id="input-text--mirror" class="input-text"></div>
<textarea
id="input-text--original"
cols="30"
rows="6"
autofocus
class="input-text"
placeholder="Enter your text here"
autocomplete="off"
autocorrect="off"
spellcheck="false"
></textarea>
</div>
<section>
#import url('https://fonts.googleapis.com/css2?family=Inter:wght#400;500&display=swap');
html {
font-size: 62.5%;
box-sizing: border-box;
scroll-behavior: smooth;
}
*,
*::after,
*::before {
margin: 0;
padding: 0;
border: 0;
box-sizing: inherit;
vertical-align: baseline;
}
body {
height: 100vh;
}
section {
display: flex;
flex-direction: column;
min-height: 100%;
padding: 1rem;
}
.input-text__container {
width: 100%;
position: relative;
flex: 1;
}
.input-text {
width: 100%;
height: 100%;
position: absolute;
font-size: 3.2rem;
overflow-wrap: break-word;
font-family: "Inter";
}
#input-text--mirror {
background-color: #e9ecf8;
color: #0a3871;
overflow: hidden;
}
#input-text--original {
background-color: transparent;
-webkit-text-fill-color: transparent;
resize: none;
outline: none;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
#input-text--original::placeholder {
color: #e9ecf8;
-webkit-text-fill-color: #052051;
}
#input-text--original::selection {
-webkit-text-fill-color: #ffffff;
}
.invalid {
font-weight: 400;
color: #ff0000;
}
#input-text--original::-webkit-scrollbar {
display: none;
}
let invalidInput = false;
const patterns = {
valid: "a-z ",
invalid: "[^a-z ]",
linebreaks: "\r|\r\n|\n",
};
const textIn = document.querySelector("#input-text--original");
const mirror = document.querySelector("#input-text--mirror");
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent;
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
mirror.scrollTop = textIn.scrollTop;
});
textIn.addEventListener(
"scroll",
() => {
mirror.scrollTop = textIn.scrollTop;
},
{ passive: true }
);
On a desktop screen typing the first 8 natural numbers in a column should be enough to reproduce the issue.
The last thing I checked, but perhaps the most relevant so far was this. It seems to deal with the exact same issue on React, but I'm afraid I don't know how to adapt that solution to Vanilla JavaScript, since I'm just starting to learn React. Please, notice, I'm trying to find a solution that doesn't depend on libraries like jQuery or React.
Besides that, I tried the solution described in the aforementioned blog, by replacing return "<br/>"; with return "<br/> "; in my validateInput function but that didn't work. I also added a conditional to append a space to plain in const plain = textIn.value; in case the last char was a linebreak, but I had no luck.
I also included console.log commands before and after mirror.scrollTop = textIn.scrollTop; in the textIn scroll handler to track the values of each scrollTop and even when they were different, the mirror scrollTop wasn't updated. I read it might be because divs weren't scrollable by default, but adding "overflow: scroll" to its styles didn't fix the problem either.
I read about other properties related to scrollTop, like offsetTop and pageYOffset, but they're either read-only or not defined for divs.
I've reviewed the following posts/sites, too, but I've still haven't been able to fix this problem.
https://codepen.io/Goweb/pen/rgrjWx
https://stackoverflow.com/questions/68092068/making-two-textareas-horizontally-scroll-in-sync
Scrolling 2 different elements in same time
React : setting scrollTop property of div doesn't work
sync scrolling of textarea input and highlighting container
.scrollTop(0) not working for getting a div to scroll to the top
How to attach a scroll event to a text input?
I no longer remember what else I've reviewed, but nothing has worked and I no longer know what else to do. Thank you for your attention and help.
After trying to replicate the solution for a React app that I mentioned in the post, using vanilla JavaScript (demo here), I tried to apply that to my own project and all I had to do was adding a <br> tag to the mirror in the end of my validateInput function. That is: mirror.innerHTML = mirrorContent + "<br>";.
Besides that, updating the mirror's scrollTop every time the input event on the textarea was triggered was not needed. Neither was it to pass the { passive: true } argument to the scroll event.
The modified code is here:
function validateInput(string, className) {
let anyInvalidChar = false;
// Generate regular expressions for validation
const regExpInvalids = new RegExp(patterns.invalid, "g");
const regExpLinebreaks = new RegExp(patterns.linebreaks);
// Generate innerHTML for mirror
const mirrorContent = string.replace(regExpInvalids, (match) => {
if (regExpLinebreaks.test(match)) {
return "<br/>";
} else {
anyInvalidChar = true;
return `<span class=${className}>${match}</span>`;
}
});
// Update mirror
mirror.innerHTML = mirrorContent + "<br>";
return anyInvalidChar;
}
textIn.addEventListener("input", (e) => {
const plain = textIn.value;
const newInputValidity = validateInput(plain, "invalid");
});
textIn.addEventListener("scroll", () => mirror.scrollTop = textIn.scrollTop);

JS Variable Cannot Change Element Properties

Quick question here, I encountered this problem today while practicing some JS. I wanted to create a basic prototype to loop through a "div" background-color array on click, but I realized that assigning the element property to a variable (instead of using the event target) impedes me to change the actual values.
This is the JS code:
let colors = ["blue", "yellow", "orange", "red"]
let n = 1;
document.querySelectorAll('div').forEach(occurence => {
occurence.addEventListener('click', (e) => {
let classes = e.target.className;
classes = colors[n];
n++;
console.log(classes);
if (n >= 4) {n = 0;}
});
});
So, changing the actual e.target.className works just fine, but trying to change the assigned "classes" variable does nothing. I feel like this may be a matter of specificity, or JS being unable to access the actual property values, or some akin beginner mistake.
e.target.className passes by value when you have let classes = e.target.className, so classes contains a copy of its data. Changing classes just changes the copy, rather than what's stored in e.target.classname.
Actually, you are not changing the value of e.target.className. What you do, is assigning the value of e.target.className to the variable/let-binding classes. To assign one of the color values to the className property, the assignment has to be the other way around:
e.target.className = colors[n];
let classes = e.target.className will assign the current string value of className to classes. And while you can assign a new colour value to classes that won't assign the new colour value to the className property of the element. For that you want to explicitly assign it: e.target.className = colors[i].
You may also want to remove the need to add a event listener to all the elements. Event delegation allows you to add one listener to a parent element which captures events from its child elements as they "bubble up" the DOM.
Here's an over-wrought example:
const colors = ['blue', 'yellow', 'orange', 'red'];
// Cache the elements first, and add a listener to
// the container
const counter = document.querySelector('.index');
const container = document.querySelector('.container');
container.addEventListener('click', handleClick);
let count = 0;
function handleClick(e) {
// Check to see if the element that was clicked
// was a div element
if (e.target.matches('.container div')) {
// Update the counter element, the `className` value,
// and the `textContent of the "box", and then update
// the count value
counter.textContent = `Color index: ${count}`;
e.target.className = colors[count];
e.target.textContent = colors[count];
count = count < colors.length - 1 ? ++count : 0;
}
}
.container { display: grid; gap: 0.4em; grid-template-columns: repeat(2, 50px); }
.container div { display: flex; justify-content: center; align-items: center; height: 50px; width: 50px; border: 1px solid lightgray; }
.container div:hover { cursor: pointer; border: 1px solid darkgray; }
.blue { background-color: lightblue; }
.yellow { background-color: #ffff00; }
.orange { background-color: darkorange; }
.red { background-color: red; }
.index { margin-top: 0.5em; }
<div class="container">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="index">Color index:</div>

How do I filter an array by display type if display type is set by bootstrap?

I am trying to filter an array of portfolio cards so that I can see which items are displayed or not.
let pfCard = document.getElementsByClassName("PortfolioCard")
const visibleCards = [...document.getElementsByClassName("PortfolioCard")].filter(x => x.style.display != "none");
console.log(visibleCards.length);
The displayed cards are having the display set to none by a bootstrap class when the media query is met. The code I have been trying to run still grabs all the elements in the array no matter the display type. There are 8 elements in the array and 2 are set to display: none; currently.
Because the element's display property is set by the CSS file, you'll need to get the computed style using window.getComputedStyle:
let pfCard = document.getElementsByClassName("PortfolioCard")
const visibleCards = [...document.getElementsByClassName("PortfolioCard")].filter((x) => {
return window.getComputedStyle(x).display != "none"
});
console.log(visibleCards.length);
.PortfolioCard {
width: 100px;
height: 100px;
background: red;
margin: 10px;
}
.PortfolioCard:nth-child(3) {
display: none;
}
<div class="PortfolioCard"></div>
<div class="PortfolioCard"></div>
<div class="PortfolioCard"></div>

Scroll function to navigate to appropriate section using JavaScript

My goal is to complete a dynamic single landing page using JavaScript. HTML and CSS files were already provided and I managed to build an unordered list by manipulating the DOM.
The thing that got me stuck is: When clicking an item from the navigation menu, the link should scroll to the appropriate section.
I cannot get this to work :/
Below is the JS code so far.
/* Declare variables for the fictive document and menu list to retrieve and store variables as unordered list */
const container = document.createDocumentFragment();
const menuList = document.getElementsByTagName('section');
/* Function to create the navigation menu as a clickable link */
function navigationLink(id, name) {
const navLink = `<a class = "menu__link" data-id=${id}">${name}</a>`;
return navLink;
}
/* Function for the navigation list, built as an unordered list */
function createNavigation() {
for (let i = 0; i < menuList.length; i++) {
const newMenuListItem = document.createElement('li');
const menuListName = menuList[i].getAttribute('data-nav')
const menuListID = menuList[i].getAttribute('id')
newMenuListItem.innerHTML = navigationLink(menuListID, menuListName)
container.appendChild(newMenuListItem);
}
/* Retrieve the id from the ul section to be added to the document fragment: container */
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.appendChild(container);
}
// Add class 'active' to section when near the top of viewport
function setActiveClass() {
for (let i = 0; i < menuList.length; i++) {
if (isInViewport(menuList[i])) {
menuList[i].classList.add("your-active-class");
} else {
menuList[i].classList.remove("your-active-class");
}
}
}
To solve the problem I looked into another piece of code that I haven't been able to function properly.
function scrollToElement(event) {
if (event.target.nodeName === 'A') {
const menuListID = event.target.getAttribute('data-id');
const menu = document.getElementById(menuListID);
menu.scrollIntoView({ behavior: "smooth" });
}
}
document.addEventListener('scroll', function () {
setActiveClass();
});
const navBarMenu = document.getElementById('navbar__list')
navBarMenu.addEventListener('click', function (event) {
scrollToElement(event)
})
You can use anchor to do this. This will make your life easier than trying to do this in js.
To use it, you just have to set an id to a node. Like <div id="myFirstId"></div> and then, set the href of your link to #myFirstId.
If you want your scroll to not be instant, you can add the scroll-behavior: smooth to the scrollable element.
html {
scroll-behavior: smooth;
}
#scrollable {
overflow:auto;
}
#firstDiv {
background-color: green;
height: 700px
}
#secondDiv {
background-color: yellow;
height: 200px;
}
#thirdDiv {
background-color: blue;
height: 500px;
}
firstDiv
SecondDiv
thirdDiv
<div id="scrollable">
<div id="firstDiv"></div>
<div id="secondDiv"></div>
<div id="thirdDiv"></div>
</div>
For your code, just change this line <a class = "menu__link" data-id=${id}">${name}</a>
To this <a class="menu__link" href="#${id}">${name}</a>

Why is the class information for this html element not accessed by Javascript?

In my HTML for my file I have a div with the id "divNavyBox." The code is below.
<div id="divNavyBox" class="box" onmouseover="animated.doAnimation()"></div>
Note that once the mouse hovers over it, it executes the doAnimation() from var animated.
var animated = {
el : document.getElementById("divNavyBox"),
doAnimation : function() {
if (el.className=="box") {
el.className="boxAlt";
}
if (el.className=="boxAlt") {
el.className="box";
}
}
};
I want it to switch between these two cs classes once the method doAnimation is executed. However, it doesn't do anything. I put an alert statement inside of the if(el.className="box" and it didn't ring up as I executed the function, even though the class really IS box. The two CS classes that I want to be used are listed below:
.box {
width: 100px;
height: 100px;
background-color: navy;
}
.boxAlt {
width: 100px;
height: 100px;
background-color: red;
}
Why does the boolean statement el.className="box" keep returning false?
here you assign boxAlt if current = box
if (el.className=="box") {
el.className="boxAlt";
}
here you switch back if current is boxAlt which is allways true if the class has been box from the beginning.
if (el.className=="boxAlt") {
el.className="box";
}
Change it to something like:
doAnimation : function() {
el.className = el.className == "box" ? "boxAlt" : "box";
}
There are at least three problems with your code:
I wonder why there is a ; before onmouseover.
Then you use el.className="box" in the if() which assigns "box" to className. Use == to compare.
Lastly, el.style.className is undefined.
el.className = "box" is not a Boolean statement, it's an assignment.
el.className == "box" is a boolean statement.

Categories