How to retrieve updated input value in loop - javascript

I have this html:
<div v-for="item in my_items">
<div>
<input type="text" :value=item.name />
</div>
<div>
<button #click="edit(item.id, item.name)">Edit</button>
</div>
</div>
And then this javascript:
export default {
data() {
return {
my_items: []
}
},
methods: {
async edit(id, name){
console.log("edit", id);
console.log("name", name);
},
},
async mounted() {
this.my_items = [
{id: 1, name: 'name1'}
,{id: 2, name: 'name2'}
];
}
}
So when component is mounted, two rows will be displayed: "name1" and "name2". Along with a button to edit it.
When i write something else in the input field and then click on the edit button, in the console I still see the "old" name for the variable. How can I access the current value in the input field from the function "edit()"?

You can use v-model or else you need #input along with :value:
const app = Vue.createApp({
data() {
return {
my_items: []
};
},
mounted() {
this.my_items = [{id: 1, name: 'name1'}, {id: 2, name: 'name2'}
];
},
methods: {
edit(item) {
console.log("item", item);
},
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="item in my_items" :key="item.id">
<div>
<input type="text" v-model="item.name" />
</div>
<div>
<button #click="edit(item)">Edit</button>
</div>
</div>
</div>

Related

search bar in Vue select hovered list items

I wonder how can I by clicking the individual string item of the bottom list send it's value to the input ? I mean when I click on the "car" the input gets filled with "car". I assume I'm setting incorrect params to selectCategorie method
<template>
<input type="text" v-model="input" placeholder="Search vehicle..." />
<div v-for="(categorie, index) in filteredList()" :key="categorie">
<p #onClick="selectCategorie(index)" >{{ categorie }}</p>
</div>
<div v-if="input&&!filteredList().length">
<p>No results found!</p>
</div>
</template>
<script>
export default {
data() {
return {
input: '',
categoriesStatic: ['car', 'bus', 'moto', 'bike' ]
}
},
methods: {
filteredList() {
return this.categoriesStatic.filter((categ) =>
categ.toLowerCase().includes(this.input.toLowerCase())
);
},
selectCategorie(index) {
this.input = categorie(index)
}
}
}
</script>

Vue 3 component not rendering but show up on the elements tree

I'm new to vue and I was trying to change one of my components in to the new syntax to learn vue 3. The component was working just fine before the change so I must be missing something.
this is the mother component:
<template>
<div class="user-profile">
<div>
<div class="user-profile__user-panel">
<h1 class="user-profile__username">#{{ user.username }}</h1>
<div v-if="user.isAdmin" class="user-profile__admin-badge">Admin</div>
<div class="user-profile__follower-count">
<string><b>Followers: </b></string> {{ followers }}
</div>
</div>
<NewTwoot #create-twoot="createNewTwoot" />
</div>
<div class="user-profile__twoots-wraper">
<TwootItem
v-for="twoot in user.twoots"
:username="user.username"
:key="twoot.id"
:twoot="twoot"
#favorit="toggleFavorite(id)"
/>
</div>
</div>
</template>
<script>
import TwootItem from "./TwootItem.vue";
export default {
name: "UserProfile",
components: {
TwootItem,
},
data() {
return {
followers: 0,
user: {
id: 1,
username: "GabrielBG",
firstName: "Gabriel",
lastName: "Gutierrez",
email: "gbg#scienceiscoll.com",
isAdmin: true,
twoots: [
{
id: 2,
content: "This is my second twoot",
},
{
id: 3,
content: "This is my third twoot",
},
{
id: 1,
content: "This is my first twoot",
},
],
},
};
},
watch: {
followers(newFollowerCount, oldFollowerCount) {
if (oldFollowerCount > newFollowerCount) {
console.log("You lost a follower!");
} else {
console.log("You gained a follower!");
}
},
},
computed: {
fullName() {
return this.user.firstName + " " + this.user.lastName;
},
},
methods: {
followUser() {
this.followers++;
},
toggleFavorite(id) {
console.log("Toggling favorite for twoot with id: " + id);
},
createNewTwoot(newTwootContent) {
this.user.twoots.unshift({
id: this.user.twoots.length + 1,
content: newTwootContent,
});
},
},
mounted() {
this.followUser();
},
};
</script>
And this is the component that was refactored but now it does not render:
<template>
<form class="create-twoot" #submit.prevent="createNewTwoot">
<lable for="new-twoot"
><strong>New Twoot</strong> ({{ newTwootCharCount }}/180)
</lable>
<textarea id="new-twoot" rows="5" v-model="state.newTwootContent" />
<div class="create-twoot-type">
<lable for="twoot-type">
<strong>Twoot Type</strong>
</lable>
<select id="twoot-type" v-model="state.selectedTwootType">
<option
:value="option.value"
v-for="(option, index) in state.twootTypes"
:key="index"
>
{{ option.name }}
</option>
</select>
</div>
<button
type="submit"
:disabled="
newTwootContent.length === 0 ||
newTwootContent.length > 180 ||
newTwootType == 'draft'
"
>
Twoot
</button>
</form>
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "NewTwoot",
setup(_props, ctx) {
const state = reactive({
newTwootContent: "",
selectedTwootType: "instant",
twootTypes: [
{ value: "draft", name: "Draft" },
{ value: "instant", name: "Instant Twoot" },
],
});
const newTwootCharCount = computed(() => state.newTwootContent.length);
function createNewTwoot() {
ctx.emit("create-twoot", state.newTwootContent);
state.newTwootContent = "";
}
return {
state,
newTwootCharCount,
createNewTwoot,
};
},
};
</script>
I can see it on the elements tree but it show up as <newtwoot></newtwoot> as if it was empty.
I see two errors:
you only import TwootItem in the parent but not NewTwoot. This explains why it is not rendered properly.
You don't define the emit in the child component, look at my answer here:
vue 3 emit warning " Extraneous non-emits event listeners"
So importing the missing component import NewTwoot from "./NewTwoot.vue"; and adding it to the components should do the trick:
components: {
NewTwoot,
TwootItem,
}

How to control multiple checkbox in Vue

I have a checkbox component in Vue:
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="checkbox">
</div>
</template>
<script>
export default {
data(){
return {
checkbox: false
};
},
};
</script>
So in the parent component I want to control these checkbox. So here is my parent component:
<div class="card">
<div class="card-header">
<CheckBox />
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox />
</div>
</div>
So checkbox in card-body can be added when user clicks to add. So if a user clicks 3 times, 3 checkbox are being added inside of card-body. What I am trying to achieve is, as you see in card-header there is another checkbox, and when this checkbox is clicked, I want to check all the checkboxes inside card-body, and when it is unchecked in card-header, I want to unchcecked everything inside card-body.
So do you have any idea how to achieve this?
Thanks...
You can try like this :
Vue.component('checkbox', {
template: `
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" #change="getCheck" v-model="value">
</div>
`,
props: {
checked: {
type: Boolean,
default: false
}
},
data(){
return {
value: this.checked
};
},
methods: {
getCheck() {
this.$emit("set-checked", this.value)
}
},
watch: {
checked(){
this.value = this.checked
}
}
})
new Vue({
el: '#demo',
data(){
return {
all: false,
cart: [
{id: 1, check: false},
{id: 2, check: false},
{id: 3, check: true},
{id: 4, check: false}
]
};
},
watch: {
cart() {
this.cart.find(c => c.check === false) ? this.all = false : this.all = true
}
},
methods: {
checkAll(val) {
this.all = val
this.cart = this.cart.map(c => {
c.check = val
return c
})
},
checkItem(id) {
this.cart = this.cart.map(c => {
if(c.id === id) {
c.check = !c.check
}
return c
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<div class="card">
<div class="card-header">
<p>All</p>
<checkbox :checked="all" #set-checked="checkAll" />
</div>
<br/>
<div class="card-body" v-for="item in cart" :key="item.id">
<div class="checkbox-area">
<checkbox :checked="item.check" #set-checked="checkItem(item.id)" />
</div>
</div>
</div>
</div>
First of all you need to add some props to your Component. than a wachter to emit a sync when the Value of the checkbox changes.
<template>
<div class="checkbox">
<input class="checkbox-input" name="input" type="checkbox" v-model="value">
</div>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
default: false
}
}
data(){
return {
value: this.checked
};
},
watch: {
value(){
this.$emit("update:checked", this.value)
}
}
};
</script>
on the cart you need to watch for theses changes an than you can check/uncheck all the items.
<template>
<div class="card">
<div class="card-header">
<CheckBox :checked.sync="global"/>
</div>
<div class="card-body" v-for="item in cart" :key="item.product.id">
<div class="checkbox-area">
<CheckBox :checked.sync="item"/>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return {
global: false,
cart: [
false,
false,
false
]
};
},
watch: {
global(){
for (let i = 0; i < this.cart.length; i++) {
this.chart[i] = this.global
}
}
}
};
</script>
I have not tested this code, but this should work...
Using checkbox input as custom components can be a little tricky see if this code can help you:
code sandbox
Vue.component('checkbox', {
template: `
<label>
<input type="checkbox" :value="value" v-model="deltaValue" />
{{ label }}
</label>
`,
name: "checkbox",
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
label: String,
value: [Object, Boolean, Array],
modelValue: {
type: [Array, Boolean],
default: () => [],
},
},
computed: {
deltaValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
});
new Vue({
el: '#app',
data() {
return {
areAllSelected: false,
checkedItems: [],
cart: [
{ id: 1, name: "tablet"},
{ id: 2, name: "phone"},
{ id: 3, name: "PC" },
],
};
},
watch: {
areAllSelected(areAllSelected) {
if (areAllSelected) {
this.checkedItems = [...this.cart];
return;
}
this.checkedItems = [];
},
checkedItems(items){
if (items.length === this.cart.length) {
this.areAllSelected = true;
return;
}
if (items.length === 0) {
this.areAllSelected = false;
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="card">
{{ checkedItems }}
<div class="card-header">
<checkbox label="check all" v-model="areAllSelected" />
</div>
<hr />
<div class="card-body" v-for="product in cart" :key="product.id">
<checkbox
:label="product.name"
:value="product"
v-model="checkedItems"
/>
</div>
</div>
</div>

How to get rid of cloning / duplication of elements in vue?

I'm a beginner programmer and I create a spa like trello. Creates boards. In the board creates lists, they are displayed different with different id, but the list items are displayed with the same id and they are duplicated in each list. Sorry for my english:) Help me, please and tell in detail what the problem is.. Thank you very much
vuex file router.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
boards: JSON.parse(localStorage.getItem('boards') || '[]'),
lists: [],
items: []
// items: JSON.parse(localStorage.getItem('items') || '[]')
// lists: JSON.parse(localStorage.getItem('lists') || '[]')
},
mutations: {
addBoard(state, board) {
state.boards.push(board)
localStorage.setItem('boards', JSON.stringify(state.boards))
},
addList(state, list) {
state.lists.push(list)
// localStorage.setItem('lists', JSON.stringify(state.lists))
},
createItemListt(state, item) {
state.items.push(item)
// localStorage.setItem('items', JSON.stringify(state.items))
}
},
actions: {
addBoard({commit}, board) {
commit('addBoard', board)
},
addList({commit}, list) {
commit('addList', list)
},
createItemListt({commit}, item) {
commit('createItemListt', item)
}
},
getters: {
boards: s => s.boards,
taskById: s => id => s.boards.find(t => t.id === id),
lists: d => d.lists,
items: a => a.items
},
modules: {
}
})
the page on which the lists are created MyBoard.vue
<template>
<div>
<div class="wrapper">
<div class="row">
<h1>{{board.title}}</h1>
<div class="list " v-for="list in lists" :key="list.idList">
<div class="list__title">
<h3>{{list.titleList}}</h3>
</div>
<div class="list__card" v-for="item in items" :key="item.idItemList">
<span class="list__item">{{item.itemList}}</span>
<a class="btn-floating btn-tiny btn-check" tag="button">
<i class="material-icons">check</i>
</a>
</div>
<createItemList />
</div>
<createList />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
board() {
return this.$store.getters.taskById(+this.$route.params.id);
},
lists() {
return this.$store.getters.lists;
},
items() {
return this.$store.getters.items;
}
},
components: {
createList: () => import("../components/createList"),
createItemList: () => import("../components/createItemList")
}
};
</script>
CreateList.Vue
<template>
<div>
<div class="row">
<div class="new-list" v-show="isCreating">
<div class="list__title input-field">
<input
type="text"
required
id="list-title"
class="none validate"
tag="button"
autofocus
v-model="titleList"
v-on:keyup.enter="createList"
/>
<label for="list-title">Enter Title List</label>
</div>
<a class="btn-floating transparent btn-close" tag="button" #click="closeList">
<i class="material-icons">close</i>
</a>
</div>
<div class="create-list z-depth-2" v-show="!isCreating">
<p>Create list</p>
<a
class="btn-floating btn-large waves-effect waves-light deep-purple lighten-2 pulse"
tag="button"
#click="addList"
v-on:keyup.enter="addList"
>
<i class="material-icons">add</i>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isCreating: false,
titleList: "",
idList: ""
}),
methods: {
addList() {
this.isCreating = true;
},
closeList() {
this.isCreating = false;
},
createList() {
if (this.titleList == "") {
return false;
}
const list = {
idList: Date.now(),
titleList: this.titleList
};
this.$store.dispatch("addList", list);
this.titleList = "";
this.isCreating = false;
console.log(list.titleList);
}
}
};
</script>
CreateItemList.vue
<template>
<div>
<div class="add-item">
<div class="textarea-item input-field" v-show="isAdding">
<input
type="text"
class="validate"
id="list-item"
v-model="itemList"
v-on:keyup.enter="createItemList"
autofocus
/>
<label for="list-item">Enter Item List</label>
</div>
<a class="waves-effect waves-light btn" v-show="!isAdding" #click="addCard">
<i class="material-icons right">add</i>Add Card
</a>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isAdding: false,
itemList: "",
}),
methods: {
addCard() {
this.isAdding = true;
},
createItemList() {
if (this.itemList == "") {
return false;
}
const item = {
idItemList: Date.now(),
itemList: this.itemList
};
this.$store.dispatch("createItemListt", item);
this.itemList = "";
this.isAdding = false;
}
}
};
</script>
attach photo
Tried to go with the basic idea of the structure you laid out. I added:
id to all items, so they can be identifed
children to appropriate items, so you can keep track of what's in them
const store = new Vuex.Store({
state: {
tables: [
{ id: 1, children: ['1.1', '1.2'] },
{ id: 2, children: ['2.1'] }
],
lists: [
{ id: '1.1', children: ['1.1.1'] },
{ id: '1.2', children: ['1.2.1'] },
{ id: '2.1', children: ['2.1.1', '2.1.2'] },
],
cards: [
{ id: '1.1.1' },
{ id: '1.2.1' },
{ id: '2.1.1' },
{ id: '2.1.2' },
]
},
mutations: {
ADD_CARD(state, listId) {
const list = state.lists.find(e => e.id === listId)
const cards = state.cards
const card = { id: Date.now() }
cards.push( card )
list.children.push( card.id )
},
ADD_LIST(state, tableId) {
const table = state.tables.find(e => e.id === tableId)
const lists = state.lists
const list = { id: Date.now(), children: [] }
lists.push( list )
table.children.push( list.id )
},
ADD_TABLE(state) {
const tables = state.tables
const table = { id: Date.now(), children: [] }
tables.push( table )
},
TRY_MOVING_LIST(state) {
const table1 = state.tables.find(e => e.id === 1)
const table2 = state.tables.find(e => e.id === 2)
const item = table1.children.pop() // remove the last item
table2.children.push(item)
}
},
actions: {
addCard({ commit }, listId) {
commit('ADD_CARD', listId)
},
addList({ commit }, tableId) {
commit('ADD_LIST', tableId)
},
addTable({ commit }) {
commit('ADD_TABLE')
},
tryMovingList({ commit }) {
commit('TRY_MOVING_LIST')
}
},
getters: {
getTables: s => s.tables,
getListById: s => id => s.lists.find(e => e.id === id),
getCardById: s => id => s.cards.find(e => e.id === id),
}
})
Vue.component('CustomCard', {
props: ['card'],
template: `<div>
card ID: {{ card.id }}<br />
</div>`
})
Vue.component('CustomList', {
props: ['list'],
template: `<div>
list ID: {{ list.id }}<br />
<custom-card
v-for="item in list.children"
:key="item"
:card="getCard(item)"
/>
<button #click="addCard">ADD CARD +</button>
<hr />
</div>`,
methods: {
getCard(id) {
return this.$store.getters.getCardById(id)
},
addCard() {
this.$store.dispatch('addCard', this.list.id)
}
}
})
Vue.component('CustomTable', {
props: ['cTable'],
template: `<div>
table ID: {{ cTable.id }}<br />
<custom-list
v-for="item in cTable.children"
:key="item"
:list="getList(item)"
/>
<button #click="addList">ADD LIST +</button>
<hr />
</div>`,
methods: {
getList(id) {
return this.$store.getters.getListById(id)
},
addList(id) {
this.$store.dispatch('addList', this.cTable.id)
}
}
})
new Vue({
el: "#app",
store,
computed: {
tables() {
return this.$store.state.tables
}
},
methods: {
addTable() {
this.$store.dispatch('addTable')
},
tryMovingList() {
// this function will move the last list in table ID 1
// to the end of table ID 2's lists
// NOT FOOLPROOF - you should add error handling logic!
this.$store.dispatch('tryMovingList')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<button #click="tryMovingList()">MOVE LIST</button><br />
<button #click="addTable()">ADD TABLE +</button>
<hr />
<custom-table v-for="item in tables" :key="'table-' + item.id" :c-table="item" />
</div>
With this setup you can change the hierarchy quite easily: just delete an ID from one Array of children and add it to another (e.g. remove '1.1' from table ID 1's children array and add it to table ID 2's children array - everything moved to table ID 2. tryMovingList() does exactly this - this method/action is not foolproof, just for you to try out moving a whole list)
There could be other patterns to solve this problem (like a real linked list data structure or the mediator pattern), but for smaller apps this is OK, I think (I would use it... :) ).
ONE PIECE OF ADVICE
If you want to store state in localStorage on mutations, don't do it by yourself - use Vuex's integrated subscribe mechanism: https://vuex.vuejs.org/api/#subscribe

Remove dynamically created Vue Components

I followed the solution to create dynamic form elements here:
Dynamically adding different components in Vue
Works great, my next conundrum is now I want to remove form elements if the user adds too many.
The way it works is the user creates a "set" which is defined as a group of inputs. So each set could be for a different person, or place, etc.
Here is my JSfiddle https://jsfiddle.net/61x784uv/
Html
<div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">
</div>
<button id='button-add-pii-component' v-on:click="addFormElement('pii-entry-field')">Add Set</button>
</div>
Javascript
Vue.component('pii-entry-field', {
data: function () {
return {
fields: [],
count: 0,
}
},
methods: {
addFormElement: function(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
},
template: ` <div class='pii-field'><div>
<component v-for="field in fields" v-bind:is="field.type":key="field.id"></component>
</div>
<button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
<hr>
</div>`,
})
Vue.component('pii-input-field', {
data: function () {
return {
}
},
template: ` <div>
<select>
<option disabled>Select Classification</option>
<option>Name</option>
<option>Address</option>
<option>Email</option>
<option>Phone</option>
<option>Medical</option>
<option>Financial</option>
</select>
<div>
<input type="text" placeholder="Input">
</div>
<button v-on:click="removeFormElement">Remove Input</button></span>
</div>`,
})
var app = new Vue({
el: '#app',
data: {
fields: [],
count: 0,
},
methods: {
addFormElement: function(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
}
});
Here is a working fiddle: https://jsfiddle.net/e12hbLcr/
Walkthrough:
You need to give the component some kind of id to know what component should be removed. You can do this with props.
<component v-for="field in fields" v-bind:is="field.type" :id="field.id" :key="field.id">
Now create the function in the child-component and edit the button so it sends the id.
<button v-on:click="removeFormElement(id)">Remove Input&lt/button>
Remember in Vue that props go down (parent -> child) and events up (child-parent). So now we need to tell the parent that this button was clicked and an id was sent.
removeFormElement(id) {
console.log('sending message up to remove id', id)
this.$emit('remove', id)
}
Tell the parent component to listen to that event and attach a function to that event.
<component v-for="field in fields" v-bind:is="field.type" :id="field.id" #remove="removeFormElement" :key="field.id">
Note that the # is same as v-on:
Finally remove that item from the fields array.
removeFormElement(id) {
console.log('removing form element', id)
const index = this.fields.findIndex(f => f.id === id)
this.fields.splice(index,1)
}
You should probably move these remove buttons into a <slot> of the component so you could pass in the scoped id.
But if you can't, you could $emit removal event on the $parent of the individual components, passing the id of the item to remove.
Vue.component('pii-entry-field', {
data() {
return {
fields: [],
count: 0,
}
},
mounted() {
this.$on('removeFormElement', this.removeFormElement);
},
methods: {
addFormElement(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
removeFormElement(id) {
const index = this.fields.findIndex(f => f.id === id);
this.fields.splice(index, 1);
}
},
template: `
<div class='pii-field'>
<component v-for="field in fields" v-bind:is="field.type" :key="field.id"></component>
<button id='button-add-pii-input' v-on:click="addFormElement('pii-input-field')">Add Input</button>
<hr>
</div>`,
})
Vue.component('pii-input-field', {
data() {
return {
}
},
methods: {
removeFormElement() {
const id = this.$vnode.key;
this.$parent.$emit('removeFormElement', id);
}
},
template: `
<div>
<select>
<option disabled>Select Classification</option>
<option>Name</option>
<option>Address</option>
<option>Email</option>
<option>Phone</option>
<option>Medical</option>
<option>Financial</option>
</select>
<div>
<input type="text" placeholder="Input" />
</div>
<button v-on:click="removeFormElement">Remove Input</button>
</div>`,
})
var app = new Vue({
el: '#app',
data() {
return {
fields: [],
count: 0,
}
},
methods: {
addFormElement(type) {
this.fields.push({
'type': type,
id: this.count++
});
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div id="component-pii-input" v-for="field in fields" v-bind:is="field.type" :key="field.id">
</div>
<button id="button-add-pii-component" v-on:click="addFormElement('pii-entry-field')" class="uk-button uk-button-primary uk-width-1-1 uk-margin-small">Add Set</button>
</div>

Categories