I've been struggling all day to find a way to conditionally render a directive on an element.
I ended up on this page: https://vuejs.org/guide/extras/render-function.html but then I wasn't able to append my compiled template (using vue-template-compiler, since I'm using some version of vue which doesn't include the compiler).
At the end of the day this is what I figured:
<div>Some foo</div>
<template v-if="withDirective">
<input :value="value"
:disabled="disabled"
:type="type"
v-some-directive="someValue" />
</template>
<template v-else>
<input :value="value"
:disabled="disabled"
:type="type" />
</template>
<div>Some bar</div>
Is there a better way?
I have a lot more attributes on the input, so there's really a lot of duplicate code which I would love to avoid.
This html is inside a custom component, so all the values like disabled, required, etc, are props passed from outside.
The best way I found so far is compiling a string template conditionally using compileToFunctions.
So instead of having the template tags and the v-if in the template, I simply add the conditional directive(s) before compiling the component.
ATTENTION
Keep in mind that the compilation is runtime, I don't have enough experience with Vue (yet) to tell if this way is faster than using v-if. What is sure is that we saved a lot of duplicate lines in the template, which is handy when editing common parts.
Related
Sorry for the beginner question (I'm fairly sure this will be a duplicate, but I actually can't figure out what terms to use in order to find it). I just started with Vue.
I am just getting started with Vue, and following this course (https://www.vuemastery.com/courses/intro-to-vue-js/communicating-events). In this problem, there is (and here I don't know the term, so I'm going to go with...) app-level data parameter called cart. cart is an array which holds the id of each item a user has added to cart.
The problem tells us to add a button to remove items from the cart.
I ran into problems trying to create a computed property, which would allow me to hide the "remove" button in the event the selected item is not in the cart (eg. `
Communicating data from the app-level (the cart array), to the component-level (to a computed property in the product component), so that I could use something like :hidden="!inCart" on the "Remove from Cart" button, which is itself defined in the component. inCart would be a computed value here.
Communicating the selected product from the component to the app-level, computing inCart at the app level, then using the computed value at the component-level.
Either way, I can't seem to figure out how to do this in the way I would want to, which would look something like how v-bind operates. Namely, I think I may be able to hack together a solution using methods (which I believe have to be triggered by certain events), but I don't understand how I might go about this using built-in functionality such that the value of inCart is dynamically auto-computed.
Maybe there would be an answer to this in the next few courses, but I don't see us covering that in the intro material. Sorry for the neophyte question. Thank you in advance.
In Vue the way you communicate "state" from higher-level objects to lower-level objects is through props.
So, assuming your app looks something like...
<MyApp>
<MyShoppingPageWithItems>
<MyItem></MyItem>
<MyItem></MyItem>
<MyShoppingPageWithItems>
</MyApp>
You need to pass the cart object down as a prop.
So in your MyShoppingPageWithItems template, you'll have something like...
<template>
<div>
<MyItem v-for="item in items" :item=item :cart="cart"?
</MyItem>
</div>
</template>
And in your item template...
<template>
<div>
<div>
{{item.name}}
</div>
<div v-if="cart.includes(item.id)">
Remove button or whatever
</div>
</div>
</template>
Not that the .includes() method is a native JavaScript method, which you can read more about here.
Edit
To reference a prop in a computed property (or anywhere else in a Vue component), just refer to this.propName, as demonstrated here in the Vue docs.
So, if you want to create a computed property, you can do the following:
<template>
<div>
<div>
{{item.name}}
</div>
<div v-if="isInCart">
Remove button or whatever
</div>
</div>
</template>
<script>
export default {
props: ['cart', 'item'],
computed: {
isInCart() {
return this.cart.includes(this.item.id)
}
}
}
</script>
Note that the formula is the exact same as above, but just includes this. for cart and item. In templates, the this. is implied when referring to props, data, and computed properties.
Is it allowed to use <template> tag inside template something like this?
I have to check some value.
<template>
<div>
<template v-for="category_field in category_fields">
<template v-if="category_field.show_type == 'new-row'">
//and also here can be more nested template tags
</template>
<template v-else>
//and also here can be more nested template tags
</template>
</template>
</div>
</template>
I am using this system in my project and wondering whether this is correct.
Yes, you can nest <template>s, and it's quite useful sometimes.
Vue's <template> is very similar to <React.Fragment>. It's a virtual container used for grouping or applying layout logic (using structural directives - e.g: v-for, v-if), without creating an actual DOM element.
Because it doesn't output a DOM element, it's used as a wrapper for SFC's HTML markup.
Technically, the limit on having only one child in Vue 2 was not coming from the <template> tag itself, but from the components, as Vue required them to have only one root element, which became the component's $el. More detail here.
Besides the typical usage of wrapping an SFC's markup, <template> tags are also used for:
combining structural directives (v-for, v-if) and letting Vue know in which order to apply the directives, as changing the order would likely change the result
applying layout or rendering logic (e.g: v-if) to multiple elements at once, without having to create an actual DOM wrapper around them, particularly useful when you don't want to break the parent/child relation of DOM elements (e.g: flex or grid parent/children, <ul>/<ol> + <li>, <tr> + <td>, <tbody> + <tr>, etc.).
reducing template boilerplate (e.g: moving the same v-if from multiple siblings on a <template> wrapper, so the condition is only written once).
Try using this. <template> require only one child.
<template v-for="category_field in category_fields">
<div>
<template v-if="category_field.show_type == 'new-row'">
//and also here can be more nested template tags
</template>
<template v-else>
//and also here can be more nested template tags
</template>
</div>
</template>
I have a Vue.js project (Nuxt.js) and as UI I use the Vuetify.
For e2e testing I use the Cypress.
Below is my scenarios of test in Cypress:
I have a problem while creating test for page where I use v-autocomplete component.
The problem is that I can't use Vuetify native classes to get the element I want to test.
below is an example with data-cy selector
<v-autocomplete
v-model="model"
:items="items"
item-text="Description"
item-value="API"
label="Public APIs"
placeholder="Start typing to Search"
data-cy="autocomplete"
></v-autocomplete>
I type some text into search input.
Then in v-autocomplete have been found search results.
And example of one of there is below:
...
<div>
<a class="v-list__tile v-list__tile--link theme--light">
<div class="v-list__tile__content">
<div class="v-list__tile__title">Result item
<span class="v-list__tile__mask">result item</span>
</div>
</div>
</a>
</div>
...
Then I want select one of search items by clicking to one of found results.
And for that I should to use native classes of Vuetify, but it is not have stability (.v-list__tile--link class сan be renamed by developers).
How I can add data-cy selector into result search html item?
Maybe who know any another way to resolve this problem?
I don't think v-list__tile--link can be changed by developers, it seems to be added at runtime DOM by the Vuetify library (unless you mean Vuetify developers, then sure it can change between versions).
In any case, if you want to be more content-oriented in your selectors, use Cypress .parent() selector
For example,
cy.contains('div', 'itemTextToSelect').parent('a').click()
If posssible make sure 'itemTextToSelect' is really distinctive (if you can set it in the test fixture).
BTW, before the user starts typing the autocomplete list is display: none, so you will need to either .type('something') into the input, or .click({force: true}) the item.
In Vue.js, if you want to conditionally render multiple elements via a v-if/v-else-if directive you can wrap them inside <template> tags and apply the directive to the <template> tag, as explained here. However, you can also do the same with <div> tags wrapping the multiple elements. Are there any noticeable performance benefits to using <template> over <div> or any other similar native HTML5 tag?
I doubt there is a performance change but a big difference is that the <template> node does not appear in the DOM.
This is an important distinction especially when grouping children that are the only valid node types for their parent such as list items, table rows, etc:
This is valid:
<ul>
<template v-if="something">
<li>Text</li>
<li>Text</li>
</template>
</ul>
This is not:
<ul>
<div v-if="something">
<li>Text</li>
<li>Text</li>
</div>
</ul>
I know that the question is quite old, but I found out one more thing
if you use divs - your div will be in DOM, but empty, if v-if is false and it can make some spaces looks like margins
if you use template - you don't have anything in DOM and it just don't appear
I think this is possibly a bug I've stumbled across, not sure. I'm getting this Vue.js warning for a component:
vue.js:2611 [Vue warn]: Cannot use <template> as component root element because it may contain multiple nodes:
The problem seems to be this:
<template id="tpl-field">
<template v-if="fieldType==='checkbox-inline'">
<label class="checkbox-inline">[SNIP]</label>
</template>
<template v-else>
[SNIP]
</template>
</template>
So I have two template nodes, which seems to be the multiple nodes it's choking on (certainly each of the template nodes contains just a single node). Yet this is an if-else in Vue - if one of the nodes is present, the other logically cannot be.
Demo of the problem here: https://jsfiddle.net/jonmor51/Ldz3k0jp/1/. If I wrap everything in a div, it works. But without, it fails. (Unfortunately, in the context where I want to use this, namely for inline checkboxes within a Bootstrap grid, wrapping in a div breaks things).
Not sure if this will solve your problem with bootstrap... but you could wrap you inner templates with a <transition> tag and set a key to each one.
Please check this working fiddle
https://jsfiddle.net/AldoRomo88/7c7znu3p/
helpful thing - just use div display: contents as a root of the component and browser will ignore that element and consider child elements (which can be many) as children of upper dom element
<div style="display: contents">
<template v-if="...">
<template v-for="..."> ...
</template>
<template v-if="...">
</template>
</div
works even inside tables!
The inner templates direct children, are they single elements? If so, you can just remove the inner templates and move v-if to the label.
Or, just use span rather than div as your quick fix, which won't break inline elements' style.