Reuben Reyes
Reuben Reyes

Reputation: 71

Issue with dynamically setting `grid-template-areas`

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:

Upvotes: 5

Views: 1943

Answers (1)

Reuben Reyes
Reuben Reyes

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

Related Questions