Cannot set properties of undefined in key-value store - javascript

I have this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./framework.js"></script>
</head>
<body>
<p f-text="name"></p>
<script>
Framework.store('name', 'Joe');
</script>
</body>
</html>
document.querySelectorAll('*').forEach((element) => {
if (element.hasAttribute('f-text')) {
const textValue = element.getAttribute('f-text');
element.innerHTML = window.fStore[textValue];
}
});
window.Framework = {
store: (key, value = '') => {
if (value === '') {
return window.fStore[key];
}
window.fStore[key] = value;
}
}
But get this error:
TypeError: Cannot set properties of undefined (setting 'name')
at Object.store (/framework.js:15:24)
at /:12:15
I want the page to render 'Joe' by getting the key from f-text, finding the key's value in window.fStore, then setting the element.innerHTML as the value. Framework.store() takes a key and a value, if there is no value it returns the value from the key in window.fStore, if there is then it sets window.fStore[key] to the value.

You need to check whether window.fStore exists first.
window.Framework = {
store: (key, value = '') => {
if(!window.fStore) window.fStore = {}
if (value === '') {
return window.fStore[key];
}
window.fStore[key] = value;
}
}
Framework.store('name', 'Joe');
document.querySelectorAll('*').forEach((element) => {
if (element.hasAttribute('f-text')) {
const textValue = element.getAttribute('f-text');
element.innerHTML = window.fStore[textValue];
}
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p f-text="name"></p>
</body>
</html>

You might also need to wait till the window loads first. Some browsers will give you a headache if you dont
window.addEventListener('load', e=>{
window.Framework = {
store: (key, value = '') => {
if(!window.fStore)
window.fStore = {};
if (value === '')
return window.fStore[key];
window.fStore[key] = value;
}
}
window.Framework.store('name', 'Joe');
document.querySelectorAll('*').forEach((element) => {
if (element.hasAttribute('f-text'))
element.innerHTML = window.fStore[element.getAttribute('f-text')];
});
}

Related

How can I gradually change the element from my page?

I'm trying to display all elements of an array, by iterating through the arrray, but after I chose the file (from the input), the element in page changes to : "unidentified". Why?
function getElement() {
console.log('sfgsdf')
document.getElementById('files').onchange = function() {
console.log('sfgsdf')
let file = this.files[0];
let reader = new FileReader();
reader.readAsText(file);
reader.onload = function() {
variableIs = this.result
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function display(asd) {
const usingSplit = asd.split(' ')
lengthOf = usingSplit.length
for (var i = 0;i < lengthOf;i++) {
sleep(1000).then(() => {
document.getElementById('test').innerHTML = usingSplit[i];
});
}
}
display(variableIs);
}
}
}
getElement()
The HTML code is just this simple one :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="test">TEST</h1>
<script src="test4.js"></script>
</body>
</html>
The problem is the var inside the for loop.
You should use let instead due to the way both work.
Read this post to understand their difference:
What is the difference between "let" and "var"?
The way you're using the sleep function is not gonna work since they will execute at the same time.
You can solve this by using async await.
function delay(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const inputFile = document.getElementById('files')
const test = document.getElementById('test')
function init() {
inputFile.onchange = async function() {
const file = this.files[0];
const content = await file.text()
const splitText = content.split(' ')
for (let i = 0; i < splitText.length; i++) {
test.innerHTML = splitText[i]
// Here we are actually waiting in the for loop since this is an async function
await delay(1000)
}
}
}
init()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 id="test">TEST</h1>
<input id="files" type="file">
</body>
</html>

Javascript Dynamic Data binding code not working

I am writing code that uses data binding to change the innerHTML of an span to the input of the user, but I can't get it to work. What it should do is show the input on the right side of the input field on both the input fields, but it doesn't. Can someone please help me out.
HTML:
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Frontend Framework</title>
</style>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" bit-data="name"/>
<span bit-data-binding="name" style="margin-left: 1rem;"></span>
</div>
<div>
<label>Lastname:</label>
<input type="text" bit-data="LastName"/>
<span bit-data-binding="LastName" style="margin-left: 1rem;"></span>
</div>
<script src="frontend-framework.js"></script>
</body>
</html>
Javascript:
const createState = (stateObj) => {
return new Proxy(stateObj, {
set(target, property, value) {
target[property] = value;
render();
return true;
}
});
};
const state = createState({
name: '',
lastName: ''
});
const listeners = document.querySelectorAll('[bit-data]');
listeners.forEach((element) => {
const name = element.dataset.model;
element.addEventListener('keyup', (event) => {
state[name] = element.value;
console.log(state);
});
});
const render = () => {
const bindings = Array.from(document.querySelectorAll('[bit-data-binding]')).map(
e => e.dataset.binding
);
bindings.forEach((binding) => {
document.querySelector(`[bit-data-binding=${binding}]`).innerHTML = state[binding];
document.querySelector(`[bit-data=${binding}]`).value = state[binding];
});
}
https://jsfiddle.net/Mauro0294/g3170whc/4/
I made some changes to the fiddle to get the desired result. The problem was with your logic to refer the elements using the dataset attributes, so I tried to simplify it.
Some notable changes :
Updated the data-bit to use lastName instead of LastName. Made it same as your state.
Used getAttribute to get the value of the data-* properties to correctly get the reference.
I think this is what you're looking for:
const createState = (stateObj) => {
return new Proxy(stateObj, {
set(target, property, value) {
target[property] = value;
render();
return true;
}
});
};
const state = createState({
name: '',
lastName: ''
});
const listeners = document.querySelectorAll('[bit-data]');
listeners.forEach((element) => {
const name = element.getAttribute('bit-data');
console.log('here', element.getAttribute('bit-data'), JSON.stringify(element.dataset))
element.addEventListener('keyup', (event) => {
state[name] = element.value;
console.log(state);
});
});
const render = () => {
const bindings = Array.from(document.querySelectorAll('[bit-data-binding]')).map((e) => {
return e.getAttribute('bit-data-binding');
});
//console.log('bindings:', bindings, document.querySelectorAll('[bit-data-binding]'));
(bindings ?? []).forEach((binding) => {
document.querySelector(`[bit-data-binding=${binding}]`).innerHTML = state[binding];
document.querySelector(`[bit-data=${binding}]`).value = state[binding];
});
}
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My Frontend Framework</title>
</head>
<body>
<div>
<label>Name:</label>
<input type="text" bit-data="name"/>
<span bit-data-binding="name" style="margin-left: 1rem;"></span>
</div>
<div>
<label>Lastname:</label>
<input type="text" bit-data="lastName"/>
<span bit-data-binding="lastName" style="margin-left: 1rem;"></span>
</div>
</body>
</html>
Your main issue is this part:
const bindings = Array.from(document.querySelectorAll('[bit-data-binding]')).map(
e => e.dataset.binding
);
or more specifically e.dataset.binding. Your elements do not a have data-binding attribute, which would be the prerequisite for using dataset.binding. You can use e.getAttribute('bit-data-binding') instead.
But your logic is also flawed: As it currently stands, entering text into an input is pointless, as the state is never updated.
Finally, note that you spell LastName with a capital L in your DOM but lowercased in your state object.

How to make script execute once the first script is loaded

I'm trying to make a web framework and one feature will be a key-value state management tool. I need the second <script> tag to only run after ./script.js loads in.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./framework.js"></script>
</head>
<body>
<p f-text="name"></p>
<script>
Framework.store('name', 'Joe');
</script>
</body>
</html>
framework.js:
document.querySelectorAll('*').forEach((element) => {
if (element.hasAttribute('f-text')) {
const textValue = element.getAttribute('f-text');
const key = window.fStore.find((x) => x.key === textValue);
element.innerHTML = key.value;
}
});
window.Framework = {
store: (key, value?) => {
if (!value) {
const foundKey = window.fStore.find((x) => x.key === key);
return foundKey.value;
}
window.fStore = [...window.fStore, { key: key, value: value }];
}
}
Error:
SyntaxError: Unexpected token ')'
at /framework.js:12:22
ReferenceError: Framework is not defined
at /:12:5
You need to wait that your script is loaded, you can use this
window.addEventListener('load', function() {
Framework.store('name', 'Joe');
})

How to filter object array on javascript

im new in js
I want to make a code with onekeyup to search an object in an array. I don't know how to do it with one/specific parameter, not all of the parameter.
for ex. If I type tokyo, then it will not show anything because its not "name" parameter
script.js and index.html
function checkOnKeyUp(input){
const filtered = datas.filter(element => {
for (const value of Object.values(element)) {
if (
value.toString()
.toLowerCase()
.includes(input.value.toLowerCase())
)
return true;
}
})
console.log('Name: ', filtered);
document.getElementById("result").innerText=JSON.stringify(filtered)
}
const datas = [{
"nickname":"abi","name":"Abi sholeh","id":123,"birth":"1999-05-09","address":"new york"},{"nickname":"abc","name":"abc james","id":112,"birth":"1999-05-04","address":"tokyo"}];
<!DOCTYPE html>
<html lang="en">
<head>
<title>Pengenalan Javascript</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<input type="text" id='search' name='search' onkeyup="checkOnKeyUp(this)">
<div id="result"></div>
<script src="./script.js"></script>
</body>
</html>
thanks
I think you are trying to the follow:
datas.map(data => Object.values(data))
.flat()
.filter(value =>
value.toString()
.toLowerCase()
.includes(input.value.toLowerCase()
)
.forEach(data => {
data = 'nama';
});
Maybe this is what you are looking for:
function checkOnKeyUp(input){
let value = input.value.toLowerCase();
let filtered = datas.filter(element => {
return element.name.toLowerCase() === value;
})
console.log('Name: ', filtered.name);
document.getElementById("result").innerText=JSON.stringify(filtered)
}

Why won't the contents of the API be displayed?

I'm trying to display contents of an API but for some reason I'm getting an error in the console that says Uncaught (in promise) TypeError: Cannot read property 'appendChild' of null which sort doesn't make sense to me because I've set to innerHTML = <p>${this.items[i]}</p>.
What am I doing wrong and how can I fix this? If you need more information, please let me know.
Here's my html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="baseball">
<h1>test</h1>
</div>
<script type="application/javascript" src="index.js"></script>
<script type="text/javascript">
window.addEventListener("load", function() {
console.log("document is loaded");
var baseballStats = new BaseballStats();
baseballStats.init("https://statsapi.mlb.com/api/v1/people/660670/stats?stats=byDateRange&season=2018&group=hitting&startDate=&endDate=&leagueListId=mlb_milb", true);
});
</script>
</body>
</html>
Here's my javascript
class BaseballStats {
constructor() {
this.totalItems = 0;
this.list = document.querySelector("baseball");
}
init(url, bool) {
this.bool = bool;
var that = this;
console.log(url);
fetch(url)
.then(resp => resp.json())
.then(data => {
console.log(data.stats);
that.data = data;
if (this.bool) {
that.items = that.data.stats;
this.totalItems = that.items.length;
console.log("about to loop");
for (var i = 0; i < this.totalItems; i++) {
var listNode = document.createElement("LI");
listNode.innerHTML = `<p>${this.items[i]}</p>`;
console.log("did it reach here");
this.list.appendChild(listNode);
}
}
});
}
}
Try to console.log the list variable. You will see it's null. You're treating this variable as an object, but the content of the variable is null.
Your problem is the BaseballStats' list member is null. This is because you're misusing document.querySelector - it selects in the same way as CSS, so to select an element with a class you need to use the . selector - https://www.w3schools.com/cssref/css_selectors.asp.
You are selecting for 'baseball' however, which means it is trying to find an element with the tag name <baseball>. Change this to '.baseball' and it will work

Categories

Resources