我有一个平面阵列的页面,我试图从他们创建一个层次网站地图。到目前为止,我做的很乱,所以我想要一个更好的方法来做。
这是我拥有的页面数组
const pages = [
{
"name": "Page 1",
"url": "/page-1/",
},
{
"name": "Page 2",
"url": "/page-2/",
},
{
"name": "Child 1",
"url": "/page-1/child-1/",
},
{
"name": "Child 2",
"url": "/page-1/child-1/child-2/",
},
{
"name": "Child 3",
"url": "/page-1/child-1/child-2/child-3/",
}
]
,这是我想输出的结果
<ul>
<li>
<a href="/page-1/">Page 1</a>
<ul>
<li>
<a href="/page-1/child-1/">Child 1</a>
<ul>
<li>
<a href="/page-1/child-1/child-2/">Child 2</a>
<ul>
<li>
<a href="/page-1/child-1/child-2/child-3/">Child 3</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="/page-2/">Page 2</a></li>
</ul>
这是我目前的工作,但想找到一个更好的方法来做到这一点
const generateSitemap = function(pages) => {
let sitemap = "";
let organisedPages = [];
const sortPages = function (runs) {
if (pages.length === 0) return;
pages.forEach((page) => {
const title = page.name;
const url = page.url;
// Get homepage and content pages only
let pageObj = {
title: title,
url: url,
children: [],
};
// Handle top level pages first then build up children as you go deeper
if (pageLevel(url) === 1) {
organisedPages.push(pageObj);
pages = pages.filter((page1) => page !== page1);
} else if (runs === 2) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = `/${urlParts.slice(0, -1).join("/")}/`;
if (oPage.url === parentUrl) {
organisedPages[i].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
} else if (runs === 3) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
if (oPage.url === parentUrl2Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrlComp) {
organisedPages[i].children[j].children.push(pageObj);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
} else if (runs === 4) {
organisedPages.forEach((oPage, i) => {
// Check to see if url is in segments and matches
let parseUrl = url.substring(1).slice(0, -1);
const urlParts = parseUrl.split("/");
const parentUrl = urlParts.slice(0, -1);
const parentUrlComp = `/${parentUrl.join("/")}/`;
const parentUrl2 = parentUrl.slice(0, -1);
const parentUrl2Comp = `/${parentUrl2.join("/")}/`;
const parentUrl3 = parentUrl2.slice(0, -1);
const parentUrl3Comp = `/${parentUrl3.join("/")}/`;
if (oPage.url === parentUrl3Comp) {
organisedPages[i].children.forEach((child, j) => {
if (child.url === parentUrl2Comp) {
organisedPages[i].children[j].children.forEach((child1, k) => {
if (child1.url === parentUrlComp) {
organisedPages[i].children[j].children[k].children.push(
pageObj
);
pages = pages.filter(
(page1) => pageObj.url !== page1.systemProperties.url
);
return;
}
});
}
});
}
});
}
});
runs++;
if (runs < 5) {
sortPages(runs);
}
};
sortPages(1);
/**
* Check if page is a parent
*
* @param {string} url page url
* @returns {number} length of segments
*/
function pageLevel(url) {
// Remove first and last forward slash that is provided
let parseUrl = url.substring(1).slice(0, -1);
// Split parsed url by forward slash
const urlParts = parseUrl.split("/");
// Check segment length
return urlParts.length;
}
/**
* Loop through organised pages and set listing.
*/
organisedPages.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
// Check if we need children loop for each parent page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
// Check if we need children loop for each sub-child page
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
if (page.children.length) {
sitemap += `<ul>`;
page.children.forEach((page) => {
sitemap += `<li>`;
sitemap += `<a href="${page.url}">${page.title}</a>`;
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
sitemap += `</ul>`;
}
sitemap += `</li>`;
});
return sitemap;
};
generateSitemap(pages)
我会选择从HTML格式中打破对象的逻辑嵌套,因为我认为这会使函数更简单。为了方便地嵌套,我还将添加一个帮助函数来从url获取父id,例如,
getParentId ("/page-1/") //=> ""
getParentId ("/page-1/child-1/") //=> "/page-1"
getParentId ("/page-1/child-1/child-2/") //=> "/page1/child-1"
// ... etc
我们可以很容易地在nest
中内联这个函数,但我认为它作为一个帮助器更干净。这里有一点奇怪的复杂性,因为在开始或结束的地方有一个额外的斜杠。当我们在列表中搜索子节点时,我们选择将最终的/
切掉。
代码如下:
const getParentId = (url) => url .slice (0, url .slice (0, -1) .lastIndexOf ('/'))
const nest = (pages, parentId = "", id = parentId .slice (0, -1)) => pages
.filter (({url}) => getParentId (url) == id)
.map (({url, ...rest}) => ({...rest, url, children: nest (pages, url)}))
const format = (nodes) => `<ul>${nodes .map (({name, url, children}) =>
`<li><a href="${url}">${name}</a>${children .length ? format (children) : ''}</li>`
).join('')}</ul>`
const pages2html = (pages) => format (nest (pages))
const pages = [{name: "Page 1", url: "/page-1/"}, {name: "Page 2", url: "/page-2/"}, {name: "Child 1", url: "/page-1/child-1/"}, {name: "Child 2", url: "/page-1/child-1/child-2/"}, {name: "Child 3", url: "/page-1/child-1/child-2/child-3/"}]
console .log (pages2html (pages))
.as-console-wrapper {max-height: 100% !important; top: 0}
这里nest
把你的节点变成这样的格式:
[
{
name: "Page 1",
url: "/page-1/",
children: [
{
name: "Child 1",
url: "/page-1/child-1/",
children: [
{
name: "Child 2",
url: "/page-1/child-1/child-2/",
children: [
{
name: "Child 3",
url: "/page-1/child-1/child-2/child-3/",
children: []
}
]
}
]
}
]
},
{
name: "Page 2",
url: "/page-2/",
children: []
}
]
,然后format
将其转换为HTML。我们在pages2html
中将它们包装在一起,以便调用一个函数,但是工作是在这两个至少可能可重用的函数中完成的。
请注意,我们不会尝试在您请求的格式中保留空白。我们可以这样做,代价是使format
相当丑陋。这里有一个快速的尝试,我认为是正确的。但我不会发誓:
const format = (nodes, depth = 0) => `${depth > 0 ? 'n' : ''
}${' '.repeat (2 * depth) }<ul>${nodes .map (({name, url, children}) => `
${' ' .repeat (2 * depth + 1) }<li>
${' ' .repeat (2 * depth + 2) }<a href="${url}">${name}</a>${
children .length ? `` + format (children, depth + 1) : ''
}
${' ' .repeat (2 * depth + 1) }</li>`
).join('')}
${' '.repeat (2 * depth)}</ul>`
我们简单地使用depth
参数来记录嵌套的级别,并使用该参数来计算在行开始处使用多少空格。
一般来说,我发现这种风格,作为一个转换序列,更容易使用。是的,我们可以在一个函数中完成所有这些。它甚至可能比我的四个独立函数的代码行数更少。但是,其中每一个都更容易理解,并且在需要时更容易更改。
递归将比多级分支更加灵活和简单,从next
中相当简单的递归代码中可以看出。