Assuming I have an array (already sorted by Name):
let obj = {
CategoryOne:[
{
name: "An Item"
color: "Red"
},
{
name: "Blue Item"
color: "Blue"
}
],
CategoryTwo: [
...
]
...
}
I'd like to chunk it into three columns per category based on first letter. Currently, this is my solution:
function sort(obj) {
let sorted: any = {};
for(let key in obj) {
const category = obj[key]
let chunks: any = [];
const chunkSize = Math.ceil(category .length / 3) 5;
for (let i = 0; i < category.length; i = chunkSize) {
const chunk = category.slice(i, i (chunkSize)).reduce((r: any, a: any) => {
r[a.name[0].toUpperCase()] = [...r[a.name[0]] || [], a];
return r;
}, {});;
chunks.push(chunk);
}
// Set {firstCol, secondCol, thirdCol} to sorted[key]
sorted[key] = {
firstCol: chunks[0],
secondCol: chunks[1],
thirdCol: chunks[2],
};
}
return sorted;
});
This works. However, I cannot figure out a way to evenly distribute based on first letter while accounting for titles. For example, the above will produce this:
I'm not sure if letters need to be weighted differently based on how many are in a column, if this can be resolved with CSS, or if it is even worth the effort to get the columns to an even distribution (with last column, presumably, being shortest)
Any help is much appreciated!
Edit (based on responses)
CSS can handle an even distribution of columns, however, I still need titles to be per column (see "S" titled in Col 2 and Col 3 below):
CodePudding user response:
Use the columns
[1] CSS property for equal columns.
Edit: To ensure that column breaks don't happen after titles, set the break-after
css property of the title elements to avoid
[2]
test.innerHTML = Array(5).fill(0).map((_,i) => `<ul><h1>${i}</h1>
${`<li>${i}</li>`.repeat(Math.floor(Math.random()*50))}</ul>`).join('')
#test {
columns: 3
}
h1 {
break-after: avoid;
}
<div id="test"></div>
To get the result with the column headers repeating: Assuming you start with an object of keys with assorted arrays:
- Traverse the the data in order, keeping track of your current row and column index.
- Update the row number with each new element, and check it against the max column height, which is the total number of
(elements keys some tuning) / 3
- When you pass the max height, increment the column position and reset the row position.
- If you're about to add an inner element at the first row of a column (
currentRow === 0
), add the column title first
const obj = {};
// Set up obj
Array(5).fill(0).forEach((_,i) => obj[i] = Array.from({length:20}, _=>i))
let length = Object.values(obj).flat().length Object.keys(obj).length // Tune it a bit, consider the extra potential titles
const checkpoint = Math.floor(length / 3)
let currentKey;
let currentRow = 0;
let columns = [[],[],[]];
let currentColumn = 0;
function updatePosition(){
if (currentRow === checkpoint) {
currentColumn ;
currentRow = 0;
} else {
currentRow
}
}
for (let key in obj) {
let columnTitle = `<h2>${key}</h2>`
columns[currentColumn].push(columnTitle)
updatePosition();
for (let item of obj[key]) {
if (currentRow === 0) {
columns[currentColumn].push(columnTitle)
updatePosition();
}
columns[currentColumn].push(`<li>${item}</li>`)
updatePosition();
}
}
columns.forEach(col => {
let container = document.createElement('div')
container.innerHTML = col.join('')
test.append(container)
})
#test {
display: flex;
gap: 60px;
}
h2 {
padding: 0;
margin: 0;
}
#test div {
display: flex;
flex-direction: column;
}
<div id="test"></div>