Reputation: 71
Background: I'm writing an app in vanilla JS which can dynamically generate and update CSS grid properties. In particular, I'm interested in setting grid-template-areas
dynamically. This example might get convoluted, so here's a CodeSandbox link to the current state of the project.
Expected behavior: I'm able to set these properties on initial render without any issues. The code for that looks something like this:
function GridMap(targetElement, {
aspectWidth,
aspectHeight,
density,
defaultTrackName = `x`
}) {
/* output: [`x x`, `x x`] */
this.areaMap = Array.from(
Array(aspectHeight)
.map(() => `${defaultTrackName} `
.repeat(aspectWidth)
.trim()
)
)
/* assign CSS grid properties to target element */
this.targetElement.style.display = `grid`
this.targetElement.style.gridTemplateAreas = this.areaMap
.map(row => `"${row}"`) // output: [`"x x"`, `"x x"`]
.join(` `) // output: `"x x" " x x"`
}
const target = document.getElementById(`target`)
const grid = new GridMap(target, {
aspectWidth: 2,
aspectHeight: 2,
density: 1,
})
Which results in the following element:
<div id="target" style="display: grid; grid-template-areas: "x x" "x x";"></div>
Problem: The above code works on initial render. It will generate a correct element like I show above. But when I try to do it afterwards with different (dynamically generated) properties, the element does not get updated. From this point on it's hard to isolate code to give a good example, so here's the project so far on CodeSandbox. This is what the code in question looks like:
function Entity({ trackName, shape }) {
this.trackName = trackName
this.shape = shape
.split(`\n`)
.filter(track => track)
.map(track => track.trim())
.filter(track => track)
}
grid.placeEntity = function placeEntity(
{ x = 0, y = 0 },
{ shape, trackName },
) {
let currentEntityRow = 0
grid.areaMap = grid.areaMap.map((row, i) => {
if (currentEntityRow < shape.length) {
if (i >= y) {
row = row.split(` `)
row.splice(
x,
shape[currentEntityRow].split(` `).length,
...shape[currentEntityRow].split(` `).map(() => trackName),
)
row = row.join(` `)
currentEntityRow += 1
}
}
return row
})
grid.update()
}
const entity = new Entity({
trackName: `entity`,
shape: `
o o
o
`
})
grid.placeEntity({ x = 1, y = 1 }, entity)
Looking at the console output in my CodeSandbox example, you see that the new grid.areaMap
does indeed get generated. But for some reason, when trying to update it via grid.placeEntity()
, the updates aren't reflected in the newly rendered element.
What I've tried:
grid.placeEntity
call and:
element.style.gridTemplateAreas
to something simple like "foo bar" "hello world"
. This works as expected.element.style.gridTemplateAreas
to the exact same string I am generating in the above example. This doesn't work! This tells me that there's a problem with how I'm generating grid.areaMap
, but I can't point out that problem.Upvotes: 5
Views: 1943
Reputation: 71
I found the problem: Named CSS grid tracks must always be adjacent and must always form a rectangle. Example here:
#grid {
display: grid;
grid-template-rows: repeat(4, 100px);
grid-template-columns: repeat(4, 100px);
/* this isn't valid CSS */
grid-template-areas:
'entity-1 entity-2'
'entity-2 entity-4';
/* this is valid */
grid-template-areas:
'entity-1 entity-2'
'entity-3 entity-4';
}
Makes sense, but kinda sucks as far as what this project is trying to do. I'm sure there's a workaround to be found here!
Upvotes: 2