Vuejs get $refs value in methods - javascript

I have multiple rows and I wish to get input value inside my tr refs but it returns undefined.
Code
Component
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<td><strong>Serial Number</strong></td>
<td><strong>Product</strong></td>
<td><strong>Amount</strong></td>
<td><strong>Price</strong></td>
<td width="50"></td>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in serial_numbers" :key="index">
<td>
<el-input ref="barcoded" v-model="row.barcode_id"></el-input>
</td>
<td>
<el-input ref="product" v-model="row.product"></el-input>
</td>
<td>
<el-input ref="amount" v-model="row.amount"></el-input>
</td>
<td>
<el-input ref="price" v-model="row.price" readonly></el-input>
</td>
<td>
<el-link v-on:click="removeElement(index);" style="cursor: pointer">Remove</el-link>
</td>
</tr>
</tbody>
</table>
<div>
<el-button type="primary" class="button btn-primary" round #click="addRow">Add row</el-button>
</div>
Script
methods: {
focusInput() {
this.$refs.barcode_id.focus();
},
addRow() {
var barcodes = document.createElement('tr');
this.serial_numbers.push({
barcode_id: '',
product: '',
amount: '',
price: ''
});
// try to get value of barcode input
console.log(this.refs.barcoded); // undefined
this.$nextTick(function () {
const nbBarcodes = this.$refs.barcoded.length;
this.$refs.barcoded[nbBarcodes - 1].focus();
});
},
}
Any idea?
Update
Demo

Using the serial_numbers object instead of refs
console.log(this.serial_numbers[this.serial_numbers.length - 1]);
// this was moved from above
this.serial_numbers.push({
barcode_id: '',
product: '',
amount: '',
price: ''
});
You should push the new element after retrieving the values you need
https://codepen.io/albertor24/pen/wvKYxOg

Related

Create a list of dict with VueJS

I have an HTML table where I took some values displayed in the table and other inputed by user and only for the row the checkbox is checked.
A row of my table looks like this:
Here is the code of my table:
<template>
<table id="Ref" class="table table-bordered table-striped">
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Type</th>
<th scope="col">Nouvel ID</th>
<th scope="col">Nouveau Type</th>
<th scope="col">Nouvel URL</th>
<th scope="col">Nouvelle date d'expiration</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows">
<td> <a :href="row.url" target="_blank" rel="noreferrer noopener">{{ row.id }}</a></td>
<td> {{ row.typ }} </td>
<td> <input v-model.trim="row.newID" type="text" placeholder="ID"> </td>
<td> <input v-model.trim="row.newType" type="text" placeholder="Type"> </td>
<td> <input v-model.trim="row.newURL" type="text" placeholder="URL"> </td>
<td> <input v-model.trim="row.newExpDate" type="text" placeholder="Date d'expiration"> </td>
<td style="text-align: center; vertical-align: middle;"> <input v-model="row.isSelected" type="checkbox"> </td>
</tr>
</tbody>
</table>
</template>
My goal is to return a list of dict like this:
[{
"id1": "newID1",
"type1": "newType1",
"url1": "newURL1",
"exp1" "expDate1"
},
{
"id2": "newID2",
"type2": "newType2",
"url2": "newURL2",
"exp2" "expDate2"
},
{
"id3": "newID3",
"type3": "newType3",
"url3": "newURL3",
"exp3" "expDate3"
}]
I tried with reduce but it will only return me a dict, not a list.
putInvalidsRef() {
const selectedRef = this.rowsInvalidsRef.filter((ref) => ref.isSelected === true);
const refsDicts = selectedRef.reduce((acc, item) => {
acc[item.id] = item.newID || null;
acc[item.typ] = item.newType || null;
acc[item.url] = item.newURL || null;
acc[item.exp] = item.newExpDate || null;
return acc;
}, {});
console.log(refsDict);
this.$http.admin.putInvalidsRef(refsDict)
.then(getInvalidsRef);
}
Sorry if the answer is trivial, I'm really new to frontend.
Instead of {} use [] to return list:
function putInvalidsRef() {
const selectedRef = this.rowsInvalidsRef.filter((ref) => ref.isSelected === true);
const refsDicts = selectedRef.reduce((acc, item) => {
return [
...acc,
{
[item.id]: item.newID || null, // Be sure if here item has id and newID property
[item.typ]: item.newType || null,
[item.url]: item.newURL || null,
[item.exp]: item.newExpDate || null
}
]
}, []);
console.log(refsDicts);
this.$http.admin.putInvalidsRef(refsDicts)
.then(getInvalidsRef);
}

Vuejs sum on event change

I have following code that sum price of product to existed number when user increase the quantity of product. But the issue with my code is if user decrease the amount price still gets increased.
Code
html
<el-input-number style="width: 100%;" ref="amount" v-on:change="amoutChanged($event, row)" v-model="row.amount" :min="1"></el-input-number>
script
data() {
return {
subtotal: 0,
}
},
methods: {
amoutChanged: function(event, row) {
console.log('amount row: ', row);
console.log('amount event: ', event);
// ISSUE: regardes that user increase or decrease the amount number this price gets increased
this.subtotal = parseInt(this.subtotal) + parseInt(row.price);
},
}
Console results
amount row: {barcoded: "8995078803078", …}
amount: (...)
barcode_id: (...)
barcoded: "8995078803078"
price: (...)
product: (...)
amount event: 2 // this is amount input by user
Question
How to properly increase and decrease price based on user action?
I have done it like this:
I changed subtotal to an computed propertie and sum it with .reduce() and i added a new property called singlePrice so we can multiply with it
var Main = {
data() {
return {
serial_numbers: [{
barcode_id: '45634643',
product: 'dfgs546',
amount: 1,
price: 100,
singlePrice: 100,
},{
barcode_id: '23523fd',
product: 'rgdg46546',
amount: 1,
price: 100,
singlePrice: 100,
},{
barcode_id: 'fdghdh',
product: '345634643',
amount: 1,
price: 100,
singlePrice: 100,
}],
total: 0,
num1: 1
};
},
computed: {
subtotal(){
return this.serial_numbers.reduce((a,v)=> a + v.price,0)
}
},
methods: {
addRow() {
var barcodes = document.createElement('tr');
this.serial_numbers.push({
barcode_id: '675476547',
product: 'hjfgj67',
amount: 1,
price: 100,
singlePrice: 100,
});
},
removeElement: function(index) {
this.serial_numbers.splice(index, 1);
},
amountChanged($event, index){
let amount = $event;
this.serial_numbers[index].amount = amount;
this.serial_numbers[index].price = this.serial_numbers[index].singlePrice * amount;
}
}
};
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
#import url("//unpkg.com/element-ui#1.4.0/lib/theme-default/index.css");
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui#1.4.0/lib/index.js"></script>
<div id="app">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<td><strong>Serial Number</strong></td>
<td><strong>Product</strong></td>
<td><strong>Amount</strong></td>
<td><strong>Price</strong></td>
<td width="50"></td>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in serial_numbers" :key="index">
<td>
<el-input ref="barcoded" v-model="row.barcode_id"></el-input>
</td>
<td>
<el-input ref="product" v-model="row.product" readonly></el-input>
</td>
<td>
<el-input-number style="width: 100%;" ref="amount" #change="amountChanged($event, index)" v-model="row.amount" :min="1"></el-input-number>
</td>
<td>
<el-input ref="price" v-model="row.price" readonly></el-input>
</td>
<td>
<el-link :underline="false" type="danger" v-on:click="removeElement(index);" style="cursor: pointer"><i class="fa-2x el-icon-remove"></i></el-link>
</td>
</tr>
</tbody>
</table>
<div>
<el-button type="primary" class="button btn-primary" round #click="addRow"><i class="el-icon-circle-plus"></i> Add row</el-button>
</div>
<el-row :gutter="10">
<el-col :span="8" :offset="16">
<table class="table table-bordered table-striped table-hover">
<tbody>
<tr>
<th width="100"><strong>Sub total</strong></th>
<td>
{{subtotal}}
</td>
</tr>
<tr>
<th width="100"><strong>Total</strong></th>
<td>
{{total}}
</td>
</tr>
</tbody>
</table>
</el-col>
</el-row>
</div>

Component template should contain exactly one root element nuxt

I'm not sure what could be the issue here but I'm using nuxt to make a SPA app. and I'm getting an error from an already compiled piece of code I got from codepen. link to codepen
https://codepen.io/jjelic/pen/yevNLZ?editors=1010
When I try this code my my nuxt app I get an error.
I've added a file called monitor.vue in pages folder and added the html and js like so
Is this root element error common as I have never encountered it before with html and how can I avoid?
Vue.filter('currencyDisplay', {
// model -> view
read: function(val) {
if (val > 0) {
return accounting.formatMoney(val, "$", 2, ".", ",");
}
},
// view -> model
write: function(val, oldVal) {
return accounting.unformat(val, ",");
}
});
Vue.directive('sortable', {
twoWay: true,
deep: true,
bind: function() {
var that = this;
var options = {
draggable: Object.keys(this.modifiers)[0]
};
this.sortable = Sortable.create(this.el, options);
console.log('sortable bound!')
this.sortable.option("onUpdate", function(e) {
that.value.splice(e.newIndex, 0, that.value.splice(e.oldIndex, 1)[0]);
});
this.onUpdate = function(value) {
that.value = value;
}
},
update: function(value) {
this.onUpdate(value);
}
});
var vm = new Vue({
el: '#app',
data: {
rows: [
//initial data
{
qty: 5,
description: "Something",
price: 55.20,
tax: 10
},
{
qty: 2,
description: "Something else",
price: 1255.20,
tax: 20
},
],
total: 0,
grandtotal: 0,
taxtotal: 0,
delivery: 40
},
computed: {
total: function() {
var t = 0;
$.each(this.rows, function(i, e) {
t += accounting.unformat(e.total, ",");
});
return t;
},
taxtotal: function() {
var tt = 0;
$.each(this.rows, function(i, e) {
tt += accounting.unformat(e.tax_amount, ",");
});
return tt;
}
},
methods: {
addRow: function(index) {
try {
this.rows.splice(index + 1, 0, {});
} catch (e) {
console.log(e);
}
},
removeRow: function(index) {
this.rows.splice(index, 1);
},
getData: function() {
$.ajax({
context: this,
type: "POST",
data: {
rows: this.rows,
total: this.total,
delivery: this.delivery,
taxtotal: this.taxtotal,
grandtotal: this.grandtotal,
},
url: "/api/data"
});
}
}
});
<template>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th>Description</th>
<th style="width: 80px;">Qty</th>
<th style="width: 130px;" class="text-right">Price</th>
<th style="width: 90px;">Tax</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody v-sortable.tr="rows">
<tr v-for="row in rows" track-by="$index">
<td>
{{ $index +1 }}
</td>
<td>
<input class="form-control" v-model="row.description" />
</td>
<td>
<input class="form-control" v-model="row.qty" number />
</td>
<td>
<input class="form-control text-right" v-model="row.price | currencyDisplay" number data-type="currency" />
</td>
<td>
<select class="form-control" v-model="row.tax">
<option value="0">0%</option>
<option value="10">10%</option>
<option value="20">20%</option>
</select>
</td>
<td>
<input class="form-control text-right" :value="row.qty * row.price | currencyDisplay" v-model="row.total | currencyDisplay"
number readonly />
<input type="hidden" :value="row.qty * row.price * row.tax / 100" v-model="row.tax_amount | currencyDisplay"
number />
</td>
<td>
<button class="btn btn-primary btn-xs" #click="addRow($index)">add row</button>
<button class="btn btn-danger btn-xs" #click="removeRow($index)">remove row</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5" class="text-right">TAX</td>
<td colspan="1" class="text-right">{{ taxtotal | currencyDisplay }}</td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right">TOTAL</td>
<td colspan="1" class="text-right">{{ total | currencyDisplay }}</td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right">DELIVERY</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery | currencyDisplay"
number /></td>
<td></td>
</tr>
<tr>
<td colspan="5" class="text-right"><strong>GRANDTOTAL</strong></td>
<td colspan="1" class="text-right"><strong>{{ grandtotal = total + delivery | currencyDisplay }}</strong></td>
<td></td>
</tr>
</tfoot>
</table>
<button #click="getData()">SUBMIT DATA</button>
<pre>{{ $data | json }}</pre>
</div>
</template>
This problem is actually a very simple problem. I don't know vue, but the render method has the same limits of react's one: every component must have only one root element in its template.
This means that a situation like this isn't accepted:
<template>
<div></div>
<div></div>
</template>
But like this is correct:
<template>
<div></div>
</template>
This means that surely, somehow in the code you didn't show us, you're putting two elements as root of your template

ng-table header select all check box

I am using ng-table for display data. i am using below code to do select all checkboxe. that select all check box insert using text/ng-template. if i use select all checkbox outside the ng-table, all functions work. but inside the ng-table with text/ng-template , functions not working.
$scope.selectAll = function() {
angular.forEach($scope.invoiceApprovalList, function(invoiceApproval) {
invoiceApproval.checkboxItem= $scope.selectedAll;
});
};
// use the array "every" function to test if ALL items are checked
$scope.checkIfAllSelected = function() {
$scope.selectedAll = $scope.invoiceApprovalList.every(function(invoiceApproval) {
return invoiceApproval.checkboxItem == true;
})
};
<script type="text/ng-template" id="all.html">
<input type="checkbox" ng-model="selectedAll" ng-click="selectAll()" />
</script>
<table ng-table="invoicetableParams" ng-show="invoicetableParams!=null" class="table scroll table-condensed table-bordered table-striped table-scroll" id="in-app">
<tbody>
<tr ng-repeat="invoiceApproval in $data">
<td header="'all.html'" class="row-center" style="width:20%">
<input class="myCheckBox" ng-model="invoiceApproval.checkboxItem" type="checkbox" ng-click="checkIfAllSelected()"/></td>
<td data-title="'Customer Name'" style="width:20%" filter="{'customerName':'text'}" sortable="'customerName'" class="row-center">
<div class="row-center">{{invoiceApproval.customerName}}</div>
</td>
<td data-title="'Invoice Number'" style="width:20%" sortable="'invoiceNumber'" class="row-center">
<div class="row-center">{{invoiceApproval.invoiceNumber}}</div>
</td>
<td data-title="'Invoice Date'" style="width:20%" class="row-center">
<div class="row-center">{{invoiceApproval.invoiceDate | date}}</div>
</td>
<!-- <td data-title="'Action'" class="row-center" style="text-align:right">
<button type="button" class="btn grid-btn row-view sm-btn" ng-click="loadCustomerInvoicePDFviewPDF(invoiceApproval.customerInvoiceHeaderID,1)">
<span class="glyphicon glyphicon-list"></span> Preview
</button>
</td> -->
</tr>
</tbody>
</table>
It does work as expected. Unfortunately you didn't show enough code to reproduce your problem. Please compare your solution and ensure your using ng-include in the right way:
View
<div ng-controller="MyCtrl">
<table ng-table="invoicetableParams" ng-show="invoicetableParams!=null" class="table scroll table-condensed table-bordered table-striped table-scroll" id="in-app">
<tbody>
<tr ng-repeat="invoiceApproval in data">
<td ng-include="'all.html'"></td>
<td data-title="'Customer Name'" style="width:20%" filter="{'customerName':'text'}" sortable="'customerName'" class="row-center">
<div class="row-center"> {{invoiceApproval.customerName}}</div>
</td>
<td data-title="'Invoice Number'" style="width:20%" sortable="'invoiceNumber'" class="row-center">
<div class="row-center">{{invoiceApproval.invoiceNumber}}</div>
</td>
<td data-title="'Invoice Date'" style="width:20%" class="row-center">
<div class="row-center">{{invoiceApproval.invoiceDate | date}}</div>
</td>
</tr>
</tbody>
</table>
</div>
<script type="text/ng-template" id="all.html">
<input type="checkbox" ng-model="invoiceApproval.checkboxItem" ng-click="selectAll()" />
</script>
AngularJS application
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', function($scope) {
$scope.selectAll = function() {
$scope.data.forEach(function(invoiceApproval) {
return invoiceApproval.checkboxItem = true;
});
}
$scope.invoicetableParams = true;
$scope.data = [{
checkboxItem: false,
customerName: 'test name 1',
invoiceNumber: 'test name 1',
invoiceDate: new Date()
},
{
checkboxItem: false,
customerName: 'test name 2',
invoiceNumber: 'test name 2',
invoiceDate: new Date()
},
{
checkboxItem: false,
customerName: 'test name 3',
invoiceNumber: 'test name 3',
invoiceDate: new Date()
},
{
checkboxItem: false,
customerName: 'test name 4',
invoiceNumber: 'test name 4',
invoiceDate: new Date()
}
];
});
> demo fiddle

Calculate sum of dynamically added values with vuejs

I am dynamically adding new row with selects and inputs in table if option is selected in first column of last row.
It looks like this https://jsfiddle.net/zgykgery/ so when you select 'Service' new row is added and total price of that row is calculated taking service price, default 10% discount and amount.
I am getting service price a bit clumsy with
:value="item.id+'|'+item.price"
so I have filter to take it from value.
But it works and now I don't know how I can loop through all rows and sum price of service without dicount, sum discount and then full price with servce price and discount.
I know I should use computed properties or watcher but don't know how.
How can I compute sum of those values in table and display them in smaller table below?
Update
I am new in vue and didn't use computed properties so this is what I tried but without success:
var Main = {
data() {
return {
rows_top: [{
service: '',
position: '',
visit: '',
amount: '',
discount: 10
}],
services: [{
id: 1,
name: 'Service1',
price: 100
},
{
id: 2,
name: 'Service2',
price: 200
},
{
id: 3,
name: 'Service3',
price: 300
},
],
total_discount: 0,
total_full_price: 0
}
},
methods: {
addRow(row, index) {
// console.log(row)
if (this.rows_top[this.rows_top.length - 1].service !== '') {
this.rows_top.push({
service: '',
position: '',
visit: '',
amount: '',
discount: 10
})
}
},
deleteRow(index) {
this.rows_top.splice(index, 1)
}
},
computed: {
total_price: () => {
if (this.rows_top) {
return this.rows_top.map((r) => {
return r.amount * this.$options.filters.after_line(r.service)
})
}
}
},
filters: {
after_line: (value) => {
if (!value) return ''
let after = value.split('|')
return after.pop()
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
#import url("//unpkg.com/element-ui#2.0.11/lib/theme-chalk/index.css");
table {
border-collapse: collapse
}
table,
th,
td {
border: 1px solid #ddd
}
th,
td {
padding: 5px
}
tr:nth-child(odd) {
background-color: #f9f9f9
}
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/element-ui#2.2.0/lib/index.js"></script>
<div id="app">
<template>
<table>
<thead>
<tr>
<th>Service</th>
<th>Position</th>
<th>Visit</th>
<th>Amount</th>
<th>Price</th>
<th>Discount %</th>
<th>Full price</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows_top">
<td>
<el-select placeholder="Select" filterable="filterable" #change="addRow(row, index)" v-model="row.service">
<el-option v-for="item in services" :key="item.id" :label="item.name" :value="item.id+'|'+item.price"></el-option>
</el-select>
</td>
<td>
<el-select placeholder="Select" v-model="row.position" multiple="multiple" filterable="filterable" allow-create="allow-create" #change="checkNumber(result, index)"></el-select>
</td>
<td>
<el-select placeholder="Select" v-model="row.visit" allow-create="allow-create" filterable="filterable">
<el-option v-for="i in 10" :key="i" :label="i" :value="i"></el-option>
</el-select>
</td>
<td>
<el-select placeholder="Select" v-model="row.amount" allow-create="allow-create" filterable="filterable">
<el-option v-for="i in 30" :key="i" :label="i" :value="i"></el-option>
</el-select>
</td>
<td>{{ row.service | after_line }}</td>
<td>
<el-input v-model="row.discount"></el-input>
</td>
<td><span v-if="row.service && row.amount">{{ ($options.filters.after_line(row.service) * row.amount - (($options.filters.after_line(row.service) * row.amount) / 100) * row.discount).toFixed(2) }}</span><span v-else-if="row.service">{{ ($options.filters.after_line(row.service) - ($options.filters.after_line(row.service) / 100) * row.discount).toFixed(2) }}</span>
<el-button v-if="row.service" icon="el-icon-delete" size="mini" #click="deleteRow(index)" class="push-right"></el-button>
</td>
</tr>
</tbody>
</table><br/>
<table>
<tr>
<td>Total price</td>
<td>{{ total_price }}</td>
</tr>
<tr>
<td>Total discount</td>
<td>{{ total_discount }}</td>
</tr>
<tr>
<td>Total full price</td>
<td>{{ total_full_price }}</td>
</tr>
</table>
</template>
</div>
Here is also updated fiddle and total price is still empty on change https://jsfiddle.net/zgykgery/21/
As #Roy J said, just use a computed function to get you the values of your calculation, knowing that it will automatically change when the data from item changes
EDIT: a simple method is more designed in the case when you use a v-for loop
methods: {
priceID (item) {
return item.id + '|' + item.price
}
}
And you would use it simply like that in your Vue
<tr v-for="(row, index) in rows_top">
<td>
<el-select placeholder="Select" filterable="filterable" #change="addRow(row, index)" v-model="row.service">
<el-option v-for="item in services" :key="item.id" :label="item.name" :value="priceID(item)"></el-option>
</el-select>
</td>
...

Categories