How to extend the behaviour of components in Vue - javascript

I mostly have experience in React and am wondering what the Vue centric way of doing this would be:
I would like extend this component: https://element.eleme.io/#/en-US/component/form so that label-position is top on mobile and left on desktop. I'm unusure how I would spread the properties passed into the component on to the element.
Here is the pseudo-code I have so far:
<template>
<el-form v-bind="formProps" :label-position="labelPosition">
<slot />
</el-form>
</template>
<script>
import Vue from 'vue';
export default Vue.component('el-form-responsive', {
data() {
return {
labelPosition: 'top',
};
},
created() {
this.mobileQuery = window.matchMedia('(max-width: 720px)');
this.onMobileQueryTrigger(this.mobileQuery);
this.mobileQuery.addListener(this.onMobileQueryTrigger);
},
beforeDestroy() {
this.mobileQuery.removeListener(this.onMobileQueryTrigger);
},
methods: {
onMobileQueryTrigger(query) {
if (query.matches) {
console.log('is mobile');
this.$data.labelPosition = 'top';
} else {
this.$data.labelPosition = 'left';
console.log('is not mobile');
}
},
},
});
</script>
From what I understand v-bind does not copy over events and directives, so this doesn't work:
<el-form-responsive
:formProps="{
class: 'form',
':model': 'formValues',
'status-icon': true,
':rules': 'rules',
ref: 'form',
'label-width': 'auto',
'#submit.native.prevent': 'submitForm'
}"
>
It's also inconvenient and ugly, I would rather just do:
<el-form-responsive
class="form"
:model="formValues"
status-icon
:rules="rules"
ref="form"
label-width="auto"
#submit.native.prevent="submitForm"
>
But I'm unsure how to spread these props on to el-form? Is this not the Vue centric way to go about this? Seems like a fundamental thing so maybe I have it wrong.

I'm not able to test this now, but I believe $attrs and $listeners should do what you want to achieve or at least point you in the right direction:
<template>
<el-form v-bind="$attrs" v-on="$listeners" :label-position="labelPosition">
<slot />
</el-form>
</template>

You made a mistake in passing props like below:
<el-form v-bind="formProps" label-position=":labelPosition">
<slot />
</el-form>
In the above codes, label-position=":labelPosition" is wrong.
It should be:label-position="labelPosition", so;
<el-form v-bind="formProps" :label-position="labelPosition">
<slot />
</el-form>

Related

move dialog to a compontent in Vuetify

here's my code:
<v-btn #click="showDialog = true" >
<v-dialog
v-model="showDialog"
max-width="600"
>
<v-btn #click="showDialog = false">save</v-btn>
</v-dialog>
</v-btn>
I know that this is fairly simple code, but I would like to extract whole v-dialog to a component. I don't know how to manage showDialog. Should I use prop? I now I shouldn't modify prop from inside of the component. What's the other way?
You can use a function as a prop for changing value of the showDialog in the another component . ( To avoid getting error avoid mutating a prop directly )
exampleComponent.vue
<template>
<v-dialog v-model="showDialog" max-width="600">
<v-btn #click="hideDialog">save</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
showDialog : Boolean ,
hideDialog: Function,
},
};
</script>
mainFile.vue
<template>
<example :hideDialog="hideMethod" :showDialog="showDialog"></example>
</template>
<script>
export default {
data() {
return {
showDialog : false
}
},
methods: {
hideMethod() {
this.showDialog = false;
},
},
};
</script>
I've never been a fan of driving the visibility of a dialog with a reactive v-model value. Normally, dialogs need to do some amount of setup and state related things before displaying and before hiding.
So, what I do is I move showDialog to be a hidden internal value to the component itself, I put a ref= on the component, I implement an open() method on the component and call that when I want to show it.
This pattern feels more natural when a dialog is performing more complicated tasks than just showing static information.
So in your case:
<script id="myDialog" type="text/x-template">
<v-dialog
v-model="showDialog"
max-width="600"
>
<v-btn #click="save">save</v-btn>
</v-dialog>
</script>
[...]
<v-btn #click="openMyDialog">
<myDialog ref="myDialog">
</myDialog>
</v-btn>
On myDialog:
data: function () {
return {
[ other attributes ]
showDialog: false
}
},
methods: {
[ other methods ]
open: function (initializationData) {
[ initialization code ]
this.showDialog = true;
},
save: function (event) {
[ save code ]
this.showDialog = false;
}
}
On the parent component:
methods: {
[ other methods ]
openMyDialog: function (event) {
this.$refs.myDialog.open([ initialization data ]);
}
}

Add condition to .sync in vue.js

I have a component that takes an options property. The options can be synced. As described below.
<component :options.sync="options">...</component>
The component is found in a parent component that has a shouldSync prop. As described below:
<template>
<div>
<component :options.sync="options">...</component>
</div>
</template>
<script>
export default {
name: 'parent',
props: ['shouldSync']
}
</script>
I want to use .sync modifier only if shouldSync property of the parent component is true. I tried using a dynamic prop with a computed property as below but it didn't work as expected.
<template>
<div>
<component :[optionsProp]="options">...</component>
</div>
</template>
<script>
export default {
name: 'parent',
props: ['shouldSync'],
computed: {
optionsProp: () => {
return this.shouldSync ? 'options.sync' : 'options'
}
}
}
</script>
Unfortunately, it didn't work.
Another alternative would be to duplicate the component tag, one with .sync and the other without, and use the v-if directive to determine which one to use. As described below:
<template>
<div>
<component v-if="shouldSync" :options.sync="options">...</component>
<component v-else :options="options">...</component>
</div>
</template>
But I don't want to do this because the default slot of <component /> contains much code and I don't want to duplicate that. I don't also want to transfer the default slot codes to a new component and include it here.
Is there any better way I can handle this situation? Thanks.
<component :options.sync="options"> is just syntactic sugar for <component :options="options" #update:options="options = $event">
So just use:
<component :options="options" #update:options="onUpdateOptions">
methods: {
onUpdateOptions(newValue) {
if(this.shouldSync)
this.options = newValue
}
}
or...
<component :options="options" #update:options="options = shouldSync ? $event : options">

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

What does v-on="..." syntax mean in VueJS?

I came across a Vuetify example for the v-dialog component which has the scoped slot called activator defined as follows:
<template v-slot:activator="{ on }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
I understand the purpose of scoped slots from VueJS docs and the concept of destructuring slot props but I don't understand what the meaning of v-on="on" is in this example. In particular what it means when the event is not specified with the v-on directive?
The VueJS docs on v-on only show its usage in combination with an event name explicitly specified (eg. v-on:click="...") but there is no explanation of just using it as v-on="...".
Can someone explain this syntax and its usage in the Vuetify example?
TLDR:
basic usage
<!-- object syntax (2.4.0+) -->
<button v-on="{ mousedown: doThis, mouseup: doThat }"></button>]
So basically #click="..." equals v-on:click="..." equals v-on="{click:...}"
TLDR:
vuetify implementation:
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
}
Some insight:
It is useful if you want to abstract components and pass down multiple listeners at once instead of writing multiple lines of assignments.
Consider a component:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
}
}
<template>
<div>
<slot name="activator" :on="on" :otherSlotPropName="value" >
<defaultComponent v-on="on" />
</slot>
</div>
</template>
Given the component above, you can access the slot properties and pass them into your custom component:
<ExampleComponent>
<template v-slot:activator="{ on, otherSlotPropName }">
<v-btn
color="red lighten-2"
dark
v-on="on"
>
Click Me
</v-btn>
</template>
<ExampleComponent />
Somethimes its easier to see it in plain javascript:
Comparing the component from above - with render function instead of template:
export default {
data() {
return {
on: {
click: console.log,
contextmenu: console.log
},
value: "any key value pair"
}
},
render(h){
return h('div', [
this.$scopedSlots.activator &&
this.$scopedSlots.activator({
on: this.on,
otherSlotPropName: this.value
})
|| h('defaultComponent', {
listeners: this.on
}
]
}
}
In the source:
In case of a blank v-on="eventsObject" the method bindObjectListener will be called resulting in the assignment of the events to data.on.
This happens in the createComponent scope.
Finaly the listeners are passed as VNodeComponentOptions and updated by updateListeners.
Where Vue extends - the Vuetify implementation inspected:
When taking into account that one can join and extend vue instances, one can convince himself that any component can be reduced to a more atomic version.
This is what vuetify utilizes in the e.g. v-dialog component by creating a activator mixin.
For now one can trace down the content of on mounted by the activatable:
const simplyfiedActivable = {
mounted(){
this.activatorElement = this.getActivator()
},
watch{
activatorElement(){
// if is el?
this.addActivatorEvents()
}
},
methods: {
addActivatorEvents(){
this.listeners = this.genActivatorListeners()
},
genActivatorListeners(){
return {
click: ...,
mouseenter: ...,
mouseleave: ...,
}
},
genActivator () {
const node = getSlot(this, 'activator', Object.assign(this.getValueProxy(), {
on: this.genActivatorListeners(),
attrs: this.genActivatorAttributes(),
})) || []
this.activatorNode = node
return node
},
}
}
With above snippet all there is left is to implement this into the actual component:
// vuetify usage/implemention of mixins
const baseMixins = mixins(
Activatable,
...other
)
const sympliefiedDialog = baseMixins.extend({
...options,
render(h){
const children = []
children.push(this.genActivator())
return h(root, ...options, children)
}
})

How pass a v-model on the parent into a template

I'm trying to compose the UI for a search page, but wanna use components to reuse code. However, I need a way to pass the model of the page to the search component, and don't see how:
In index.html:
<template id="search">
<q-search inverted placeholder="Look" float-label="Search" v-model="search" /> <-- BIND HERE
</template>
<template id="ListCustomersPage">
<q-layout>
<q-layout-header>
<search v-model="search"></search> <-- HOW PASS INTO THIS
</q-layout-header>
</q-layout>
</template>
And the code:
const search = {
template: '#search',
props: ['search']
};
const ListCustomersPage = {
key: 'ListCustomersPage',
template: '#ListCustomersPage',
components: { search },
data() {
return {
title: 'Select Customer',
search:'' <-- FROM THIS TO 'BIND HERE'
}
}
};
I'm not sure if I'm 100% following what you're asking, but it seems like you just want to pass a property to a child component?
<search :search="search"></search> <-- HOW PASS THIS
Passing a prop to a child is done with v-bind or the colon short hand for it.
<child-component :property="parent_data"></child-component>
<child-component v-bind:property="parent_data"></child-component>
See the documentation here.

Categories

Resources