In my application, I serialize DOM into JSON and send it back to server.
I need help finding an efficient way to later reconstruct the DOM from this serialized JSON using React.
To share a concrete example, let's start with this DOM:
<div id="parent">
<div id="one"></div>
<span id="two"></span>
</div>
This will get serialized into following JSON:
[
{
"index": 0,
"tagName": "DIV",
"attributes": {"id": "parent"}
},
{
"index": 1,
"parent": 0,
"tagName": "DIV",
"attributes": {"id": "one"}
},
{
"index": 2,
"parent": 0,
"tagName": "SPAN",
"attributes": {"id": "two"}
}
]
Now, in vanilla Javascript, I can re-build DOM like following:
var nodes = {};
for (var i = 0; i < json.length; i++) {
var node = json[i];
// Create element
var element = document.createElement(node.tagName);
// Add attributes
for (var attribute in node.attributes) {
element.setAttribute(attribute, node.attributes[attribute]);
}
// Add parent-child association
if (node.parent != undefined) nodes[node.parent].appendChild(element);
// Cache it for later reference
nodes[node.index] = element;
}
I'm new to React, and so far I haven't figured out a way to establish dynamic parent-child relationship between components that I can change later. What would be a good way to accomplish this using React, or may be, is it even a good application of React?
The example above is a reduced example, but you can imagine the complexity can increase as different JSON instructions are introduced to re-parent, add or remove a particular DOM node.
If you deserialize your JSON recursively it saves the need to manage parent-child relations explicitly.
Provide your JSON-HTML representation as a tree (rather than a list), and make sure your serialization observes a consistent pattern for all tags - they can then (at least in theory) be interpreted by the same React component.
Somewhat elaborate below, two things to note
Each tag needs a unqiue key, e.g. a hash or application-id
All nodes - and specifically text-only nodes - need a html root (I've chosen the tag <text> to enable text-only content in <p> and <div> bodies
Output should be
<div id="root_node">
<div>
<p><i>hi </i><b>there </b><text>friend</text></p>
<p><b>hey</b></p>
</div>
<p><i>hey again</i></p>
</div>
React code
class JsonToHtml extends React.Component {
constructor(props) {
super(props);
this.state = {
json_html:
{ node : 'root',
child: [
{ type: 'parent', tag: 'div', key: '84a4fb',
child: [
{ type: 'parent', tag: 'p', key: '1bb600',
child: [
{type: 'child', tag: 'i', val: 'hi ', key: 'f6a3ad'},
{type: 'child', tag: 'b', val: 'there ', key: '57ef5e'},
{type: 'child', tag: 'text', val: 'friend', key: '57ef52'}
]
},
{ type: 'parent', tag: 'p', key: 'a43a9f',
child: [
{type: 'child', tag: 'b', val: 'hey', key: '4e2d46'},
]
}
]
},
{ type: 'parent', tag: 'p', key: '3e21f6',
child: [
{type: 'child', tag: 'i', val: 'hey again', key: 'ed5a49'},
]
}
]
}
};
}
render() {
return (
<div id='root_node'>
{this.state.json_html.child.map(function(node) {
return (
<HtmlComponent key={node.key} node={node} />
)
})
}
</div>
)
}
} export default JsonToHtml
class HtmlComponent extends React.Component {
render() {
if (this.props.node.type == "child") {
return React.createElement(
this.props.node.tag, {},
this.props.node.val)
}
if (this.props.node.type == "parent") {
return React.createElement(
this.props.node.tag, {},
this.props.node.child.map(function(node) {
return (
<HtmlComponent key={node.key} node={node} />
)
})
)
}
}
}
Use should use
React.createElement(type, props, children)
-
type: can be string or ReactClass, in your case you can 'DIV', 'SPAN'
props: are properties of element, in your case you pass {id:'elm_id',className:'css_class'}
children: This is where you build hierarchy. This can be reactText, reactNode, reactFragment (reactNodes)
I am not writing the loop, I am just explaining how to create hierarchy.
var child1 = React.createElement('div',{id:'one'}, null);
var child2 = React.createElement('span',{id:'two'}, null);
var reactFragment = [child1,child2];
var parent = React.createElement('div',{id:'parent'}, reactFragment);
Related
I'm a react.js beginner, searching for methods to alter my data structure. For example, I want to push new objects into the children-array or remove them by key.
What is the appropriate way to do that?
const [treeData, setTreeData] = useState([
{
title: "parent 1",
key: "0-0",
icon: <UserAddOutlined />,
children: [
{
title: "parent 1-0",
key: "0-0-0",
icon: <UserAddOutlined />,
children: [
{
title: "leaf",
key: "0-0-0-0",
icon: <UserAddOutlined />,
},
{
title: "leaf",
key: "0-0-0-1",
icon: <UserAddOutlined />,
},
],
},
{
title: "parent 1-1",
key: "0-0-1",
icon: <UserAddOutlined />,
children: [
{
title: "sss",
key: "0-0-1-0",
icon: <UserAddOutlined />,
},
],
},
],
},
]);
So you should not update the state directly. It is not allowed.
Maybe where you are receiving data from, suppose via api and the data is response.payload.data etc.
So in your case use the setTreeData(response.payload.data) method to add stuff in it.
Now if you want to update certain value (remove or update using index etc). Obviously you will have to have index somehow.
So for deleting say you will have some click and against that a handler for it
removeItem(e) {
item_to_remove = e.target..... etc // to get the item's reference for matching
setTreeData(treeData.filter(items => item.<someproperty> != item_to_remove))
// In your case could also be targetting children maybe
// setTreeData(treeData.Children.filter(items => item.<someproperty> != item_to_remove))
}
I would say maybe handle childrens' array inside another useState variable (childrenTreeData maybe). But you will have to look it's feasibility too. Just an idea after seeing your data
JUST for INFO
This is something similar I did for updating prices inside each cards in my project
const getCurrentPrice = useCallback(() => { // <======= maybe you do not need this
const updatedTilesData = tilesData.map((tile: any) => {
return {
...tile, // <======= get everything here and then update the price below for item
currentPrice: calculateDNCurrentPrice(
tile.startingPrice,
tile.dnTimestamp
),
};
});
setTilesData(updatedTilesData);
}, [tilesData]);
I am trying to make a tree table reorderable via drag and drop.
Here's the render for easier visualization:
I am also using React-DnD.
Here's a piece of my code:
const moveRow = useCallback(
(record: StructureElement) => (dragIndex, hoverIndex) => {
// console.log(record.name, dragIndex, hoverIndex);
const dragRow = sortableData[dragIndex];
if (!dragRow) {
console.log(sortableData, dragIndex);
} else {
console.log('drag', dragRow.name, 'hover', sortableData[hoverIndex].name);
}
// todo - change the order in array
},
[sortableData],
);
<StyledTreeTable
components={components}
dataSource={sortableData}
onRow={(record, index) => {
return {
index,
moveRow: moveRow(record as StructureElement),
};
}}
/>
So the problem is, my sortableData looks like this:
[
{ name: 1 },
{ name: 2 },
{
name: 3,
children: [
{ name: 11 },
{ name: 22 },
{ name: 33, children: [{ name: 111 }, { name: 222 }, { name: 333 }] },
],
},
];
So my objects can have more objects nested in children.
But moveRow function doesn't see it this way. For it, ABSTRACT_STATE_4 is of index 4. So when I try to reorder the array, I try to do this: data[4], which results in undefined.
Is there a way I can reorder the items in tree table with drag and drop, that I haven't found yet?
The only way I see of solving it now, is to recreate the array the way hover and drag indexes match. But that's a lot of seemingly unnecessary work.
Is there a way to get the record I am hovering over, like I'm getting hoverIndex?
Hey I'm trying to implement nested drag&drop within re-order sequencesin my MERN app. I working to find ideal approach for mongodb data model and implement to Lexicographic order or linked lists for infinite sub folders. I used Model Tree Structures in this link but every node have limitless children for that require recursion and recursive functions or currying. Documentations not clear enough for make do that.
I want show all tree once and not sohuld appear after than click to arrow icon.There is my doodles for front side generation that working with only one depth such like graph nodes. Maybe Modified Preorder Tree Traversal implementation examples you have for this scenario.
const tree = data => { // immutable array
let ascendants = data.filter(d=>d.parent==null)
let descandants = data.filter(d=>d.parent)
**strong text**
let form = []
ascendants.map(a=>{
let node1 = {...a}; /// copying
let node1Children = [];
descandants.map(b=>{
let node2 = {...b};
if(node1._id == b.parent){
node1Children.push(node2)
}
})
node1.children = node1Children;
form.push(node1);
})
return form;
}
I cant take result with using $graphLookup because list format is not what i want.Could you give me some mongodb playground or grouping aggregate solutions? Below json examples shown my expecting results. I can do before but hardcode is unapropriate and performless. Is comparing good way?
[
// mongo database
{_id:123, title:'Books', slug:'books', parent:null },
{_id:124, title:'Programming', slug:'programming', parent:null },
{_id:125, title:'JavaScript', slug:'javascript', parent:'programming' },
{_id:126, title:'C++',slug:'cpp', parent:'programming' },
{_id:127, title:'React', slug:'react', parent:'javascript' },
{_id:128, title:'Redux', slug:'redux', parent:'react' },
{_id:129, title:'Toolkit', parent:'redux' },
{_id:130, title:'Saga', parent:'redux' },
{_id:131, title:'Nodejs', parent:'programming' },
{_id:132, title:'Databases', slug:'databases' },
{_id:133, title:'MongoDB', parent:'databases' },
]
[
// what i want
{ title: "Books"},
{ title: "Programming", parent:"computer-science", children: [
{ title: "JavaScript", children: [
{ title: "React", children: [
{ title: "Redux", children: [
{ title: "Saga" },
{ title: "Thunk" },
{ title: "Mobx" },
{ title: "Observable" },
{ title: "Context" },
{ title: "GraphQL" },
{ title: "Toolkit", children:[
{ title: "typescript" },
{ title: "slices", children:[
{ title: "createAsyncThunk" },
{ title: "createSlice" },
] },
] },
] },
{ title: "Nextjs" },
]},
{ title: "Vue", },
{ title: "angular", },
]},
{ title: "C++", },
{ title: "NodeJS", },
] },
{ title: "MongoDB", parent: "databases"},
]
You could create a Map to key your objects by slug. The values per key will be the result objects for parent objects. Include an entry for null, which will collect the top-level elements.
Then iterate the data again to populate children arrays -- when that property does not exist yet, create it on the fly. Finally output the top-level elements.
function makeTree(data) {
let children = []; // Top-level elements
let map = new Map(data.map(({title, slug}) => [slug, { title }]))
.set(null, {children});
for (let {slug, parent, title} of data) {
(map.get(parent || null).children ??= [])
.push(slug ? map.get(slug) : {title});
}
return children;
}
// Your mongodb data:
const data = [{_id:123, title:'Books', slug:'books', parent:null },{_id:124, title:'Programming', slug:'programming', parent:null },{_id:125, title:'JavaScript', slug:'javascript', parent:'programming' },{_id:126, title:'C++',slug:'cpp', parent:'programming' },{_id:127, title:'React', slug:'react', parent:'javascript' },{_id:128, title:'Redux', slug:'redux', parent:'react' },{_id:129, title:'Toolkit', parent:'redux' },{_id:130, title:'Saga', parent:'redux' },{_id:131, title:'Nodejs', parent:'programming' },{_id:132, title:'Databases', slug:'databases' },{_id:133, title:'MongoDB', parent:'databases' }];
console.log(makeTree(data));
I'm a total newbie, so please bear with me if I'm still grasping with the coding fundamentals.
I want to use a template that is defined in the prop. The template is inside the DOM. The reason I want to do it this way is that I want to reuse the component logic (specifically the pagination), but may want to change how the way the template displays the data in different pages. So I wanted to seaparate the template from the script file.
This is the HTML File:
<div id="store-list">
<paginated-list :list-data="stores" use-template="#displayList"></paginated-list>
</div>
<script type="text/template" id="display-list">
<div>
<div v-for="p in paginatedData">
{{p.BusinessName}}
</div>
</div>
</script>
This is the .js file:
Vue.component('paginated-list', {
data() {
return {
pageNumber: 0
}
},
props: {
listData: {
type: Array,
required: true
},
useTemplate: {
type: String,
required: false
},
size: {
type: Number,
required: false,
default: 10
}
},
computed: {
pageCount() {
let l = this.listData.length,
s = this.size;
return Math.floor(l / s);
},
paginatedData() {
const start = this.pageNumber * this.size,
end = start + this.size;
return this.listData
.slice(start, end);
}
},
//template: document.querySelector('#display-list').innerHTML // this works
template: document.querySelector(useTemplate).innerHTML // this does not
});
var sl = new Vue({
el: '#store-list',
data: {
stores: [{
"BusinessName": "Shop Number 1"
}, {
"BusinessName": "Shop Number 2"
}, {
"BusinessName": "Shop Number 3"
}]
}
});
var shoppingList = new Vue({
el: '#shopping-list',
data: {
header: 'shopping list app',
newItem: '',
items: [
'1',
'2',
'3',
]
}
})
Any help is greatly appreciated. Thank you.
You can use the inline-template attribute to override the component's template at render time. For example
<paginated-list :list-data="stores" inline-template>
<div>
<div v-for="p in paginatedData">
{{p.BusinessName}}
</div>
</div>
</paginated-list>
See https://v2.vuejs.org/v2/guide/components-edge-cases.html#Inline-Templates
Your component can still have a default template but this will override it if set.
How to display value on dialog and console in different ways for an object array using paper-check box inside dom-repeat
This is the program and How I want the output is mentioned below. Please check the code and help me solve this.
Polymer({
is: 'check-list',
properties: {
checkdata: {
type: Array,
value: [{
name: 'Bike',
no: 1,
}, {
name: 'Car',
no: 2,
}, {
name: 'Cycle',
no: 3,
}, {
name: 'Bus',
no: 4,
}, {
name: 'Truck',
no: 5,
}],
},
},
checkall: function() {
var checkvalue = this.checkdata;
var checktext = [];
for (i = 0; i < checkvalue.length; i++) {
var checkarray = {
vehiclename:checkvalue[i].name,
vehiclenumber:checkvalue[i].no,
};
if (checkvalue[i].checked == true) {
checktext.push(checkarray);
this.checkeditem = checkarray.vehiclename;
}
}
console.log(checktext);
});
<dom-module id="check-list">
<template>
<template is="dom-repeat" items="{{checkdata}}">
<paper-checkbox on-tap="checkall" checked="{{item.checked}}">{{item.name}}</paper-checkbox>
</template>
</template>
</dom-module>
There are several things to be fixed, just as a starting point, since I'm not quite sure what you're trying to do, and what your context is:
It's necessary to add all values (which you want to be able to bind to in your local DOM, or expose for use in parent elements) to your properties array. I'm not sure about checkedItem, but I added it anyway.
If you want bindings on your properties to be updated by Polymer, add the notify attribute to your property. Also, if you want to notify subproperties on Objects (e.g. this.checkdata[i].checked, which is set by the checkbox), you need to define that subproperty on initialization.
When initializing a property's value with something other than a primitive (i.e. Array or Object), use a function for that; otherwise you'll end up setting the value on your Polymer Object's prototype, which will apply subsequent changes (e.g. Array pushes) to all instances of your component.
Use const/let instead of var within a for-loop. Any var declaration would be hoisted to the top of your function and closured within any functions defined in the loop's scope, which might lead to unexpected results (though your code would be safe as it is—for the lack of functions—but again, good practice).
I have integrated the above points into your code snippet.
Also, checkeditem is now a bindable/notifying array property, which is appended with each selection. Obviously, you'd have to also remove items, whenever its according checkbox is deselected.
Polymer({
is: 'check-list',
properties: {
checkdata: {
type: Array,
notify: true, // ??
value: function() {
return [{
name: 'Bike',
no: 1,
checked: false
}, {
name: 'Car',
no: 2,
checked: false
}, {
name: 'Cycle',
no: 3,
checked: false
}, {
name: 'Bus',
no: 4,
checked: false
}, {
name: 'Truck',
no: 5,
checked: false
}];
}
},
checkeditem: {
type: Array,
value: function() { return []; },
notify: true
}
},
checkall: function() {
const checkvalue = this.checkdata;
const checktext = [];
for (i = 0; i < checkvalue.length; i++) {
const checkarray = {
vehiclename: checkvalue[i].name,
vehiclenumber: checkvalue[i].no,
};
if (checkvalue[i].checked) {
checktext.push(checkarray);
this.push('checkeditem', checkarray.vehiclename);
}
}
console.log(checktext);
}
});
<dom-module id="check-list">
<template>
<template is="dom-repeat" items="{{checkdata}}">
<paper-checkbox on-tap="checkall" checked="{{item.checked}}">[[item.name]]</paper-checkbox>
</template>
</template>
</dom-module>