Vue.js add component option (prop) using custom directive - vue.js

I have Custom component using my own directive (v-color):
<custom v-color="color" />
And my script, which I define this.color and this.changeColor():
{
   data () {
     return {
       color: red
     }
   },
   methods: {
     changeColor (color) {
       this.color = color
     }
   }
}
How can I write the code of v-color directive to change v-bind:color of <custom />?
In other words, the value of v-bind:color will be red when the component is loaded. If this.color is modified by a method (such as this.changeColor('blue')), value of v-bind:color would be auto-updated.
I would appreciate solutions that avoid "watch", because I will use v-color many times.

Something like this seems to fit what you're looking for:
Vue.component('third-party-component', {
props: ['color'],
template: '<div :style="{ color }" v-cloak>{{color}}</div>'
});
Vue.component('hoc-component', {
props: ['color'],
computed: {
transformedColor () {
if (this.color === "blu") return "blue";
if (this.color === "re") return "red";
if (this.color == "or") return "orange";
if (this.color == "pur") return "purple";
return this.color;
}
},
template: '<third-party-component :color="transformedColor" />'
});
new Vue({
el: '#app'
});
<html>
<body>
<div id="app" v-cloak>
<div>
<hoc-component color="blu"></hoc-component>
<hoc-component color="or"></hoc-component>
<hoc-component color="re"></hoc-component>
<hoc-component color="pur"></hoc-component>
<hoc-component color="pink"></hoc-component>
<hoc-component color="green"></hoc-component>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</body>
</html>
Here we are taking advantage of the Higher Order Component pattern in order to modify the data we need and pass it on to the third party component. This is a much more effective way of mutating and handling data change without the side effects that directives have.
Hope this helps!

Related

vuejs3 dynamic attribute with no value

How can I set up a dynamic attribute within vuejs3. Vanilla js is a lot easier, but within Vue this is apparently not obvious.
I want to be able to use a variable as an attribute.
Something like this:
<q-input
outlined <---(This must be variable "item.design" without any value)
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
I've read some examples and documentation but the examples are mainly for vuejs2.
Do I miss something?
You can bind data vars to attributes just as easily using v-bind: on the attribute (or the shorthand :):
<q-input
:outlined="outlined"
:filled="filled"
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
// script (options api)
data() {
return {
item: {
design: 'filled',
},
data: {
value: null,
},
};
},
computed: {
filled() {
return this.item.design === 'filled';
},
outlined() {
return this.item.design === 'outlined';
},
}
Take a look at following snippet you can pass true/false to binded attributes:
const { ref, computed } = Vue
const app = Vue.createApp({
setup () {
const data = ref({value: null})
const item = ref({design: 'filled'})
const design = (type) => {
return item.value.design === 'filled' ? 'outlined' : 'filled'
}
return { data, item, design }
}
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
<div class="q-pa-md">
<q-btn color="white" text-color="black" label="toogle design" #click="item.design = item.design === 'filled' ? 'outlined' : 'filled'" >
</q-btn>
<q-input
:filled="design(item.design)"
:outlined="design(item.design)"
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.umd.prod.js"></script>

How to access a Vue component's data from a script

Here are the simplified html and javascript files of the page. It has a button and component which is a text displays the data of the component. I want the component's data to be changed when I click the button. But how to access the component's data from a script?
index.html
<body>
<div id="app">
<my-component></my-component>
<button id="btn"> change data </button>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script src="./main.js"></script>
</body>
main.js
let app = Vue.createApp({});
app.component('my-component', {
data: function() {
return {
component_data : "foo"
}
},
template: '<p> data = {{ component_data }} </p>'
}
);
app.mount("#app");
document.querySelector("btn").onclick = function() {
// HOW TO CHANGE component_data TO "bar"
}
One possibility is to incorporate the button into the HTML within the component's template. If that's feasible for your app then you can add a function to the component and bind the function to the button's click event.
E.g. (Note this is untested so may have typos)
app.component('my-component', {
data: function() {
return {
component_data : "foo"
}
},
methods: {
changeData() {
this.component_data = "The data changed";
}
},
template: `<p> data = {{ component_data }} </p>
<button #click="changeData">Change data</button>`
}
);
If the button can't be incorporated into my-component then I'd recommend using the Vuex datastore. Vuex is a reactive datastore that can be accessed across the entire application.
You can use component props change data between components.
index.html
<body>
<div id="app">
<my-component :component-data="text"></my-component>
<button #click="handleBtnClick"> change data </button>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script src="./main.js"></script>
</body>
main.js file
let app = Vue.createApp({
data() {
return { text: 'foo' }
},
methods: {
handleBtnClick() {
this.text = 'bar';
}
}
});
app.component('my-component', {
props: {
componentData: {
type: String,
default: 'foo'
}
}
template: '<p> data = {{ componentData }} </p>'
}
);
app.mount("#app");
I think you new in Vuejs. You have to first read Vue documentation
To get the reference of a component outside of it, you can use the template refs
Here is the refactor of the code provided in the above question to access the components data from the script.
<div id="app">
<my-component ref="my_component"></my-component>
<button #click="onBtnClick()"> change data </button>
</div>
let app = Vue.createApp({
methods: {
onBtnClick() {
this.$refs.my_component.component_data = "bar";
}
}
});

Conditionally attach event listener and handler to Vue component

I have a Vue component that is used several places in my app. In some of these cases, I need to handle a click event with a specific function, like:
<div #click="checkNav" />
However, I would only like to attach this handler when needed, so it doesn't fire unnecessarily when it's not needed.
I've tried passing a prop to the component and attaching the handler conditionally, like so:
<div #click="isCheckNeeded ? checkNav : null" />
and then in props, I've specified:
isCheckNeeded {
type: Boolean,
required: false,
default: false,
}
However, my checkNav function never fires, and I've double checked that isCheckNeeded is true in Vue devtools.
Is this kind of conditional check not possible, or not recommended? Is there a better way to conditionally attach event listeners/handlers?
It might help to see how your template is being compiled to understand the cause of the problem...
When v-on receives a method name, vue-template-compiler compiles it into a method lookup, where the resolved method becomes the event handler [1]. For instance, your template <div #click="checkNav" /> is compiled into this render function:
with (this) {
return _c("div", { on: { click: checkNav } })
}
On the other hand with an inline event handler, <div #click="isCheckNeeded ? checkNav : null" /> is compiled into this:
with (this) {
return _c("div", {
on: {
click: function ($event) {
isCheckNeeded ? checkNav : null
},
},
})
}
Notice a couple things here:
The expression is wrapped in an anonymous function, which becomes the event handler.
The result of the expression is either a method name (as opposed to a method call) or null. Evaluating a method name is effectively a no-op.
Solution 1: Change method name into method call
This is probably the simplest solution, but it has the disadvantage of the handler always being invoked upon click (although a falsy isCheckNeeded would cause an early return).
<!-- BEFORE: -->
<!--
<div #click="isCheckNeeded ? checkNav : null" />
-->
<!-- AFTER: -->
<div #click="isCheckNeeded ? checkNav() : null" />
<!-- OR: -->
<div #click="isCheckNeeded && checkNav()" />
Solution 2: Use dynamic event
This is slightly more complex, but it has the advantage of registering the event handler only when necessary. The event handler is automatically unregistered when isCheckNeeded is falsy.
<div #[clickEvent]="checkNav" />
...
<script>
export default {
computed: {
clickEvent() {
return this.isCheckNeeded ? 'click' : null
}
},
}
</script>
Vue.component('my-component', {
template: `<div #[clickEvent]="checkNav"><slot/></div>`,
props: {
isCheckNeeded: Boolean
},
computed: {
clickEvent() {
return this.isCheckNeeded ? 'click' : null
}
},
methods: {
checkNav() {
console.log('checkNav')
}
}
})
new Vue({
el: '#app',
data() {
return {
isCheckNeeded: false
}
}
})
.click-area {
border: solid 1px;
padding: 2rem;
margin: 1rem;
}
<script src="https://unpkg.com/vue#2.6.12"></script>
<div id="app">
<button #click="isCheckNeeded = !isCheckNeeded">Toggle click handler</button>
<pre>isCheckNeeded={{isCheckNeeded}}</pre>
<my-component :is-check-needed="isCheckNeeded">
<div class="click-area">
<span v-if="isCheckNeeded">Click me!</span>
<span v-else>Clicking ignored</span>
</div>
</my-component>
</div>
Uses #click="enabled && clickHandler($event)".
new Vue ({
el:'#app',
data () {
return {
enabled: true
}
},
methods: {
clickHandler: function () {
console.info('clicked')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div class="container">
<a #click="enabled && clickHandler($event)">
Test: {{enabled}}
</a>
<input type="checkbox" v-model="enabled">
</div>
</div>
Another option is use render function like below:
new Vue ({
el:'#app',
render (h) {
let props = {}
if (this.enabled) {
this.$set(props, 'on', {click: this.clickHandler})
}
let self = this
return h('div', [
h('input', {
attrs: {type: 'checkbox'},
domProps: {checked: this.enabled},
on: {
input: function (event) {
self.enabled = event.target.checked
}
}
}),
h('a', props, `Test: ${this.enabled}`)
])
},
data () {
return {
enabled: true
}
},
methods: {
clickHandler: function () {
console.info('clicked')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app"></div>
You could use a v-if statement to render the div with or without the click handler.
<div v-if="isCheckedNeeded" #click="checkNav"></div>
<div v-else></div>

How to change font size using custom directive in vuejs

I want to change the font size of the element using a custom directive
Thus I have tried below code for that
<template>
<div class="hello"><label v-onhover>CLICK ME TO CHANGE FONT</label></div>
</template>
<script>
export default {
name: "CustomDirective",
props: {
msg: String
},
directives: {
onhover: {
bind(el, binding) {
el.onmouseover = function() {
el.fontSize = "100px";
};
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
Inside the bind, I'm getting the complete label element but don't know how to get it worked for changing the user-defined font size on mouse hover
You want el.style.fontSize instead of el.fontSize.
Vue.directive('onhover', {
bind(el, binding) {
el.onmouseover = function() {
el.style.fontSize = "100px";
};
}
});
new Vue().$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="hello">
<label v-onhover>Hover on me to change font</label>
</div>
</div>
After trying a few different ways, I have solved it out the situation
Below is my code for the solution
<template>
<div class="hello">
<label v-onhover>{{ msg }}</label>
</div>
</template>
<script>
export default {
name: "CustomDirective",
data() {
return {
str: "",
msg: "Welcome to Your Vue.js App"
};
},
directives: {
onhover: {
bind(el, binding) {
el.onmouseover = function() {
el.style.fontSize = "100px";
};
el.onmouseout = function() {
el.style.fontSize = "15px";
};
}
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
Here in the above example, I have used two different event mouseover and mouseout event and based on the event state, we can change the property of an element

vuejs data doesn't change when property change

I am very new to Vuejs so although I can probably devise a solution myself by using a watcher or perhaps a lifecycle hook I would like to understand why the following does not work and what should be done instead.
The problem is that the mutated local data doesn't update whenever the component consumer changes the property cellContent. The parent owns cellContent so using the property directly is a no-no (Vue seems to agree).
<template>
<textarea
v-model="mutableCellContent"
#keyup.ctrl.enter="$emit('value-submit', mutableCellContent)"
#keyup.esc="$emit('cancel')">
</textarea>
</template>
<script>
export default {
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data () {
return {
mutableCellContent: this.cellContent
}
}
}
</script>
<style>
...
</style>
In data (mutableCellContent: this.cellContent) you are creating a copy of the prop, that's why when the parent changes, the local copy (mutableCellContent) is not updated. (If you must have a local copy, you'd have to watch the parent to update it.)
Instead, you should not keep a copy in the child component, just let the state be in the parent (and change it through events emitted in the child). This is a well known the best practice (and not only in Vue, but in other frameworks too, if I may say it).
Example:
Vue.component('cell-editor', {
template: '#celleditor',
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data () {
return {}
}
});
new Vue({
el: '#app',
data: {
message: "Hello, Vue.js!"
}
});
textarea { height: 50px; width: 300px; }
<script src="https://unpkg.com/vue"></script>
<template id="celleditor">
<textarea
:value="cellContent"
#keyup.ctrl.enter="$emit('value-submit', $event.currentTarget.value)"
#keyup.esc="$event.currentTarget.value = cellContent">
</textarea>
</template>
<div id="app">
{{ message }}
<br>
<cell-editor :cell-content="message" #value-submit="message = $event"></cell-editor>
<br>
<button #click="message += 'parent!'">Change message in parent</button>
</div>
You have to create a watcher to the prop cellContent.
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.config.debug = false
Vue.config.silent = true
Vue.component('component-1', {
name: 'CellEditor',
props: ['cellContent', 'cellId'],
data() {
return {
mutableCellContent: this.cellContent
}
},
template: `
<textarea
v-model="mutableCellContent"
#keyup.ctrl.enter="$emit('value-submit', mutableCellContent)"
#keyup.esc="$emit('cancel')">
</textarea>
`,
watch: {
cellContent(value) {
this.mutableCellContent = value;
}
}
});
var vm = new Vue({
el: '#app',
data() {
return {
out: "",
cellContent: ""
}
},
methods: {
toOut(...args) {
this.out = JSON.stringify(args);
},
changeCellContent() {
this.cellContent = "changed at " + Date.now();
}
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<component-1 :cell-content="cellContent" #value-submit="toOut" #cancel="toOut"></component-1>
<p>{{out}}</p>
<button #click="changeCellContent">change prop</button>
</div>