Passing boolean Vue prop value in HTML - vuejs2

I am fairly new to Vue and have started with a project with vue-cli.
I am looking into conditional rendering based on a prop sent from parent.
Home.vue (parent)
<template>
<Header have-banner="true" page-title="Home">
</Header>
</template>
<script>
import Header from "./Header";
export default {
components: {
Header,
},
name: "Home",
data() {
return {
header: "Hello Vue!",
};
},
};
</script>
Header.vue (child)
<template>
<header>
<div v-if="haveBanner == 'true'">
...
</div>
...
</header>
</template>
I have looked at another conventional way to achieve this but vue-cli renders templates differently.
As passing the prop in the HTML markup, the prop haveBanner evaluates as a string and, therefore, even if I did:
Parent
<Header have-banner="false"></Header>
Child
<div v-if="haveBanner"`>
...
</div>
That <div> would still display and, because of this, I am having to do an explicit check to see if it evaluates to 'true'. I am not a fan of this due to possible issues with type coercion and I am thrown a warning with a type check (===) saying:
Binary operation argument type string is not compatible with type string
Is there a way to for either the child to evaluate this prop as a boolean or for the parent to pass it as a boolean in the markup?

If passing in JS keywords such as boolean values or references to variables, you will need to use v-bind (or :), i.e.:
<Header v-bind:have-banner="true" page-title="Home">
This will have the effect of binding the boolean true to the prop, not a "true" string. If you are not using v-bind, the haveBanner prop will always be truthy because it is a string of non-zero length, no matter if you assign "true" or "false" to it.
Friendly hint: HTML tags are not case-sensitive, so you might want to use custom-header or my-header-component instead of Header:
<custom-header v-bind:have-banner="true" page-title="Home">
See proof-of-concept:
Vue.component('custom-header', {
template: '#customHeader',
props: {
haveBanner: Boolean,
pageTitle: String
}
});
new Vue({
el: '#app'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.15/vue.min.js"></script>
<div id="app">
<custom-header v-bind:have-banner="true" page-title="Home"></custom-header>
<custom-header v-bind:have-banner="false" page-title="Home"></custom-header>
</div>
<script type="text/x-template" id="customHeader">
<header>
<div v-if="haveBanner">
<code>haveBanner</code> is true!
</div>
<div v-else>
<code>haveBanner</code> is false!
</div>
</header>
</script>
Pro tip: Use : shorthands to make your template more readable, i.e.:
<custom-header :have-banner="true" page-title="Home">

To use a Boolean type as a prop, you have to use v:bind
<Header v-bind:have-banner="true" page-title="Home">
or using a short syntax
<Header :have-banner="true" page-title="Home">

Related

passing object to component using v-for

I am trying to send a series of objects that are in an array to a child component using v-for, but when I try to access them from the child component, it tells me that the props are not defined.
Im using Quasar Framework actually
This is how I pass the data:
<div class="row justify-center">
<foo
v-for="brand in brands"
:key="brand.id"
:brand="brand"
></foo>
</div>
<script>
import foo from "src/components/foo.vue";
export default {
components: {
foo
},
data() {
return {
brands: []
};
},
methods: {
async getData() {
let x = await get.getData();
this.brands = x.data;
console.log(this.brands);
}
},
mounted() {
this.getData();
}
};
</script>
brands is an array that obtains two objects from a request made to a local database, which I have already verified that it receives the data correctly
And this is the component file and how I try to get the properties:
<q-card class="my-card" flat bordered>
<q-img
:src="require(`../assets/${brand.img}`)"
:alt="brand.img + ' Logo'"
/>
<div class="text-h5 q-mt-sm q-mb-xs">{{ brand.name }}</div>
<div class="text-caption text-grey">
<p>
{{ brand.price }}
</p>
</div>
<script>
export default {
name: "foo",
props: ["brand"],
data() {
return {
expanded: false
};
},
};
</script>
but when I try to execute the code it gives me the following error:
Error in render: "Error: Cannot find module './undefined'
I know one way to make it work, and it is by creating a property for each of the object's values, for example:
<component
v-for="brand in brands"
:key="brand.id"
:name="brand.name"
:price="brand.price"
></component>
But I dont think thats the correct way to do this....
try to change
import component from "src/components/component.vue";
to
import foo from "src/components/component.vue";
on your components section you just call foo instead of foo:component
I am not sure, but:
Looks like ${brand} is empty. Your function GetData() is async, so the <foo> is created before the GetData() has its data set/returned.
You can change
<foo v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To
<foo v-if="brands.length> 0" v-for="brand in brands" :key="brand.id" :brand="brand"></foo>
To make sure that the element is renderd after the data if set.
Note: v-if is when the html is rendered, v-show is just a css display hide, but the html is always renderd

Undestanding Custom Component and V-Model in VueJS

NOTE: I have made a video version of this quesiton, which you can view here:
https://www.loom.com/share/6a23d0791c2444068e964b388ed5497e
The VueJS documentation dicusses how to use v-model with components: https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
If I create a component exactly has written in the documentation, it works just fine. Here is the code:
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
But now let me try and change the name of the prop in that component -- for example, to bar:
Vue.component('custom-input', {
props: ['bar'],
template: `
<input
v-bind:value="bar"
v-on:input="$emit('input', $event.target.value)"
>
`
})
Now it only half-works. That is to say, let's say I am binding custom-input to a data property called message, like this: <custom-input v-model="message"></custom-input>. If I dynamically update the value of message, then the value of custom-input will update -- but only once. If I update the value again it will not update the value of custom-input.
However, if I change the prop name back to value (instead of bar), then the value of custom-input will update each and every time I udpate the value of message.
[again, here is the video description of what I am talking about: https://www.loom.com/share/6a23d0791c2444068e964b388ed5497e]
Why is that? Why should it matter what name I give the prop? Does it have to be named value because I am binding to the value attribute? If so, why?
In short, what is going on here?
Thanks.
Yes, the attribute must be named value because you are using v-model to bind. v-model is an abbrevistion of binding the attribute value and listening to the event input. It's a special use case for binding a value two ways.
So, this is exactly the same:
<custom-input v-model="user" />
And:
<custom-input :value="user" #input="user = $event" />
If you prefer the full attribute notation:
<custom-input v-bind:value="user" v-on:input="user = $event" />
With the variable $event, you can, directly in the template, access the emitted value. You can also write a function name without brackets into the template to pass the emitted value as first parameter (e.g. #input="setUser", then declare a method setUser(user)).
props is used to get data down from a parent component to a child component
$emit is used to send back data from the child to the parent
v-model is used for 2 way data binding
always make the data reactive with a return data(){return{...}}
props example:
Vue.component('product',{
template:`
<div>
<item :items="items"/>
</div>
`,
data() {
return{
items:'hi'
}
}
})
Vue.component('item',{
template:`
<div>
{{items}}
</div>
`,
props:{
items:{
type:String,
required:false
}
}
})
var app= new Vue({
el:"#app"
})
//in html file
<div id="app">
<product/>
</div>

Scoping of HTML element id in Vue component

Is there some built-in scoping mechanism for Vue component in the sense that value of id attribute of html element inside Vue component be uniquely defined without programmer's efforts to do it?
In the following code, I create two components and hope each behaves independently to each other. So, ideally if I click on each button, each is required to print out "foo" but actually not because value of ids are duplicated.
<!DOCTYPE html>
<head>
<title></title>
</head>
<body>
<div id="app">
<my-comp></my-comp>
<my-comp></my-comp>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue"></script>
<script>
Vue.component('my-comp', {
template: `
<div>
<button id="btn" #click="onClick">Click me</button>
<div id="pid"></div>
</div>
`,
methods: {
onClick(e) {
$('#pid').text("foo");
}
},
});
const vm = new Vue({
el: '#app',
data: () => ({}),
methods: {}
});
</script>
</body>
</html>
Don't use id in vue components unless you are passing a unique value for it using props. You should very rarely ever actually need to get a reference to an element in vue and if you do find you need to then you should be using refs.
In your case you can just use a property and template binding to handle things for you:
Vue.component('my-comp', {
template: `
<div>
<button #click="onClick">Click me</button>
<div>{{ text }}</div>
</div>
`,
data() {
text: ''
},
methods: {
onClick(e) {
this.text = 'foo'
},
},
})
It looks like the vue-uniq-ids package is what you're looking for.
It is a trend to use components. Components are cool, they are small,
obvious, easy to use and modular. Untill it comes to the id property.
Some HTML tag attributes requires using an id property, like
label[for], input[form] and many of aria-* attributes. And the problem
with the id is that it is not modular. If several id properties on the
page will has the same value they can affect each other.
VueUniqIds helps you to get rid of this problem. It provides the set
of id-related directives which value is automatically modified by
adding unique string while keeping the attrbitue easy to read.

Custom Vue directive to omit tag but render tag's contents?

I'd like to create a custom Vue directive to omit the tag but render the tag's contents when the directive is true.
So for example, if the data for my vue instance is defined as
data:{
omitIt: true
}
And if the markup looks like this:
<div v-omit="omitIt" class="someClass">
Hello world!
</div>
When omitIt is set to false as it is above, I'd like the following rendered into the dom:
<div class="someClass">
Hello world!
</div>
But when omitIt is true I'd like only the following rendered into the dom:
Hello world!
I initially tried to solve this by doing the following (admittedly not a custom vue directive):
<template v-if="!omitIt">
<div class="someClass">
</template>
Hello world!
<template v-if="!omitIt">
</div>
</template>
The above isn't pretty but I thought perhaps it would work. But alas what gets rendered into the dom when omitIt is false is:
<div class="someClass"></div>
Hello world!
Any suggestions on how to achieve the results I'm looking for?
I thought #Nit's answer was a great and simple one and upvoted it, but it does have one flaw: a slot may not be a root element so the component will fail when the wrapper needs to be omitted. This is because slots can contain more than one element and if the slot does contain more than one, there could end up being more than one root element, which is not allowed.
I have a partial solution that renders just the first element in the slot if the component should not wrap.
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
Here is an example of it working.
console.clear()
Vue.component("wrapper", {
props:{
nowrap: {type: Boolean, default: false}
},
render(h){
// Log a warning if content is being omitted
const omissionMessage = "Wrapper component contains more than one root node with nowrap specified. Only the first node will be rendered."
if (this.$slots.default.length > 1 && this.nowrap)
console.warn(omissionMessage)
// This will *only* render the *first* element contained in
// the default slot if `nowrap` is set. This is because a component
// *must* have a single root element
if (this.nowrap) return this.$slots.default[0]
// Otherwise, wrap the contents in a DIV and render the contents
return h('div', this.$slots.default)
}
})
new Vue({
el: "#app"
})
.someClass{
color: blue
}
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<wrapper class="someClass">Hello World</wrapper>
<wrapper nowrap>No wrap, single root</wrapper> <br>
<wrapper nowrap>
No wrap, two roots. Paragraph is ommitted.
<p>Some other content</p>
</wrapper>
</div>
A couple notes: The component will always wrap unless you add nowrap as an attribute. Also, notice the class is added to the wrapped container without specifying it as a prop. This is because Vue automatically renders attributes that aren't specified as props on the root element of a component, unless you tell it not to.
This answer is wrong, slots cannot be used in this manner. Please see Bert's answer instead.
The easiest solution would be to create a wrapper component with slots for this purpose, passing the omitting argument as a prop.
The content distribution part becomes rather straightforward.
In the wrapper component template:
<slot v-if="omitIt"></slot>
<div v-else>
<slot></slot>
</div>
Wherever you want to use the wrapper:
<wrapper v-bind:omitIt="omitIt">
// Content
</wrapper>

Vue Multiselect does not update {{ value }} via v-model

I am using this example for Vue Multiselect "^2.0.0-beta.14" in Laravel 5.3. https://github.com/monterail/vue-multiselect/tree/2.0#install--basic-usage
The plugin renders correctly but I cannot get the selection via v-model. I am expecting #{{ selected }} to update with the current selection.
app.js
Vue.component('dropdown', require('./components/Multiselect.vue'));
VUE JS
<template>
<div>
<multiselect
v-model="value"
:options="options">
</multiselect>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
components: { Multiselect },
data () {
return {
value: null,
options: ['list', 'of', 'options']
}
}
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
HTML
<div id="app">
<h3>Dropdown</h3>
<div>
<label class="typo__label">Single select</label>
<dropdown></dropdown>
<pre class="language-json"><code>#{{ value }}</code></pre>
</div>
</div>
NB
The official example uses selected instead of value but this does not work either. According to the docs selection is replaced by value as of V2.
If you are using TypeScript Interfaces with Vue.js 2.0, avoid using a optional properties to store the value from child components. i.e. if your property is
value:? IMyCustomInterface instead use value: MyCustomObject|null and set the object to null in the constructor.
If the property is optional, it will compile fine, but child components won't update it properly.
The reason value is not showing up in root is because the data is isolated to the dropdown component. To get your data from a component to show up in the Root you need to use props.
See this question for a detailed explanation
How to get data from a component in VueJS