How to pass dynamic data to a component? - vue.js

I'm new to Vue, so this might be dumb. In the main component I have this:
components: [FormSelect],
data() {
return {
listing: {
country: ''
}
}
}
I want to include a component:
<form-select
name="country"
model="listing"
:onchange="onCountryChange"
:options="{{ ListingStatic::getCountries()->toSelect()->toJson() }}"
label="{{ trans('listing::form.country') }}"
placeholder="{{ trans('listing::form.choose_country') }}">
</form-select>
Which is specified as:
<template>
<div class="col-md-6 form-group" :class="{'has-error': getModel.errors.has(getName)}" >
<label for="" class="control-label">{{ label }}</label>
<v-select
:value.sync="getModel.fields.getName"
:on-change="onchange"
:options="options"
placeholder="placeholder">
</v-select>
<span class="help-block" v-show="getModel.errors.has(getName)">
#{{ getModel.errors.get(getName) }}
</span>
</div>
</template>
<script type="text/ecmascript">
module.exports = {
name: 'FormSelect',
props: ['label', 'placeholder', 'options', 'model', 'onchange'],
computed: {
getModel() {
console.log(this.model)
return this.model;
},
getName() {
return this.name;
}
}
};
</script>
Basically I want to generalize select field by wrapping it into a component
Should I use computed properties to get correct model and key? How can I use getModel and getName, so it would return based on properties passed in?

Related

Vue : params value don't change in created method

I have this link to another component with params :
<div class="dropdown-menu bg-light" aria-labelledby="navbarDropdown">
<div v-for="category in categories" :key="category.id">
<div v-if="lang=='ku'"><li><a class="dropdown-item font-weight-bold py-2" href="#" ><router-link :to="{name:'category',params:{id:category.id}}">{{category.catagory_ku}}</router-link></a></li></div>
<div v-else><li><a class="dropdown-item font-weight-bold py-2" href="#" ><router-link :to="{name:'category',params:{id:category.id}}">{{category.catagory_ar}}</router-link></a></li></div>
</div>
</div>
the category component like this :
<template>
<div style="margin-top:132px">
Categories {{id}}
</div>
</template>
<script>
export default {
data(){
return {
id :''
}
},
created(){
this.id=this.$route.params.id
}
}
</script>
when i pressed the route link the id value changed in url but in the page view it just take first id value I clicked and not be changed after I clicked another same link by different id value
Try to define the id as computed property :
<template>
<div style="margin-top:132px">
Categories {{id}}
</div>
</template>
<script>
export default {
computed:{
id(){
return this.$route.params.id
}
}
}
</script>
this should be in the mounted hook
created(){
this.id=this.$route.params.id
}
// replace with
mounted(){
this.id = this.$route.params.id
}
also if you wanna perform some extra actions related to the id changes, you can watch the route
<template>
<div style="margin-top:132px">
Categories {{ id }}
</div>
</template>
<script>
export default {
data() {
return {
id: ''
}
},
mounted() {
this.id = this.$route.params.id
},
watch: {
$route(to) {
this.id = to.params.id
// do other logics here
}
}
</script>

Why can't you add a Vue.js reactive property directly to root model?

Here is some code that uses $set() to add a new reactive prop to the model. It works fine.
<template>
<div id="app">
<div>
Prop1: {{ x.prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
x: {}
};
},
methods: {
go() {
this.$set(this.x, 'prop1', 'yay');
}
}
};
</script>
Now, if I remove the x root property and try to add prop1 directly to the this it doesn't work.
<template>
<div id="app">
<div>
Prop1: {{ prop1 }}
</div>
<div>
<input type="button" value="Go" #click="go()">
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
};
},
methods: {
go() {
this.$set(this, 'prop1', 'yay');
}
}
};
</script>
I get that you should do this kind of thing, but I can't figure out why it doesn't work.
As stated in the docs:
The target object cannot be a Vue instance, or the root data object of a Vue instance.
It's a technical limitation.

How to pass form data from parent component to child?

I am trying to learn some vuejs and am struggling to understand how to pass data from a parent component to its child component. I know more is required but i'm not sure which way to go. How do pass the name in an input field in the parent component when the submit button is pressed to display in the child component?
I have tried using v-model because from what i have read and understand it is supposed to do what i need but it updates it without me even needing to press the button.
//Parent component
<template>
<div id="app">
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<Name lname="lname" fname="fname"></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit() {
submittedFname = fname,
submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
I am expecting to display the full name on the child component when the button is pressed but instead it is displayed as i am typing it.
//Parent component
<template>
<div id="app">
<form>
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
</form>
<button #click="handleSubmit(fname,lname)">submit</button>
<Name :submittedFname="submittedFname" :submittedLname="submittedLname" ></Name>
</div>
</template>
<script>
import Name from './components/fullName.vue'
export default {
name: 'app',
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
components: {
Name
},
methods: {
handleSubmit(fname,lname) {
this.submittedFname = fname,
this.submittedLname = lname
}
}
}
</script>
//child component
<template>
<div id="my-name">
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>
</template>
<script>
export default {
name: 'my-name',
data () {
return {
}
},
props: {
submittedFname: String,
submittedLname: String
}
}
</script>
in case I forgot some things here are screenshots:
Parent component
child component
v-model means that the fname and lname instance data properties are updated each time the value of their respective input elements changes (it uses the input event behind the scenes). You then pass fname and lname directly as props to the child component. These props are reactive so it behaves as you see and the name is updated as you type.
To only change the name when submit is pressed, you can do this:
Add 2 more data properties in the parent component (e.g. submittedfname and submittedlname)
Add an #submit event listener on the form that copies the values from fname and lname to submittedfname and submittedlname
Use submittedfname and submittedlname as props for the child component.
Working code:
//Parent component
Vue.component('app', {
template: `
<div>
<form #submit.prevent="handleSubmit">
<input type="text" name="fname" id="fname" placeholder="First Name" v-model="fname">
<input type="text" name="lname" id="lname" placeholder="Last Name" v-model="lname">
<input type="submit" value="Submit Name">
</form>
<name-comp :submittedFname="submittedFname" :submittedLname="submittedLname"></Name>
</div>`,
data () {
return {
fname: '',
lname: '',
submittedFname: '',
submittedLname: ''
}
},
methods: {
handleSubmit() {
this.submittedFname = this.fname;
this.submittedLname = this.lname;
}
}
});
//child component
Vue.component('name-comp', {
template: `
<div>
<label>Your name is:</label>
{{ submittedFname }} {{ submittedLname }}
</div>`,
props: {
submittedFname: String,
submittedLname: String
}
});
var vapp = new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<app />
</div>
You were missing ":" in front of your props given to the Name component. Also you didn't use this like in this.lname.

Send data from one component to another in vue

Hi I'm trying to send data from one component to another but not sure how to approach it.
I've got one component that loops through an array of items and displays them. Then I have another component that contains a form/input and this should submit the data to the array in the other component.
I'm not sure on what I should be doing to send the date to the other component any help would be great.
Component to loop through items
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Name</p>
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in entries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry />
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
}
}
</script>
Component for adding / sending data
<template>
<div
class="entry-add"
v-bind:class="{ 'entry-add--open': addEntryIsOpen }">
<input
type="text"
name="addEntry"
#keyup.enter="addEntries"
v-model="newEntries">
</input>
<button #click="addEntries">Add Entries</button>
<div
class="entry-add__btn"
v-on:click="openAddEntry">
<span>+</span>
</div>
</div>
</template>
<script>
export default {
name: 'add-entry',
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push(this.newEntries);
this.newEntries = '';
},
openAddEntry() {
this.addEntryIsOpen = !this.addEntryIsOpen;
}
}
}
</script>
Sync the property between the 2:
<add-entry :entries.sync="entries"/>
Add it as a prop to the add-entry component:
props: ['entries']
Then do a shallow merge of the 2 and emit it back to the parent:
this.$emit('entries:update', [].concat(this.entries, this.newEntries))
(This was a comment but became to big :D)
Is there a way to pass in the key of name? The entry gets added but doesn't display because im looping and outputting {{ entry.name }}
That's happening probably because when you pass "complex objects" through parameters, the embed objects/collections are being seen as observable objects, even if you sync the properties, when the component is mounted, only loads first level data, in your case, the objects inside the array, this is performance friendly but sometimes a bit annoying, you have two options, the first one is to declare a computed property which returns the property passed from the parent controller, or secondly (dirty and ugly but works) is to JSON.stringify the collection passed and then JSON.parse to convert it back to an object without the observable properties.
Hope this helps you in any way.
Cheers.
So with help from #Ohgodwhy I managed to get it working. I'm not sure if it's the right way but it does seem to work without errors. Please add a better solution if there is one and I'll mark that as the answer.
I follow what Ohmygod said but the this.$emit('entries:update', [].concat(this.entries, this.newEntries)) didn't work. Well I never even need to add it.
This is my add-entry.vue component
<template>
<div
class="add-entry"
v-bind:class="{ 'add-entry--open': addEntryIsOpen }">
<input
class="add-entry__input"
type="text"
name="addEntry"
placeholder="Add Entry"
#keyup.enter="addEntries"
v-model="newEntries"
/>
<button
class="add-entry__btn"
#click="addEntries">Add</button>
</div>
</template>
<script>
export default {
name: 'add-entry',
props: ['entries'],
data: function() {
return {
addEntryIsOpen: false,
newEntries: ''
}
},
methods: {
addEntries: function() {
this.entries.push({name:this.newEntries});
this.newEntries = '';
}
}
}
</script>
And my list-entries.vue component
<template>
<div class="container-flex">
<div class="wrapper">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Competition Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredEntries">
{{ entry.name }}
</li>
</ul>
</div>
<add-entry :entries.sync="entries"/>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
import pickWinner from '#/components/pick-winner.vue'
export default {
name: 'entry-list',
components: {
addEntry,
pickWinner
},
data: function() {
return {
search: '',
entries: [
{
name: 'Geoff'
},
{
name: 'Stu'
},
{
name: 'Craig'
},
{
name: 'Mark'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredEntries() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
</script>

Extend Vue function in component to display ID

I have a Vue component and I am using internalValue to access the value attribute. How would I extend this to also get the ID?
ie internalValue = value, id
I have tried but I don't know how to add this inside the internalValue function. I've even tried to only get the ID by changing all instances of value to id but it still spits out the value.
I'd be happy to have them as one ie value, id or access them like data.value and data.id
Initialise Vue
new Vue({
el: '#topic',
data: {
selectedTopic: null
}
});
Use Component
<div class="form-group" id="topic">
<topic v-model="selectedTopic"></topic>
</div>
Register Component
Vue.component('topic', require('./components/Topicselect.vue'));
Component
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" v-model="internalValue" name="topics_radio" :id="topic.id" :value="topic.name">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ internalValue }}</li>
</ul>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
internalValue: this.value,
topics: []
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
},
watch: {
internalValue(v){
this.$emit('input', v);
console.log('Topicselect: the value is ' + this.internalValue);
}
}
}
</script>
Use the selected topic as your value. Basically, eliminate internalValue altogether, and just emit the topic associated with any given radio button when it's clicked. This will satisfy v-model, since it listens to input events (unless you customize it).
export default {
props: ['value'],
data () {
return {
topics: []
}
},
methods:{
selectValue(topic){
this.$emit('input', topic)
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
}
})
And your template
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail">
<input type="radio" #click="selectValue(topic)" name="topics_radio" :id="topic.id" :value="topic.name" :checked="value && topic.id == value.id">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
<ul class="hidden">
<li>{{ value }}</li>
</ul>
</div>
</template>
This will set selectedTopic in your Vue to a topic, which is something like
{
id: 2,
name: "some topic"
}
based on how you use it in your template.
Working example.