vue property as prop name without value - vue.js

I want to pass a property that doesn't have any value - only name. I want the name to be dynamic according the prop.
I want to create a component of tool-tip that get the tool-tip side -> top left right or bottom. in v-tooltip there isn't property side, all side is another property without value. I want it to change according the prop (side is a variable - a prop of my component).
<template>
<v-tooltip side>
<template slot="activator">
<slot slot="activator"></slot>
</template>
<span>{{text}}</span>
</v-tooltip>
</template>
<script>
export default {
props: {
text: {
type: String,
required: true,
},
side: {
default: 'top',
},
},
}
</script>
I can't find a way to use prop as the name of the property

There are two things that you need to realise. In Vue, calling a component with <my-component has-this-prop /> it the same as calling a component with <my-component :has-this-prop="true" />. The boolean basically toggles the prop.
You can also dynamically change which props are bound to a component by using the v-bind syntax without a prop name, as outlined in the documentation.
This means that if you have a prop called side in your parent component that always contains the correct side, you can create a computed property that contains all the props you want to pass, then use v-bind on your v-tooltip to pass the props:
<template>
<v-tooltip v-bind="tooltipProps">
<!-- Something -->
</v-tooltip>
</template>
<script>
const validSides = [
'top',
'top left',
'top center',
'top right',
// you get the idea
];
export default {
props: {
side: {
type: String,
default: 'top',
validator(prop) {
return validSides.contains(prop);
}
}
},
computed: {
tooltipProps() {
if (!validSides.contains(this.side)) {
return {};
}
const propsNames = this.side.split(' ');
const props = {};
for (const propName of propsNames) {
props[propName] = true;
}
return props;
}
}
}
</script>

Related

How to access child's props from wrapper component in Vue?

I have a bunch of Vue components on the marketing website. There are stateful and functional (aka stateless) components. For example badge and button. I need to create a wrapper component for a testing purpose, that takes any component, renders that component but additionally adds a table below with props-values, passed along with child component. For instance:
<XButton hello="hi" :some-other-prop="2" label="Hey">You Will Never Appear</XButton>
will be rendered to a Button tag with the information below:
<button>Such text with label Hey</button>
some-other-prop | 2
label | Hey
Button.js
Vue.component('XButton', {
name: 'XButton',
functional: true,
props: {
label: {
type: String,
default: 'Default text',
},
},
render(createElement, context) {
return createElement('button', context.data, 'Such text with label ' + context.props.label);
},
});
Wrapper.vue
<template>
<div>
<slot/>
</div>
</template>
<script>
export default {
functional: true,
props: {
listPropsOfDoubleNestedChild: {
type: Boolean,
default: false
}
},
render(createElement, context) {
const vNode= context.props.listPropsOfDoubleNestedChild
? context.children[0].children // or even get props of double nested child
: context.children[0];
if (vNode.componentOptions) {
console.log("Props of nested stateful component", vNode.componentOptions.propsData);
} else {
console.log("Props of nested stateless component are not reachable thru componentOptions", vNode.componentOptions);
}
return null;
}
</script>
App.vue
<template>
<Wrapper>
<XButton hello="hi" :some-other-prop="2" label="Hey">You Will Never Appear</XButton>
</Wrapper>
</template>
So, I can only get passed props of the stateful component.
Some screenshots:
Stateful component:
Functional component:
In React it is very simple to accomplish with React.Children.only(props.children).props and what about Vue?

Vue component wrapping

What is the correct way to wrap a component with another component while maintaining all the functionality of the child component.
my need is to wrap my component with a container, keeping all the functionality of the child and adding a trigger when clicking on the container outside the child that would trigger the child`s onclick event,
The parent component should emit all the child component events and accept all the props the child component accepts and pass them along, all the parent does is add a clickable wrapper.
in a way im asking how to extend a component in vue...
It is called a transparent wrapper.
That's how it is usually done:
<template>
<div class="custom-textarea">
<!-- Wrapped component: -->
<textarea
:value="value"
v-on="listeners"
:rows="rows"
v-bind="attrs"
>
</textarea>
</div>
</template>
<script>
export default {
props: ['value'], # any props you want
inheritAttrs: false,
computed: {
listeners() {
# input event has a special treatment in this example:
const { input, ...listeners } = this.$listeners;
return listeners;
},
rows() {
return this.$attrs.rows || 3;
},
attrs() {
# :rows property has a special treatment in this example:
const { rows, ...attrs } = this.$attrs;
return attrs;
},
},
methods: {
input(event) {
# You can handle any events here, not just input:
this.$emit('input', event.target.value);
},
}
}
</script>
Sources:
https://www.vuemastery.com/conferences/vueconf-us-2018/7-secret-patterns-vue-consultants-dont-want-you-to-know-chris-fritz/
https://zendev.com/2018/05/31/transparent-wrapper-components-in-vue.html

Vue closing component returns avoid mutating a prop directly

I have an component that I want to use on different pages. Well, it is working well till the first toggle. It shows like it used to, but when I click the 'Close' button, it closes, but console outputs :
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "visible"
found in
---> at src/components/Snackbar.vue
at src/views/Login.vue
And after that it doesn't show up on click
Any way to fix this?
Snackbar.vue
<template>
<v-snackbar v-model.sync="visible" :timeout="5000" bottom>
{{ content }}
<v-btn flat color="primary" #click.native="visible = false">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
name: 'snackbar',
props: [
'visible',
'content'
]
}
</script>
Login.vue
<template>
<div class="login">
<Snackbar :visible="snackbar.visible" :content="snackbar.content"></Snackbar>
</div>
</template>
<script>
import Snackbar from '#/components/Snackbar.vue'
export default {
components: {
Snackbar
},
data: function() {
return {
email: '',
password: '',
snackbar: {
visible: false,
content: ''
}
}
},
methods: {
login: function() {
if (this.email != '' && this.password != '') {
// Do something
} else {
this.snackbar.content = 'Fields can\'t be empty';
this.snackbar.visible = true;
}
}
}
}
</script>
The console error is being triggered by this:
#click.native="visible = false"
The component is directly mutating the incoming prop. If you want to keep this level of control where the parent component controls the visibility you'll have to do it by having the click event emit an event, which the parent component receives and sets this.snackbar.visible = false thereby triggering a prop change and the child component is hidden.
<Snackbar :visible="snackbar.visible" :content="snackbar.content"
v-on:requestClose="close"></Snackbar>
<v-btn flat color="primary" #click.native="$emit('requestClose')">Close</v-btn>
methods: {
close: function() {
this.snackbar.visible = false;
}
}

How to properly pass data from child to parent and parent to child component?

First of all, this is my current structure
CHILD COMPONENT
// HTML
<v-select
v-bind:items="selectItems"
v-model="selectedItem"
label="Category"
item-value="text"
></v-select>
<v-text-field
label="Enter Value"
type="number"
v-model="compVal"
></v-text-field>
// JS
props: {
selectItems: {
type: Array,
required: true
},
selectedItem: {
type: String
},
compVal: {
type: Number
},
}
PARENT COMPONENT
// HTML
<component :selecItems="selectOneItems" :selectedItem="selectOneItem"
:compVal="compOneVal"></component>
<component :selecItems="selectTwoItems" :selectedItem="selectTwoItem"
:compVal="compTwoVal"></component>
// JS
data () {
return {
selectOneItems: [someArray],
selectedOneItem: null,
compOneVal: 0,
selectTwoItems: [someArray],
selectedTwoItem: null,
compTwoVal: 0
}
}
My Scenario
1) At initial state, i have to get value from Child's Text Input and store it in local. (Child to Parent)
2) After browser refresh, i will check for any local data, if it exsist, i have to populate child's value (Parent to Child)
The error with current structure
whenever i type or select something it triggers this error
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "compOneValue"
so how to avoid this error? how to properly implement this structure, so that i can reuse this component wherever i want.
You are using props with v-model. Props will be overridden when the parent component is re-renders.
Use data with v-model and set the initial values from the props:
CHILD COMPONENT
// HTML
<v-select
v-bind:items="selectItems"
v-model="selectedItemModel"
label="Category"
item-value="text"
></v-select>
<v-text-field
label="Enter Value"
type="number"
v-model="compValModel"
></v-text-field>
// JS
props: {
selectItems: {
type: Array,
required: true
},
selectedItem: {
type: String
},
compVal: {
type: Number
},
},
data: function() {
return {
selectedItemModel: this.selectedItem,
compValModel: this.compVal
}
}
It might be worth changing your props to be called initialSelectedItem and initialCompVal instead, and then have the data properties called selectedItem and compVal.
You could use a locally bound setter then pass it to the child. The child has then a method to set property of the parent.
Here's an example.
<!-- Component.vue -->
<template>
<div class="login">
{{ property }}
<button v-on:click="setProperty('Something')">
</button>
<router-view/>
</div>
</template>
<script>
const setProperty = (propertyValue) => {
this.property = propertyValue
}
export default {
name: 'Login',
data () {
return {
// The 'bind(this)' is crucial since otherwise
// 'this' will refer to the context of the caller
// instead of what it is in this context.
setProperty: setProperty.bind(this),
property: 'Not something'
}
}
}
</script>

vuetify: programmatically showing dialog

vuetify says: If you want to programmatically open or close the dialog, you can do so by using v-model with a boolean value.
However I am quite unclear on what this means. Saying "using v-model" is vague at best. The parent component knows on setup if it should open but I am unclear on how to dynamically change this in the child. Am i supposed to pass it using v-bind?
<login v-bind:showDialog></login>
If so how does the child component deal with this?
Vuetify Dialog info here: https://vuetifyjs.com/components/dialogs
As I understand you have a child component which have a dialog within it. Not sure that this is 100% right, but this is how I implement it. Child component with dialog:
<template>
<v-dialog v-model="intDialogVisible">
...
</template>
<script>
...
export default {
props: {
dialogVisible: Boolean,
...
},
computed: {
intDialogVisible: {
get: function () {
if (this.dialogVisible) {
// Some dialog initialization code could be placed here
// because it is called only when this.dialogVisible changes
}
return this.dialogVisible
},
set: function (value) {
if (!value) {
this.$emit('close', some_payload)
}
}
}
in parent component we use it:
<my-dilaog :dialogVisible="myDialogVisible"
#close="myDialogClose">
</my-dialog>
data () {
return {
myDialogVisible: false
}
},
methods: {
myDialogClose () {
this.myDialogVisible = false
// other code
}
}
Дмитрий Алферьев answer's is correct but get "Avoid mutating a prop directly" warning, because when close dialog, v-dialog try change v-model to false, while we passed props to v-model and props value won't change. to prevent the warning we should use :value , #input
<template>
<v-dialog :value="dialog" #input="$emit('update:dialog',false)" #keydown.esc="closeDialog()" >
...
</v-dialog>
</template>
<script>
export default {
props: {
dialog: Boolean
},
methods: {
closeDialog(){
this.$emit('closeDialog');
}
}
In parent
<template>
<v-btn color="primary" #click="showDialog=true"></v-btn>
<keep-alive>
<my-dialog
:dialog.sync="showEdit"
#closeDialog="closeDialog"
>
</my-dialog>
</keep-alive>
</template>
<script>
data(){
return {
showEdit:false,
},
},
methods: {
closeDialog(){
this.showEdit = false;
},
}
v-model is a directive. You would use v-model, not v-bind.
The page you link has several examples. If you click on the <> button on the first one, it shows HTML source of
<v-dialog v-model="dialog">
v-model makes a two-way binding on a prop that is named value inside the component. When you set the bound variable's value to true, the dialog will display; when false, it will hide. Also, if the dialog is dismissed, it will set the variable's value to false.