Calling a method in a modal initially hidden (transition) - vue.js

I have a simple method "updateTotal" initialized in my app / vue :
// start app
new Vue({
el: '#app2',
data: {
showModal1: false,
showModal2: false,
total : 0
},
methods: {
updateTotal: function (num) {
this.total = this.total + num
}
}
})
When I call this method from by example a button in the HTML code in the app2 section, it's OK (text "total" updated).
When I call this method from a div section hidden at the loading of the page (it's a modal, with vue.js "transition" system), I have this error :
Property or method "updateTotal" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
The code of this modal / transition (it's IN the app2 div) is :
<!-- template for the modal component SERVICES-->
<script type="text/x-template" id="modal2-template">
<transition name="modal2">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
Header
</slot>
</div>
<div class="modal-body">
<!-- LISTE SERVICES -->
<div>
<div>
<div class="control">
<label class="radio">
<input type="radio" name="service1" v-on:click="updateTotal(10)">
Oui
</label>
<label class="radio">
<input type="radio" name="service1" checked v-on:click="updateTotal(-10)">
Non
</label>
</div>
</div>
<div>
<div class="control">
<label class="radio">
<input type="radio" name="service2" v-on:click="updateTotal(20)">
Oui
</label>
<label class="radio">
<input type="radio" name="service2" checked v-on:click="updateTotal(-20)">
Non
</label>
</div>
</div>
</div>
<!-- LISTE SERVICES -->
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
What can I do to be able to call this method from this modal / transition div ?
Thanks !
MAMP MySQL PHP5

The issue occurs since you try to call updateTotal() from within the template of a different component.
The context within the template is always the component itself (this). Since the method updateTotal() is not defined in this component, you cannot call it.
There are several workarounds, but I consider two the cleanest:
For simple projects/apps: emit an event that triggers the method in the parent (like your close event)
For more complex apps: Use shared state with Vuex and make your method an action

Related

VueJs 3 checkbox v-model on array of objects not working properly

I am having a hard time understanding why my v-model isn't working correctly
I have an 'service' object which contains a property 'actions' of type IAction[]
I also declared an object actions which is an array of IAction and am currently trying to bind checkBoxes to the actions array, but it is not working.
I feel like i am missing something obvious here but would need a little help understanding what it is.
Here is the relevant code
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
I would appreciate any feedback as I am relatively new to VueJs,
Thank you
I think you might not understand what you are doing in code, so I wrote examples.
Bad Code:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
// here you iterate thro array and assign to action variable
<div v-for="action in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ action.EnumName }}
</span>
// Here you using actions with "s" on end so you using empty array declered in script
<input v-model="actions" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If you are getting some data from service.Actions use them! v-model will override those actions if they are ref() or `reactive().
Example:
<script lang="ts">
let actions = [] as IAction[];
</script>
<template>
<div v-for="item in service.Actions" :key="action.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<input v-model="item.is" :value="action" type="checkbox" />
</div>
</div>
</div>
</template>
If service.Actions is only array actions you want to add to array in script actions v-model is not a way you do that!
Probably code you need:
<script lang="ts">
const actions = ref([]) // Use refs everywhere !!! A specially in forms.
function deleteItem() {
// ToDo delete item from actions array
}
</script>
<template>
<div v-for="item in service.Actions" :key="item.Id" class="row">
<div class="col-md-12 d-flex">
<div>
<span class="pe-3">
{{ item.EnumName }}
</span>
<button #click="actions = [...actions, item]">ADD</button>
</div>
</div>
</div>
<div>
<div v-for="{ item, index } in actions" :key="item.id">
<span>{{ item.EnumName }}</span><button #click="deleteItem(index)">X</button>
</div>
</div>
</template>
As Mises pointed out, the v-model has to be a part of the same object as the v-for, so i just put my services and the actions array in an object
let foo = { services: serviceStore.services, actions: [] as IAction[] }

Submit values from multiple components

I am using vuejs-wizard to create registration page, I have each tab in component like this
<form-wizard color="#fcab1a" title="" subtitle="" finish-button-text="Register">
<tab-content title="Personal Info" icon="icon-location3 fa-2x">
<personal-info></personal-info>
</tab-content>
<tab-content title="Contact Info" icon="icon-box fa-2x">
<contact-info></contact-info>
</tab-content>
<tab-content title="Address" icon="icon-alarm fa-2x">
<address></address>
</tab-content>
</form-wizard>
personal info:
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Personal Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Full Name <span class="text-danger">*</span></label>
<input type="text" value="" class="form-control" v-model="name" />
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Age <span class="text-danger">*</span></label>
<input type="number" value="" class="form-control" v-model="age" />
</div>
</div>
</div>
</div>
</div>
</template>
contact info:
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Contact Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Mobile <span class="text-danger">*</span></label>
<input type="text" value="" class="form-control" v-model="mobile" />
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Email <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="email"
/>
</div>
</div>
</div>
</div>
</div>
</template>
so my question is, what is the best way to submit the form, do I need vuex to store the state or is there a better/easier way ?
Thanks
It depends...
The answer depends on various factors. For example, are you using vuex already, size of the app, test-ability of app, and even how are fields get validated (asynch/api validations?).
When it's a simple app, and I only have direct parent=>child relationships, I tend to skip adding the Vuex as a dependency. (but I mostly deal with SPAs, so I usually use it) YMMV.
In this case, that would require that the fields are defined in the parent. Then adding props and emitters for each value to the children, and a listener on the parent. As you start to add more fields though, you might find this tedious, and opt to pass fields in an object either in groups, or as whole (and only pick the ones you need in tab), and then you can implement your own v-model in the tab components which can make it pretty easy to pass the object around
If you're using a more recent Vue version (2.6+), you could use vue.observable to
share a store between multiple components without the bells/whistles of Vuex
There's a good article that shows how to build a vuex clone with it, but in reality it's much, much simpler than that to create a store that would suit your needs. Let me know in the comments if you're interested in how to implement it, and I can describe it.
Rolling with custom store
it's really as simple as this
Create a store.js file with...
import Vue from 'vue';
const store = Vue.observable({
name: null,
email: null,
age: null,
mobile: null,
});
export default store;
then in any component that you want to have access to it, add the store during create
import store from "../store";
export default {
name: "PersonalInfo",
created() {
this.$store = store;
}
};
now the all the store is available to you in the template through $store
<input type="text" value class="form-control" v-model="$store.name">
codesandbox example
You lose the benefits, such as "time-traveling" over mutations that Vuex offers. Because you're not dealing with the scale that the flux pattern (vuex) was meant for, I would use this solution in an app this size.
Ya you should just use vuex, it combines like data so that it isn't spread out across multiple files. If you are going to be sending this information back to your backend, it makes it easier to have most backend connections in one place. without the wizard thing I redid your code with a store like this. Note the computed property instead of using data. By doing it as a computed property you don't have to write any code to change the variables stored inside of the store.
Personal.vue
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Personal Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Full Name <span class="text-danger">*</span></label>
<input
type="text"
value=""
class="form-control"
v-model="register.name"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Age <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="register.age"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
register() {
return this.$store.state.register;
},
},
};
</script>
Contact.vue
<template>
<div class="card">
<div class="card-header">
<h5 class="card-title">Contact Info</h5>
</div>
<div class="card-body">
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Mobile <span class="text-danger">*</span></label>
<input
type="text"
value=""
class="form-control"
v-model="register.mobile"
/>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<div class="col-md-6">
<label>Email <span class="text-danger">*</span></label>
<input
type="number"
value=""
class="form-control"
v-model="register.email"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
register() {
return this.$store.state.register;
},
},
methods: {
submit() {
this.$store.dispatch("register", {
person: this.register,
});
},
},
};
</script>
<style></style>
store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
register: {},
},
actions: {
// register({commit}, data){
//put in some stuff here.
//},
},
});
If you decide to go the store route, all you have to do is this
1. npm install vuex
2. add a folder inside of your src folder called store
3. add a file named index.js
4. go to main.js and add this line"import store from "./store";"
5. where it says new "Vue({" add "store" this will register it.
Vuex is super easy and makes life way easier as your project gets bigger.
The .sync modifier provides two way binding pattern to props, you can read about it here https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier.
In the parent component you can use the .sync modifier this way:
<ChildComponent :name.sync="parentNameProperty" />
...
data: () => ({ parentNameProperty: '' }),
...
Then in the child component you receive name as prop and you can emit an event to update the value in the parent component, by using watch or a method:
...
props: {
name: {
type: String,
default: ''
}
}
...
this.$emit(update:parentNameProperty, newValue)
...
Vuex is a great way to handle state, but is fine to use the above pattern for small applications.

VueJS - v-model in for-loop

I'm trying to build something like the questions in OkayCupid, but all the questions - which are different forms - are located on the same component.
I use an object of questions and 3 possible answers for each question, and I use v-for to loop through the object and create cards with a question, 3 answers with radios, and a submit button.
The problem is that I want to get not only the answer the user chooses, but also the question it belongs to.
Here is my form in the template:
<div class="container">
<div class="row">
<div
class="col-lg-3 col-md-4 col-6"
v-for="(question,index) in questionCollection"
:key="index"
>
<form class="form">
<div class="img-fluid img-thumbnail shadow-lg p-3 mb-5 bg-white rounded">
<!-- <input type="text" :value="question.question" v-model="q" /> -->
<h3 class="d-block mb-4 h-100" alt data-holder-rendered="true">{{ question.question }}</h3>
<div class="card-body container">
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a1"
:value="question.answer1"
v-model="answer"
/>
<h4 class="font-weight-light" for="a1">{{ question.answer1 }}</h4>
</div>
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a2"
:value="question.answer2"
v-model="answer"
/>
<h4 class="font-weight-light" for="a2">{{ question.answer2 }}</h4>
</div>
<div class="card-text form-check">
<input
class="form-check-input"
type="radio"
name="gridRadios"
id="a3"
:value="question.answer3"
v-model="answer"
/>
<h4 class="font-weight-light" for="a3">{{ question.answer3 }}</h4>
</div>
</div>
<div class="card-text container">
<small class="text-muted">{{ question.user }}</small>
<button
href="#"
class="btn btn-primary my-3 mx-10 btn float-right shadow-sm rounded"
#click.prevent="answerQuestion"
>Save</button>
</div>
</div>
</form>
</div>
</div>
</div>
And the script:
export default {
name: "questions",
data() {
return {
q: null,
answer: null
};
},
}
As you can see, at the beginning of the form, I tried to get the question element using v-model in a "fake" input, but it gives me an error that it's conflicted with the v-bind of the value (the question) I want to grab. Of course, I can't use v-model on the headline itself because Vue allows to use it only on inputs.
I've tried to change the v-model into v-model="questionCollection[index].question, but then I have no idea how to get it in the script and, let's say, log it to the console with the corresponding answer.
One way to handle this is to submit the question and answer together in the Save button's click-handler. That is, change answerQuestion() to receive question (the iterator variable in v-for) and answer, and update the Save button's click handler in the template to pass those two variables:
// template
<div v-for="(question, index) in questionCollection">
...
<button #click.prevent="answerQuestion(question, answer)">Save</button>
</div>
// script
answerQuestion(question, answer) {
console.log({ question, answer })
}
demo

Vue v-model data binding issue

I have a pair of standard Bootstrap radio buttons and I wanted v-model to set a value in the vue data object when one is selected.
This works just fine in a JSfiddle, however this doesn't work when running locally on my own environment.
I can get other directives working fine like 'v-if', but v-model just doesn't seem to work at all.
Nothing comes up in the console, and when using vue dev tools extension; I can see that the 'test' object is never updated when one of the radio buttons is selected.
I'm using the latest version of bootstrap, jquery and the vue development version.
<div class="container margin-top-rem" id="app">
<div class="row">
<div class="col-12">
<div class="card text-center" v-if="testIf">
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active card-body-padding">
<div class="row">
<div class="col-12">
<div class="form-group">
<div class="btn-group btn-group-toggle" data-toggle="buttons">
<label class="btn btn-secondary">
<input type="radio" name="options" autocomplete="off" v-model="testVModel" :value="false" />false
</label>
<label class="btn btn-secondary">
<input type="radio" name="options" autocomplete="off" v-model="testVModel" :value="true" />true
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div>test value is {{testVModel}}</div>
</div>
</div>
</div>
var app = new Vue({
el: '#app',
data: {
testIf: 'blah',
testVModel: ''
}
})
It looks like this is a conflict between bootstrap and Vue.
Two options to resolve are:
1) Use Bootstrap-vue
2) Remove 'data-toggle="buttons"' from:
<div class="btn-group-toggle" data-toggle="buttons">

Hide html elements in vuejs2

I want to hide html elements during the initial load, by clicking a button or link i will show those html elements. I can't find a solution to hide or show the element in vuejs2 version. I can able to see few options in vuejs but i am not sure how to use those methods. Below is my component code in that i want to hide the html element(id) "Message".
<template>
<div class="row">
<div class="col-lg-12">
<label class="checkbox checkbox-inline no_indent">
<input type="checkbox" value="">Show stats
</label>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel-group">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">List Values</h3>
</div>
<div class="panel-body">
<button type="button" id="btn1" class="btn btn-warning btn-md" v-on:click="showWorkflow">Test 1</button>
<button type="button" id="btn2" class="btn btn-danger btn-md" v-on:click="showWorkflow">Test 2</button>
<button type="button" id="btn3" class="btn btn-info btn-md" v-on:click="showWorkflow">Test 3</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div id="Message">Hello i am here</div>
</div>
</template>
<script>
export default {
name: 'jobs',
methods: {
showWorkflow: function (e) {
console.log(e.target.id)
}
}
}
</script>
In Vue, you use the v-if directive to conditionally render elements.
You could also use the v-show directive if you wanted to just toggle the CSS display property.
See the section in the docs on Conditional Rendering for more info.
In your specific case, make showWorkflow a data property initially set to false.
Use this as the argument for a v-if directive on the content that you want to initially hide.
Then, when you want to show the content, set showWorkflow to true:
new Vue({
el: '#app',
data() {
return {
showWorkflow: false,
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<div v-if="showWorkflow">
Here is the workflow
</div>
<button #click="showWorkflow = true">Show Workflow</button>
</div>
Here is the documentation on conditional rendering in Vue