Recently, due to the needs of the vue background management project, the page needs to make an infinite tree.I have return json data of http://private-4f7c1-zyl1.apiary-mock.com/questions. Go to the enter link description here.
Return is like this:
[
{
"id":1,
"name":"HuaWei",
"pid":0
},
{
"id":2,
"name":"Apple",
"pid":0
},
{
"id":3,
"name":"Iphone X",
"pid":2
},
{
"id":4,
"name":"nove 3",
"pid":1
},
{
"id":5,
"name":"Iphone 8 plus",
"pid":2
}
]
I have Vue code:
new Vue({
el: "#app",
data: {
phoneList: []
},
mounted() {
axios.get('https://private-4f7c1-zyl1.apiary-mock.com/questions')
.then(response=> {
console.log(response);
this.phoneList = response.data;
})
.catch(error => {
console.log(error);
});
},
methods: {
}
})
I can provide an effect HTML template like this:enter link description here.
I want this effect:
This is a data structure problem. You could reshape the Axios response into a data structure that facilitates rendering that particular tree:
In a computed property (e.g., named "phoneGroups"), get all the groups, indicated by pid of 0:
const groups = this.phoneList.filter(x => x.pid === 0);
For each group, get the items belonging to that group, indicated by a pid that matches the group ID:
const data = {};
for (const g of groups) {
const subitems = this.phoneList.filter(x => x.pid === g.id);
data[g.id] = {
group: g,
subitems,
};
}
return data;
In the template, render the computed result as follows:
<ul>
<li v-for="({group, subitems}) in phoneGroups" :key="group.id">
<span>{{group.name}}</span>
<ol>
<li v-for="subitem in subitems">-- {{subitem.name}}</li>
</ol>
</li>
</ul>
demo
Related
I have an a state object in React that looks something like this (book/chapter/section/item):
const book = {
id: "123",
name: "book1",
chapters: [
{
id: "123",
name: "chapter1",
sections: [
{
id: "4r4",
name: "section1",
items: [
{
id: "443",
name: "some item"
}
]
}
]
},
{
id: "222",
name: "chapter2",
sections: []
}
]
}
I have code that adds or inserts a new chapter object that is working. I am using:
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
newChapter // insert new object
]
}
})
And for the chapter update, this is working:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return ch.id === selectedChapterId
? {...ch, name: selectedChapter.name}
: ch
})
]
}
})
But for my update/create for the sections, I'm having trouble using the same approach. I'm getting syntax errors trying to access the sections from book.chapters. For example, with the add I need:
// for creating a new section:
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters,
...old.chapters.sections?
newSection // how to copy chapters and the sections and insert a new one?
]
}
})
I know with React you're supposed to return all the previous state except for what you're changing. Would a reducer make a difference or not really?
I should note, I have 4 simple lists in my ui. A list of books/chapters/sections/items, and on any given operation I'm only adding/updating a particular level/object at a time and sending that object to the backend api on each save. So it's books for list 1 and selectedBook.chapters for list 2, and selectedChapter.sections for list 3 and selectedSection.items for list 4.
But I need to display the new state when done saving. I thought I could do that with one bookState object and a selectedThing state for whatever you're working on.
Hopefully that makes sense. I haven't had to do this before. Thanks for any guidance.
for adding new Section
setSelectedBook( book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
selectedChapter.sections=[...selectedChapter.sections, newSection ]
return {...book}
})
For updating a section's name
setSelectedBook(book=>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
selectedSection.name = newName
return {...book}
})
For updating item's name
setSelectedBook(book =>{
let selectedChapter = book.chapters.find(ch => ch.id === selectedChapterId )
let selectedSection = selectedChapter.sections.find(sec => sec.id === selectedSectionId )
let selectedItem = selectedSection.items.find(itm => itm.id === selectedItemId)
selectedItem.name = newItemName
return {...book}
})
I hope you can see the pattern.
I think the map should work for this use case, like in your example.
setSelectedBook(old => {
return {
...old,
chapters: [
...old.chapters.map(ch => {
return { ...ch, sections: [...ch.sections, newSection] }
})
]
}
})
In your last code block you are trying to put chapters, sections and the new section into the same array at the same level, not inside each other.
Updating deep nested state objects in React is always difficult. Without knowing all the details of your implementation, it's hard to say how to optimize, but you should think hard about different ways you can store that state in a flatter way. Sometimes it is not possible, and in those cases, there are libraries like Immer that can help that you can look in to.
Using the state object you provided in the question, perhaps you can make all of those arrays into objects with id for keys:
const book = {
id: "123",
name: "book1",
chapters: {
"123": {
id: "123",
name: "chapter1",
sections: {
"4r4": {
id: "4r4",
name: "section1",
items: {
"443": {
id: "443",
name: "some item"
}
}
}
}
},
"222": {
id: "222",
name: "chapter2",
sections: {},
}
]
}
With this, you no longer need to use map or find when setting state.
// for creating a new chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[newChapter.id]: newChapter
}
}
})
// for updating a chapter:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: selectedChapter,
}
}
})
// for updating a section:
setSelectedBook(old => {
return {
...old,
chapters: {
...old.chapters,
[selectedChapter.id]: {
...selectedChapter,
sections: {
[selectedSectionId]: selectedSection
}
},
}
}
})
Please let me know if I misunderstood your problem.
I am making a Blog using Notion as a content management system. There is an unofficial API provided by notion-api-js, with a function getPagesByIndexId(pageId) that returns a page's content, its subpages' contents, and, its parent's contents. So, an array of objects is returned, looking like:
[
{ moreStuff: ...,
Attributes: { slug: "home page slug", id: "home page id", moreStuff... },
},
{ moreStuff: ..., Attributes: { slug: "parent to homepage", id: "homepage's parent id", moreStuff: ... }
{ moreStuff: ..., Attributes: { slug: "sub page slug 0", id: "sub page id 0", moreStuff: ... } },
{ moreStuff: ..., Attributes: { slug: "sub page slug 1", id: "sub page id 1", moreStuff: ... } },
];
I want to build a tree that is created by recursively looping through the given id and the ids that getPagesByIndexId(given id) return to extract all slugs and ids. The function stops recursing when getPagesByIndexId(id) returns objects with ids already crawled through.
I use a crawledIdsList array to keep track of ids already crawled through, fetchPage is the same as getPagesByIndex, and I use flatmap to ignore empty []s passed by from the map function. Thanks in advance! To run this locally on node, the dependency required is npm i notion-api-js
The tree structure of the page I provided the ID with (I provided the id to the "Dev" page in homePageId) looks like:
My current code follows. It hits the "end" and returns successfully, but it is returning many pages a lot more than once.
const Notion = require("notion-api-js").default;
const token_v2 = "543f8f8529f361ab34596f5be9bc972b96ab8d8dc9e6e41546c05751b51a18a6c7d40b689d80794babae3a91aeb5dd5e47c34edb724cc356ceceacf3a8061158bfab92e68b7614516a0699295990"
const notion = new Notion({
token: token_v2,
});
const fetchPage = (id) => {
return notion.getPagesByIndexId(id);
};
const homePageId = "3be663ea-90ce-4c45-b04e-41161b992dda"
var crawledIdsList = [];
buildTree(tree={}, homePageId).then(tree => {console.log(tree)})
function buildTree(tree, id) {
return fetchPage(id).then((pages) => {
tree.subpages = [];
tree.slug = pages[0].Attributes.slug;
tree.id = id;
crawledIdsList.push(id);
return Promise.all(
pages.flatMap((page) => {
var currentCrawlId = page.Attributes.id;
if (crawledIdsList.indexOf(currentCrawlId) === -1) {
// executes code block if currentCrawlId is not used in fetchPage(id) yet
crawledIdsList.push(currentCrawlId);
return buildTree({}, currentCrawlId).then((futureData) => {
tree.subpages.push(futureData);
return tree;
});
} else {
if (crawledIdsList.indexOf(id) >= 0) {
return [];
}
return tree; // end case. futureData passed to earlier calls is tree, which looks like {subpages: [], slug: someSlug, id: someId}
}
})
)
});
}
I'm Using Drupal 8 as a headless CMS with Gatsby generating static pages.
In Drupal I have set up some node Types (Article, Image, other...). All entities have brand and category term relationships.
category is a single selection and brand is a multiple selection
Gatsby creates pages from my template on URL's like.
http://example.com/category-1/brand-1/
http://example.com/category-1/brand-2/
http://example.com/category-2/brand-1/
http://example.com/category-2/brand-2/
The articles and image nodes with the terms selected will display on the corresponding url's.
The problem I'm having is since I changed the brand term to a multi selection, some URL's are not being created.
Because of field_brand[0].name Gatsby will only create the URL for the first selection of brand on the article node.
// gatsby-node.js
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions;
const pageTemplate = path.resolve(`src/templates/pageTemplate.js`);
return graphql(`
{
taxonomyTermBrand {
field_colours
name
}
allNodeImage {
nodes {
relationships {
field_image_drop {
uri {
url
}
}
}
}
}
allNodeArticle {
nodes {
body {
processed
}
relationships {
field_brand {
name
}
field_category {
name
}
}
}
}
}
`, { limit: 1 }).then(result => {
if (result.errors) {
throw result.errors
}
result.data.allNodeArticle.nodes.forEach(node => {
const brand_path = node.relationships.field_brand[0].name;
const category_path = node.relationships.field_category.name;
createPage({
path: `${category_path}/${brand_path}`,
component: pageTemplate,
context: {
brandName: node.relationships.field_brand[0].name,
categoryName: node.relationships.field_category.name,
},
})
})
})
}
Essentially the value of taxonomyTermBrand.name is the same as node.relationships.field_brand[0].name when the data is passed to the template, but I can't use taxonomyTermBrand.name because the path in createPage is in the allNodeArticle.forEach()
Is there a better aproach or another way to set the paths and display the tagged content on those pages?
I think you'd be able to do a forEach over each of the field_brand terms for each of the articles.
Something like:
result.data.allNodeArticle.nodes.forEach(node => {
const brands = node.relationships.field_brand;
const category = node.relationships.field_category.name;
brands.forEach(brand => {
createPage({
path: `${category}/${brand.name}`,
component: pageTemplate,
context: {
brandName: brand,
categoryName: category,
},
})
});
})
I want to filter out the only content which is book marked by user,
the structure of the data is shared below
this.contentList = afs.collection < Content > ('Content',
ref =>
ref.where('isActive', '==', true)
.where("BookmarkedBy.UserID", "==", "hvC2WQL5JJG3Hbq1hxJe")
)
.snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data() as Content;
data.$key = a.payload.doc.id;
return { ...data
};
});
});
I think i'm making mistake here
.where("BookmarkedBy.UserID","==" , "hvC2WQL5JJG3Hbq1hxJe")
Is there any way I can apply filter on array items inside a collection?
any help in this regards will be highly appreciated.
{
title: "My great post",
categories: [
"technology",
"opinion",
"cats"
]
}
With the data structure above, there is no way to perform this query.
Consider this alternative data structure, where each category is the key in a map and all values are true:
{
title: "My great post",
categories: {
"technology": true,
"opinion": true,
"cats": true
}
}
Reference Link
I am working with a json object that has nested arrays as well as names with spaces such as Account ID. I need to display just the Account ID's in my Vue.js application. I am able to get my entire response.data json object but not too sure how to get just the Account ID when it's nested like the example below.
JSON
"response": {
"result": {
"Accounts": {
"row": [
{
"no": "1",
"FL": [
{
"val": "ACCOUNT ID",
"content": "123456789"
},
...
Vue.js
<script>
import axios from "axios";
export default {
name: 'HelloWorld',
data () {
return {
accounts: [],
accountIDs: []
}
},
mounted() {
var self = this;
axios.get('https://MYAPIGETREQUEST')
.then( function(res){
self.accounts = res.data;
self.accountIDs = //This is where I want to get the Account ID
console.log('Data: ', res.data);
})
.catch( function(error){
console.log('Error: ', error);
})
}
}
</script>
Try something like this
if(res.data.response.result.Accounts.row[0].FL[0].val === 'ACCOUNT ID') {
self.accountIDs = res.data.response.result.Accounts.row[0].FL[0].content;
...
}
You can also try something like this:
let rowAccounts = response.result.Accounts.row
.map(row => row.FL
.filter(FL => FL.val === 'ACCOUNT ID')
.map(acc => acc.content)
);
self.accountIDs = [].concat.apply([], rowAccounts);
In rowAccounts, you get and array of accounts array per row like:
[
0: ['acc row 1', 'another acc row1'],
1: ['acc row 2'....]
]
Now it all depends upon your implementation the way you like it.