Loving project-x!
I've been able to test out many of the things in stimulus and recreate them. One issue i'm not sure how to handle referencing repeating row elements.
Scenario: displaying the subtotal from a list of shopping cart items. I'd like to loop over a data-attribute value containing the subtotal of each line item.
Here's an example in VueJs
https://stackoverflow.com/questions/44063337/add-all-values-in-vuejs
In Stimulus, elements having the same name can be accessed through this.[name]Targets and I would typically loop over the resulting array and sum the data attributes.

Perhaps $refs can be expanded to access multiple items having the same name as an array?
Open to other ways as well. Thanks!
P.S. I wasn't sure the best place to ask this (github or twitter). A subtopic in the new livewire forum might be useful.
Hey @keyurshah, that's great to hear! Glad you're enjoying it!
First, maybe paste in a sample code snippet so we can talk more concretely about this specific problem.
Second, if there wasn't a simple way to use the native DOM js API to get a list of elements you need, then maybe expanding $refs to return a group of x-ref is on multiple elements is the way to go.
I don't have a concrete example at the moment, but I think I can resolve this by adding a new class / data element to each div and then using document.querySelectorAll and foreach to loop and sum. If I run across an example, I'll post back.
$refs is handy for a single element. So having a utility to apply an expression to multiple refs might still be useful in the future.
I should be ok for now and I'll close the issue. Thanks!
@calebporzio @keyurshah I just stumbled upon a great example working on a side project today...
Let's take the Github comment timeline (a simplistic version at least) and imagine that we want to parse some markdown content that is being rendered server-side:
<ul x-data="MarkdownParser()" x-init="() => { compile() }">
<li class="comment markdown" x-ref="comment">
Alpine is an **awesome** library. Thanks Caleb!
</li>
<li class="comment markdown" x-ref="comment">
Alpine is indeed an **awesome** library :)
</li>
</ul>
<script>
function MarkdownParser(){
return {
compile() {
this.$refs.comment.forEach((el) => {
let html = MdCompiler.compile(el.innerHTML)
el.innerHTML = html
})
}
}
}
</script>
The code above was supposed to compile the markdown and change the element's content all at once, but right now this is not possible because this.$refs.comment refers only to the latest element declared.
I guess this approach is useful for avoiding cases where the component initialization is expensive (not even considering Alpine's component bootstrap itself).
The ways you have around this today are either:
1) Adding classes to the elements and getting them upon initialization using document.querySelectorAll("comment")
2) Initializing the component on each <li>. This approach has two major drawbacks, which are performance (probably) and not having an umbrella component to keep state for the whole list
I'd like to suggest adding a modifier for this. Something like:
<li class="comment markdown" x-ref:comments></li>
<li class="comment markdown" x-ref:comments></li>
// this.$refs.comments[0]
// this.$refs.comments[1]
For pushing the element directly into a
commentsarray
<li class="comment markdown" x-ref:comments="foo"></li>
<li class="comment markdown" x-ref:comments="bar"></li>
<li class="comment markdown" x-ref:comments="uuid()"></li>
// this.$refs.comments["foo"]
// this.$refs.comments["bar"]
// this.$refs.comments["d86abf47-9bc9-424e-b7fe-a05e86db41b2"]
For pushing the element into a
commentsarray with the given key
The main functionality remains the same with only the addition of the "placement" modifier that lets you define which place to push the element inside the $refs.
PS.: @keyurshah I guess we could reopen the issue to keep track of the conversation.
Hi guys, did anyone have the time to take a look at the example I posted? What are your thoughts?
I can't see any benefits to be fair.
Markdown should be converted server side otherwise you have to hide it to avoid content flickering.
X-ref is a normal html attribute (it will be renamed to ref in v3), Alpine doesn't store a list of references anywhere but it looks up for elements at runtime so performance wise there's no much difference compared to other dom selectors.
Hello again @SimoTod! 馃槃
Markdown should be converted server side otherwise you have to hide it to avoid content flickering.
I don't think this is objectively true, is it!? I worked in a recent project with this requirement and didn't experience such a thing. I guess because the markdown compilation is done in the component initialization - also, this is why I wanted to have access to the items bellow without having to query the DOM (again!?).
If you have time, could you give an example of how could this be a problem in this particular case? However, forget about markdown, it's just an example; if content flickering is a problem it would happen for other cases as well, right? So I'm not sure this workflow is completely invalid.
Bear with me: Even though x-ref is just a normal HTML attribute, this don't prevent us to create a better, let's say "syntactic sugar" for accessing those values, wouldn't you think so?
I have outlined the main reasons I can think this is a good idea before:
The ways you have around this today are either:
- Adding classes to the elements and getting them upon initialization using document.querySelectorAll("comment")
- Initializing the component on each
- . This approach has two major drawbacks, which are performance (probably) and not having an umbrella component to keep state for the whole list
It was related to your snippet. You have Markdown content in your page and alpine kicks in when you get the page ready event. It means that there is a small interval of time where your page is going to show the original unformatted content, then alpine kicks in and it 'fixes' your content.
It's generally true but not obvious unless you change the content of a dom element after page loading as you are trying to do.
This is the reason why we have x-cloak and x-if/x-for use template tags (which are naturally hidden).
About the second point, if you look at the code, you can figure out why the suggested implementation would be complex. That "syntax sugar" would add complexity to the library, bugs and tickets when people start to use it and i can't see a real benefit.
That being said, it's my personal opinion. You can work on that feature if everyone else is happy/feels we need it.
P.s. I'll send you an example over the weekend
@SimoTod I see, thanks for your time, though. I guess I was looking for a more concrete example on your side on how this could be such a problem. I'll take your advice to take a look at the source code, but just to be on the same page: could you also provide some corner-cases examples that I should be looking for?
P.s. I'll send you an example over the weekend
Ok, thanks!
I appreciate the response was quick and without many details but I promise I'll send you a more exhaustive one later this week (probably Friday, it's bank holiday in UK). I'm just super busy with work this week and I need some quality time to wind down in the evening.
P.s. My consideration are not generic but only based on the current architecture of Alpine if you have time to have a look at the code and try some example, you should see what I mean.
This is the ref logic at the moment: https://github.com/alpinejs/alpine/blob/master/src/component.js#L370
References are calculated on request.
@thiagomajesk Right, so I'm replying now even if I said I would have not. :)
This should show the issue I was talking about.聽https://codepen.io/SimoTod/details/GRpQEdw
It's not a problem but people should consider serving the right content from the backend and using Alpine to enhance聽it and to add reactivity after the page has loaded, mostly when users click buttons, type texts, etc.
This is because Alpine starts聽when the browser receives the DOMContentLoaded event so, if you have a fat page and a slow connection, you would see that flickering. You can use x-cloak to hide it, as I said it's not a problem, but it shows as separation of concerns could improve the general UX. Alpine does not want to see a templating system (for that reason he doesn't offer interpolation like other libraries, the {{ ... }} thingy to be precise) even if nobody stops you from using it in a different way.
Now, the solution that you suggested would add complexity to a relatively聽simple implementation. It means that, when starting a component, we need to save a list of all references and keep that list up to date when the DOM changes (e.g. we added new elements, removed elements, changed attributes, etc). It also means that we need to support/parse multiple formats, evaluate the content if it's a function (so the attribute should have nested quotes if you really want to use a string otherwise you can't tell them apart, you either evaluate the content or you do not so you would have x-ref:comments="'foo'" which is a breaking change). It also goes against the v3 roadmap where Caleb is planning to use the standard ref attribute so we probably don't want to make it Alpine specific.
As you can imagine, this is a possible source of bugs so, given Alpine is non-commercial, it's something that I personally don't see as necessary because it will make people spend聽time to fix those bugs rather than real ones, especially given that it's already possible with a relatively simple syntax (I don't see much difference between this.$refs.comments.forEach and document.querySelector('#component .comments').forEach).
The only possible we can MAYBE talk about (less powerful聽that the one you described) is to return an array if there are the more components sharing the same x-ref (still walking the DOM at runtime though). It would still add complexity when your reference are generated dynamically by x-for or x-if. In that case you won't know how many of them are in the page at a given moment so your output could be either a DomElements or a list of DomElements and you have to type check the variable before using it.
I hope it makes a bit more sense the before. Long story short, it's more complex than it looks like.
@SimoTod Thanks for sparing the time to do this.
About the "content flickering": you are right, I was confused at first about what you meant by "flickering", I thought you were talking about something that happens constantly (like an event/ digest loop or something)
Also, I recognize that using Markdown as an example was not very relatable, but I think you know it was not the main focus. However, just to expand on that a little (I won't dig this anymore): Since page size is a constraint that is different for each application/ requirement, I guess whoever is proposing the solution should be aware of the tradeoffs; for example, I chose to compile Markdown on the client-side because users can batch edit and preview Markdown and I couldn't ask for the server to retrieve both the compiled and original version - because of payload size (which would double). Also, your suggestion of using x-cloak could be very nicely used with content placeholders to show a loading state as feedback which is a UX pattern that many applications use to go around this situation.
About the relevant parts though: Thanks a lot for spending the necessary time to explain that to me, really. Now I know exactly the points you were trying to make...
When I was describing that way of accessing refs, I didn't think about actually keeping state of an array, I thought about evaluating it at runtime - the same way Alpine already does.
I think I've generated this confusion because I have used the word "pushing" and "array" while describing it, but I was actually talking about a "virtual-abstract-hypothetical" array, sorry for that 馃槃. So, I think your second perception was more in line with what was my intention.
I think we wouldn't have to support any other formats besides what x-data, x-init, and x-model already accepts. If you pass a 'string', it's a string, if you pass an [array] it's an array and if you pass an {object} it's an object, right? The same is valid for having to distinguish if there are more than one refs, the user is defining it, let the user treat/ decide - it's the same principle of any HTML attribute wouldn't you agree? Also, I would make this easier and say that x-ref="foo" is always one element ($refs.foo) while x-ref:key="foo" is always an array ($refs.key["foo"]). So the user is actually making a clear distinction when he chooses to use one or the other.