Vue2 Custom Directive modifying component call - vuejs2

I am attempting to write a Vue directive that updates the properties of a component before the component is evaluated.
For example, consider the following.
<b-modal v-i18n:title="'i18n.key'">
Hello
</b-modal>
b-modal is a Vue Component and it takes a property called 'title'. I would like to have a custom directive that can set the property title after translating the supplied key.
That is, I would like the above code to get rewritten by the directive to:
<b-modal title="Translated Text">
Hello
</b-modal>
So far I have read the following resources and found no reference on how to do this.
https://css-tricks.com/power-custom-directives-vue/
http://optimizely.github.io/vuejs.org/guide/directives.html
My current attempt looks like this:
Vue.directive('i18n', {
inserted: function (el,binding) {
const i18nKey = binding.value;
const attrName = binding.arg;
el.setAttribute(attrName, i18nKey);
}
})
This attempt sadly falls short. It results in a change to the final DOM element and has no affect on the property being past to the Vue component.
How can I can the above directive be modified to change the properties being past to the b-modal component?

Related

Vue3 Reactivity in script setup for translation

I am adding some DOM elements in the script setup side but I want the messages to change when I change the language. I am using vue-i18n plugin. It's easy to do it in the template section because I can basically use the useI18n().t method but how can I do this in the script setup section. Using the useI18n().t method doesn't ensure reactivity.
Example Code:
$(".time")[0].innerHTML = `
<div>0<span>${useI18n().t("details.hour")}</span></div>
<div>0<span>${useI18n().t("details.minute")}</span></div>
<div>0<span>${useI18n().t("details.second")}</span></div>
`
Manipulating DOM directly inside the script leads to inconsistence in your app, you should drive your component by different reactive data to achieve your goal.
In your current situation try to define a computed property based on the translation then render it inside the template based on its different properties :
<script setup>
const {t} =useI18n()
const time = computed(()=>{
return {
hour:t(""details.hour"),
minute:t(""details.minute"),
second:t(""details.second"),
}
})
</script>
<template>
<div class="time">
<div>0<span>{{time.hour}}</span></div>
<div>0<span>{{time.minute}}</span></div>
<div>0<span>{{time.second}}</span></div>
</div>
</template>

Understanding when to use : , # and without any in vue

I have been going through an already existing code and trying to understand different parameters passed inside a tag.
<some-element
placeholder ="show first name"
:someElement = "true"
#error = "showErrorAlert"
>
so what exactly is the difference between these three parameters passed and when to pass them.
I am very new to vue so I am struggling a bit.
If any one needs any further information please let me know.
to communicate data between components in vuejs we have two concept: props and events.
vue props
vue custom events
read the two links above to learn about them from vue documentation but in short say we have a component structure like this:
<parent-component>
---- | <child-component>
data flow from parent to child is done with the help of props. that is when you're defining child-component you can set in its vue instance object that this component can accept some prop like this.
props: {
text: { type: String },
value: { type: Boolean },
}
then when you use this component in the parent one you can pass the defined props to the component like this:
<child-component text="some text" :value="false" />
notice the : notation is just a shorthand for v-bind:value="false", if you don't use this binding the false will be sent to the child component as a string. for any other value type you should use binding.
please read the docs to learn more about binding in vue
attribute binding
v-bind shorthand
now, the data flow from child to parent is done via events so in your child component you can emit an event for the parent one to listen to like this:
this.$emit('event-name', payload)
then in the parent component where you used child component you can listen to this event like this
<child-component #event-name="doSomethingFun($event)" />
where doSomethingFun() is a method in parent component and we are passing the payload sent from child component to this method with $event
notice # notation is shorthand for v-on like v-on:event-name="doSomethingFun($event)"
: is shorthand for v-bind: and this is used for dynamic rendering for the attributes such as src, href etc.
# is shorthand for v-on:click which is event handler for function calls.
You can read more Event handling shorthand syntax

Scope of Vue Template Syntax Expressions

In my Vue.js app, a child components emits an event in the following format:
this.$emit('done-editing', payload)
My parent component is designed in the following manner:
<child-component
v-on:done-editing="console.log(data)">
</child-component>
But when I execute this code, It throws an error saying
TypeError: Cannot read property 'log' of undefined
What I understood was the console object was not found in this scope. (It is originally defined on the window object). I want to know that what is the scope of JavaScript expressions inside v-on:event="…" and how to use console.log inside Vue template syntax.
I know I can do the same thing as below. But is there a way to do it inside a template expression?
<template>
<child-component
v-on:done-editing="logMethod(data)">
</child-component>
</template>
<script>
methods : {
logMethod(data) {
console.log(data)
}
}
</script>
Each handler in a v-on directive is "bound to this". That means, when you try to do:
v-on:some-event="console.log('test')"
You're actually doing:
this.console.log('test')
Which is not valid because this points to the Vue component instance. That's why you can do this:
v-on:some-event="someHandler"
…
methods: {
someHandler() { … }
}
Because the expression inside the v-on directive is automatically prefixed with this. It calls this.someHandler which exists. The same goes for expressions in v-bind directives. Specifically, in the documentation:
[...] all Vue handler functions and expressions are strictly bound to the ViewModel that’s handling the current view [...]
And:
These expressions will be evaluated as JavaScript in the data scope of the owner Vue instance.
I wrote a Vue plugin so you can use $window and $document in your templates.
https://www.npmjs.com/package/window-plugin
This will work after you install the plugin:
<child-component
v-on:done-editing="$window.console.log(data)">
</child-component>

Vue component prop change does not trigger rerender

In my Vue 2 application I have a big object that is passed from the root component down several levels to some child components as a prop. When I change some property of the object the child components should update and rerender. In some cases they do, in other cases they don't. I need some help spotting why it does not work.
Here is a child component, which does not update:
<template>
<div class="upgradeBar">
{{level}}
<div
v-for="lvlNum in maxLevel + 1"
class="level"
v-bind:class="{reached: isLevelReached(lvlNum - 1)}"
></div>
<button
class="btnUpgrade"
#click="onLevelUp()"
v-if="!isLevelReached(maxLevel)"
>
+
</button>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from 'vue-property-decorator';
import Upgradable from "../../../models/Upgradable";
#Component()
export default class UpgradeBar extends Vue {
name: 'UpgradeBar';
#Prop() entity: Upgradable;
get level(): number {
return this.entity.level;
}
get maxLevel(): number {
return this.entity.MAX_LEVEL;
}
onLevelUp() {
this.entity.levelUp();
}
isLevelReached(level: number): Boolean {
return this.entity.level >= level;
}
}
</script>
The component is called like this:
<UpgradeBar :entity="entity" />
All the code works. When I click the btnUpgrade button entity.level is indeed changed, but I need to close and reopen the component to see the changes. Also the browser dev tool does not show the change instantly. I need to click on the component to refresh the values in the debugger.
EDIT:
The entity class looks basicly like this (excerpt):
class Entity {
name: string = 'some name';
level: number = 1;
}
I searched deeper and it seems to boils down to this: Some properties of the object are reactive (they have getters / setters created by vue) and some don't. entity.name has a setter, so changing it updates the component. entity.level does not. Here's the question: Why are they treated differently? Here is a log:
Can't tell for sure without seeing the code for entity.levelUp, but it seems like a reactivity issue, that may be solved by using Vue.$set inside that function.
You can confirm this being the case by adding this.$forceUpdate(); after this.entity.levelUp();
update
this._level = this._level + 1;
can be changed to
Vue.$set(this, _level, this._level + 1);
You will need to import Vue in that component/file to access the $set function
You don't show (or I can't find) the code that changes the object, but are you using $set() or Vue.set() instead of simply changing the object's properties directly? Changing a property directly generally doesn't work because of reactivity limitations
Edited to add:
I see now. I think you want something like:
this.$set(this, '_level', this._level + 1);

Access v-for model used within Vue 2 custom directive

Question
How can I update the model data used within a Vue 2 custom directive?
Setup:
<ul>
<li v-for="item in items">
<select my-directive="item">...</select>
</li>
</ul>
So let's say I have a directive with a hook like this:
Vue.directive('chosenjs', {
inserted: function (el, binding, vnode) {
// Here, I'm setting up a callback with the jQuery Chosen library, but this could be any callback.
jQuery(el).chosen().change(function(event, change) {
// CODE HERE...
});
}
});
In the CODE HERE... section, if binding.value is a pointer (array/object), then this is straight-forward. For example, for an array, I'd do e.g. binding.value.push(someValue), and Vue's observable will handle it. But if the value is a primitive, then what can be done?
If the directive is not used within a v-for loop, you can use the vnode to modify the data in the component. It works great as I show here.
But if it is in a v-for, it seems there's no way. Even with access to the binding.expression, there's no way to get at the v-for item.
Background
I'm trying to work with Vue and the ChosenJS jQuery library. I got most of the way there with this answer, but there is a bug when the directive is used within a v-for loop.