Closed caikan closed 2 years ago
@caikan For the second case, you can change the code as below.
<template>
<div>
<div v-if="conditionA"></div>
<div v-if="conditionB"></div>
</div>
</template>
Because your code may cause two div element in template which is not allowed in vue. And you first sample code only results in one template and one div in template.
I'm curious, why can't the template contain multiple nodes and just become <div>
, and avoid the nested single div. If the reasoning is so that you can pick the element you want as the root, can't that be solved by <template as="footer">
??
@larryu Thanks. But sometimes I want to use the wrapper elements as few as posiblle.
This would technically require much more complex analyzing of the template structure and I'm not sure if it's worth it when you can achieve the equivalent with this:
<div v-if="!conditionA"></div>
<div v-else-if="conditionC"></div>
<div v-else-if="conditionD"></div>
<div v-else></div>
If the implementation process is complex, can we temporarily use a warning instead of a compilation error and allow its first child node as the root element?
BTW: <keep-alive>
can be used as component root element now(Vue v2.3.4), but the non-first child node will be discarded. I think there should be a warning here, and <template>
can be treated in the same way.
OK, I think I may use <keep-alive>
instead of <template>
as the root elements wrapper now.
+1, feature request.
had to make <th>
s, <td>
s as root elements of <template>
, otherwise <table>
's layout would break.
<!-- some-table.vue -->
<table>
<tr>
<copy-first :arr="arr">
<template slot="elem" slot-scope="s">
<td>{{s.index}}</td>
</template>
</copy-first>
</tr>
</table>
<style>
tr th:nth-child(1), tr th:nth-child(2),
tr td:nth-child(1), tr td:nth-child(2) {
/* hack of fixed column */
}
</style>
<!-- copy-first.vue -->
<template>
<slot v-if="arr[0]" name="elem" :index="0" :elem="arr[0]"></slot>
<slot v-for="(elem, i) in arr" :key="i" name="elem" :index="i" :elem="elem"></slot>
</template>
<script>
export default {
name: 'copy-first',
props: { arr: Array }
}
</script>
+1 When it comes to generating tables it simplifies things
+1
Perhaps something as simple as allowing <root>...</root>
as the root component wrapper? If that element is used it could just return everything within the element, but not the root wrapper itself?
React has a nice implementation of this in the current version: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
How about add fragment for root like a Web Component with shadow root.
Another use for this is that am refactoring a component in order to make it easier to maintain. It's a form and various parts of it fall on a CSS grid. The extra wrapper required when I group several related elements in the same component is causing the grid to treat that whole component as one grid-item. I'm sure I can work around that, but hope we end up with a fragment syntax like react's (if we don't already have such a thing?)
My primary need for this is to maintain valid HTML. I have a component that generates table rows with different rendering depending on different logic, and I can't give the <tr>'s a div parent without violating HTML rules. Accessibility is a priority of this table, so good HTML structure and semantics are important. I'm fairly certain I can satisfy my need with render(), but it's a bummer that I have to sacrifice a clean declarative implementation.
@njleonzhang For what it's worth, though I haven't tried it yet, I recently learned about portal-vue feel a workaround may be possible and relatively easy with it for many cases where the component structure Vue needs is different to the DOM structure we want to actually end up with (https://github.com/LinusBorg/portal-vue). I'm looking forward to playing with it. I will post a demo if I get time to create a clean example, but wanted to go ahead and share the library anyway so others can maybe check it out.
@markbrouch currently I make all grid items in one component and provide a layout
props
to customize the component. I will try portal-vue
next time, and tnx for your information.
now, I handle this problem in this way:
// Bar.vue
<template>
<div class='row'>
<div class='col'> <input v-model='col1'></input> </div> // common cols
<div class='col'> <input v-model='col2'></input> </div>
<div class='col'> <input v-model='col3'></input></div>
<slot></slot> // for special col
</div>
</template>
<script>
export default {
props: ['value'],
name: 'Bar',
computed: {
col1: {
get() { return this.value.col1},
set(val) {this.$emit('input', { ...this.value, ...{col1: val}})}
},
col2: ..........,
col3: ..........
},
provide() { // provide itself
return {
bar: this
}
}
}
</script>
Notice the component contains a slot
, provide itself, and support v-model
To extends the component and make it contain more col
, define components for the special col
.
// Col4.vue
<template>
<div class='col'> <input v-model='bar.value.col4'></input></div> // dirty
</template>
<script>
export default {
name: 'Col4',
inject: [
'bar'
],
created() {
this.$set(this.bar.value, 'col4', '') // dirty
}
}
</script>
At last, I use Col4
as slot of Bar
:
<bar v-model='someData'>
<col4></col4>
</bar>
The Bar
contains 4 col now, and the no wrapper element problem.
I define multi col components and compose them to meet different requirement:
<bar v-model='someData'>
<col7></col7>
<col4></col4>
<col8></col8>
</bar>
<bar v-model='someData'>
<col4></col4>
<col9></col9>
</bar>
This method is somehow dirty, but it help me out.
Hope this can help someone who is still struggling on this problem.
The whole issue could be irrelevant if fragments were supported instead. What's the reason why it's disallowed really? The patching algorithm already works on arrays, it's just the fact that it starts from node to iterate on it's children, instead of starting iterating on array to visit all nodes in this array. I had similar issue with vuelidate once, where I ported simplified version of DOM patching into validation tree to find differences in "virtual validation tree". It was required to support multiple nodes on each level, as there were no guarantees about data shapes. It turned out it was pretty easy to just turn the algorithm upside down to treat the array as entry point.
This is a very important technology.
Because vue currently prohibits more than one element as the root element of the template.
We are unable to create a multiple-root-element template.
but if vue allow the
<script type="text/x-template" id="row-template">
<template>
<tr>
<td v-for="col in cols">{{row[col.col]}}</td>
<td v-on:click="ep=!ep"> <span v-text="ep?'collapse':'expand'"></span></td>
</tr>
<tr v-show="ep">
<td v-bind:colspan="cols.length+1">Content of collapse Area</td>
</tr>
</template>
</script>
thats will be very good way to create multi root-element in template.
We do not need to add an extra element to wrap it. Because the disposal of surplus extra element is a waste of time. also, the extra element will degrade performance.
Actually, this issue has been referred to by more than one issue. https://github.com/vuejs/vue/issues/7088 https://github.com/vuejs/vue/issues/8191 https://github.com/vuejs/vue/issues/655 But still not been solved.
@yyx990803
Can solve it as soon as possible. If this issue cannot resolve in 2.X, Please inform all users, this issue is 2. X version will never be solved.
Finally, in any case Thanks for vue community.👍
When I write this code (The structure is shown as an example.):
<template>
<template tag="span">
<div class="one"></div>
<div class="two"></div>
</template>
</template>
I want to get this result:
<span>
<div class="one"></div>
<div class="two"></div>
</span>
and when i write
<template>
<template tag="div">
<div class="one"></div>
<div class="two"></div>
</template>
</template>
i want this:
<div>
<div class="one"></div>
<div class="two"></div>
</div>
But i get error. Yes, and in principle, when this is the record:
<template>
<div>
<div class="one"></div>
<div class="two"></div>
</div>
</template>
This kind of view is not entirely correct in appearance and use:
<div>
<div class="one"></div>
<div class="two"></div>
</div>
This is similar to the fact that some kind of empty wrapper is used and very often it seems to me that I forgot to add something, and it doesn't look very good in the skin.
@Jlomaka use the is
attribute to dynamically set the tag in anything but a template
(div
or component
)
+1 Rendering tables is the most common practical scenario where this would be useful, and I strongly agree with this request. <tr>
and <td>
tags can't be wrapped in a <div>
.
Is this still under consideration? Or is there a workaround I don't know about yet?
Another possible solution would be for vue to have a switch statement for the contents like
<template v-switch="myComponentType">
<componentTypeA v-case="componentTypeA" ...></componentTypeA>
<componentTypeB v-case="componentTypeB" ...></componentTypeB>
</template>
@RPainter8West If I understand your need correctly, I think this is already possible with Vue's built-in dynamic component feature: https://vuejs.org/v2/guide/components.html#Dynamic-Components, though maybe docs don't explore the potential of this enough. I've used this pattern in the past:
<template>
<component :is="someProp"></component>
</template>
Where someProp
is any component's name, and there's some other logic or styling in the file that's reusable across multiple inner components. The named component will render in place of the <component>
element.
So for the use case where a person wants to render only one of a group of components, with no extra wrapper element, I think it's already covered.
Totally agree with @maxnorth .
I am trying to implement a simple table with data coming from Algolia Vue InstantSearch and the tr
is wrapped with a div
. This breaks the table and the solution is to work with custom widgets (connectors). This is a shame since HTML tables are one of the most used elements.
the same problem here with tr that is wrapped with a div
The requirement of wrapping with a div, or some other element, breaks content flow all the time.... There has got to be a way around this?
All I honestly want from a <template>
is:
Parent:
...
<ul>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
<child></child>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
</ul>
...
Child:
<template>
<li>child list element</li>
<li>child list element</li>
</template>
All in order to get this beauty:
...
<ul>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
<li>child list element</li>
<li>child list element</li>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
</ul>
...
If I use a work-around like such then it breaks my CSS:
<template>
<div>
<li>child list element</li>
<li>child list element</li>
</div>
</template>
because I get this instead:
...
<ul>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
<div>
<li>child list element</li>
<li>child list element</li>
</div>
<li>parent list element</li>
<li>parent list element</li>
<li>parent list element</li>
</ul>
...
:cry:
Did someone find a solution for this table problem ?
As a (temporary) solution, you can use vue-fragment
.
<template>
<fragment>
Your content here
</fragment>
</template>
Output:
<!--fragment#1690ec9267a#head-->
Your content here
<!--fragment#1690ec9267a#tail-->
The good news is that Fragments are included in Vue 3 so we'll have multiple root elements in a template!
https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/
Until Vue 3 version comes out I would strongly recommend using a method proposed by @FedericoBiccheddu.
If for any reason, you don't want to use the third party lib, you can use this hack:
<template v-if="true">
<p>Sibling 1</p>
<p>Sibling 2</p>
</template>
Unfortunately, this solution is limiting (same as in conditional rendering), so you can't return a component that has 2 siblings without root element. This won't work:
<template>
<template v-if="true">
<p>Sibling 1</p>
<p>Sibling 2</p>
</template>
</template>
This will:
<template>
<div>
<template v-if="true">
<p>Sibling 1</p>
<p>Sibling 2</p>
</template>
</div>
</template>
As a (temporary) solution, you can use
vue-fragment
. ...
This deserves a lot more upvotes!
Duplicate of https://github.com/vuejs/vue/issues/7088
Here are the current alternatives: https://github.com/vuejs/vue/issues/7088#issuecomment-421510160
What problem does this feature solve?
From compiling template error message:
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
ButCannot use
as component root element because it may contain multiple nodes.
So I can't use
<template>
as component root element even if it contains only one node.What does the proposed API look like?
Allow the following code as component root.
Do not allow the following code as component root.