Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access nested items in x-for #158

Closed
ryangjchandler opened this issue Feb 1, 2020 · 38 comments
Closed

Access nested items in x-for #158

ryangjchandler opened this issue Feb 1, 2020 · 38 comments

Comments

@ryangjchandler
Copy link
Contributor

This issue is to open a discussion about on the problem found here: https://twitter.com/devgummibeer/status/1223415287447461889?s=21

I replied to this tweet with an alternative syntax for this too, see here: https://twitter.com/ryangjchandler/status/1223664594784264194?s=21

I personally think this syntax would be pretty clean and takes a PHP-approach with the syntax too, using the 'as' keyword. Removed the need for nested x-fors, when you only need to do the second loop on a single nested item. In theory, this could be taken a lot further with deeply nested items.

I am playing around with the x-for code currently to find a good way of achieving this syntax, and if it's something people would find useful, I'll throw a PR in for Caleb to ponder over. Have a good one guys! 🤙🏼

@ryangjchandler
Copy link
Contributor Author

As a short follow up to this and a quick chat with @SimoTod , there might still be some cases where you want to print something from the initial, 'parent' loop item. I'll find a syntax for this and report back.

@rzenkov
Copy link
Contributor

rzenkov commented Feb 1, 2020

This is my IMHO, but i hope it will main way.

If you think about alpine like vue replacement, you should correct some expectations. Alpine use to be small, DOM-oriented library, without massive DOM manipulation.

In Alpine way you should get not an items array, but a html fragment with rendered items. If you want other - go with Vue.

@SimoTod
Copy link
Collaborator

SimoTod commented Feb 1, 2020

I think people would expect to be able to write

<div x-data="{items: [{title: 'test',tags: ['ok1','ok2']}, {title: 'test2',tags: ['ok2']}]}">
    <template x-for="item in items">
        <div>
            <span x-text="item.title"></span>
            <template x-for="tag in item.tags">
                <span x-text="tag"></span>
            </template>
        </div>
    </template>
</div>

which doesn't seem unrealistic to me.

In my opinion, either Alpine should support it or the documentation should make it clear that it's not a valid use case.
If it's the latter, I'm pretty sure it would alienate a few devs. Just imagine you start the project and close to the finish line, because a new requirement comes up, you have to convert everything to VueJS only because Alpine doesn't support nested loop, you would find it a bit annoying.

@ryangjchandler and I were chatting about that, we don't think it's impossible even with the current structure and it should't add any complexity to the project.

@ryangjchandler
Copy link
Contributor Author

In response to this, I agree with @SimoTod , no reason this shouldn't be supported. Gonna throw a PR in tomorrow, it's currently midnight in the UK so too late for me.

Also going to investigate the syntax I proposed in the twitter thread, as a way of reducing the nested loops and hard to read code. If people don't like it, that's fine.

@rzenkov
Copy link
Contributor

rzenkov commented Feb 2, 2020

I agree that the documentation should describe the main approaches.
x-for inside x-for not only case that comes to my mind. How about x-if inside x-for or opposite, or x-data inside any of this. Some of this cases works, some not, But should we use them?

Now i know that x-for > x-data > x-for > x-data > x-if works perfect, but without x-props this apporach cannot be used. Why we should worry about x-for in x-for, if we can use x-props tomorrow? There is so many questions with one answer - use x-html with prerendered html.

EDIT:
For more clarity, (other portion of IMHO) prerendered html should come from backend ( you should not render html in js ). Therefore If your backend is json api - you should go with Vue or other vDOM lib.

@SimoTod
Copy link
Collaborator

SimoTod commented Feb 2, 2020

I agree that backend should do most of the work but, with that in mind, we shouldn't have supported x-for and x-if at all, right?

As far as I know, x-if inside x-for works correctly. Looking at the source code, x-for is the only directive where we don't pass the extraVars variable, all the other combinations are supported as expected.

We will litteraly pass a variable in 3 points of the code, no added complexity nor massive increment in size, so it seems reasonable to support it.

I appreciate we can use x-html but it leads to complicated and error-prone code (my personal view) and I wouldn't suggest it as a best practice (devs with less experience would find that hard).

@rzenkov
Copy link
Contributor

rzenkov commented Feb 2, 2020

x-if and x-for has some use cases, where prerendered html is not applicable. For example - tags input component. or other cases when we need react to user input. But when we have x-for we can iterate over any list, why we can't get json from backend and iterate over it?
IMHO for more clarity and for avoiding wrong expectations we should get some preferences and declare it.

@Gummibeer
Copy link

@ryangjchandler thanks for creating this issue.
I don't know if it's the valid scope of this package. But I don't want to load the whole Vue.js runtime only to render a simple dynamic list.

@ryangjchandler
Copy link
Contributor Author

@ryangjchandler thanks for creating this issue.
I don't know if it's the valid scope of this package. But I don't want to load the whole Vue.js runtime only to render a simple dynamic list.

100%. I don't think you should have to load Vue for this single component. I'm going to throw a PR in tonight that should hopefully fix the problem you were having, we'll see how it goes.

@alessandrotesoro
Copy link

Ah! I started using Alpine today on a project without checking here the issues on github. Immediately stumbled upon the same problem while I was building a form with nested repeater fields. I'd like for this feature to be added too so I can remove Vuejs from my project 👍

@lindsaykwardell
Copy link

lindsaykwardell commented Feb 10, 2020

Adding my voice for nested x-for templates. We're using Alpine to present a single view of our application that other applications can inject, and I'd much rather use this than Vue for something so lightweight.

Our use case wouldn't work with the "item.iterable as thing in list" approach, as I'm having to loop through one list of items, then finding all the matching items in a second list. For example:

const list1 = [{id: 1, text: "hello"},{id: 2, text:"there"}]
const list2 = [{ref: 1, addlText: "world"}, {ref: 1, addlText: "everyone"}]

<template x-for="item in list1">
    <div>
        <template x-for="itemMatch in list2.filter(i => i.ref === item.id)">
        ...
        </template>
    </div>
</template>

It's not a big deal if this isn't possible, but it makes sense to me that if statements can be nested, they can use the scope of the current iteration for the nested loops.

@the94air
Copy link
Contributor

the94air commented Feb 17, 2020

Hi, guys. Obviously, we need this feature to loop through an array of objects twice. So, this happens mostly with people working with APIs.
But there is something we actually (maybe) forgot about. Which is the loop "index". We mainly use this for keying loop elements or manipulating the logic of a specific iteration (sometimes using math).
Thanks for this useful tool.

@SimoTod
Copy link
Collaborator

SimoTod commented Feb 17, 2020

HI @the94air, good point. My understanding is that accessing the index variable using the same syntax as VueJS is in the pipeline (see #133).

@rzenkov
Copy link
Contributor

rzenkov commented Feb 18, 2020

There is how to deal with indexes right now (not Vue-way)

  <h3>Iterate array</h3>
  <div x-data="{list:['a', 'b', 'c']}">
    <template x-for="[idx, val] in Object.entries(list)">
      <div>
        <span x-text="idx"></span>
        <span x-text="val"></span>
      </div>
    </template>
  </div>

  <h3>Iterate object</h3>
  <div x-data="{obj: {a: 'A', b: 'B', c: 'C'}}">
    <template x-for="[idx, val] in Object.entries(obj)">
      <div>
        <span x-text="idx"></span>
        <span x-text="val"></span>
      </div>
    </template>
  </div>

One notice about idx from Object.entries(Array) - it is always has string type, So if you want check it with === you should firstly convert it to number.

Also check browser support at caniuse

@richcorbs
Copy link

richcorbs commented Mar 5, 2020

I'm interested in this issue.

@ryangjchandler
Copy link
Contributor Author

@SimoTod has this been fixed now that the new reactivity core has been merged?

@SimoTod
Copy link
Collaborator

SimoTod commented Mar 10, 2020

I believe it's still an issue. I mentioned in #230 that the new reactivity component won't fix it, this is the reason why it shows in this conversation.
It's not related to the reactivity core but to the extraVars not being passed in the directive handler.
#183 would fix it but there are conflicts right now so it needs updating.
I'm waiting to know if we are interested in merging it. If we are, I can spend more time to update the PR.

@ryangjchandler
Copy link
Contributor Author

Have just seen that PR mention! Sorry I missed it.

@samadadi
Copy link

Hi. I am confused. Does alpine support nested loops in current version(2.x.x)?

@SimoTod
Copy link
Collaborator

SimoTod commented Mar 10, 2020

@samadadi not yet

@samadadi
Copy link

Is there any plan from alpine team to add nested loop implementation in future?

@HugoDF
Copy link
Contributor

HugoDF commented Mar 10, 2020

@samadadi pretty sure it's something that will be added, you've seen the PR for it right? #183

@SimoTod is just waiting on feedback from Caleb that his approach is going to get accepted.

@SimoTod
Copy link
Collaborator

SimoTod commented Mar 11, 2020

yeah, that PR needs updating since there have been a few changes to the x-for logic since so it won't work straight away.
I'm waiting for Caleb to confirm that it's something we want to integrate before spending more time on it.

@the94air
Copy link
Contributor

the94air commented Mar 14, 2020

@samadadi check this out and see if it fixes your issue temporarily :)
#158 (comment)
Also it seems to be supported everywhere except IE
https://caniuse.com/#feat=object-entries

@rrrrando
Copy link

Any updates on this?

@calebporzio
Copy link
Collaborator

Thanks everyone, this issue will be fixed in the next release. Here is the PR: #316

Note: for now, we are not supporting directly nesting <temlate> tags, but we WILL support nesting template tags with wrapping elements like so:

<div x-data="{ foos: [{bars: [{bobs: ['one', 'two']}, {bobs: ['three', 'four']}]}, {bars: [{bobs: ['five', 'six']}, {bobs: ['seven', 'eight']}]}] }">
    <template x-for="foo in foos">
        <div>
            <template x-for="bar in foo.bars">
                <div>
                    <template x-for="bob in bar.bobs">
                        <span x-text="bob"></span>
                    </template>
                </div>
            </template>
        </div>
    </template>
</div>

@wilari932
Copy link

Is nested loops supported yet I tried with the lastest version but doesn't seem to work 🤔

@Uplink03
Copy link

Is nested loops supported yet I tried with the lastest version but doesn't seem to work 🤔

Yes, this works now.

I landed on this report because I had this problem earlier, but the cause is weird: AlpineJS got tripped by void elements e.g. <span x-text="myVar"/>. Replace it with an empty open/close tag pair and things start working.

I don't know if this what your problem was though.

@ekwoka
Copy link
Contributor

ekwoka commented Jan 15, 2024

@Uplink03 There is no such thing as a void element in HTML.

/> does not actually mean anything at all.

Elements are all either self closing, or an open tag. You cannot choose which.

If you do <span /> you are opening a span element, and the browser will follow various rules to decide when it should auto close it (like closing another element that was around it, or opening an element that is not allowed inside of the element).

<span/> works in jsx, and it is CONVENTION alone that has it common on self-closing tags like img, not actual specification.

@ekwoka
Copy link
Contributor

ekwoka commented Jan 15, 2024

@wilari932 Nested for loops have worked since 2021.

@Sans84
Copy link

Sans84 commented Mar 10, 2024

Thanks everyone, this issue will be fixed in the next release. Here is the PR: #316

Note: for now, we are not supporting directly nesting <temlate> tags, but we WILL support nesting template tags with wrapping elements like so:

<div x-data="{ foos: [{bars: [{bobs: ['one', 'two']}, {bobs: ['three', 'four']}]}, {bars: [{bobs: ['five', 'six']}, {bobs: ['seven', 'eight']}]}] }">
    <template x-for="foo in foos">
        <div>
            <template x-for="bar in foo.bars">
                <div>
                    <template x-for="bob in bar.bobs">
                        <span x-text="bob"></span>
                    </template>
                </div>
            </template>
        </div>
    </template>
</div>

What's the current state of play with supporting directly nesting tags? Very often I need to use data from nested items, but I can't use wrapper tags.

<div x-data="{foos: [{bar: 'test', bazs: ['one', 'two']}]}"> <!-- parent element -->
    <template x-for="foo in foos">
        <template x-for="baz in foo.bazs">
            <div x-text="foo.bar + ' ' + baz"></div> <!-- child element -->
        </template>
    </template>
</div>

child elements must be direct descendants of the parent element

@ekwoka
Copy link
Contributor

ekwoka commented Mar 11, 2024

Easy, flatten your data.

X-for="Baz in foos.flatMap(foo => foo.bars)"

Or use the display contents wrapper element

@Sans84
Copy link

Sans84 commented Mar 11, 2024

Easy, flatten your data.

X-for="Baz in foos.flatMap(foo => foo.bars)"

Or use the display contents wrapper element

You didn't take into account that you also need to have foo.bar. Yes, this can be done by going through the array several times

x-for="[baz, bar] in foos.flatMap(foo => [ foo.bazs.map( baz => [baz, foo.bar] ) ] ).flat()"

but it's not very good, imho

@ekwoka
Copy link
Contributor

ekwoka commented Mar 11, 2024

I'd probably jsut use a display: contents wrapper.

@SimoTod
Copy link
Collaborator

SimoTod commented Mar 11, 2024

@Sans84 the inner loop has access to the parent scope without workarounds. What issue are you having exactly? This post was about v2 where you couldn't do it but it's been supported for years.
Nested loops have issues with the order elements are printed, if anything, which is where an intermediate div is often required.

@Sans84
Copy link

Sans84 commented Mar 12, 2024

Nested loops have issues with the order elements are printed, if anything, which is where an intermediate div is often required.

This is the problem. Either we get implicit behaviour with the order in which elements are displayed, or we are forced to use wrapper elements, which is not always possible

I'd probably jsut use a display: contents wrapper.

This creates other implicit issues with CSS

@ekwoka
Copy link
Contributor

ekwoka commented Mar 12, 2024

Which issues does it create?

It can impact selectors that use direct child selectors, which is able to be worked around, but it won't impact actual CSS styling.

I use display:contents a lot for these kinds of "conditional grouping" in the layout and it works wonderfully in every case (except inside tables that hate divs but then a tbody normally is fine for that)

@SimoTod
Copy link
Collaborator

SimoTod commented Mar 12, 2024

Nested loops have issues with the order elements are printed, if anything, which is where an intermediate div is often required.

This is the problem. Either we get implicit behaviour with the order in which elements are displayed, or we are forced to use wrapper elements, which is not always possible

I'd probably jsut use a display: contents wrapper.

This creates other implicit issues with CSS

Ok, I was confused because the original post was about something else and your original example didn't have any problems. The div with `display: contents' is practically invisible to css rules unless you use a direct child selector. You can try to submit a pr if you find a way to fix it, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.