i have project working with vuejs2
this is my html code
<tr v-for="arrayresult , key in arrayresults">
<td>#{{ arrayresult.item_desc_ar}}</td>
<td><input class='form-control quantity' type='text' #input='changeQuantity()' v-model='quantity'/></td>
<td>#{{ arrayresult.item_smallest_unit_selling_price}}</td>
<td><a class='fa fa-trash' #click='deleteItem(arrayresult.id,key)'></a></td>
</tr>
and this is my vuejs2 code
data:{
message:'',
item_search_array:false,
arrayresults:[],
total:0,
quantity:1,
},
methods:{
changeQuantity:function()
{
},
deleteItem:function(key)
{
this.arrayresults.splice(key, 1);
}
}
now i have this method called changeQuantity i need when keyup the input with model name quantity send the value and the key index to the method changeQuantity my problem thats they are many input with same model name quantity thanks
You need to use object property as v-model for each input.
<input ... v-model="quantities[input_id_iterator]" />
Do not forget to define quantities object in data.
Think of each item in the arrayresults array as a model, then in your input, you update the particular model model='arrayresult.qty'.
You then can use computed properties to get the totals.
For example:
//
var vm = new Vue({
el: '#app',
computed: {
totalQty: function () {
var total = 0;
this.arrayresults.forEach(item => {
total += Number(item.qty);
})
return total
},
totalPrice: function () {
var total = 0;
this.arrayresults.forEach(item => {
total += Number(item.item_smallest_unit_selling_price * item.qty);
})
return total
}
},
data() {
return {
arrayresults:[
{id: 1, item_desc_ar: 'A', item_smallest_unit_selling_price: 5, qty:1},
{id: 2, item_desc_ar: 'B', item_smallest_unit_selling_price: 10, qty:1},
{id: 3, item_desc_ar: 'C', item_smallest_unit_selling_price: 15, qty:1},
{id: 4, item_desc_ar: 'D', item_smallest_unit_selling_price: 20, qty:1},
]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<div id="app">
Total Qty: {{totalQty}}<br>
Total Price: {{totalPrice}}
<table>
<tr v-for="arrayresult , key in arrayresults">
<td>#{{ arrayresult.item_desc_ar}}</td>
<td><input class='form-control quantity' type='text' v-model='arrayresult.qty'/></td>
<td>#{{ arrayresult.item_smallest_unit_selling_price}}</td>
<td><a class='fa fa-trash' #click='deleteItem(arrayresult.id,key)'></a></td>
</tr>
</table>
</div>
p.s: also try avoid item_, if you think of each model in an items array as an item you dont need to include item in the property name.
Related
Have been task to have a look at a Vue app (gulp....react dev here) for a colleague that left the company. However it is built with a string that renders the table data in a for loop such as this:
var table = "";
for (var i = 0; i < companies.length; i++) {
table += "<tr>";
table += `<td id="checkUpdateBox"><input onClick=${this.updateSomeCompanies(companies[i].id)}
type="checkbox"/></td>`;
table += `<td>${companies[i].id}</td>`;
table += `<td>${companies[i].name}</td>`;
table += `<td>${companies[i].custom_fields.seno}</td>`;
table += `<td>${companies[i].created_at}</td>`;
table += "</tr>";
}
methods: {
updateSomeCompanies(id) {
console.log(id);
},
// next method and so on
Am trying to do an update based on the id of the company. Right now, I am just console.logging the id on the method updateSomeCompanies.
However, when the page is loading I am getting the id of the company in the console even though the checkbox haven't been clicked, how come it works like this, is the strange string loop that build the tabledata or am something strange in the code above?
Furthermore, when clicking the checkbox nothing happens?
Have tried with the v-on:clicked and the #Click (same results as above).
Console.logs all of the id's then checkbox do not work.
Suggestions :
As you are using Vue.js then no need to use Vanilla JS to create a table. You can do it dynamically in the template itself.
Use #click/v-on:click directive of Vue to trigger the click event instead of JS onClick.
Live Demo :
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!',
companies: [{
id: 1,
name: 'Alpha',
created_at: 'test'
}, {
id: 2,
name: 'Beta',
created_at: 'test'
}, {
id: 3,
name: 'Gamma',
created_at: 'test'
}]
},
methods: {
updateSomeCompanies(id) {
console.log(id);
}
}
})
table, th, td {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tr>
<th></th>
<th>ID</th>
<th>Name</th>
<th>Created At</th>
</tr>
<tr v-for="company in companies" :key="company.id">
<td id="checkUpdateBox">
<input v-on:click="updateSomeCompanies(company.id)" type="checkbox"/>
</td>
<td>{{ company.id }}</td>
<td>{{ company.name }}</td>
<td>{{ company.created_at }}</td>
</tr>
</table>
</div>
I'm starting with Vue.js and I don't know how can I calculate the partial value and total value inside a v-for display.
I'm getting some info from a JSON with the next structure:
[saldos]
[bank]
[data]
[account]
- name
- balance
[account]
- name
- balance
[meta]
- info
[bank]
[data]
[account]
- name
- balance
[account]
- name
- balance
[meta]
- info
Each bank could be 0 accounts, 1 account or more accounts.
I need to get the partial value of each bank (it is the sum of all accounts 'balance' inside the same bank) and the total value (it is the sum of all partial values previously calculated for each bank)
My Vue.js file is:
var saldo = new Vue({
el: "#listasaldos",
data: {
saldos:[],
},
created: function(){
console.log("Cargando ...");
this.get_saldos();
},
methods:{
get_saldos: function(){
fetch("./api.php?action=saldosapi")
.then(response=>response.json())
.then(json=>{this.saldos=json.saldos})
}
}
});
And my HTML file is:
<div id="listasaldos">
<h1>Title</h1>
<h2>{{totalValue}}</h2>
<div v-for="bank in saldos">
<h3>{{partialValue}}</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Balance</th>
</tr>
</thead>
<tbody v-for="account in bank.data">
<tr> {{account.name}}</tr>
<tr> {{account.balance}}</tr>
</tbody>
</table>
</div>
</div>
How can I do it?
Thanks!
For the saldos total, you could add a computed property and use reduce to calculate the total:
computed: {
totalValue() {
return this.saldos.map(({data}) => data).flat().reduce((a, b) => a + b.balance, 0);
}
}
I'm not familiar with how one would go about adding vue computed properties inside a loop. According to this answer you either have to create a component for each bank or add a function called getBankTotal inside methods:
getBankTotal: function(bank){
return bank.data.reduce((a, b) => a + b.balance, 0)
}
And use it in your HTML:
<h3>{{getBankTotal(bank)}}</h3>
Here's a working snippet:
(I have not used vue before. So, correct me if any syntax or pattern is wrong)
var saldo = new Vue({
el: "#listasaldos",
data: {
saldos: [],
},
created: function() {
this.get_saldos();
},
methods: {
getBankTotal: function(bank) {
return bank.data.reduce((a, b) => a + b.balance, 0)
},
get_saldos: function() {
this.saldos = [{
data: [{
name: "a/c 1",
balance: 100
}]
}, {
data: [{
name: "a/c 2",
balance: 300
}, {
name: "a/c 3",
balance: 400
}]
}]
}
},
computed: {
totalValue() {
return this.saldos.map(({data}) => data).flat().reduce((a, b) => a + b.balance, 0);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="listasaldos">
<h1>Title</h1>
<h2>Total Value: {{totalValue}}</h2>
<div v-for="bank in saldos">
<h3>Partial Value: {{getBankTotal(bank)}}</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<tr v-for="account in bank.data">
<td>{{account.name}}</td>
<td>{{account.balance}}</td>
</tr>
</tbody>
</table>
<hr>
</div>
</div>
I am trying to get specific values from an observablearray. In this example I'm trying to get the number of two values I have created in a array.
I have set the code up like this:
HTML
<div id="test" style="width: 100%">
<table style=" width: 50%; display: block; float: left; margin: 0; padding: 0; border: none">
<tr>
<td>Item 1</td>
<td><span data-bind="text: someArray()[0].number"></span></td>
</tr>
<tr>
<td>Item 2</td>
<td><span data-bind="text: someArray()[1].number"></span></td>
</tr>
</table>
</div>
JavaScript
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js" type="text/javascript"></script>
<script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>
<script type="text/javascript">
var viewModel = {
someArray: ko.observableArray([]) //
}
ko.applyBindings(viewModel);
$(function () {
getData();
});
var getData = function () {
viewModel.someArray([
{
number: 123,
text: "abc"
},
{
number: 456,
text: "def",
}
]);
}
</script>
I have also uploaded it to JSfiddle.
How can I access specific positions in the array?
The following works:
var viewModel = {
someArray: ko.observableArray([]) //
}
var getData = function () {
viewModel.someArray.push(
{number: 123,text: "abc"},
{number: 456,text: "def"},
{number: 789,text: "ghi"}
);
}
getData();
ko.applyBindings(viewModel);
You were pushing an array into viewModel.someArray rather than 3 separate objects. You also have to apply the bindings after inserting the array objects or else you will get a cannot parse binding exception when there are no rows in someArray.
You might consider using the foreach binding to better fit your needs Knockout Foreach Binding. Then you can call applyBindings anytime. If you're going to have other rows in the array that you don't want, create a computed observable array out of the indices that you do want and build a foreach binding around that.
Your fiddle doesn't work for a few reasons, the biggest being that you're trying to use JQuery, but it's not loaded into the page.
Here's the modified javascript that works:
var viewModel = {
someArray: ko.observableArray([]) //
}
var getData = function () {
viewModel.someArray.push(
{
number: 123,
text: "abc"
});
viewModel.someArray.push(
{
number: 456,
text: "def",
});
viewModel.someArray.push(
{
number: 789,
text: "ghi",
});
}
getData();
ko.applyBindings(viewModel);
When a user clicks on a row from a list in the UI (html table tblAllCert), I have a click event that fires to populate another observable array that should populate a second html table (tblCertDetails). My click event fires, I can see data come back, and i can see data in the observable array, but my view does not update.
Here is an overview of the sequence in the code-
1. user clicks on row from html table tblAllCert, which fires selectThing method.
2. In viewmodel code, selectThing passes the row data from the row selected to the GetCertificateDetails(row) method.
3. GetCertificateDetails(row) method calls getCertDetails(CertificateDetails, source) function on my data service (the data service gets data from a web api). getCertDetails(CertificateDetails, source) function returns an observable array.
4. once the data is returned to selectThing, CertificateDetails observable array is populated. It does get data to this point.
Step 5 should be updating the UI (specifically tblCertDetails html table), however the UI does not get updated.
Here is my view-
<table id="tblAllCert" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: allCertificates">
<tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
<td>
<ul style="width: 100%">
<b><span data-bind=" text: clientName"></span> (<span data-bind=" text: clientNumber"></span>) <span data-bind=" text: borrowBaseCount"></span> Loan(s) </b>
<br />
Collateral Analyst: <span data-bind=" text: userName"></span>
<br />
Certificate: <span data-bind="text: lwCertID"></span> Request Date: <span data-bind=" text: moment(requestDate).format('DD/MMM/YYYY')"></span>
</td>
</tr>
</tbody>
</table>
<table id="tblCertDetails" border="0" class="table table-hover" width="100%">
<tbody data-bind="foreach: CertificateDetails">
<tr id="Tr1" style="cursor: pointer">
<td>
<ul style="width: 100%">
<b>Loan: <span data-bind=" text: loanNum"></span>
</td>
</tr>
</tbody>
</table>
My viewmodel-
define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
var allCertificates = ko.observableArray([]);
var myCertificates = ko.observableArray([]);
var isSelected = ko.observable();
var serverSelectedOptionID = ko.observable();
var CertificateDetails = ko.observableArray([]);
var serverOptions = [
{ id: 1, name: 'Certificate', OptionText: 'lwCertID' },
{ id: 2, name: 'Client Name', OptionText: 'clientName' },
{ id: 3, name: 'Client Number', OptionText: 'clientNumber' },
{ id: 4, name: 'Request Date', OptionText: 'requestDate' },
{ id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
];
var activate = function () {
// go get local data, if we have it
return SelectAllCerts(), SelectMyCerts();
};
var vm = {
activate: activate,
allCertificates: allCertificates,
myCertificates: myCertificates,
CertificateDetails: CertificateDetails,
title: 'Certificate Approvals',
SelectMyCerts: SelectMyCerts,
SelectAllCerts: SelectAllCerts,
theOptionId: ko.observable(1),
serverOptions: serverOptions,
serverSelectedOptionID: serverSelectedOptionID,
SortUpDownAllCerts: SortUpDownAllCerts,
isSelected: isSelected,
selectThing: function (row, event) {
CertificateDetails = GetCertificateDetails(row);
isSelected(row.lwCertID);
}
};
serverSelectedOptionID.subscribe(function () {
var sortCriteriaID = serverSelectedOptionID();
allCertificates.sort(function (a, b) {
var fieldname = serverOptions[sortCriteriaID - 1].OptionText;
if (a[fieldname] == b[fieldname]) {
return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
}
return a[fieldname] > b[fieldname] ? 1 : -1;
});
});
return vm;
function GetCertificateDetails(row) {
var source = {
'lwCertID': row.lwCertID,
'certType': row.certType,
}
return CertificateDataService.getCertDetails(CertificateDetails, source);
}
function SortUpDownAllCerts() {
allCertificates.sort();
}
function SelectAllCerts() {
return CertificateDataService.getallCertificates(allCertificates);
}
function SelectMyCerts() {
return CertificateDataService.getMyCertificates(myCertificates);
}
});
And releveant excerpt from my data service-
var getCertDetails = function (certificateDetailsObservable, source) {
var dataObservableArray = ko.observableArray([]);
$.ajax({
type: "POST",
dataType: "json",
url: "/api/caapproval/CertDtlsByID/",
data: source,
async: false,
success: function (dataIn) {
ko.mapping.fromJSON(dataIn, {}, dataObservableArray);
},
error: function (error) {
jsonValue = jQuery.parseJSON(error.responseText);
//jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
}
});
return dataObservableArray;
}
Ideas of why the UI is not updating?
When you're using Knockout arrays, you should be changing the contents of the array rather than the reference to the array itself. In selectThing, when you set
CertificateDetails = GetCertificateDetails(row);
the new observableArray that's been assigned to CertificateDetails isn't the same observableArray that KO bound to your table.
You're very close to a solution, though. In your AJAX success callback, instead of creating a new dataObservableArray and mapping into that, you can perform the mapping directly into parameter certificateDetailsObservable. ko.mapping.fromJSON should replace the contents of the table-bound array (you would also get rid of the assignment in selectThing).
I'm looking for a way to add rows to a table. My data structure looks like that:
rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
I want to create a table which looks like that:
table
row1
row1.1
row1.2
row2
Is that possible with angular js ng-repeat? If not, what would be a "angular" way of doing that?
Edit:
Flatten the array would be a bad solution because if i can iterate over the sub elements i could use different html tags inside the cells, other css classes, etc.
More than one year later but found a workaround, at least for two levels (fathers->sons).
Just repeat tbody's:
<table>
<tbody ng-repeat="row in rows">
<tr>
<th>{{row.name}}</th>
</tr>
<tr ng-repeat="sub in row.subrows">
<td>{{sub.name}}</td>
</tr>
</tbody>
</table>
As far as I know all browsers support multiple tbody elements inside a table.
More than 3 years later, I have been facing the same issue, and before writing down a directive I tried this out, and it worked well for me :
<table>
<tbody>
<tr ng-repeat-start="row in rows">
<td>
{{ row.name }}
</td>
</tr>
<tr ng-repeat-end ng-repeat="subrow in row.subrows">
<td>
{{ subrow.name }}
</td>
</tr>
</tbody>
</table>
You won't be able to do this with ng-repeat. You can do it with a directive, however.
<my-table rows='rows'></my-table>
Fiddle.
myApp.directive('myTable', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var html = '<table>';
angular.forEach(scope[attrs.rows], function (row, index) {
html += '<tr><td>' + row.name + '</td></tr>';
if ('subrows' in row) {
angular.forEach(row.subrows, function (subrow, index) {
html += '<tr><td>' + subrow.name + '</td></tr>';
});
}
});
html += '</table>';
element.replaceWith(html)
}
}
});
I'm a bit surprised that so many are advocating custom directives and creating proxy variables being updated by $watch.
Problems like this are the reason that AngularJS filters were made!
From the docs:
A filter formats the value of an expression for display to the user.
We aren't looking to manipulate the data, just format it for display in a different way. So let's make a filter that takes in our rows array, flattens it, and returns the flattened rows.
.filter('flattenRows', function(){
return function(rows) {
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
return flatten;
}
})
Now all we need is to add the filter to ngRepeat:
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Rows with filter</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows | flattenRows">
<td>{{row.name}}</td>
</tr>
</tbody>
</table>
You are now free to combine your table with other filters if desired, like a search.
While the multiple tbody approach is handy, and valid, it will mess up any css that relies on the order or index of child rows, such as a "striped" table and also makes the assumption that you haven't styled your tbody in a way that you don't want repeated.
Here's a plunk: http://embed.plnkr.co/otjeQv7z0rifPusneJ0F/preview
Edit:I added a subrow value and used it in the table to show which rows are subrows, as you indicated a concern for being able to do that.
Yes, it's possible:
Controller:
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
}
]
);
HTML:
<table>
<tr ng-repeat="row in rows">
<td>
{{row.name}}
<table ng-show="row.subrows">
<tr ng-repeat="subrow in row.subrows">
<td>{{subrow.name}}</td>
</tr>
</table>
</td>
</tr>
</table>
Plunker
In case you don't want sub-tables, flatten the rows (while annotating subrows, to be able to differentiate):
Controller:
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
$scope.flatten = [];
var subrows;
$scope.$watch('rows', function(rows){
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
delete row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
$scope.flatten = flatten;
});
}
HTML:
<table>
<tr ng-repeat="row in flatten">
<td>
{{row.name}}
</td>
</tr>
</table>
Plunker
Here is an example. This code prints all names of all the people within the peopeByCity array.
TS:
export class AppComponent {
peopleByCity = [
{
city: 'Miami',
people: [
{
name: 'John', age: 12
}, {
name: 'Angel', age: 22
}
]
}, {
city: 'Sao Paulo',
people: [
{
name: 'Anderson', age: 35
}, {
name: 'Felipe', age: 36
}
]
}
]
}
HTML:
<div *ngFor="let personsByCity of peopleByCity">
<div *ngFor="let person of personsByCity.people">
{{ person.name }}
</div>
</div>