11ty / webc

Single File Web Components
MIT License
1.33k stars 39 forks source link

Can't access data in nested `webc:for` loops #175

Open monochromer opened 1 year ago

monochromer commented 1 year ago

There are data that need to be output in the form of a table - an array of people and an array of fields

// index.11tydata.js

module.exports = {
  users: [
    {
      id: 1,
      name: 'Alex',
      email: 'alex@example.com'
    },
    {
      id: 2,
      name: 'Bob',
      email: 'bob@example.com'
    },
    {
      id: 3,
      name: 'Steave',
      email: 'steave@example.com'
    }
  ],

  columns: ['id', 'name', 'email']
}

There is a component to which I pass data:

<c-data-table :@columns="columns" :@items="users"></c-data-table>

Here is the implementation of the component:

<table>
  <thead>
    <tr>
      <th webc:for="column of columns" @text="column"></th>
    </tr>
  </thead>
  <tbody webc:if="items?.length > 0">
    <tr webc:for="item of items">
      <td webc:for="column of columns" @text="item[column]"></td>
    </tr>
  </tbody>
</table>

I get an error about an unavailable variable item:

[11ty] 1. Having trouble rendering webc template ./src/pages/index/index.webc (via TemplateContentRenderError)
[11ty] 2. Check the dynamic attribute: `@text="item[column]"`.
[11ty] Original error message: Cannot read properties of undefined (reading 'id') (via Error)
[11ty] 
[11ty] Original error stack trace: Error: Check the dynamic attribute: `@text="item[column]"`.
sc0ttes commented 1 year ago

Hey @monochromer,

Very late but I just ran into the same issue as you -- it seems that with nested webc:for loops, a child element with a for loop on it can't access any loop values from a parent loop -- only current loop and global variables.

So in your instance for <td webc:for="column of columns" @text="item[column]"></td>, you're able to have the column value but not the item value since it's in the parent loop.

Having spent the last day looking at this, luckily it seems JavasScript render functions can solve this(alternatively you could use template syntax as well).

Since the JavaScript render has access to all variables in the stack, what worked for me and I would imagine should work for you is to replace:

<tr webc:for="item of items">
    <td webc:for="column of columns" @text="item[column]"></td>
</tr>

with

<tr webc:for="item of items">
  <script webc:type="js">
    let html = '';
    for(column of columns){
      html += `<td>${item[column]}</td>`;
    }
    html;
  </script>
</tr>

Just for awareness -- it seems webc has some issues with table elements as well. Probably worth reading my other issue #183 and PR

refactorized commented 2 months ago

I just ran into this, and found it very unitnuitive, and kind of a deal-breaker for using webc components over any other template type or plain javascript for more complex use cases ( and lit for proper web components ).

Don't take that as whining, so much as just perspective - i think this is an issue that should be addressed asap. I have started to play with the source a little myself but I think the best thing I can do to help is put in a documentation PR to clarify this and maybe reference this issue in the meantime.

The flexibility of 11ty leaves some decent work-arounds in the meantime; particularly webc:type="js" if we want to stick with a webc-centered setup

refactorized commented 2 months ago

https://github.com/11ty/11ty-website/pull/1725

Snapstromegon commented 2 months ago

Maintainer's note: Once this is resolved, https://github.com/11ty/11ty-website/pull/1725 needs to be reverted/updated.

mjawn commented 1 month ago

I ran into this issue today. After trying a bunch of different things, I just discovered that the loops work if you break them out into their own components. Clunky, but it's working for me.

<section>
    <div class="container mx-auto px-5 py-10 xl:px-64 xl:py-20">
        <h2 class="mb-8">Swatches</h2>
        <swatch-list :@colors="data.colors"></swatch-list>
    </div>
</section>
<!-- swatch-list.webc -->
<div webc:for="color in colors" webc:nokeep>
    <h3 @text="color"></h3>
    <div class="flex space-x-4 mb-8 last:mb-0">
        <swatch :@color="color" :@shades="colors[color]"> </swatch>
    </div>
</div>
<!-- swatch.webc -->
<div :class="'bg-' + color + '-' + shade" webc:for="shade in shades">
    <p @text="shade"></p>
</div>
monochromer commented 1 month ago

I just discovered that the loops work if you break them out into their own components

In your exmaple, color and colors has one scope.