create method dynamically in vue.js - vue.js

nomally we predefine methods in vue.js like below.
methods : {
func1: function(){
}
}
and call a function in template
<button #click="func1">click</button>
is it possible to add method dynamically in vue.js?
[for example]
//actually $methods is not exist. i checked $data is exist. so it is my guess.
this.$methods["func2"] = function(){
}
in angular.js it is possible like this.
$scope["function name"] = function(){
}

Functions in javascript are like any other variable, so there are various ways you can dynamically add functions. A very simple solution would look like this:
<template>
<div id="app">
<div #click="userFuncs.myCustomFunction">Click me!</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
// These contain all dynamic user functions
userFuncs: {}
}
},
created () {
window.setTimeout(() => {
this.$set(this.userFuncs, 'myCustomFunction', () => {
console.log('whoohoo, it was added dynamically')
})
}, 2000)
}
};
</script>
It will however give off warnings and potentially errors when the function is invoked while there is no function attached. We can get around this by having a boilerplate function that executes a default function unless a new function is defined.
We would then change the template to:
<div #click="executeDynamic('myCustomFunction')">Click me!</div>
and add the following to the component:
methods: {
executeDynamic (name) {
if (this.userFuncs[name]) {
this.userFuncs[name]()
} else {
console.warn(`${name} was not yet defined!`)
}
}
}
You should always try to use Vue's event handlers via #someEvent or v-on:someEvent handlers, because Vue will automatically attach and detach event handlers when appropriate. In very very very rare cases something you may want to do may not be possible with Vue, you can attach event handlers yourself. Just make sure you use the beforeDestroy hook to remove them again.

Related

Vuejs computed and watch

I was reading this Vue guide about computed and watcher properties.
I was wondering about this part: this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) in the created property.
My question is
is this formula going to add this.debouncedGetAnswer in the data or methods option/property of the component? Because I can see that debouncedGetAnswer is accessed with this like you would have if it is inside the data or methods property of the component. Any this.propertyName would be added to the vue component?
Also in the watch question, how do I know that functions can have argument like that? I tried console log of newQuestion and oldQuestion and saw the old and new value of the input. I am a beginner and I've been seeing a lot of functions that have arguments that I do not know they come from, but it is like they are inherent or something.
Thank you!
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<!-- Since there is already a rich ecosystem of ajax libraries -->
<!-- and collections of general-purpose utility methods, Vue core -->
<!-- is able to remain small by not reinventing them. This also -->
<!-- gives you the freedom to use what you're familiar with. -->
<script src="https://cdn.jsdelivr.net/npm/axios#0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.13.1/lodash.min.js"></script>
<script>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// whenever question changes, this function will run
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},
created: function () {
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we access
// yesno.wtf/api, waiting until the user has completely
// finished typing before making the ajax request. To learn
// more about the _.debounce function (and its cousin
// _.throttle), visit: https://lodash.com/docs#debounce
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer: function () {
if (this.question.indexOf('?') === -1) {
this.answer = 'Questions usually contain a question mark. ;-)'
return
}
this.answer = 'Thinking...'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'Error! Could not reach the API. ' + error
})
}
}
})
</script>
debouncedGetAnswer won't be added to data or methods because you didn't add it there. It doesn't serve a good purpose to add it to methods because debouncing is generally should be applied to component instance, not all instances. It's possible to add it to data by adding it to data instead of created but this isn't needed because debouncedGetAnswer won't benefit from being reactive.
watch functions are callbacks. newQuestion and oldQuestion parameters are provided by Vue. This is specified in API documentation.
if you want to limit HTTP calls by debounce function, you can try to do these ways:
Use :value and #input to instead of v-model
<input :value="question" #input="debouncedGetAnswer($event)" />
implement debouncedGetAnswer on the methods of component
methods: {
debouncedGetAnswer: _.debounce(function (event) {
const inputValue = event.target.value;
this.getAnswer(inputValue);
}, 500),
getAnswer: function (newQuestionValue) {
// call API
}
full example
https://codesandbox.io/s/compassionate-germain-25kcd
<template>
<div id="watch-example">
<p>
Ask a yes/no question:
<input :value="question" #input="debouncedGetAnswer($event)" />
</p>
<p>{{ answer }}</p>
</div>
</template>
<script>
import _ from "lodash";
import axios from "axios";
export default {
name: "HelloWorld",
data: function () {
return {
question: "",
answer: "I cannot give you an answer until you ask a question!",
};
},
methods: {
debouncedGetAnswer: _.debounce(function (event) {
const inputValue = event.target.value;
this.getAnswer(inputValue);
}, 500),
getAnswer: function (newQuestionValue) {
if (newQuestionValue.indexOf("?") === -1) {
this.answer = "Questions usually contain a question mark. ;-)";
return;
}
this.answer = "Thinking...";
var vm = this;
axios
.get("https://yesno.wtf/api")
.then(function (response) {
vm.answer = _.capitalize(response.data.answer);
})
.catch(function (error) {
vm.answer = "Error! Could not reach the API. " + error;
});
},
},
};
</script>
<style scoped>
</style>

Access instance via methods inside a custom Vue component

I'm trying to access the instance methods/data through a triggered method inside a custom registered Vue component.
Below a basic example:
Vue.component('example-component', {
template: `<div>
<h2>Count: {{count}}</h2>
<button class="btn btn-primary" v-on:click="increment()">Increment</button>
</div>`,
data: () => {
return {
count: 0
}
},
methods: {
increment: () => {
console.log("Click!");
console.log("Current count: ", this.count);
this.count++;
console.log("New count: ", this.count);
},
decrement: () => {
// other function
}
},
mounted: () => {
console.log("Example component mounted!");
}
});
Results:
Example component mounted!
Click!
Current count: undefined
New count: NaN
As you might notice the property 'count' has been loaded during the component mount and is available/rendered inside the HTML. The method 'increment()' has also been triggered. However, 'this.count' seems to be unreachable like possible other methods (e.g. 'this.decrement()') which will throw a TypeError this.decrement is not a function.
Any suggestions if this approach is even possible?
PS. I'm aware of the default approach via a .vue file registery like:
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
Explanation from the official docs:
Vue automatically binds the this value for methods so that it always refers to the component instance. This ensures that a method retains the correct this value if it's used as an event listener or callback. You should avoid using arrow functions when defining methods, as that prevents Vue from binding the appropriate this value.
The answer above by Phoenix seems to be valid, and I can only add that you can write the functions in a short form too like:
increment() { ... },
decrement() { ... }
which looks nicer in my opinion, although there is a slight difference.
Arrow functions don't bind with this. Use normal functions instead for your methods.
increment: function() { ... },
decrement: function() { ... }

Vuex registerModule method registers an empty module

I have two modules. One load statically, the other dynamically.
StaticLoadingStore.js:
export default {
namespaced: false,
state() {
return {
propertySL: 'Some value from a statically loaded module',
}
},
getters: {
getPropertySL(state) {
return state.propertySL
},
},
}
DynamicLoadingStore.js
export default {
namespaced: true,
state() {
return {
propertyDL: 'Some value from a dynamically loaded module',
}
},
getters: {
getPropertyDL(state) {
return state.propertyDL
},
},
}
Dynamically loaded module shows that it is empty. Why?
HelloWorld.vue:
<template>
<div>
<h1>SL</h1>
<h5>propertySL:</h5>
<p>{{ propertySL }}</p>
<h5>stateSL:</h5>
<code>{{stateSL}} </code>
<h1>DL</h1>
<h5>propertyDL:</h5>
<p>{{ propertyDL===undefined?'undefined':propertyDL }}</p>
<!-- return undefined -->
<h5>stateDL:</h5>
<code>{{stateDL}} </code>
<!-- return {} -->
</div>
</template>
<script>
import SLModule from '../StaticLoadingStore'
const DLModule = () => import('../DynamicLoadingStore.js');
export default {
data: () => ({
stateSL: '',
stateDL: '',
}),
computed: {
propertySL() {
return this.$store.getters['getPropertySL']
},
propertyDL() {
return this.$store.getters['dlModule/getPropertyDL']
},
},
created() {
this.$store.registerModule('slModule', SLModule);
this.stateSL = JSON.stringify(this.$store.state['slModule'], null, 2);
this.$store.registerModule('dlModule', DLModule());
this.stateDL = JSON.stringify(this.$store.state['dlModule'], null, 2);
}
}
</script>
My knowledge in vue and js is very limited, and I ask the question through Google translator, so I apologize in advance for incompetence.
Without waiting for an answer, he began to experiment.
That's how it worked.
DynamicLoadingStore.js
...
async created() {
const moduleLoader = await DLModule();
this.$store.registerModule('dlModule', moduleLoader.default);
...
But why this is not as recommended in the examples is not clear.
New problem. Reactivity does not work. alert(this.$store.getters['dlModule/getPropertyDL'])
gives expected data.
But the propertySL in template is empty. Tell me what's wrong, please.
But why this is not as recommended in the examples is not clear.
If you talking about this official guide Dynamic Module Registration. I think the author doesn't want to specify how to get the module since there are a lot of ways to do.
In your example I think both modules should call dynamic module, static module is the module that declared at store creation.
But you import it with different methods which are static import and dynamic import. You can read more about import from MDN.
To use dynamic import, there is no need to wrap import statement with function:
...
await import('../DynamicLoadingStore.js')
...
...
// This will useful when you use dynamic component
() => import('../DynamicLoadingStore.js')
...
New problem. Reactivity does not work.
alert(this.$store.getters['dlModule/getPropertyDL']) gives expected
data.
But the propertySL in template is empty. Tell me what's wrong, please.
If you register slModule before dlModule, the propertySL should still work fine but not propertyDL.
The reason is this is the how computed property works, since you are using async created instead of created, the computed property doesn't wait until async created finished. So when Vue try to compute the dependency of the property it cannot do correctly because your getters will return undefined.
You can solve this problem by use another data to trigger computed property to recompute like this:
this.dlModuleReady && this.$store.getters["dlModule/getPropertyDL"];
See example.

Open modal dialog on event bus event

I've created a backend and am now trying to build a frontend, using it. I'm very new to Vue.js and am having a hard time telling it to do what I want; probably because of missing some basic concepts. Hopefully someone can point me in the right direction.
The App.vue groups following components: Header, main section (routed), footer and a modal login dialog.
The issue I'm trying to solve is to display the modal login dialog when clicking the Login button (which lives in the header component); currently, nothing besides the messages being logged happens.
For this I've created an event bus and am firing an event:
export default {
name: 'SppdTeamTunerHeader',
methods: {
emitShowLoginDialogEvent () {
EventBus.$emit('ShowLoginDialog', true)
}
}
}
Emitting the event works as I can see in the Vue DevTools for Chrome.
Here's the complete code of App.vue:
<template>
<div id="app">
<SppdTeamTunerHeader/>
<router-view></router-view>
<SppdTeamTunerFooter/>
<LoginDialogModal
v-show="isLoginDialogVisible"
/>
</div>
</template>
<script>
import SppdTeamTunerHeader from '#/components/TheHeader'
import SppdTeamTunerFooter from '#/components/TheFooter'
import LoginDialogModal from '#/components/LoginDialogModal'
import { EventBus } from '#/common/EventBus'
export default {
name: 'App',
components: {
SppdTeamTunerHeader,
SppdTeamTunerFooter,
LoginDialogModal
},
data: function () {
return {
isLoginDialogVisible: false
}
},
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + this.isLoginDialogVisible)
if (isVisible) {
this.isLoginDialogVisible = true
} else {
this.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + this.isLoginDialogVisible)
})
},
destroyed () {
EventBus.$off('ShowLoginDialog')
}
}
</script>
When checking the console, following is being printed when clicking the login button:
Setting ShowLoginDialog isVisible=true. isLoginDialogVisible=undefined
Finished setting isLoginDialogVisible=true
The value logged for isLoginDialogVisible can't come from the variable defined in the data function as it prints undefined, whereas it has been defined as false (I guess that's my main problem).
I've read quite a few articles about the subject, e.g:
https://codingexplained.com/coding/front-end/vue-js/why-components-data-properties-must-be-functions
https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
The modal dialog example I've based the implementation comes from here: https://alligator.io/vuejs/vue-modal-component/
This is happening because you are not using an Arrow function. Instead of a plain function, use arrow function like this:
mounted () {
// Note the use of arrow function.
EventBus.$on('ShowLoginDialog', (isVisible) => {
// .. All your code
})
}
If you use plain function function () {}, then this pointer is not accessible within inner function. Arrow function will lexically bind this pointer to mounted() function's this context. So use an arrow function i.e. () => {};
Note: If you insist on using plain old function syntax then use closure variable to keep track of this pointer:
mounted () {
// Assign this pointer to some closure variable
const vm = this;
EventBus.$on('ShowLoginDialog', function (isVisible) {
console.log('Setting ShowLoginDialog isVisible=' + isVisible + '. isLoginDialogVisible=' + vm.isLoginDialogVisible)
if (isVisible) {
vm.isLoginDialogVisible = true
} else {
vm.isLoginDialogVisible = false
}
console.log('Finished setting isLoginDialogVisible=' + vm.isLoginDialogVisible)
})
}
This has nothing to do with Vue.js. It is a typical JavaScript behavior.
I believe your listener for the EventBus events needs to be accessible to App. Right now EventBus and App are two separate instances. You could mount the event handler inside App like this:
mounted () {
EventBus.$on('ShowLoginDialog', function (isVisible) {
...
});

Can be exclude `this` keyword in vue.js application?

Actually I am following Douglas Crockford jslint .
It give warning when i use this.
[jslint] Unexpected 'this'. (unexpected_a) 
I can not see any solution around for the error . Don't say add this in jslist.options and mark it true.
Is there is any approach without using this?
EDIT
ADDED CODE
// some vue component here
<script>
export default {
name: "RefereshPage",
data() {
return {
progressValue: 0
}
},
methods:{
getRefreshQueue(loader){
console.log(this.progressValue); // ERROR come here [jslint] Unexpected 'this'. (unexpected_a) 
}
}
}
</script>
Check out this jsfiddle. How can you avoid using this?
https://jsfiddle.net/himmsharma99/ctj4sm7f/5/
as i already stated in the comments:
using this is an integral part of how vue.js works within a component. you can read more about how it proxies and keeps track of dependencies here: https://v2.vuejs.org/v2/api/#Options-Data
As others have said, you're better off just disabling your linter or switching to ESLint. But if you insist on a workaround, you could use a mixin and the $mount method on a vue instance to avoid using this altogether ..
let vm;
const weaselMixin = {
methods: {
getdata() {
console.log(vm.users.foo.name);
}
},
mounted: function () {
vm.getdata();
}
};
vm = new Vue({
mixins: [weaselMixin],
data: {
users: {
foo: {
name: "aa"
}
}
}
});
vm.$mount('#app');
See the modified JSFiddle
As you can see, this only complicates what should be a fairly simple component. It only goes to show that you shouldn't break the way vue works just to satisfy your linter.
I would suggest you go through this article. Particularly important is this part ..
Vue.js proxies our data and methods to be available in the this context. So by writing this.firstName, we can access the firstName property within the data object. Note that this is required.
In the code you posted, you appear to be missing a } after getRefreshQueue definition. It's not the error your linter is describing, but maybe it got confused by the syntax error?
It is possible using the new Composition API. Vue 3 will have in-built support, but you can use this package for Vue 2 support.
An example of a component without this (from vuejs.org):
<template>
<button #click="increment">
Count is: {{ state.count }}, double is: {{ state.double }}
</button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
}
</script>