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

[feature request] v-empty for v-for #4174

Closed
gbezyuk opened this issue Nov 10, 2016 · 21 comments
Closed

[feature request] v-empty for v-for #4174

gbezyuk opened this issue Nov 10, 2016 · 21 comments

Comments

@gbezyuk
Copy link

gbezyuk commented Nov 10, 2016

It's a common issue when rendering lists to show a message in case the list is empty. In Django, for example, {% for %} template tag supports {% empty %} for this:

{% for platform in game.platform_set.all %}       
    <td>{{ platform.system }} -- ${{ platform.price}}</td> 
{% empty %}
    <td>No Platforms</td>
{% endfor %}

Probably we could benefit from the similar feature in Vue.

There are cases with the list being contained inside an element to be hidden when the list is empty, and it works just fine with Vue if we want to hide the whole container:

<ul v-if="items.length">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
<p v-else>Empty list</p>

When it's not the case, however, the syntax is getting a little verbose. Emulating the Django example mentioned above:

<td v-for="platform in game.platforms" v-if="game.platforms.length">
  {{ platform.system }} -- ${{ platform.price }}
</td>
<td v-else>No Platforms</p>

we could benefit from a simplification, getting rid of expicit v-if:

<td v-for="platform in game.platforms">
  {{ platform.system }} -- ${{ platform.price }}
</td>
<td v-else>No Platforms</p>

UPD: v-empty could form a nice name as well, in case we'd like to avoid conflicts with existing v-else implementation.

@posva
Copy link
Member

posva commented Nov 10, 2016

I prefer the explicit ways, especially if your v-if depends upon another condition:

<td v-for="platform in game.platforms" v-if="showPlatforms">
  {{ platform.system }} -- ${{ platform.price }}
</td>
<td v-else>No Platforms</p>

I'd rather create another directive v-empty to handle that syntactic sugar 🙂

@gbezyuk
Copy link
Author

gbezyuk commented Nov 10, 2016

@posva, my vote for v-empty

@gbezyuk gbezyuk changed the title [feature request] v-else for v-for [feature request] v-empty for v-for Nov 10, 2016
@posva
Copy link
Member

posva commented Nov 10, 2016

@gbezyuk I was, however suggesting you to create it as a separate package 😛 . IMO the benefits are not enough to include it in the core

@gbezyuk
Copy link
Author

gbezyuk commented Nov 10, 2016

@posva, well, right now I just sorta raise the idea for a discussion =)
I agree that it's not something really important. Currently I have other tasks, but maybe somebody else would like to implement this feature.

@dsonet
Copy link
Contributor

dsonet commented Nov 14, 2016

I would rather use the v-else with v-if.
v-empty or v-else with v-for is not that easy to understand.
keep it simple stupid.

@yyx990803
Copy link
Member

I agree with @dsonet - I'm not fond of the idea of introducing special behavior directives when you can simply do v-if="!arr.length" - a dedicated directive may save a few keystrokes, but at the cost of explicitness, and requires you to backtrack for the corresponding v-for to be sure of what it does.

@juanr2001
Copy link

juanr2001 commented Mar 14, 2018

@yyx990803 I have a question. Which takes more precedence v-else with v-for

<h1 v-if="typeof obj === 'string">Show string</h1>
<h1
v-else
v-for="i in obj"
:keys="i.id
>{{i}}</h1>

How does Vue handle this situation? Do you recommend this?

@matthiasg
Copy link

@yyx990803 I am also concerned about enlarging APIs for convenience, but since v-for also handles the case of iteration through objects that aren't arrays, I would indeed value a v-empty or v-for-empty directive. Iterating through e.g an objects properties does not have a direct length property etc. The fact that an iterator is empty seems very much symmetrical to v-if/v-else to me.

In my current case, I iterate through properties (easy with v-for) without necessarily needing to express in the template how the iterator is implemented. Since v-for has to iterate anyway, extending it to keep a boolean (empty/notempty) and rendering an 'empty' branch if the loop content was never touched after iteration should be helpful, straightforward and more performant than calling Object.keys(obj).length even if the template includes an understanding of the iterator implementation.

Empty Iterator branches also have precedence in all other template languages I used so far.

The current way seems quite clumsy (especially since it binds the template to an implementation detail of the iterator) and it won't really get any better when using a component method on this either.

<section v-for="(part,key) of locals.missing">
  <h3>{{key}}</h3>
  <span>{{part}}</span>
</section>
<section v-if="Object.keys(locals.missing).length === 0">
  <span v-translate="vt>All translations available</span>
</section>

@jods4
Copy link

jods4 commented Apr 2, 2020

Sorry to revive a dead issue 🧟‍♂️

This is not just convenience as pointed in the previous comment (that has 19 👍)

In addition to ranges and objects previously mentionned: modern JS uses Iterables for several things, e.g. Map, Set, custom code (generators).

v-if='!array.length' is good enough for arrays but not for those.
The workaround is clumsy and implies running the iterator a second time, which may be costly or yield different results (shouldn't but hey, who knows).

Built-in support could do this with a simple syntax, without adding new watchers, nor running iterators a second time.

This is even more an issue if you try to create a generic list component.
Consider a component with props items:

<template>
  <div v-for='item in items' v-slot='item'>
    <slot />
  </div>
  <div v-if='ouch'> Empty! </div>

Try to support binding anything v-for accepts into items. It's ugly.

v-empty (open to bike shedding) would be a nice addition to Vue 3.

EDIT I'm gonna add one bit: because v-for is something baked into the compiler, Vue core is in a special position to implement this, which wouldn't be easy to perform as efficiently in user-land lib.

@Sarke
Copy link

Sarke commented Jan 19, 2021

I'm actually surprised this hasn't been implemented. If you look at other templating languages it is a common feature.

@sirlancelot
Copy link

sirlancelot commented Jan 19, 2021

I've read through this a few times and I personally don't think v-empty is needed. Nearly every time I think I might like this, I find out that I wasn't completely understanding my problem. The following two code snippets are all that I've ever needed. Start with number one, migration to number two is very straightforward:

<!-- Option 1 -->
<template v-if="items.length">
  <td v-for="item in items">{{ item.message }}</td>
</template>
<td v-else>Empty list</td>

(See: Conditional Groups with v-if on <template> for details.)

When the occasion calls for a proper wrapping element, you just need to change the template tag to a real tag:

<!-- Option 2 -->
<ul v-if="items.length">
  <li v-for="item in items">{{ item.message }}</li>
</ul>
<div v-else>Empty list</div>

Every loop in every codebase I manage falls in to one of these two options. I understand that other template languages have an empty feature, but I feel like adding more Vue-specific syntax will raise the barrier of entry for newcomers.

Finally, avoid v-if with v-for on the same element.

@matthiasg
Copy link

matthiasg commented Jan 20, 2021

To be honest I would rather write:

<td v-for="item in items">{{ item.message }}</td>
<td v-empty>Empty list</td>

than

<template v-if="items.length">
  <td v-for="item in items">{{ item.message }}</td>
</template>
<td v-else>Empty list</td>

@jods4
Copy link

jods4 commented Jan 20, 2021

@sirlancelot Now what about an iterable/generator object? That doesn't have a length.

@matthiasg
Copy link

@jods4 iterators usually have state and would need to be recreated every time it renders (e.g. as an output of a computed property or function) I would advise against doing that anyway. But it is correct that those would have a length of course, whether they should be supported at all is a different matter (could lead to weird mistakes)

@MattiasMartens
Copy link

This nice bit of syntactic sugar is an odd thing not to have imo.

Apart from issues where it has direct utility, e.g.:

  1. An iteratee derived from a semi-complex expression, where the need to refer to it twice adds cruft and introduces the possibility of bugs if the two references go out of sync
  2. An iteratee that cannot be declared empty just by referencing length (especially e.g. a generator, perhaps returned from a method call and so only accessible in that scope)

...It also has value as a way of nudging one towards best practice. @sirlancelot said that any time he thought he needed v-empty it turned out he didn't understand the problem fully. For me it's the exact opposite: many times I've coded up a list only to realize that the design would misdirect the user in the case where the list is blank. I have found that a fallback is needed more often than not.

And of course @sirlancelot 's ‘Option 1’ is exactly equivalent to the function of v-empty. It's just slightly more verbose, and hence slightly less idiomatic-looking. The idea, to me, is that if a pattern is universally considered Right and Good, and it comes up often, that pattern ought to have the simplest syntax it reasonably can.

(I do agree that no new syntax is called for to simplify @sirlancelot 's ‘Option 2’; that looks the way it should. I don't see how it could be abbreviated in a way that's worthwhile.)

I'll also add that given how many template languages do have a feature like this, the absence of such a feature in Vue becomes confusing. I mean, that's how I wound up here.

@mrleblanc101
Copy link

mrleblanc101 commented Mar 29, 2022

I agree with @dsonet - I'm not fond of the idea of introducing special behavior directives when you can simply do v-if="!arr.length" - a dedicated directive may save a few keystrokes, but at the cost of explicitness, and requires you to backtrack for the corresponding v-for to be sure of what it does.

This create more useles template nesting and is different from most other templating language (Twig, Blade, Handlebar, etc). I think you should reconsider.

@jods4
Copy link

jods4 commented Mar 29, 2022

It's indeed sad that this request is closed:

  • It has enough upvotes to easily be on the first page of open issues;
  • People keep coming back to it since 2016;
  • It's a super common scenario that most template languages have support for;
  • Nobody has addressed things that don't have .length;
  • Community can't fill-in the feature because it's baked in Vue compiler.
  • It has no drawback?

@yyx990803 please consider re-opening this issue.
JS has changed since 2016 and .length > 0 is not a comprehensive answer anymore.

@matthiasg
Copy link

The vue community and core team are very much focused on keeping the API simple and I respect that very much.

Nonetheless the implementation effort of a v-empty directive is so minutive and the benefit of aligning with common expectations coming from other template approaches in addition to keeping the template itself simple and structured should outweigh these hesitations.

If it is just a resource issue I would understand that and the team could ask for a PR including commitment to include changes to docs and tests of course. That would be understandable. The fastify team does that often and it is a good approach to judge actual desire of people to get a feature.

If it is a philosophical issue I cannot follow it.

@SyedHusaini
Copy link

SyedHusaini commented Apr 6, 2022

I first fell in love with for-else loop when I was introduced to it in Python.

I switched to Vue as my framework of choice because of the pure simplicity of it's syntax in many places where other frameworks did little to simplify things. Hence, it was disappointing to find out that not only does Vue not have this loop built in (which I guess is understandable considering this loop is not mainstream), but Vue has failed to entertain this feature request so far. It is disappointing to see this issue marked as closed

@jods4
Copy link

jods4 commented Apr 6, 2022

@SyedHusaini @mrleblanc101 @MattiasMartens and anyone who would still like to have this:
I opened an issue in Vue 3 repo a while ago and was directed to create an RFC discussion instead.
Maybe we'll have more impact if you make your voice heard on vuejs/rfcs#256 rather than this old, closed issue 📢

@mrleblanc101
Copy link

@jods4 Thanks. I guess my reflex is to come to this repo since I use Nuxt which is still Vue 2 based for now.

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

No branches or pull requests