I want to change the color of the input field which contains are value or is not empty respectively. First, all DOM elements are stored in an object. In this regard, I created a new array with the .filter
method which is filtering all filled input fields from the array which I created with the Object.values
method. Finally, I looped through the array filledFields
with the intention to manipulate the style settings of each DOM element which is the content of filledFields
but the following error messages occur: Uncaught TypeError: Cannot set properties of undefined (setting 'backgroundColor')
. Why does it happen?
window.onload = () => {
const input = {
caseNumber: {
month: document.getElementById("number-sequence"),
year: document.getElementById("year"),
},
clientsInformation: {
gender: document.getElementById("gender"),
inpName: document.getElementById("name"),
},
case: {
case: document.getElementById("case"),
},
adress: {
street: document.getElementById("street"),
houseNumber: document.getElementById("house-number"),
postCode: document.getElementById("post-code"),
city: document.getElementById("city"),
receiver: document.getElementById("receiver"),
},
};
addEventListener("input", () => {
let filledFields = Object.values(input).filter((item) => item.value != "");
for (vals of filledFields) {
console.log(vals);
vals.style.backgroundColor = "e7f0fe";
}
});
};
.flexbox-container {
display: flex;
gap: 30px;
flex-direction: column;
}
.items {
display: flex;
flex-direction: row;
gap: 5px;
min-height: 20px;
flex-wrap: wrap;
}
input,
select {
width: 50px;
text-align: center;
font-size: 19px;
border-radius: 5px;
border-style: solid;
border: 1px solid;
}
label {
width: 100px;
display: flex;
align-items: center;
font-size: 20px;
margin-right: 30px;
}
<body>
<div >
<div >
<label>Aktenzeichen</label>
<input id="number-sequence">
<input id="year">
</div>
<div >
<label>Anliegen</label>
<input id="case">
</div>
<div >
<label>Name</label>
<select id="gender">
<option>Herr</option>
<option>Frau</option>
</select>
<input id="name">
</div>
<div >
<label id="adress-label">Adresse</label>
<input id="street">
<input id="house-number">
<input id="post-code">
<input id="city">
<input id="receiver">
</div>
</div>
</body>
CodePudding user response:
The filter method is not targeting the actual input DOM elements you have stored in the object. if you console.log the item
in the filter you'll see it's the objects that are nested in input
object. Same goes for in the for loop, where you get the error because the backgroundColor
is not available in style
which is not defined in vals
, since vals
are not DOM elements.
For this algorithm you are trying to implement, there is a more straightforward approach. There is no need to store the whole selected element inside an object. You just need to store the IDs of them, loop through them and check the values and do whatever you want with them accordingly. However, if you insist on storing the elements in an object you can go ahead and do that, but you just need to write another loop to iterate through each value of that nested object too. Here is how:
window.onload = () => {
const inputs = { // changed the variable name here
caseNumber: {
month: document.getElementById("number-sequence"),
year: document.getElementById("year"),
},
clientsInformation: {
gender: document.getElementById("gender"),
inpName: document.getElementById("name"),
},
case: {
case: document.getElementById("case"),
},
adress: {
street: document.getElementById("street"),
houseNumber: document.getElementById("house-number"),
postCode: document.getElementById("post-code"),
city: document.getElementById("city"),
receiver: document.getElementById("receiver"),
},
};
addEventListener("input", () => {
for(group in inputs){
for(item in inputs[group]){
let input = inputs[group][item];
if(input.value != ''){
input.style.backgroundColor = '#ee0077'
}
}
}
});
};
.flexbox-container {
display: flex;
gap: 30px;
flex-direction: column;
margin-left: 8rem;
}
.items {
display: flex;
flex-direction: row;
gap: 5px;
min-height: 80px;
flex-wrap: wrap;
}
input,
select {
width: 200px;
text-align: center;
font-size: 19px;
border-radius: 5px;
border-style: solid;
border: 1px solid;
}
label {
width: 100px;
display: flex;
align-items: center;
font-size: 20px;
margin-right: 30px;
}
<body>
<div >
<div >
<label>Aktenzeichen</label>
<input id="number-sequence">
<input id="year">
</div>
<div >
<label>Anliegen</label>
<input id="case">
</div>
<div >
<label>Name</label>
<select id="gender">
<option>Herr</option>
<option>Frau</option>
</select>
<input id="name">
</div>
<div >
<label id="adress-label">Adresse</label>
<input id="street">
<input id="house-number">
<input id="post-code">
<input id="city">
<input id="receiver">
</div>
</div>
</body>
CodePudding user response:
You have 2 objects inside each other. For example, input
contains a few objects such as caseNumber
, clientsInformation
, and so on; caseNumber
contains another object for month
and year
. If you want to loop through all sub-objects, you need to flat them with flatMap
like
Object.values(input).flatMap(Object.values).filter((item) => item.value !== "");
Side note you also miss #
in the background color e7f0fe
, so it won't apply properly. For the fix, you need to set it #e7f0fe
window.onload = () => {
const input = {
caseNumber: {
month: document.getElementById("number-sequence"),
year: document.getElementById("year"),
},
clientsInformation: {
gender: document.getElementById("gender"),
inpName: document.getElementById("name"),
},
case: {
case: document.getElementById("case"),
},
adress: {
street: document.getElementById("street"),
houseNumber: document.getElementById("house-number"),
postCode: document.getElementById("post-code"),
city: document.getElementById("city"),
receiver: document.getElementById("receiver"),
},
};
addEventListener("input", () => {
let filledFields = Object.values(input).flatMap(Object.values).filter((item) => item.value !== "");
for (vals of filledFields) {
vals.style.backgroundColor = "#e7f0fe";
}
});
};
.flexbox-container {
display: flex;
gap: 30px;
flex-direction: column;
}
.items {
display: flex;
flex-direction: row;
gap: 5px;
min-height: 20px;
flex-wrap: wrap;
}
input,
select {
width: 50px;
text-align: center;
font-size: 19px;
border-radius: 5px;
border-style: solid;
border: 1px solid;
}
label {
width: 100px;
display: flex;
align-items: center;
font-size: 20px;
margin-right: 30px;
}
<body>
<div >
<div >
<label>Aktenzeichen</label>
<input id="number-sequence">
<input id="year">
</div>
<div >
<label>Anliegen</label>
<input id="case">
</div>
<div >
<label>Name</label>
<select id="gender">
<option>Herr</option>
<option>Frau</option>
</select>
<input id="name">
</div>
<div >
<label id="adress-label">Adresse</label>
<input id="street">
<input id="house-number">
<input id="post-code">
<input id="city">
<input id="receiver">
</div>
</div>
</body>
CodePudding user response:
Problems
The color needs to be prefixed with a #
vals.style.backgroundColor = "#e7f0fe";
If you really want to use the current object in OP, then you need to dig down further into the next object:
let filledFields = Object.values(input).map(obj =>
Object.values(obj).filter((item) =>
item.value != ""));
I didn't test it because what you are doing is unnecessary. There are APIs that handle the DOM, namely HTMLFormElement
interface if you wrap everything in a <form>
.
The addEventListener()
needs to be bound to either a <form>
or a form control like <input>
or <select>
<form id="main">
<input name="data">
...
// #id or [name] of <form> or form control can be used
const main = document.forms.main;
main.addEventListener("input", function(event) {...
// OR
const fc = main.elements;
const input = fc.data;
input.addEventListener("input", function(event) {...
Since there are several form controls that are affected by the event listener, your best recourse is to bind the "input" event to <form>
and handle only <input>
and <select>
when "input" event is triggered on them. In order to make referencing multiple form controls simpler, [name="data"]
was added to all of them. See events and event delegation for details.
Details are commented in example
// Reference <form>
const main = document.forms.main;
// Bind the <form> to the "input" event
main.addEventListener("input", function(event) {
/* event.target is the element the user is currently typing into
If event.target isn't <form> AND if it's [name="data"]...
If event.target value is ""...
Change its background to white...
Otherwise...
Change its background to grey
*/
if (event.target !== this && event.target.name === "data") {
if (event.target.value === "") {
event.target.style.background = "#fff";
} else {
event.target.style.background = "#e7f0fe";
}
}
});
html {
font: 300 2ch/1.2 "Segoe UI"
}
.flexbox-container {
display: flex;
flex-direction: column;
gap: 30px;
width: max-content;
margin: 20px auto;
padding: 30px 15px;
border-radius: 5px;
}
.items {
display: flex;
flex-direction: column;
gap: 5px;
min-height: 50px;
border-radius: 5px;
}
legend {
font-weight: 500;
font-size: 1.3rem;
}
input,
select {
width: 200px;
border: 1px solid;
border-radius: 5px;
font: inherit;
font-size: 1.15rem;
}
select {
width: 205px;
}
label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
font-size: 1.2rem;
}
<form id="main">
<fieldset >
<fieldset >
<legend>Aktenzeichen</legend>
<label>Sequence: <input id="sequence" name="data"></label>
<label>Year: <input id="year" name="data"></label>
</fieldset>
<fieldset >
<legend>Anliegen</legend>
<label>Case: <input id="case" name="data"></label>
</fieldset>
<fieldset >
<legend>Persönliche Daten</legend>
<label>Name: <input id="name" name="data"></label>
<label>Gender: <select id="gender" name="data">
<option selected disabled>Pick a Gender</option>
<option>Herr</option>
<option>Frau</option>
</select>
</label>
</fieldset>
<fieldset >
<legend>Adresse</legend>
<label>Reciever: <input id="receiver" name="data"></label>
<label>Street: <input id="street" name="data"></label>
<label>House Number: <input id="houseNumber" name="data"></label>
<label>Post Code: <input id="postCode" name="data"></label>
<label>City: <input id="city" name="data"></label>
</fieldset>
</fieldset>
</form>