I'm trying to make floating labels dynamically resize themselves when you click on an input like more forms do today. I'm working with vanilla JS and exclusively going to use vanilla JS for this problem.
I have it set up currently so that it works with the tag. However, for my tag, it does not. For some reason, every time I've looked into this problem across a day or two or looking and then stepping away to reset myself, I cannot see why my element comes up null when I try to add the .active class to it, so as to make the font and animation play properly. Here is the JS script as it functions right now
// add active class
const handleFocus = (e) => {
const target = e.target;
target.parentNode.classList.add('active');
target.setAttribute('placeholder', target.getAttribute('data-placeholder'));
};
// remove active class
const handleBlur = (e) => {
const target = e.target;
if(!target.value) {
target.parentNode.classList.remove('active');
}
target.removeAttribute('placeholder');
};
// register events
const bindEvents = (element) => {
const floatField = element.querySelector('input');
floatField.addEventListener('focus', handleFocus);
floatField.addEventListener('blur', handleBlur);
};
// get DOM elements
const init = () => {
const floatContainers = document.querySelectorAll('.float-container');
//I can see the textarea element in this list.
console.log(document.querySelectorAll('.float-container'));
floatContainers.forEach((element) => {
if (element.querySelector('input').value) {
element.classList.add('active');
}
//The commented section below causes a "TypeError querySeclector is null"
/*
if (element.querySelector('textarea').value) {
element.classList.add('active');
}
*/
bindEvents(element);
});
};
return {
init: init
};
})();
window.onload=FloatLabel.init();
As mentioned in the commented part of the script, when I check the console.log for the list of elements it grabs, I can see the in that list. However, when I try to add the active class for when checking if text is inside the textarea, it comes up null and breaks the script.
I'm not sure if there's something I'm not seeing, or if I fundamentally don't know/understand part of what's going on. I have also checked to make sure the TypeError I'm getting is not related to the DOM not being ready. I've also made sure that the script is at the bottom of the php file and and for good measure added the defer keyword too it. Though, from what I understand about JS, putting defer on a script that's at the bottom of the document doesn't do anything, but I digress. Here is the CSS classes involved.
{
width: 66%;
position: relative;
}
.float-container.small input
{
width: 50%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
}
.float-container.small textarea
{
width: 100%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
color: red;
}
.float-container.large input
{
width: 100%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
}
.float-container.active
{
border: 2px solid #1A57FF;
outline: 2px solid rgba(26, 87, 255, 0.3);
border-radius: 4px;
}
.float-container.active label
{
transform: translate(0, 4px) scale(.75);
}
.floating-text
{
position: absolute;
font-size: 16px;
text-decoration: underline;
color: rgb(100, 100, 100);
transform: translate(0, 16px) scale(1);
transform-origin: top left;
transition: all .1s ease-in-out;
}
Finally, here is the portion of the form from the document
<div id="floatContainer" >
<label for="fName" >First Name</label>
<input type="text" id="fName" name="fName" data-placeholder="First name"><br>
</div>
<p id="fName-validate" ><p>
<div id="floatContainer" >
<label for="lName" >Last Name</label>
<input type="text" id="lName" name="lName" data-placeholder="Last name"><br>
</div>
<p id="lName-validate" ><p>
<div id="floatContainer" >
<label for="email" >Email</label>
<input type="text" id="email" name="email" data-placeholder="Email"><br>
</div>
<p id="email-validate" ><p>
<div id="floatContainer" >
<label for="phoneNumber" >Phone Number</label>
<input type="tel" id="phoneNumber" name="phoneNumber" data-placeholder="Phone"><br>
</div>
<p id="phoneNumber-validate" ><p>
<div id="floatContainer" >
<label for="problemDesc" >Description of problem</label>
<textarea id="problemDesc" name="problemDesc"></textarea><br>
</div>
CodePudding user response:
I'd rather recommend giving your inputs and textareas a class like input
.
This way you don't have to check, your field's type and it's current value.
// add active class
const handleFocus = (e) => {
const target = e.target;
target.parentNode.classList.add("active");
target.setAttribute("placeholder", target.getAttribute("data-placeholder"));
};
// remove active class
const handleBlur = (e) => {
const target = e.target;
if (!target.value) {
target.parentNode.classList.remove("active");
}
target.removeAttribute("placeholder");
};
// register events
const bindEvents = (element) => {
const floatField = element.querySelector(".input");
if (floatField) {
floatField.addEventListener("focus", handleFocus);
floatField.addEventListener("blur", handleBlur);
}
};
// get DOM elements
const init = () => {
const floatContainers = document.querySelectorAll(".float-container");
floatContainers.forEach((element) => {
let input = element.querySelector(".input");
let inputVal = input ? input.value : "";
if (inputVal) {
input.classList.add("active");
}
bindEvents(element);
});
};
init();
/*{
width: 66%;
position: relative;
}
*/
* {
box-sizing: border-box
}
.input{
border: 2px dotted #000;
width: 100%!important;
}
.float-container.small input
{
width: 50%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
}
.float-container.small textarea
{
width: 100%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
color: red;
}
.float-container.large input
{
width: 100%;
outline-offset: 1px;
font-size: 16px;
padding: 16px 4px 10px 4px;
border-radius: 4px;
}
.float-container.active
{
border: 2px solid #1A57FF;
outline: 2px solid rgba(26, 87, 255, 0.3);
border-radius: 4px;
}
.float-container.active label
{
transform: translate(0, 4px) scale(.75);
}
.floating-text
{
position: absolute;
font-size: 16px;
text-decoration: underline;
color: rgb(100, 100, 100);
transform: translate(0, 16px) scale(1);
transform-origin: top left;
transition: all .1s ease-in-out;
}
<div id="floatContainer" >
<label for="fName" >First Name</label>
<input type="text" id="fName" name="fName" data-placeholder="First name"><br>
</div>
<p id="fName-validate" >
<p>
<div id="floatContainer" >
<label for="lName" >Last Name</label>
<input type="text" id="lName" input name="lName" data-placeholder="Last name"><br>
</div>
<p id="lName-validate" >
<p>
<div id="floatContainer" >
<label for="email" >Email</label>
<input type="text" id="email" name="email" data-placeholder="Email"><br>
</div>
<p id="email-validate" >
<p>
<div id="floatContainer" >
<label for="phoneNumber" >Phone Number oi</label>
<input type="tel" id="phoneNumber" name="phoneNumber" data-placeholder="Phone"><br>
</div>
<p id="phoneNumber-validate" >
<p>
<div id="floatContainer" >
<label for="problemDesc" >Description of problem</label>
<textarea id="problemDesc" name="problemDesc"></textarea><br>
</div>
The init function might look like this (using a ternary operator to check the current value):
const init = () => {
const floatContainers = document.querySelectorAll(".float-container");
floatContainers.forEach((element) => {
let input = element.querySelector(".input");
let inputVal = input ? input.value : "";
if (inputVal) {
input.classList.add("active");
}
bindEvents(element);
});
};
CodePudding user response:
For your commented code : you can use the optional chaining operator to check for the existence of the querySelector('textarea') return object before accessing the value.
It looks like your bindEvents function is assigning the event listener to just input elements rather than input & textarea elements.
Edit: more info.