Map html element to json object [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have an html element like this,
<div class='myparent'>
<div>
<div class="pdp-product-price">
<span> 650 rupees</span>
<div class="origin-block">
<span> 1,500 rupees</span>
<span>-57%</span>
</div>
</div>
</div>
</div>
I need to create a json of this 'myparent' div.
{
"div": {
"div": {
"div": {
"span": {},
"div": {
"span": {},
"span": {}
}
}
}
}
}
Is there a way to do this in javascript?

You can use children property of HTMLElement
Then iterate over parent recursively and get subtree.
But be aware that you cannot assign two values with the same key. Therefore, you can use an index when assigning subtree like
"div": {
"span_1": {},
"span_2": {},
}
Hope the below snippet will give you a clue.
const parent = document.getElementById('parent')
const tree = {};
const getTree = (elem) => {
const subtree = {};
for(let child of elem.children){
subtree[child.tagName.toLowerCase()] = getTree(child)
}
return subtree;
}
tree[parent.tagName.toLowerCase()] = getTree(parent);
console.log(tree);
<div id="parent" class='myparent'>
<div>
<div class="pdp-product-price">
<span> 650 rupees</span>
<div class="origin-block">
<span> 1,500 rupees</span>
<span>-57%</span>
</div>
</div>
</div>
</div>

Recursive function that builds the json. To ensure no issues with the keys (that have a possibility to be duplicates) the following was added :n where n is the index of the element.
function htmlToObject(targetElement) {
return Array
.from(targetElement.children)
.reduce((acc, cur, i) => {
acc[`${cur.tagName}:${i}`.toLowerCase()] = htmlToObject(cur);
return acc;
}, {});
}
const startElement = document.getElementsByClassName("myparent")[0];
const res = {
[startElement.tagName.toLowerCase()]: htmlToObject(startElement)
};
console.log(res);
<div class='myparent'>
<div>
<div class="pdp-product-price">
<span> 650 rupees</span>
<div class="origin-block">
<span> 1,500 rupees</span>
<span>-57%</span>
</div>
</div>
</div>
</div>

Related

How to edit a value in localStorage with TypeScript [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 10 months ago.
Improve this question
I am working on a simple "Green Light, Red light" game using Angular, and I am storing players with their score and maxScore using localStorage.
I can already read each property from the array stored in the localStorage, but now I am stuck on modifying those values once I click a button.
This is the test array I am currently working with:
[{"name":"test1","score":3,"maxScore":8},{"name":"test2","score":10,"maxScore":22}]
This array is stored with a single key named "players", so it is an array of players.
My component looks like this:
game.component.ts
export class GameComponentComponent implements OnInit {
highScoreLS: number = this.getHighScoreData();
scoreLS: number = this.getScoreData();
highScore: number = 0;
score: number = 0;
state: string = 'RUN';
faArrowRightFromBracket = faArrowRightFromBracket;
faShoePrints = faShoePrints;
constructor() {}
ngOnInit(): void {}
addPoint() {
this.score++;
if (this.score > this.highScore) {
this.highScore = this.score;
}
this.changeHighScore();
this.changeScore();
}
removePoint() {
this.score--;
if (this.score < 0) {
this.score = 0;
}
this.changeHighScore();
this.changeScore();
}
changeState() {
if (this.state === 'RUN') {
this.state = 'PAUSE';
} else {
this.state = 'RUN';
}
}
getScoreData() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
let sc = item.score;
return sc;
}
getHighScoreData() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
let hs = item.maxScore;
return hs;
}
changeHighScore() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
item.maxScore = this.highScore;
localStorage.setItem('players', JSON.stringify(item));
}
changeScore() {
let localStorageItem = JSON.parse(localStorage.getItem('players') || '[]');
let item = localStorageItem.find(
(item: { name: string }) => item.name === 'test1'
);
item.score = this.score;
localStorage.setItem('players', JSON.stringify(item));
}
}
And the html looks like this:
game.component.html
<div class="navbar navbar-dark bg-dark">
<div class="container">
<h2>Hi! 👋</h2>
<a class="navbar-brand" routerLink=""
><fa-icon [icon]="faArrowRightFromBracket"></fa-icon
></a>
</div>
</div>
<div class="container flex vh-100">
<div class="row m-3">
<h3>HIGH SCORE: {{ highScoreLS }}</h3>
</div>
<div class="row m-3">
<div class="card p-3">
<h3>{{ state }}</h3>
</div>
</div>
<div class="row m-3">
<h3>SCORE: {{ scoreLS }}</h3>
</div>
<div class="row m-3">
<div class="col">
<button class="btn btn-outline-success" (click)="addPoint()">
<fa-icon [icon]="faShoePrints"></fa-icon>
Left
</button>
<button class="btn btn-outline-success" (click)="removePoint()">
Right
<fa-icon [icon]="faShoePrints"></fa-icon>
</button>
</div>
</div>
</div>
The problem is, when I click the button to add or remove a point, it deletes the whole array of players, and creates a new one like the following:
{"name":"test1","score":0,"maxScore":1}
I have been working for a couple of days with localStorage so I do not know exactly what I am missing or what I am doing wrong.
My idea is to edit those values, score and maxScore, but I can't figure it out how.
EDIT
The first time I click on add a point, it edits only the maxScore, but once. The next time I click, it gives me this error:
ERROR TypeError: localStorageItem.find is not a function
at GameComponentComponent.changeScore (game-component.component.ts:83:33)
at GameComponentComponent.addPoint (game-component.component.ts:34:10)
You're calling localStorage.setItem with just the single item and not the whole array so every subsequent "finds" you're trying will fail.
Try this instead:
localStorage.setItem('players', JSON.stringify(localStorageItem));
Though I have to say, there's loads of duplicate code in just that one component. You should read some articles on data structures and state management.

How to display data from map method only 5 item [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I have a external Json file that displays data within the site. And I need to limit the display to only 5.
const displayCharacters = (characters) => {
characters.sort((a, b) => {
return a.rate - b.rate;
});
characters.sort((a, b) => b.rate - a.rate);
characters.forEach(() => {
const htmlString = characters
.map((character) => {
return `
<div class="col-6 my-2" onClick="addToCart(${character.id} )"></div>
<div class="menu card my-3" style="width: 120px">
<div class="card-body">
<h5 class="menu-name">${character.nama}</h5>
<p> ${character.harga}</p>
</div>
</div>
</div>
`;
})
.join('') ;
charactersList.innerHTML = htmlString;
});
};
loadCharacters();
help me guys
You can use the JS slice method to slice the array before using the map method.
const displayCharacters = (characters) => {
characters.sort((a, b) => {
return a.rate - b.rate;
});
characters.sort((a, b) => b.rate - a.rate);
characters.forEach(() => {
const htmlString = characters
.slice(0, 5)
.map((character) => {
return `
<div class="col-6 my-2" onClick="addToCart(${character.id} )"></div>
<div class="menu card my-3" style="width: 120px">
<div class="card-body">
<h5 class="menu-name">${character.nama}</h5>
<p> ${character.harga}</p>
</div>
</div>
</div>
`;
})
.join('') ;
charactersList.innerHTML = htmlString;
});
};
loadCharacters();
console.dir(object,{depth:5})
At least in node.js
You can use index in foreach and check if index is less then or equal to 5
const displayCharacters = (characters) => {
characters.sort((a, b) => {
return a.rate - b.rate;
});
characters.sort((a, b) => b.rate - a.rate);
characters.forEach(funtion(data,index) { //Here you will take now index so on index based you can check data limit
const htmlString = characters
.map((character) => {
if(index=<5){ //it will run untill index is less then and equal to 5
return
<div class="col-6 my-2" onClick="addToCart(${data.id} )"></div>
<div class="menu card my-3" style="width: 120px">
<div class="card-body">
<h5 class="menu-name">${data.nama}</h5>
<p> ${data.harga}</p>
</div>
</div>
</div>
`;
}
})
.join('') ;
charactersList.innerHTML = htmlString;
});
};
loadCharacters();

Render components based on the data from a multidimensional array in React

I have an array with items which can have even or odd number of items. I made a function which puts each two elements in their own array inside a main array, so it looks like this at the end;
items array: [{}, {}, {}, {}]
returned array: [[{}, {}], [{}, {}]]
items array: [{}, {}, {}]
returned array: [[{}, {}], [{}, undefined]]
I did this because I want to render each Bootstrap row with two columns on the page. Now, I'm not sure how to implement mapping through this array. To a certain extent I know how to do this;
Map through returned array.
If second element in current array is undefined return a row with just one item.
If both elements are defined in current array return a row with both items.
But by React rules I need to add additional key attributes to the elements I return with map so I would need to somehow keep the track of index variable. What would be the efficient way to do this?
I can't think of a nice way to map your original array, but you could use a for loop and increment by 2 each iteration and just check if the second element is truthy before using it.
Example
class App extends React.Component {
render() {
const content = [];
for (let i = 0; i < itemsArray.length; i += 2) {
content.push(
<div class="row" key={i}>
<div class="col-md-6">
<div class="option correct-option">{itemsArray[i].text}</div>
</div>
{itemsArray[i + 1] && (
<div class="col-md-6">
<div class="option correct-option">{itemsArray[i + 1].text}</div>
</div>
)}
</div>
);
}
return <div>{content}</div>;
}
}
If you want to use the array of arrays, you could map it and just check if the second element in the array is truthy before using it.
Example
class App extends React.Component {
render() {
return (
<div>
{itemsArray.map((items, index) => {
<div class="row" key={index}>
<div class="col-md-6">
<div class="option correct-option">{items[0].text}</div>
</div>
{items[1] && (
<div class="col-md-6">
<div class="option correct-option">{items[1].text}</div>
</div>
)}
</div>;
})}
</div>
);
}
}

ReactJs: target.key undefined after method is reached from li rendered by map function

I'm currently trying to coding a react app that would do the following:
- Create a list of questions from an array using a map function.
- Making each list element clickable using a onClick prop
- The linked onClick method changes the state in another file with my 'qsChange' prop.
I had a hard time making my list clickable and finally managed following this question: React: trying to add an onClick to a li tag but the click handler function is undefined
However, now I cannot make it so that my variable 'choice' returns a defined value. I would want var choice to be equal to "A ?", "B ?" or "C ?" depending on which I click.
Here's my code:
var questions = ["A ?", "B ?", "C ?"];
var Questions = React.createClass({
handleClick: function() {
var visibility;
if(this.props.visibility) {
document.getElementById('second').style.display = 'none';
visibility = false;
this.props.onChange(visibility);
} else {
document.getElementById('second').style.display = 'block';
visibility = true;
this.props.onChange(visibility);
}
},
/* Here is where my problem lies */
onItemClick: function(e){
var choice = e.target.key;
this.props.qsChange(choice);
alert(choice);
},
render: function() {
return (
<div className="bigqs">
<div id="first" className="small" style={firstStyle}>
<h1>Question :</h1>
<button style={btnStyle} onClick={this.handleClick}>
<img id="arrow" src="../../img/arrow.png" />
</button>
<h3 id="selectedQuestion">{this.props.selected}</h3>
</div>
<div id="second" className="small" style={{display: 'none'}}>
<h4>
<ul>
{questions.map(function(qs, i) {return <li key={qs[i]} onClick={this.onItemClick}>{qs}</li>;}, this)}
</ul>
</h4>
</div>
</div>
);
}
});
I am still a newbie, so please be indulgent ;-)
I hope I was clear enough.
Ps: I have also tried this guide but it didn't work for me: http://derpturkey.com/react-pass-value-with-onclick/
Instead of grabbing the question from target, you can pass question through to your handler. Also, since inside map qs is a string, qs[i] will be getting the character in the string from that index. You just need to make sure your key is unique.
onItemClick: function(choice) {
this.props.qsChange(choice)
alert(choice)
},
render() {
return (
<div>
...
{questions.map(qs =>
<li key={qs} onClick={() => this.onItemClick(qs)}>{qs}</li>
)}
...
</div>
)
}
In fact, your intermediate function isn't doing much, you can just call your props function inside render:
render() {
return (
<div>
...
{questions.map(qs =>
<li key={qs} onClick={() => this.props.qsChange(qs)}>{qs}</li>
)}
...
</div>
)
}

Javascript HTML .children traversal

I'm trying to go through all of the elements in the document and pull the ones with a target class name. Importantly, I'm needing to do it without the use of document.getElementsByClassName(className) / document.querySelectorAll, etc. — that's the point of this learning exercise.
Here's the javascript:
var getElementsByClassName = function(className){
var rootElem = document.body;
var collectionResult = [];
if (rootElem.getAttribute("class") == className) {
collectionResult.push(rootElem);
};
var nextTier = function(collectionResult, rootElem) {
var thisTier = rootElem.children;
for (i=0; i<thisTier.length; i++) {
var classes = thisTier[i].getAttribute("class");
if (classes != undefined && classes.includes(className)) {
collectionResult.push(thisTier[i]);
};
var childrenArray = thisTier[i].children;
if (childrenArray.length > 0) {
nextTier(collectionresult, childrenArray)
};
};
};
nextTier(collectionResult, rootElem);
return collectionResult;
};
Here's the section of the HTML structure I'm having trouble with:
<p>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay</span>
</div>
</div>
</p>
The code works for the rest of the page with any number of non-nested elements. But as soon as var childrenArray = thisTier[i].children get to the div.somediv element, it has childrenArray == undefined rather than pulling the div.innerdiv element.
Am I misunderstanding how element.children works?
Array.prototype.flatMap is an effective tool for flattening trees (like DOM) to an array of values (like a list of elements) -
function getElementsByClassName (node, query) {
function matchAll (children) {
return Array
.from(children)
.flatMap(c => getElementsByClassName(c, query))
}
if (node.classList && node.classList.contains(query))
return [ node, ...matchAll(node.childNodes) ]
else
return matchAll(node.childNodes)
}
const result =
getElementsByClassName(document, "targetClassName")
console.log(result)
// [ <div class="somediv targetClassName">…</div>
// , <span class="targetClassName">yay1</span>
// , <span class="targetClassName">yay2</span>
// , <span class="targetClassName">yay3</span>
// ]
<div class="somediv targetClassName">
<div class="innerdiv">
<span class="targetClassName">yay1</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay2</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay3</span>
</div>
</div>
You seem to be overcomplicating things.
function getElementsByClassName(className, root) {
if(!root) root = document.documentElement;
return [].reduce.call(root.children, function(arr, child) {
if(child.classList.contains(className)) arr.push(child);
return arr.concat(getElementsByClassName(className, child))
}, []);
}
function getElementsByClassName(className, root) {
if(!root) root = document.documentElement;
return [].reduce.call(root.children, function(arr, child) {
if(child.classList.contains(className)) arr.push(child);
return arr.concat(getElementsByClassName(className, child))
}, []);
}
console.log(getElementsByClassName("targetClassName"));
<div class="somediv targetClassName">
<div class="innerdiv">
<span class="targetClassName">yay1</span>
</div>
</div>
<div class="somediv targetClassName">
<div class="innerdiv targetClassName">
<span class="targetClassName">yay2</span>
</div>
</div>
<div class="somediv">
<div class="innerdiv">
<span class="targetClassName">yay3</span>
</div>
</div>

Categories