pattern to add to array item from child component - javascript

I have two components, a parent and child one, the
parent data attribute I've set up it like this...
data () {
return {
users: [],
}
}
the users array is populated by a button click, i share this array with the child component.
The child component is trying to add a user to this list which works(adding value to passed in props), but because the users array is declared under data the parent component refreshes and i lose my users...
is there a pattern to keep the users array values and add to them via a child...
sorry if this is obvious but as i said i'm just starting...
edit : adding code (parent component)
<template>
<div id="app">
<div>
<button v-on:click="display()">Display users</button>
<button v-on:click="displaySingleUserInput =
!displaySingleUserInput">Add user</button>
<div>
</div>
<ul v-if="errors && errors.length">
<li v-for="error of errors">
{{error.message}}
</li>
</ul>
</div>
<add-user v-on:submit_user="addUser" v-show="displaySingleUserInput"></add-user>
<user-list v-bind:users="users"></user-list>
</div>
</template>
<script>
import axios from 'axios';
import UserList from './components/UserList';
import AddUser from './components/AddUser';
export default {
components: {
'user-list': UserList,
'add-user': AddUser
},
name: 'app',
data () {
return {
users: [],
errors: [],
displaySingleUserInput: false
}
},
methods: {
display: function(string)
{
axios.get(`users.json`)
.then(response => {
// JSON responses are automatically parsed.
this.users = response.data.users
})
.catch(e => {
this.errors.push(e)
})
},
addUser: function(id) {
this.users.push({firstname: "john", lastName: 'jones'})
},
}
}
</script>
child component
<template>
<div id="singleUserAdd">
<form id=addUser aria-label="single user add">
<button v-on:click="submit()">Submit</button>
<button>Cancel</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
submit: function() {
this.$emit('submit_user', 1)
}
}
}
</script>

I assume that you have a method called addUser in your child component :
addUser(){
this.$emit("addusr",this.newuser);
}
In the parent one :
<child-comp #addusr="addNewUser" />
...
addNewUser(newuser){
this.users.push(newuser);
}

looks like i was missing
<form v-on:submit.prevent="onSubmit"
to stop my page from refreshing

Related

Vue.js - Pass data from child component to parent using emit but without button

I have a parent form template and each question of the form is inside a child component, like this
<template>
<question1
#display-answer-1="setAnswer1"
/>
<!-- other child components here... -->
</template>
<script>
import Question1 from '...path...';
export default{
components: { Question1 },
data() {
answer1: ''
},
methods: {
setAnswer1(answer1) {
this.answer1 = answer1;
}
}
};
and my child component is like this
<template>
<input type="text" v-model="answer1"/>
<div>
<button
type="button"
#click="saveQ2"
>Save
</button>
</div>
</template>
<script>
export default {
data() {
return {
answer1: ''
};
},
methods: {
saveQ2() {
const answer1 = this.answer1;
this.$emit('display-answer-1', answer1);
}
}
};
This code works, but in this way I'm forced to put a button whenever there is a question to pass data from the child to the form template parent. Is there a smart alternative not to put a save button under each question?
you can use the blur event whenever an input gets unfocused it'll fire the event .
<template>
<input #blur="saveQ2" type="text" v-model="answer1"/>
</template>
<script>
export default {
data() {
return {
answer1: ''
};
},
methods: {
saveQ2() {
const answer1 = this.answer1;
this.$emit('display-answer-1', answer1);
}
}
};

In Vue2 tag input, trying to display tag name from tag object in autocomplete

I have a Vue component with a tag input where I make an ajax call to the db to retrieve suggestions as the user is typing. I am using #johmun/vue-tags-input for this. Everything works fine except that instead of the autocomplete listing options including only the tag attribute of the Tag model, it includes the entire object.
I want to list only the tag attribute in the view, but I want to reference the array of entire Tag objects when it comes time to create the association with the user.
This what the current dropdown look like in the browser:
Here is my input component removing the irrelevant parts, so it meets SO's size constraints:
<template>
<div >
<b-container class="mt-8 pb-5">
<b-row class="justify-content-center">
<b-col lg="5" md="7">
<form>
...
<div v-if="step === 3">
<h2><strong>What topics are you interested in?</strong> (e.g tag1, tag2, etc...)</h2>
<h2>A few popular ones:
<button #click.prevent="addToTags(item)" class="btn btn-sm btn-success" v-for="item in existingTags.slice(0, 3)" :key="item.id">
{{ item.tag }}
</button>
</h2>
<vue-tags-input
v-model="tag"
v-on:keyup.native="getTags"
:tags="tags"
:autocomplete-items="filteredItems"
:autocomplete-min-length=3
#tags-changed="confirmedTags"
/>
</div>
...
</form>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import VueTagsInput from '#johmun/vue-tags-input';
import UsersService from '#/services/UsersService'
import TagsService from '#/services/TagsService'
import TagRelationsService from '#/services/TagRelationsService'
export default {
name: 'UserOnboard',
data() {
return {
tag: '',
tags: [],
...
}
};
},
components: {
VueTagsInput
},
computed: {
filteredItems() {
return this.existingTags.filter(i => {
return i.tag.toLowerCase().indexOf(this.tag.toLowerCase()) !== -1;
});
},
...
user() {
return this.$store.state.auth.user
},
existingTags() {
return this.$store.state.tags.existingTags
}
},
...
methods:{
...
},
addToTags(newTag) {
if (!this.tags.includes(newTag)) {
this.tags.push(newTag)
}
// on button click add appropriate tag to tags array
// console.log('tag array is: ',tags)
},
confirmedTags(event) {
this.tags=event
console.log(event)
},
...
getTags() { //debounce need to be inside conditional
console.log('gettin tags')
// if (this.tag.length >2) {
this.$store.dispatch('debounceTags', this.tag)
// }
}
}
}
</script>
Also, here is the debounceTags method which runs via vuex:
import TagsService from '#/services/TagsService'
import { debounce } from "lodash";
export const state = {
existingTags: []
}
export const mutations = {
setTags (state, tags) {
state.existingTags = tags
}
}
export const actions = {
debounceTags: debounce(({ dispatch }, data) => {
console.log("Inside debounced function.");
dispatch("getTags" ,data);
}, 300),
async getTags ({ commit }, data) {
await TagsService.getTags(data)
.then(function (response) {
console.log('before setting tags this is resp.data: ', response)
commit('setTags', response);
});
}
}
export const getters = {
}

how to move items to another component by click - vue.js

I was wondering how can I move my items -> book, from one component to another. I took this books from api and I show them in the list, so I have an ID.
<template>
<v-flex v-for="(book, index) in allBooks">
<div>Title: {{ book.title }}</div>
<i #click="markAsFavorite(book)" :class="{isActive: isMark}" class="fas fa-heart"></i>
</template>
//component books
<script>
name: 'Books',
data () {
return {
allBooks: [],
isMark: false,
favouriteBooks: []
}
},
mounted() {
axios.get("https://www.googleapis.com/books/v1/volumes?q=id:" + this.id)
.then(response => {
console.log(response)
this.allBooks = response.data.items.map(item => ({...item, isMark: false}))
console.log(this.allBooks)
})
.catch(error => {
console.log(error)
})
},
methods: {
markAsFavorite(book) {
this.isMark = !this.isMark
let favouriteAllBooks = this.favouriteBooks.push(book => {
book.id = // i dont know what?
})
},
}
</script>
//component favourite
<template>
<div class=showFavouriteBook>
<p></p>
</div>
</template>
I tried to compare this marked book ID to something, and then this array with marked books show in second template favourite. But I have no idea how to do this. Maybe somebody can prompt me something?
You should use a global eventBus for that. An 'eventBus' is another instance of Vue which is used to pass data via components tied to the main application.
At the root script of your application append the following:
const eventBus = new Vue({
data: function() {
return {
some_var: null,
}
}
});
You can use Vue mixin to have your event bus accessible globally easily:
Vue.mixin({
data: function() {
return {
eventBus: eventBus,
}
}
});
Now when you want to pass data between components you can use the bus:
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
this.eventBus.some_var = 'it works!'
}
}
</script>
Component 2
<template>
<div>
{{ eventBus.some_var }} <!-- it works -->
</div>
</template>
In addition you can use $emit and $on.
Component 1
// for the sake of demo I'll use mounted method, which is invoked each time component is mounted
<script>
export default {
mounted: function() {
// emit 'emittedVarValue' event with parameter 'it works'
this.eventBus.$emit('emittedVarValue', 'it works!')
}
}
</script>
Component 2
<template>
<div>
{{ some_var }} <!-- "it works" once eventBus receives event "emittedVarValue" -->
</div>
</template>
<script>
export default {
data: function() {
return {
some_var: null
}
},
mounted: function() {
// waiting for "emittedVarValue" event
this.eventBus.$on('emittedVarValue', (data)=>{
this.some_var = data;
})
}
}
</script>
Hope this answer helps you.

Pass data to another component

I have a simple form component:
<template>
<div>
<form #submit.prevent="addItem">
<input type="text" v-model="text">
<input type="hidden" v-model="id">
<input type="submit" value="enviar">
</form>
</div>
</template>
This component has a method that use $emit to add text item to a parent data:
addItem () {
const { text } = this
this.$emit('block', text)
},
Here is markup on my main file:
<template>
<div id="app">
<BlockForm #block="addBlock"/>
<Message v-bind:message="message"/>
</div>
</template>
And the script:
export default {
name: 'app',
components: {
BlockForm,
Message
},
data () {
return {
message : []
}
},
methods: {
addBlock (text) {
const { message } = this
const key = message.length
message.push({
name: text,
order: key
})
}
}
}
My question is: Message component list all items create by BlockForm component and stored inside message array. I add a edit button for each item inside Message list. How can I pass item text to be edited in BlockForm component?
You could just bind the input inside the BlockForm to a variable that is in the parent component. This way when you $emit from the child component, just add the value to the messages.
export default {
name: 'app',
components: {
BlockForm,
Message
},
data () {
return {
message : [],
inputVal: {
text: '',
id: ''
}
}
},
methods: {
addBlock () {
const key = this.message.length
this.message.push({
name: this.inputVal.text,
order: this.inputVal.text.length // If the logic is different here, you can just change it
})
this.inputVal = {
text: '',
id: ''
}
}
}
}
Now when you are calling the BlockForm,
<template>
<div id="app">
<BlockForm propVal="inputVal" #block="addBlock"/>
<Message v-bind:message="message"/>
</div>
</template>
and inside BlockForm,
<template>
<div>
<form #submit.prevent="addItem">
<input type="text" v-model="propVal.text">
<input type="hidden" v-model="probVal.id">
<input type="submit" value="enviar">
</form>
</div>
</template>
Now, when you press edit for existing message, simple assign that "Message" to inputVal data property mapping it to proper text and id.

How do you target a button, which is in another component, with a method in App.vue?

I'm making a basic To Do app, where I have an input field and upon entering a task and pressing the "Enter" key the task appears in the list. Along with the task the TodoCard.vue component also generates a button, which I would like to use to delete the task.
I've added a #click="removeTodo" method to the button, but don't know where to place it, in the TodoCard component or in the App.vue file?
TodoCard component:
<template>
<div id="todo">
<h3>{{ todo.text }}</h3>
<button #click="removeTodo(todo)">Delete</button>
</div>
</template>
<script>
export default {
props: ['todo'],
methods: {
removeTodo: function (todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
}
}
}
</script>
App.vue:
<template>
<div id="app">
<input class="new-todo"
placeholder="Enter a task and press enter."
v-model="newTodo"
#keyup.enter="addTodo">
<TodoCard v-for="(todo, key) in todos" :todo="todo" :key="key" />
</div>
</template>
<script>
import TodoCard from './components/TodoCard'
export default {
data () {
return {
todos: [],
newTodo: ''
}
},
components: {
TodoCard
},
methods: {
addTodo: function () {
// Store the input value in a variable
let inputValue = this.newTodo && this.newTodo.trim()
// Check to see if inputed value was entered
if (!inputValue) {
return
}
// Add the new task to the todos array
this.todos.push(
{
text: inputValue,
done: false
}
)
// Set input field to empty
this.newTodo = ''
}
}
}
</script>
Also is the code for deleting a task even correct?
You can send an event to your parent notifying the parent that the delete button is clicked in your child component.
You can check more of this in Vue's documentation.
And here's how your components should look like:
TodoCard.vue:
<template>
<div id="todo">
<h3>{{ todo.text }}</h3>
<button #click="removeTodo">Delete</button>
</div>
</template>
<script>
export default {
props: ['todo'],
methods: {
removeTodo: function (todo) {
this.$emit('remove')
}
}
}
</script>
App.vue:
<template>
<div id="app">
<input class="new-todo"
placeholder="Enter a task and press enter."
v-model="newTodo"
#keyup.enter="addTodo">
<TodoCard v-for="(todo, key) in todos" :todo="todo" :key="key" #remove="removeTodo(key)" />
</div>
</template>
<script>
import TodoCard from './components/TodoCard'
export default {
data () {
return {
todos: [],
newTodo: ''
}
},
components: {
TodoCard
},
methods: {
addTodo: function () {
// Store the input value in a variable
let inputValue = this.newTodo && this.newTodo.trim()
// Check to see if inputed value was entered
if (!inputValue) {
return
}
// Add the new task to the todos array
this.todos.push(
{
text: inputValue,
done: false
}
)
// Set input field to empty
this.newTodo = ''
}
},
removeTodo: function(key) {
this.todos.splice(key, 1);
}
}
</script>

Categories

Resources