Fire different functions depending on checkbox state in Vue2 - javascript

I'd like to know is there any way to fire custom functions - one function when checkbox changes to true value and another when it changes to false value (without using $watch).
For example:
I have input wrapped in root div
<div id="root">
<input type="checkbox" v-model="editModeOn">
</div>
and a vue instance with disableEditMode (on checkbox unchecked) and enableEditMode (on checkbox checked)
new Vue({
el: '#root',
props: {
editModeOn: {
type: Boolean,
default: false
}
},
methods: {
disableEditMode() {
// some code
},
enableEditMode() {
// some code
}
},
});
How can I achieve this functionality? Thanks!

Handle the change event.
#change="editModeOn ? enableEditMode() : disableEditMode()"
new Vue({
el: '#root',
data:{
editModeOn: false
},
methods: {
disableEditMode() {
console.log("disable")
},
enableEditMode() {
console.log("enable")
}
},
});
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="root">
<input type="checkbox" v-model="editModeOn" #change="editModeOn ? enableEditMode() : disableEditMode()">
</div>

Related

When does an element become available in dom after using vue v-if

When does an element become available in the dom after using a vue v-if? I would have thought you could do a query selector on the element after the v-if evaluates to true?
In the below code I need to get the .test element once the popout is shown but it shows as null - how do I get it?
new Vue({
el: "#app",
data() {
return {
showPopout: false,
};
},
methods: {
buttonClick(day) {
this.showPopout = true;
console.log(document.querySelector('.test'));
},
},
});
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<div id='app'>
<span #click="buttonClick">show popout</span>
<div v-if="showPopout"><span class="test">test</span></div>
</div>
It will be there after nextTick
new Vue({
el: "#app",
data() {
return {
showPopout: false,
};
},
methods: {
buttonClick(day) {
this.showPopout = true;
this.$nextTick(() => {
console.log(document.querySelector('.test'));
});
},
},
});
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<div id='app'>
<span #click="buttonClick">show popout</span>
<div v-if="showPopout"><span class="test">test</span></div>
</div>

Vue - How to get document/window properties bound to a computed property?

I would like to listen to all focus events on inputs accross my Vue application.
To get the currently focused input, I thought about binding the document.activeElement property to a computed property in my app component, but this is not reactive, why ?
Declaring the activeElement in the data is not reactive either.
Same thing for watchers !
The only way to get it working is by simply returning the value after a focus/blur event on the input itself, but that doesn't suit my needs here.
new Vue({
el: "#app",
data: {
activeElem: document.activeElement.tagName,
realActiveElem: document.activeElement.tagName,
},
methods: {
getActiveElem() {
this.realActiveElem = document.activeElement.tagName;
}
},
computed: {
focused() {
return document.activeElement.tagName;
}
},
watch: {
activeElem(val, oldVal) {
console.log(val !== oldVal);
},
focused(val, oldVal) {
console.log(val !== oldVal);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2 #focus="getActiveElem()">
Data: {{activeElem}}
</h2>
<h2>
Computed: {{focused}}
</h2>
<h2>
From function to data: {{realActiveElem}}
</h2>
<input placeholder="Focus/Blur me" id="test" #focus="getActiveElem()" #blur="getActiveElem()" />
</div>
Is there any way to bind document or window properties as reactive ?
Vue can only react to changes made to data, not to the DOM. The change in document.activeElement is a DOM change.
You could use use an event to fire a method to update data. For example:
new Vue({
el: "#app",
data: {
element: ""
},
created() {
document.addEventListener("focusin", this.focusChanged);
},
beforeDestroy() {
document.removeEventListener("focusin", this.focusChanged);
},
methods: {
focusChanged(event) {
this.element = event.target.tagName;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input id="mine">
<p>{{ element }}</p>
</div>

VueJs and Vuex, how to attach v-model to dynamic fields

I have a series of checkboxes generated by a database. The database call isn't usually finished before the page loads.
This is part of my Vue.
folderList is a list of folders from the database, each has a key, and a doc.label for describing the checkbox and a doc.hash for using as a unique key.
<template>
<ul>
<li v-if="foldersList!=null" v-for="folder in folderData">
<Checkbox :id="folder.key" :name="folder.hash" label-position="right" v-model="searchTypeList[folder.hash]">{{ folder.doc.label }}</Checkbox>
</li>
</ul>
</template>
export default {
name: 'Menu',
components: {
Checkbox,
RouteButton
},
props: {
foldersList: {type: Array, required: true}
},
computed: {
...mapGetters({
searchListType: 'getSearchListType'
}),
searchTypeList: {
get() {
return this.searchTypeList;
},
set(newValue) {
console.log(newValue);
//Vuex store commit
this.$store.commit(Am.SET_SEARCH_TYPES, newValue);
}
}
},
methods: {
checkAllTypes: _.debounce(function (folderList){
const initialList = {};
folderList.forEach((folder) => {
initialList[folder.hash] = true;
});
this.$store.commit(Am.SET_SEARCH_TYPES, initialList);
}, 100)
},
mounted() {
//hacky way of prefilling data after it loads
this.$store.watch(
(state) => {
return this.foldersList;
},
this.checkAllTypes,
{
deep: false
}
);
}
Checkbox is a custom component with a sliding style checkbox, it's v-model is like this
<template>
<div class="checkbox">
<label :for="id" v-if="labelPosition==='left'">
<slot></slot>
</label>
<label class="switch">
<input type="checkbox" :name="name" :id="id" :disabled="isDisabled" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)"/>
<span class="slider round"></span>
</label>
<label :for="name" v-if="labelPosition==='right'">
<slot></slot>
</label>
</div>
</template>
<script>
export default {
name: 'Checkbox',
model: {
prop: 'checked',
event: 'change'
},
props: {
id: {default: null, type: String},
name: {required: true, type: String},
isDisabled: {default: false, type: Boolean},
checked: Boolean,
labelPosition: {default: 'left', type: String},
value: {required: false}
}
};
</script>
I verified checkbox is working by using a simple non dynamic v-model and without the loop.
I want to collect an array of checked values.
In my example above, I tried with this computed to try and link to vuex like I have with other fields, but the get counts as a mutation because it is adding properties to the object as it loops through. I don't know how to solve this
Vuex:
const state = {
searchListType: {}
};
const getters = {
getSearchListType: function (state) {
return state.searchListType;
}
};
const mutations = {
[Am.SET_SEARCH_TYPES]: (state, types) => {
state.searchListType = types;
}
};
What is the correct way to link these up? I need the values in vuex so several sibling components can use the values and store them between page changes.
Also, what is the correct way to prefill the data? I assume I have a major structure problem here. FolderList is async and can load at any point, however it doesn't typically change after the application has loaded. It is populated by the parent, so the child just needs to wait for it to have data, and every time it changes, check off everything by default.
Thank you
I was going to suggest using a change event and method instead of computed get & set, but when testing I found the v-model works ok without any additional code. Below is a rough approximation of your scenario.
Maybe something in the custom component interaction is causing your issue?
Ref Customizing Components, did you use
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
in the Checkbox component?
Vue.component('Checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
new Vue({
el: '#app',
data: {
folderData: [],
searchTypeList: {}
},
created() {
// Dynamic checkbox simulation
setTimeout(() => {
this.folderData = [
{ key: 1 },
{ key: 2 },
{ key: 3 },
]
}, 2000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-if="folderData != null" v-for="folder in folderData">
<Checkbox :id="folder.key" :name="folder.key"
v-model="searchTypeList[folder.key]"></Checkbox>
</li>
</ul>
{{searchTypeList}}
</div>
Handling Vuex updates
I think the simplest way to handle updates to the store is to split v-model into :checked and #change properties. That way your control does not attempt to write back to the store directly, but still takes it's value from the store directly and reacts to store changes (both changes from an api call and from this component's $store.commit() calls).
This is the relevant guide Vuex Form Handling.
<ul>
<li v-if="folderData != null" v-for="folder in folderData">
<Checkbox :id="folder.key" :name="folder.key"
:checked="theList[folder.key]"
#change="changeChecked(folder.key)"
></Checkbox>
</li>
</ul>
...
computed: {
...mapGetters({
theList: 'getSearchListType' // Standard getter with get() only
}),
},
methods: {
changeChecked(key) {
this.$store.commit('updateChecked', key) // Handle details in the mutation
}
}

Dynamically changing props

On my app, I have multiple "upload" buttons and I want to display a spinner/loader for that specific button when a user clicks on it. After the upload is complete, I want to remove that spinner/loader.
I have the buttons nested within a component so on the file for the button, I'm receiving a prop from the parent and then storing that locally so the loader doesn't show up for all upload buttons. But when the value changes in the parent, the child is not getting the correct value of the prop.
App.vue:
<template>
<upload-button
:uploadComplete="uploadCompleteBoolean"
#startUpload="upload">
</upload-button>
</template>
<script>
data(){
return {
uploadCompleteBoolean: true
}
},
methods: {
upload(){
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
}
</script>
Button.vue:
<template>
<button
#click="onClick">
<button>
</template>
<script>
props: {
uploadComplete: {
type: Boolean
}
data(){
return {
uploadingComplete: this.uploadComplete
}
},
methods: {
onClick(){
this.uploadingComplete = false
this.$emit('startUpload')
}
</script>
Fixed event name and prop name then it should work.
As Vue Guide: Custom EventName says, Vue recommend always use kebab-case for event names.
so you should use this.$emit('start-upload'), then in the template, uses <upload-button #start-upload="upload"> </upload-button>
As Vue Guide: Props says,
HTML attribute names are case-insensitive, so browsers will interpret
any uppercase characters as lowercase. That means when you’re using
in-DOM templates, camelCased prop names need to use their kebab-cased
(hyphen-delimited) equivalents
so change :uploadComplete="uploadCompleteBoolean" to :upload-complete="uploadCompleteBoolean"
Edit: Just noticed you mentioned data property=uploadingComplete.
It is easy fix, add one watch for props=uploadComplete.
Below is one simple demo:
Vue.config.productionTip = false
Vue.component('upload-button', {
template: `<div> <button #click="onClick">Upload for Data: {{uploadingComplete}} Props: {{uploadComplete}}</button>
</div>`,
props: {
uploadComplete: {
type: Boolean
}
},
data() {
return {
uploadingComplete: this.uploadComplete
}
},
watch: { // watch prop=uploadComplete, if change, sync to data property=uploadingComplete
uploadComplete: function (newVal) {
this.uploadingComplete = newVal
}
},
methods: {
onClick() {
this.uploadingComplete = false
this.$emit('start-upload')
}
}
})
new Vue({
el: '#app',
data() {
return {
uploadCompleteBoolean: true
}
},
methods: {
upload() {
this.uploadCompleteBoolean = false
// do stuff to upload, then when finished,
this.uploadCompleteBoolean = true
},
changeStatus() {
this.uploadCompleteBoolean = !this.uploadCompleteBoolean
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button #click="changeStatus()">Toggle Status {{uploadCompleteBoolean}}</button>
<p>Status: {{uploadCompleteBoolean}}</p>
<upload-button :upload-complete="uploadCompleteBoolean" #start-upload="upload">
</upload-button>
</div>
The UploadButton component shouldn't have uploadingComplete as local state (data); this just complicates the component since you're trying to mix the uploadComplete prop and uploadingComplete data.
The visibility of the spinner should be driven by the parent component through the prop, the button itself should not be responsible for controlling the visibility of the spinner through local state in response to clicks of the button.
Just do something like this:
Vue.component('upload-button', {
template: '#upload-button',
props: ['uploading'],
});
new Vue({
el: '#app',
data: {
uploading1: false,
uploading2: false,
},
methods: {
upload1() {
this.uploading1 = true;
setTimeout(() => this.uploading1 = false, Math.random() * 1000);
},
upload2() {
this.uploading2 = true;
setTimeout(() => this.uploading2 = false, Math.random() * 1000);
},
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<div id="app">
<upload-button :uploading="uploading1" #click="upload1">Upload 1</upload-button>
<upload-button :uploading="uploading2" #click="upload2">Upload 2</upload-button>
</div>
<template id="upload-button">
<button #click="$emit('click')">
<template v-if="uploading">Uploading...</template>
<slot v-else></slot>
</button>
</template>
Your question seems little bit ambiguë, You can use watch in that props object inside the child component like this:
watch:{
uploadComplete:{
handler(val){
//val gives you the updated value
}, deep:true
},
}
by adding deep to true it will watch for nested properties in that object, if one of properties changed you ll receive the new prop from val variable
for more information : https://v2.vuejs.org/v2/api/#vm-watch
if not what you wanted, i made a real quick example,
check it out hope this helps : https://jsfiddle.net/K_Younes/64d8mbs1/

Vue 2 Component not calling main instance function

Okay, this web app I am "still" trying to make is taking longer than I expected and I still don't get some stuff about Vue 2.
For example, I have a componenent that renders in the main Vue instance and I $emit a function inside the componenet. Of course, the "inside" function is working but not the "main" one:
This is the component file PropertyHouseComponent.vue:
<template>
<fieldset class="form-group col-md">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="house"
name="house"
v-model="data"
#click="callFunc">
<label
for="house"
class="form-check-label"
>House</label>
</div>
</fieldset>
</template>
<script>
module.exports = {
props: {
value: {
type: Boolean,
required: false
}
},
data: function() {
return {
data: false
}
},
created: function() {
if(this.value) {
this.data = this.value;
}
},
methods: {
callFunc: function() { <-- this method is being called correctly
this.$emit('toggleHouse', this.data);
}
}
}
</script>
I've also tried with this.$parent.$emit('toggleHouse') and it's the same and by using instead of #click="callFunc":
watch: {
data: function(value) {
this.$emit('toggleHouse', value);
}
}
This is the main Vue instance function toggleHouse() in app.js:
Vue.component('property-house', require('./components/PropertyHouseComponent.vue'));
...
toggleHouse: function() { <-- this method is being completely ignored
if(value) {
this.csHouse = value;
}
}
Just in case you think "you are not passing value to the function, so it won't change anything, I tried with console.log(). But is not my concern right now (I think)
And finally, this is the HTML part:
<property-house :value="true"></property-house>
And then it renders in the DOM correcly:
So, what am I doing wrong this time and what I should do in order to call toggleHouse from inside the component? Thanks in advance.
You don't emit functions. You emit events.
You need to listen for the toggleHouse event, and I would additionally recommend never using camel cased event names (for reasons beyond the scope of this answer).
Let's assume you change to use toggle-house instead.
this.$emit('toggle-house', value)
Where you use the property-house should look like this
<property-house :value="true" #toggle-house="toggleHouse"></property-house>
Note the #toggle-house. This is the shortcut method for listening to an event in Vue; you could also use the full syntax, v-on:toggle-house="toggleHouse".
In either case (using #toggle-house or v-on:toggle-house) a listener is set up that listens for the toggle-house event you are emitting from the child component that calls the toggleHouse method when the event occurs.
Here is a very rudimentary example.
console.clear()
Vue.component("property-house", {
template: `
<div>
<button #click="$emit('toggle-house', 'Clicked the button')">Click Me</button>
</div>`
})
new Vue({
el: "#app",
methods: {
toggleHouse(message){
alert(message)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<property-house #toggle-house="toggleHouse"></property-house>
</div>
I needed to change the strategy since #click="callFunc" was taking the opposite checkbox value, I guess, that event was fired before changing the v-model="data".
Therefore my new components look like this:
<template>
<fieldset class="form-group col-md">
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="house"
name="house"
v-model="data">
<label
for="house"
class="form-check-label"
>House</label>
</div>
</fieldset>
</template>
<script>
module.exports = {
props: {
value: {
type: Boolean,
required: false
}
},
data: function() {
return {
data: false
}
},
created: function() {
if(this.value) {
this.data = this.value;
}
},
watch: {
data: function(value) {
this.$emit('callback', value);
}
}
}
</script>

Categories

Resources