VueJS trigger Checkbox by clicking table row - vue.js

I'm really new to vuejs and I was wondering if it is possible to trigger the checkbox by clicking the table row.
here's a fiddle for you to play.
https://jsfiddle.net/50wL7mdz/265410/
HTML
<div id="app">
<table>
<tbody>
<tr v-for="cat in categories" #click="selectCat(cat)">
<td><input type="checkbox" :value="cat" v-model="selected" name="" id=""></td>
<td>{{ cat.code}}</td>
<td>{{ cat.name }}</td>
</tr>
</tbody>
</table>
<button #click="checkData()">Check</button>
</div>
VUEJS
new Vue({
el: '#app',
data() {
return {
categories: [
{code:'HW', name:'Hardware'},
{code:'SW', name:'Software'},
{code:'OS', name:'Office Supplies'}
],
selected:[]
}
},
methods:{
selectCat(cat){
this.selected.push(cat);
},
checkData(){
alert(1);
console.log(this.selected);
}
}
})
Thanks in advance.

Add a selected model to your categories and switch that attribute on row click like so:
<div id="app">
<table>
<tbody>
<tr v-for="(cat, index) in categories" :key="index" #click="cat.selected = !cat.selected">
<td><input type="checkbox" v-model="cat.selected"></td>
<td>{{ cat.code}}</td>
<td>{{ cat.name }}</td>
</tr>
</tbody>
</table>
</div>
new Vue({
el: '#app',
data() {
return {
categories: [
{code:'HW', name:'Hardware', selected: false},
{code:'SW', name:'Software', selected: false},
{code:'OS', name:'Office Supplies', selected: false}
]
}
},
methods:{
}
})
Manipulating the state of natives components should always be done by changing their v-model, rather than going into the DOM and and setting a selected attribute. Basically let your model define the state of your view.
Here's another version that'll use a separate array for handling the selected state:
<div id="app">
<table>
<tbody>
<tr v-for="(cat, index) in categories" :key="index" #click="toggleSelect(cat)">
<td><input type="checkbox" :checked="selected.includes(cat.code)"></td>
<td>{{ cat.code}}</td>
<td>{{ cat.name }}</td>
</tr>
</tbody>
</table>
</div>
new Vue({
el: '#app',
data() {
return {
categories: [
{code:'HW', name:'Hardware'},
{code:'SW', name:'Software'},
{code:'OS', name:'Office Supplies'}
],
selected: ['HW']
}
},
methods:{
toggleSelect (cat) {
if (this.selected.includes(cat.code)) {
this.selected.splice(this.selected.findIndex(v => v === cat.code), 1)
} else {
this.selected.push(cat.code)
}
}
}
})

Related

I'm having a render error in my nuxt page with a property that cannot be read

I'm trying to add rows to a table dynamically with a method in nuxt.js, but the prop that I have defined doesn't work...
I receive this error from the console:
Property or method "invoice_customer" 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.
I'm trying with this template:
<template>
<div>
<h3>Customers</h3>
<input type="text" v-model="search" placeholder="Search for names..." />
<table v-if="customers.length > 0">
<tr>
<th>name</th>
<th>address</th>
</tr>
<tr v-for="customer in customers" :key="customer.id">
<td>{{ customer.name }}</td>
<td>{{ customer.address }}</td>
</tr>
</table>
<div class="center">
<p>Customer</p>
<select v-if="customers.length > 0">
<option v-for="customer in customers" :key="customer.id">
{{ customer.name }}
</option>
</select>
</div>
<table v-if="invoice_customers.length > 0">
<tr>
<th>Id</th>
<th>Amount</th>
<th>Expire Date</th>
<th>Note</th>
</tr>
<tr
v-for="invoice_customers in invoice_customer"
:key="invoice_customers.id"
>
<td>
<input
type="number"
name=""
id=""
v-model="invoice_customer.amount"
/>
</td>
<td>
<b-form-datepicker
id="example-datepicker"
v-model="invoice_customer.expire_date"
class="mb-2"
></b-form-datepicker>
</td>
<td>
<input type="text" name="" id="" v-model="invoice_customer.note" />
</td>
</tr>
</table>
<b-button variant="outline-success" #click="addNewInstallment">+ Add New</b-button>
</div>
</template>
and this is my script:
<script>
export default {
name: "IndexPage",
data() {
return {
selected: "",
customers: [],
search: "",
invoice_customers: [{
id: 0,
amount: 0,
expire_date: "",
note: "",
}],
};
},
props: {
},
async mounted() {
const response = await this.$axios.$get("/api/customers");
this.customers = response.data;
return this.customers.filter((p) => {
// return true if the product should be visible
// in this example we just check if the search string
// is a substring of the product name (case insensitive)
return p.name.toLowerCase().indexOf(this.search.toLowerCase()) != -1;
});
},
methods: {
addNewInstallment() {
this.invoice_customers.push({
id: 0,
amount: 0,
expire_date: "",
note: "",
});
}
}
}
</script>
invoice customer is defined in v-for cycle
You inverted the order in your v-for, it should be
v-for="invoice_customer in invoice_customers"

Can't add new item using v-model in Vue JS

I am learning Vue.
Now, I am trying to add data with the price and finally, it calculates total price:
Here is the HTML
<div id="app">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
Here is the Vue Instance:
new Vue({
el : '#app',
data : {
items: [
{ name: 'Rice', price : 12.60 },
{ name: 'Oil', price : 22.00 },
{ name: 'Mango', price : 32.50 },
{ name: 'Orange', price : 42.00 },
],
newItem : '',
newItemPrice : '',
},
computed: {
total() {
var total = 0;
this.items.forEach( item => {
total += parseFloat( item.price );
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem( index ) {
this.items.splice( index, 1 )
}
}
});
You can see it's by default showing item name and price. I want to add new item using the v-model called newItem But It's not adding the new item to the table
BUT
If I remove the Item Price column I mean this line:
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
then it's adding the new item perfectly :(
can you tell me what's wrong here?
See two issues with the fiddle:
There is no way to submit the form data
When pushing the price field was not added to the object
After fixing both of them it works well in this fiddle.
This happens because of browser implementation. As mentioned in W3C Specs:
When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
But in case of multiple elements, the enter keypress does not trigger the form submit and thus you get this behaviour.
To resolve this you can simply use #keyup.enter.prevent="addItem" to listen to the enter keypress on each input and call the addItem() function like:
new Vue({
el: '#app',
data: {
items: [{name:"Rice",price:12.6},{name:"Oil",price:22},{name:"Mango",price:32.5},{name:"Orange",price:42}],
newItem: '',
newItemPrice: null,
},
computed: {
total() {
var total = 0;
this.items.forEach(item => {
total += parseFloat(item.price);
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem(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">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name"
#keyup.enter.prevent="addItem">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price"
#keyup.enter.prevent="addItem">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
You should put a new line in your form, my suggestion is to put just above the close form tag </form>:
<input type="submit" value="add">
Another fix to do is in your methods addItem()
addItem() {
this.items.push({
name: this.newItem,
price: this.newItemPrice
});
}
Where it is the number 0 you should provide the this.newItemPrice to it work properly.

Getting data from Laravel Api to Vue Page

I'm a beginner in vue.js and I'm trying to get an api result and show it to my vue page. my Api is built with Laravel 5.7.
I've just installed Vue axios package to work with http.
Here is my code:
TaskController
public function index()
{
return response(Task::all()->jsonSerialize(), Response::HTTP_OK);
}
App.vue
<template>
<div class="app-component">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Task Title</th>
<th>Priority</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<task-component v-for="task in tasks" :key="task.id" :task="task"></task-component>
<tr>
<td><input type="text" id="task" class="form-control"></td>
<td>
<select id="select" class="form-control">
<option>Low</option>
<option>Medium</option>
<option>High</option>
</select>
</td>
<td><button class="btn btn-primary">Add</button></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import TaskComponent from './Task.vue';
export default{
data(){
return{
tasks: [],
}
},
methods: {
getTasks(){
window.axios.get('/api/tasks').then(({data})=>{
data.forEach(task =>{
this.tasks.push(task)
});
});
},
created(){
this.getTasks();
}
},
components:{TaskComponent}
}
</script>
Task Page
<template>
<tr>
<td>{{ task.id }}</td>
<td>{{ task.title }}</td>
<td>{{ task.priority }}</td>
<td><button class="btn btn-danger">Remove</button></td>
</tr>
</template>
<script>
export default{
data(){
return{
}
},
props: ['task']
}
</script>
I got no errors but no result appeared to my vue although the controller returns json data correctly
the created() hook should not be in methods:
export default {
methods: {},
created() {}
}

Vue 2 filter is not a function

trying to filter an array of objects with an input. I have tried multiple ways with no luck so far. it is telling me that my function "clients.filter" is not a function. get the same message if i set the code as "list.filter", the input for the search is actually part of a separate component and i'm not sure if maybe that is my issue. see below
this is my component where the clients are being gathered from api, and listed into data table
<template>
<table class="table table-bordered table-light table-striped table-hover">
<thead class="thead-primary">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Type</th>
<th scope="col">Email</th>
<th scope="col">Phone</th>
<th scope="col">Profile</th>
</tr>
</thead>
<tbody class="client-info">
<tr v-for="(client, index) in filterBy(clients, searchClient)" :key="index">
<td>{{ index }}</td>
<td>{{ client.name }}</td>
<td>{{ client.type }}</td>
<td>{{ client.email }}</td>
<td>{{ client.phone }}</td>
<td><router-link v-bind:to="'/client/'+client.id"><i class="far fa-eye"></i></router-link></td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'client-info',
props: {
clients: {
type: Array,
default: () => []
}
},
data() {
return {
searchClient: ''
}
},
created() {
this.$store.dispatch('retrieveClients')
},
computed: {
filterBy (clients, value) {
return clients.filter( client => {
console.log(this.clients)
return client.name.indexOf(value) > -1;
})
}
}
}
</script>
next is the parent component where the list is actually being shown. I believe my structure is off because im using vuex. But anyways im not sure why it is telling my "clients.filter" is not a function.
<template>
<div>
<!-- this is the buttons and filter above the list -->
<div class="d-flex mb-3">.
<input class="form-control w-25" placeholder="Filter By Name" v-model="searchClient" type="search">
<div class="mr-auto ml-2">
<button class="btn btn-outline-primary dropdown-toggle dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span>Type:</span>
All
</button>
<div class="dropdown-menu dropdown-menu-left">
<a class="dropdown-item" href="#">Business</a>
<a class="dropdown-item" href="#">Individual</a>
</div>
</div>
<div class="btn-group ml-auto">
<button class="btn btn-outline-secondary"><i class="fas fa-print"></i></button>
<button class="btn btn-outline-success">Import <span><i class="fas fa-download"></i></span></button>
<button class="btn btn-outline-danger">Export <span><i class="fas fa-upload"></i></span></button>
<router-link to="/add" class="btn btn-primary pt-2">Add Client</router-link>
</div>
</div>
<!-- the clients data is imported from client info file -->
<div>
<client-info :clients="allClients"></client-info>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import ClientInfo from '#/components/clientslistexperience/ClientInfo'
export default {
name: 'ClientsList',
components: {
ClientInfo
},
data() {
return {
searchClient: ''
}
},
computed: {
...mapGetters(['allClients']),
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
another option i tried was defining my computed method as such
filterClient () {
return this.clients.filter( client => {
return !this.searchClient || client.name.toLowerCase().includes(this.searchClient.toLowerCase()) > -1
})
}
and then changing my v-for too
<tr v-for="(client, index) in filterClient" :key="index">
<td>{{ index }}</td>
<td>{{ client.name }}</td>
<td>{{ client.type }}</td>
<td>{{ client.email }}</td>
<td>{{ client.phone }}</td>
<td><router-link v-bind:to="'/client/'+client.id"><i class="far fa-eye"></i></router-link></td>
</tr>
but still no luck, all though i was not getting an alarm with that definition but i also wasn't getting any results either. any help would be awesome!!
When using
props: {
list: {
type: Array,
default: () => []
}
},
with default value an empty array means that if you define a component without passing a list prop then vue will use that default value.
For example, if you have defined a component like this:
<template>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</template>
export default {
name: 'MyList',
props: {
list: {
type: Array,
default: () => [1, 2, 3],
},
},
};
and then define a component instance like:
<my-list></my-list>
your component would render:
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
However, if you defined your component instance like this:
<my-list :list="['a', 'b', 'c']"></my-list>
your component would render:
<ul>
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
Given that, and without knowing what your retrieveClients action does, what I can only assume is that the 'allClients' value that you pass to 'clients' prop is not an Array therefore the filter function does not exist.
Make sure your action mutates the state to at least an empty array if allClients is a state property. Else if, allClients is a getter make sure that returns at least an empty Array.

Dynamic v-model with v-for

I have a v-for loop that is going to spit out multiple rows of inputs that I would like to save each separate row to an array object dynamically.
v-for:
<table class="table m-0">
<tbody>
<tr v-for="fund in defaultFunds">
<td>
{{fund.name}}
<b-input v-model="newEntries[fund.id]['id']"
:value="fund.id"
name="entryFund"
type="text"
class="d-none"
:key="fund.id" />
</td>
<td>
<b-input v-model="newEntries[fund.id]['amount']"
name="newFundAmount"
id="newFundAmount"
type="text"
placeholder="Amount"
:key="fund.id"/>
</td>
</tr>
</tbody>
</table>
Desired array (using example of entering 2 rows):
newEntries: [
{ id: '1', amount: '50.00' },
{ id: '2', amount: '123.45' }
],
I load newEntries as an empty array by default. I don't know how to get the kind of array object I want with v-for. With the above code I end up with this:
newEntries: [null, '50.00', '123.45']
What am I doing wrong?
Do you want something like this:
https://jsfiddle.net/e6L5seec/
<div id="app">
newEntries: {{ newEntries }}
<table>
<tr v-for="(fund, index) in defaultFunds">
<td>{{ fund.name }}</td>
<td>
<input v-model="newEntries[index].id"
name="entryFund"
:value="fund.id"
type="text" />
</td>
<td>
<input v-model="newEntries[index].amount"
name="entryFund"
type="text" />
</td>
</tr>
</table>
</div>
new Vue({
el: "#app",
data: function() {
return {
defaultFunds: [
{
id: 0,
name: 'fund 0'
},
{
id: 1,
name: 'fund 1'
}
],
newEntries: [{}, {}]
}
},
methods: {
}
});