Vue.js and input's value that is never displayed - javascript

I'm making a simple shopping list app in Vue.js and I was curious if there's a standard way of doing what I need to do. I have a list of items with add and delete buttons:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" v-model="newItem" placeholder="Item name">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
const item = this.newItem.trim();
if (item === '') return;
this.items.push(item);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
It works just as it should, but I'm not sure about using data entry that is never displayed anywhere. There's also another approach with $refs:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" ref="newItem">
<button #click="addItem($refs.newItem.value)">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem(item) {
item = item.trim();
if (item === '') return;
this.items.push(item);
this.$refs.newItem.value = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
Instead of using separate data entry and v-model, I'm using $refs directly. Is any of these approaches more widely accepted in Vue.js community or guidelines? Or perhaps there's even more popular way?

I just wanted to share my views here. Personally I like to use v-model as it provides few added benefits like:
We can use .trim modifier with v-model which automatically trims whitespace from user input like:
<input v-model.trim="msg">
This way you don't need to write additional code to trim text like item = item.trim();. Few lines of code saved here.
Using this.newItem = '' we can easily clear out the previously entered text after button click using v-model reactivity feature. So, again less line of code instead of doing this.$refs.newItem.value = '';
Another advantage of using v-model is that, instead of doing
<button #click="addItem($refs.newItem.value)">
You can simply call the function like:
<button #click="addItem">
So, you can see these are the few benefits of using a simple v-model, which is mostly related to the developer experience (DX) point of view.
Working Demo:
const app = new Vue({
el: '#app',
data: {
items: [
'Chocolate',
'Pizza',
'Coca-Cola',
],
newItem: ''
},
template: `
<div>
<div>{{ items.length }} item{{ items.length !== 1 ? 's' : '' }}</div>
<ul>
<li v-for="(item, index) of items">
{{ item }}
<button #click="deleteItem(index)">X</button>
</li>
<li>
<input type="text" placeholder="Item name" v-model.trim="newItem">
<button #click="addItem">+</button>
</li>
</ul>
</div>
`,
methods: {
addItem() {
if (this.newItem === '') return;
this.items.push(this.newItem);
this.newItem = '';
},
deleteItem(index) {
this.items.splice(index, 1);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
</div>

Related

How do i remove a class from a dynamically created list for an individual element in the list in vuejs

The click is removing the alert class for all the li's at once , I want to remove alert class for each of the li individually after click
Template
<ul>
<li v-for= "(post, index) in posts"
:key="index" class=" lists alert"
#click="removeAlert(index)">
{{ post.name }}
{{ post.content }}
</li>
</ul>
JS
removeAlert(index){
const items = this.$el.querySelectorAll('lists');
items.forEach((item, index)=>{
item.classList.remove('alert');
});
}
You can do this by using class-binding.
As you can see I used a map to store which index of li is alert.
When click, it will call toggleAlert to update the alertMap
isAlert method will return the value(true/false) for you to determine the alert class need to be added or not.
<template>
<ul>
<li v-for= "(post, index) in posts"
:key="index" class="lists"
#click="toggleAlert(index)"
:class="{ alert: !!isAlert(index) }"
>
{{ post.name }}
{{ post.content }}
</li>
</ul>
</template>
<script>
new Vue({
data: {
alertMap: {}
},
methods: {
toggleAlert: function(index) {
const key = index.toString()
if (alertMap[key]) alertMap[key] = true
else alertMap[key] = false
},
isAlert: function(index) {
const key = index.toString()
return alertMap[key]
}
}
})
</script>
By the way, my answer was written in vue2, you can changed it to composition for vue3.

Vue Copy Paste feature, Vue list rendering

When I type some text into the input field and then hit enter it adding into the list with a button with the title "Copy"
Now I want when I click on the next to input it copies that input.
But its only copy the element with an index of 1
you can run this code
Please check this and let me know What is the issue
Thank you
new Vue({
el: "#app",
data: {
myInput: '',
items: []
},
methods: {
AddNew() {
this.items.push(this.myInput)
this.myInput= ""
},
copyText(index) {
console.log('1 =',this.items[index])
var textToCopy = document.querySelector(`.obj${this.items.indexOf(index)}`)
console.log( '2 =', textToCopy);
textToCopy.select()
document.execCommand("copy");
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-on:keyup.enter="AddNew" v-model="myInput">
<div v-for="(item, index) in items" :key="index">
<input :class="['obj-' + index]" :value="item"> {{index}}
<button v-on:click=" copyText(index)">copy</button>
</div>
</div>
You modify an array so it's not reliable to use an index as a key and to find an item to copy.
At least pass the item itself to copyText method:
<div v-for="(item, index) in items" :key="index">
<input :class="['obj-' + index]" :value="item"> {{index}}
<button v-on:click="copyText(item)">copy</button>
</div>
...
copyText(item) {
console.log('1 =',item)
var textToCopy = document.querySelector(`.obj${item}`)
console.log( '2 =', textToCopy);
textToCopy.select()
document.execCommand("copy");
}
You have a couple of errors where you select the element:
var textToCopy = document.querySelector(`.obj${this.items.indexOf(index)}`)
Should be this:
var textToCopy = document.querySelector(`.obj-${index}`)
You're missing a hyphen after obj and, as you're already passing the index, there's no need to use indexOf.
new Vue({
el: "#app",
data: {
myInput: '',
items: []
},
methods: {
AddNew() {
this.items.push(this.myInput);
this.myInput = "";
},
copyText(index) {
console.log('1 =',this.items[index])
var textToCopy = document.querySelector(`.obj-${index}`)
console.log( '2 =', textToCopy);
textToCopy.select()
document.execCommand("copy");
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-on:keyup.enter="AddNew" v-model="myInput">
<div v-for="(item, index) in items" :key="index">
<input :class="['obj-' + index]" :value="item"> {{index}}
<button v-on:click="copyText(index)">copy</button>
</div>
</div>

How render component in v-for by button from parent

How i can render v-if component by button(button in parent) click inside v-for loop? and should render only in that item where clicked
<div v-for="item in items">
<button #click >Show child<button>
<div>{{item.name}}</div>
<child v-if="this button clicked" :item="item"><child>
<div>
You have to store info about state of every item (if it was clicked) in your data. Then, when you click on button you should update clicked property for particular item. Finally if item.clicked is set on true you will show your child component (or any other html).
<template>
<div>
<div v-for="item in items" :key="item.id">
<button #click="item.clicked = true" >Show child</button>
{{item.name}}
<div v-if="item.clicked">Item child</div>
</div>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data: function() {
return {
items: [
{
id: 1,
name: 'test1',
clicked: false
},
{
id: 2,
name: 'test2',
clicked: false
},
{
id: 3,
name: 'test3',
clicked: false
}
]
}
}
}
</script>
Plain and simple you just have to set some flag for latter v-if:
<div id="app">
<div v-for="item in items">
<button #click="$set(item, 'shown', true)">Show child</button>
<div>{{ item.name }}</div>
<div v-if="item.shown">Child component</div>
</div>
</div>
Here, $set() is used because initial item could lack shown field, so setting it directly with item.shown=true won't be reactive.
You can also hide button after click:
<button #click="$set(item, 'shown', true)" v-if="!item.shown">Show child</button>
To toggle visibility you just have to do it like this:
<button #click="$set(item, 'shown', !item.shown)">
{{ item.shown ? 'Hide' : 'Show' }} child
</button>
JSFiddle
You can take advantage of an item... index available in v-for directive (e.g. v-for="(item, i) in items"), to bind it (index of the item) to the function which shows an item by changing it's property:
Update: Initial answer has been deleted after requirements refinement.
Since you prefer to keep from mutation of items, you can wrap them in Map object (as keys) and keep visibility settings separately as Map values. Unfortunately, as far as I know, for the time being Vue.js does not support reactivity for Map objects, that's why I have to trigger rerendering manually by using forceUpdate:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('child', {
template: '<p>Visible child</p>'
})
new Vue({
el: "#demo",
template: `
<div>
<div v-for="item in items">
<button #click="toggleChild(item)">Toggle child</button>
<div>{{item.name}}</div>
<child v-if="isVisible(item)" :item="item"></child>
</div>
</div>
`,
data () {
return {
itemsMap: new Map(
[
{ name: 'test1' },
{ name: 'test2' }
].map(item => [item, { visible: false }])
)
};
},
methods: {
toggleChild(item) {
this.itemsMap.set(item, { visible: !this.itemsMap.get(item).visible });
this.$forceUpdate();
},
isVisible(item) {
return this.itemsMap.get(item).visible;
}
},
computed: {
items: function() {
return Array.from(this.itemsMap.keys());
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo"></div>

Component Autocomplete VueJS

I want to create an autocomplete component, so I have the following code.
<Input v-model="form.autocomplete.input" #on-keyup="autocomplete" />
<ul>
<li #click="selected(item, $event)" v-for="item in form.autocomplete.res">
{{item.title}}
</li>
</ul>
autocomplete(e){
const event = e.path[2].childNodes[4]
if(this.form.autocomplete.input.length > 2){
this.Base.get('http://localhost:3080/post/search/post', {
params: {
q: this.form.autocomplete.input
}
})
.then(res => {
this.form.autocomplete.res = res.data
if(this.form.autocomplete.res.length > 0)
event.style.display = 'block'
})
}
},
selected(item, e){
this.form.autocomplete.item = item
console.log(e)
}
however, how would I do to have the return after selecting my item in the main file?
Ex:
Home.vue
<Autocomplete :url="www.url.com/test" />
When selecting the item I want from my autocomplete, how do I get the return from it and store it in that file, as if I were using v-model?
As Vue Guide said:
Although a bit magical, v-model is essentially syntax sugar for
updating data on user input events, plus special care for some edge
cases.
Then at Vue Using v-model in Components,
the <input> inside the component must:
Bind the value attribute to a value prop
On input, emit its own custom input event with the new value
Then follow above guide, one simple demo will be like below:
Vue.config.productionTip = false
Vue.component('child', {
template: `<div>
<input :value="value" #input="autocomplete($event)"/>
<ul v-show="dict && dict.length > 0">
<li #click="selected(item)" v-for="item in dict">
{{item.title}}
</li>
</ul>
</div>`,
props: ['value'],
data() {
return {
dict: [],
timeoutControl: null
}
},
methods: {
autocomplete(e){
/*
const event = e.path[2].childNodes[4]
if(this.form.autocomplete.input.length > 2){
this.Base.get('http://localhost:3080/post/search/post', {
params: {
q: this.form.autocomplete.input
}
})
.then(res => {
this.form.autocomplete.res = res.data
if(this.form.autocomplete.res.length > 0)
event.style.display = 'block'
})
}*/
clearTimeout(this.timeoutControl)
this.timeoutControl = setTimeout(()=>{
this.dict = [{title:'abc'}, {title:'cde'}]
}, 1000)
this.$emit('input', e.target.value)
},
selected(item){
this.selectedValue = item.title
this.$emit('input', item.title)
}
}
})
new Vue({
el: '#app',
data() {
return {
testArray: ['', '']
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<child v-model="testArray[0]"></child>
<child v-model="testArray[1]"></child>
<div>Output: {{testArray}}</div>
</div>

Vue2 error when trying to splice last element of object

I have a Vue2 app wit a list of items which I can choose and show, or delete.
When deleting the last element in the list (and only the last one) - I get Vue warn - "[Vue warn]: Error when rendering root instance: "
my HTML:
<body >
<div id="app">
<ul>
<li v-for="(item, index) in list" v-on:click = "selectItem(index)" >
<a>{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
</ul>
<div>
<span>{{selectedItem.name}}</span>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
</body>
The JS:
var app = new Vue({
el: '#app',
data: {
index: 0,
selectedItem: {},
list : [
{ id: 1, name: 'org1', desc: "description1"},
{ id: 2, name: 'org2', desc: "description2"},
{ id: 3, name: 'org3', desc: "description3"},
{ id: 4, name: 'org4', desc: "description4"}
]
},
methods: {
deleteItem: function(index) {
this.list.splice(index,1);
},
selectItem: function(index) {
this.selectedItem = this.list[index];
},
}
})
Can you please advise why does this happen and how to solve this issue?
The problem is happening as you have having selectItem bind at li level, so event when you click cross button, selectItem gets executed and that same item gets deleted as well, causing this error.
One way to solve this problem can be moving the selectItem binding inside li as follows
<li v-for="(item, index) in list">
<a v-on:click = "selectItem(index)" >{{ item.name }}</a>
<div v-on:click="deleteItem(index)">X</div>
</li>
See working fiddle.
Another approach can be when printing selectedItem.name in your HTML, you put a null check, whether selectedItem exist or not like following:
<span>{{selectedItem && selectedItem.name}}</span>
See Working fiddle.

Categories

Resources