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>
Related
How can I write a function that takes data from a dropdown, and fills in the table row containing that dropdown?
HTML:
<table id="DataTable" border="1" ALIGN="center">
<tr ALIGN="center">
<th>name</th>
<th>add</th>
<th>number</th>
<th>type</th>
</tr>
<tr class="tcat" *ngFor="let item of Tdata">
<td class="name">{{item.name}}</td>
<td class="add">{{item.add}}</td>
<td class="nyumber">{{item.number}}</td>
<td class="type" ALIGN="center">
<select *ngIf="dropData" (click)="jsFunction(item.number);">
<option>--Select--</option>
<option *ngFor="let currentData of dropData">
{{currentData.type}}</option>
</select>
</td>
</tr>
</table>
TS:
ngOnInit() {
// called first api and filled the data
this.Tdata=[
{ name: "xyz"
add: "abc"
number: 12345
type: null },
{ name: "xyz1"
add: "abc1"
number: 78900
type: null },
]
}
jsFunction(num){
// calling the second API here based of the given parameter and that
// is num.. for example I have clicked the dropdown on the first row
// which has number=12345 so in dropdown, dropData will fill the
// value type and that's not happening in my case
this.dropData=[
{number: "12345"
type: "customer"},
{number: "12345"
type: "dealer"},
{number: "12345"
type: "client"},
{number: "12345"
type: "master"},
]
}
whats should happen here is on first API call I will fill the table note that the type is null and in Html there is dropdown so after I click on any dropdown so there I will call the second API and pass that clicked row number in it and after second API call that clicked dropdown should fill data which I get in response so that I can select one-off that data.
I have explained my situation. please ask me if you don't get the question. searched on google but couldn't find anything related to this. there must be something that I missed or I don't know about that
thank you so much.
Try like this:
Template:
Bind (click) event in tr>:
<tr class="tcat" *ngFor="let item of Tdata" (click)="jsFunction(item.number);">
</tr>
TS:
jsFunction(num){
this.dropData= this.Tdata.filter(x => x.number == num)
}
Working Demo
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>
Is it possible to apply filters to select values programmatically? As you can see, I am iterating each record, and then each column. I am checking that the column.id starts with d, which means it's a datetime, so I'd like to apply the humanize filter to only that field. The result of the code you see here is NaN on every field.
<tr v-for="record in records" #dblClick="readRecord(record)">
<td v-for="column in columns">
{{ _.startsWith(record[column.id], 'd') ? record[column.id] | humanize : column.id; }}
</td>
</tr>
That's getting kind of code-thick for the view. It would be better to make a method that does that (particularly since filters are deprecated -- [update] only filters in directives are deprecated).
new Vue({
el: '#app',
data: {
columns: [{
id: '1'
}, {
id: '2'
}],
record: {
2: 'dthing'
}
},
methods: {
displayCol: function(col) {
if (s.startsWith(this.record[col.id], 'd')) {
return this.humanize(this.record[col.id]);
}
return col.id;
},
humanize: function(recVal) {
return `HUM${recVal}`;
}
}
});
<!--script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script-->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<div id="app">
<div v-for="column in columns">
{{displayCol(column)}}
</div>
</div>
You can call your filter in here: this.$options.filters.humanize
In following table there can be more than one list on particular date. So For list column there can be more than one list for given date.
I am able to insert more than one value in single cell but it shift the row in which I inserted more than one value, Please look at DEMO.
Example: Table
Date...............List
12/1/2016 .... python, angularjs
13/1/2016..... java, html
data:
$scope.todolists = [{
date: '12/1/2016',
list: {python, angularjs}
}, {
date: '13/1/2016',
list: {java, html}
}];
view:
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist">{{subject}}</td>
</tr>
</tbody>
I tried ng-repeat inside ng-repeat but it is not working. So my question is how to insert more than one value in single cell in table.
Your references are wrong.
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist.list">{{list}}</td>
</tr>
</tbody>
or, preferably,
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td>
<span ng-repeat="list in todolist.list">
{{list + ($last ? "" : ", ") }}
</span>
</td>
</tr>
</tbody>
Your JSON is invalid. If you want to store values without properties, you need to use Array but not Object. Also, if the values are string you need to wrap them with comma like: ['val1', 'val2', ...];
You can do ng-repeat inside ng-repeat but you need to iterate the right property todolist.list.
You can't use {{subject}} if you have not property subject in you model. So you need to use {{list}} when you do the ng-repeat like ng-repeat="list in todolist.list".
The full code:
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.todolists = [{ date: '12/1/2016', list: ['python', 'angularjs'] }, { date: '13/1/2016', list: ['java', 'html'] }];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<table>
<tr ng-repeat="todolist in todolists">
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist.list">{{list}}</td>
</tr>
</table>
</div>
Your datas are not properly declared. It should be:
$scope.todolists = [{
date: '12/1/2016',
list: [
'python',
'angularjs'
]
}, {
date: '13/1/2016',
list: [
'java',
'html'
]
}];
Note the '' around python, etc. and the list should be a list => [] and not {}.
A nice way to go is to write a custom filter:
angular.module("myApp", [])
.filter('nicelist', function() {
return function(input) {
if (input instanceof Array) {
return input.join(",");
}
return input;
}
});
Then, you can use :
<table>
<tr ng-repeat="todolist in todolists">
<td>{{todolist.date}}</td>
<td>{{todolist.list | nicelist}}</td>
</tr>
</table>
Here is a working fiddle.
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>