Dynamic columns with angular datatables - angularjs

So I try to generate columns in datatables based on response from api request.
$scope.getProductCustomFields = function() {
$scope.custom_fields_loading = true;
$scope.dtCustomFieldsInstance = {};
$scope.dtCustomFieldsOptions = DTOptionsBuilder.newOptions().withOption('order', []);
$scope.dtCustomFieldsOptions.withOption('ajax', {
headers: {'Authorization': 'Basic ' + $rootScope.globals.currentUser.api_token},
dataSrc: 'data',
url: API.returnUrl('/ecommerce/reports/get-product-custom-fields?' + $httpParamSerializer({product: $scope.product})),
type: 'GET'
})
.withOption('processing', true)
.withOption('serverSide', true)
.withPaginationType('full_numbers')
.withOption('createdRow', createdRow);
function createdRow(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
}
$scope.dtCustomFieldsColumns = [];
//Here I make another request to php within this function since I cannot actually use dataSrc: 'data' as array
ProductsReportsService.getProductCustomFields($scope.product).then(function (response) {
$scope.data = response.data.data;
angular.forEach($scope.data, function (value, key) {
$scope.dtCustomFieldsColumns.push(DTColumnBuilder.newColumn('value.value').withTitle(key).notSortable());
});
});
$scope.custom_fields_loading = false;
};
As you can see I make two requests, a ajax one whose data is not accessible and another one before which I have commented, that I use for my forEach.
data looks like this:
array:1 [
"test drop down" => array:2 [
0 => array:4 [
"id" => 1
"label" => "test drop down"
"value" => "test1"
"name" => "test drop down"
]
1 => array:4 [
"id" => 1
"label" => "test drop down"
"value" => "test2"
"name" => "test drop down"
]
]
So to put it simple what I try to accomplish is table that basically looks like this:
<table>
<thead>
<tr>
<th>test drop down</th>
</tr>
</thead>
<tbody>
<tr>
<td>test1</td>
</tr>
<tr>
<td>test2</td>
</tr>
</tbody>
</table>
Right now my table only has the headers right but I have no data in table body.
<table>
<thead>
<tr>
<th>test drop down</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
</tr>
<tr>
<td></td>
</tr>
</tbody>
</table>
Thank you for your time and help!

Not sure is this is what you're looking for, but displaying a table like that from given data set (assuming it's saved in $scope.dtCustomFieldsColumns) would be something like this:
<tbody>
<tr ng-repeat="column in dtCustomFieldsColumns.testdropdown">
<td>{{column.value}}</td>
</tr>
</tbody>
You should change test drop down key in your data set to something like testDropDown. That'll help you access columns directly. Good luck.

Related

Call service for each element found from another service and then populate an array with results

Problem:
Trying to populate a html table with two services. First I'm calling a service to retrieve all products. Then for each element I'm calling another service that accepts the fabricplanid as a parameter and returns the object if exists. If exists I push it to an array, but if returns an error 404 I push a string to the same array.
The problem I'm facing is that the values inside the array, don't match the corresponding fabricPlanId of the product.
This is the product.component.ts file that when called execute this service and populates a table using a ngfor.
products:Product[];
//view
fabricplan: Fabricplan[];
plan_desc: Array<String> = [];
//view select
fabricplans: Fabricplan[];
ngOnInit() {
this.productservice.getProducts().subscribe(products => {
this.products = products;
console.log("Produtos", this.products);
this.products.forEach( element => {
console.log(element.fabricPlanId);
this.fabricplanservice.getfabricplan(element.fabricPlanId).subscribe(
(response) => {
this.fabricplan = response;
this.plan_desc.push(this.fabricplan['description']);
},
(error) => {
if(error.status === 404){
this.plan_desc.push('No fabric plan');
}
});
});
});
console.log("Planos de fabrico", this.plan_desc);
}
The product.component.html file
<table class="table table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Number</th>
<th scope="col">Fabric Plan</th>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Price €</th>
<th scope="col">Ative</th>
</tr>
</thead>
<tbody>
<tr scope="row" id="table" *ngFor="let product of products let i = index"(click)="open(content, product.id)">
<td>{{ i+1 }}</td>
<td>{{ plan_desc[i] }}</td>
<td>{{ product?.name }}</td>
<td>{{ product?.description }}</td>
<td>{{ product?.price }}</td>
<td>{{ product?.active }}</td>
</tr>
</tbody>
</table>
Response body
Products
{
active: true,
description: " descrição do prod 1",
fabricPlanId: 1,
id: 1,
name: "Produto 1",
price: 1
}
FabricPlans
{
dateStart: "2019-10-30T00:00:00"
description: "Descrição do plano 1"
id: 1
operationsIds: [1, 2]
}
Based on your comment, the plan_desc are in a random order. The cause for this is
this.products.forEach( element => {
this.fabricplanservice.getfabricplan(element.fabricPlanId).subscribe(
...
you cannot control how long each request will take, so some will return sooner, some later and whenever they return, they are added to the list --> the order is random.
However it is pretty easy to make a request for each item and then get the list order with rxjs forkjoin
// create a request for each product (dont fire it yet)
const plans_desc$ = this.products.map( element =>
this.fabricplanservice.getfabricplan(element.fabricPlanId).pipe(
// map it to the value you want
map((response) => {
this.fabricplan = response;
return this.fabricplan['description'];
}),
// replace 404 responses with the 'No fabric plan'
// if not 404 throw the error again
catchError((error) => {
if(error.status === 404){
return 'No fabric plan';
} else {
throwError(error);
}
}));
});
// now make the actuall requests. Forkjoin will return, when all requests are completed.
// the order will be the be how the requests where added, not when the completed
forkJoin(plans_desc$).subscribe((plans_desc) => this.plan_desc = plans_desc);
(I wrote his code here)
imports:
import {forkJoin, throwError} from 'rxjs';
import {map, catchError} from 'rxjs/operators';

Laravel vue array in array issue

I'm trying to get data from 2 database tables and return all in 1 table body in my component.
code
controller
public function show($id)
{
$history = Payment::where('account_id', $id)->with('account')->orderby('id', 'desc')->get();
$balance = Account::where('id' , $id)->select('balance')->first();
return response()->json([
$history,$balance
]);
}
component
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th class="text-center">#</th>
<th class="text-center">Date</th>
<th class="text-center">Amount</th>
<th class="text-center">Note</th>
</tr>
</thead>
<tbody>
<tr v-for="(history,index) in histories" #key="index">
<td width="50" class="text-center">{{index+1}}</td>
<td class="text-center" width="100">
{{history.created_at}}
</td>
<td class="text-center" width="300">Rp. {{ formatPrice(history.balance) }}</td>
<td class="text-center">{{history.note}}</td>
</tr>
</tbody>
</table>
export default {
data() {
return {
histories : []
}
},
beforeMount(){
let user_id = this.user.id;
axios.get('/api/account/'+user_id).then(response => this.histories = response.data)
},
// rest of it
</script>
In image above
I get my balance from Account table in histories array while I get histories from Payment table in histories array.
What I want is to take out histories array and join it under histories
right where there balance data is.
So later I can have something like:
histories: array[3]
0:...
1:...
2:...
How can I do that?
Update
I've made changes in my controller and now data result become as I wanted (all in one array) but somehow it doesn't return all data.
code
public function show($id)
{
// $history = Payment::where('account_id', $id)->with('account')->orderby('id', 'desc')->get();
// $balance = Account::where('id' , $id)->select('balance')->first();
$history = DB::table('payments')
->where('account_id', $id)
->join('accounts', 'accounts.id', '=', 'payments.account_id')
->get();
return response()->json($history, 200);
}
It supposed to be 3 but it only returns 2.
Solved
I used push method and now it's working as i wanted. Here is last code for those in need.
public function show($id)
{
$his = Payment::where('account_id', $id)->with('account')->orderby('id', 'desc')->get();
$balance = Account::where('id' , $id)->select('balance')->first();
$history = $his->push($balance);
return response()->json($history, 200);
}
Hope it helps.

How to sum in each row the values of before rows in specific column?

I've simulated my problem in this fiddle.
I have this HTML:
<table ng-app='Payments'>
<thead>
<tr>
<th>Date</th> <th>Cash</th> <th>Total</th>
</tr>
</thead>
<tbody ng-controller='paymentsController'>
<tr ng-repeat='pay in payments | orderBy : "date"'>
<td>{{pay.date}}</td>
<td><input type="textbox" ng-model="pay.cash"/></td>
<td></td>
</tr>
</tbody>
</table
And this JS:
var appModule = angular.module('Payments', []);
appModule.controller('paymentsController', function($scope) {
$scope.payments = [
{'id' : '1', 'date' : '2015-07-27', 'cash' : '149.98'},
{'id' : '2', 'date' : '2015-07-29', 'cash' : '70.00'},
{'id' : '3', 'date' : '2015-07-27', 'cash' : '129.99'},
{'id' : '4', 'date' : '2015-07-28', 'cash' : '349.90'}
];
});
How do I fill the third column with Angular?
The third column should be initially:
149.98 // due to 0.00 + 149.98
279.97 // due to 149.98 + 129.99
629.87 // due to 279.97 + 349.90
699.87 // due to 629.87 + 70.00
Then, ng-model should do the trick to update them automatically later.
Thanks in advance.
You could add a function to the scope to calculate the total at that index. You have to keep in mind that you are using order by which means you should us the as syntax to calculate the value from the ordered list.
CalculatePay function:
$scope.calculatePay = function(index) {
var sum = 0;
for(var i = 0; i <= index; i++) {
sum += parseFloat($scope.orderedList[i].cash);
}
return sum;
};
html:
<table ng-app='Payments'>
<thead>
<tr>
<th>Date</th> <th>Cash</th> <th>Total</th>
</tr>
</thead>
<tbody ng-controller='paymentsController'>
<tr ng-repeat="pay in payments | orderBy: 'date' as orderedList track by pay.id">
<td>{{pay.date}}</td>
<td><input type="textbox" ng-model="pay.cash"/></td>
<td>{{calculatePay($index)}}</td>
</tr>
</tbody>
</table>
track by is also helpful if the id is truely unique
Example: http://plnkr.co/edit/igBGY1h5RIKMNkncxhp6?p=preview
You would need to handle sorting in code, as the orderBy in ng-repeat creates a new list used for the display, and doesn't modify the original list. This would mean that the indexes of items in the display list don't match up with that in the original list. You'll also need a watcher on the payments collection to automatically update the totals at each position.
Something like
$scope.$watch('payments', function(newPayments) {
$scope.payments = orderByFilter($scope.payments, "date");
$scope.total = 0;
angular.forEach($scope.payments, function(payment) {
$scope.total += parseFloat(payment.cash);
payment.total = $scope.total;
});
}, true);
Fiddle: http://jsfiddle.net/29mh8bfe/4/

Angular object property value change not propagated into view using ng-repeat

I'm trying to generate a table using ng-repeat.
Use case
The data to generate the table from looks as follows:
$scope.data = [
{
name : 'foo1',
group : 1
},
{
name : 'foo2',
group : 1
},
{
name : 'foo3',
group : 1
},
{
name : 'foo4',
group : 1
},
{
name : 'foobar',
group : 2
},
{
name : 'foobarbar',
group : 3
}
];
The html generated should look like this:
<tr>
<th>Group</th>
<th>Name</th>
</tr>
<tr>
<td rowspan="4">1</td>
<td>foo1</td>
</tr>
<tr>
<td>foo2</td>
</tr>
<tr>
<td>foo3</td>
</tr>
<tr>
<td>foo4</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobar</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobarbar</td>
</tr>
Implementation
I know the easiest way would probably be to pre-process the data and group the items per group in a new array of arrays. However, I chose a different approach:
<td
ng-if = "isDifferentFromPrev(items, $index, groupingData)"
rowspan = "{{item._groupSize}}"
>
with
$scope.isDifferentFromPrev = function(array, index, groupingData){
if(index === 0){
groupingData.startI = 0;
groupingData.counter = 1;
array[0]._groupSize = 1;
return true;
}
var eq = equalsMethod(array[index], array[index-1]);
if(eq){
groupingData.counter++;
array[groupingData.startI]._groupSize = groupingData.counter;
}
else{
groupingData.startI = index;
groupingData.counter = 1;
array[index]._groupSize = 1;
}
return !eq;
};
Problem
For some reason the rendered value for rowspan is always 1.
The attribute is only set for the first td of the first tr of a group, as intended, but the value for it is 1.
If I put a breakpoint inside isDifferentFromPrev(), the values seem to be updated correctly. This does not reflect in the html though.
Solution?
It occured to me that maybe ng-repeat renders each step sequentially, without returning to it. So maybe the _groupSize values for the first item of each group do get properly updated, but since they are updated after that item has already been rendered by ng-repeat, the update isn't processed anymore.
I have no idea if this reasoning is correct, nor about how to solve it. Any suggestions please?
This solution, even if a bit orthodox, does work:
var app = angular.module("myApp", []);
app.controller("myController", function($scope) {
$scope.data = [{
name: 'foo1',
group: 1
}, {
name: 'foo2',
group: 1
}, {
name: 'foo3',
group: 1
}, {
name: 'foo4',
group: 1
}, {
name: 'foobar',
group: 2
}, {
name: 'foobarbar',
group: 3
}];
$scope.itemHasRowspan = function(item) {
return typeof item === "object" && item.hasOwnProperty("rowspan");
};
var groupData = {},
currentGroup = null,
addGroup = function(firstItem) {
currentGroup = firstItem.group;
groupData[firstItem.group] = {
"firstItem": firstItem,
"count": 1
};
};
angular.forEach($scope.data, function(item, index) {
if (item.group !== currentGroup) {
addGroup(item);
} else {
groupData[item.group].count++;
}
});
angular.forEach(groupData, function(group, index) {
group.firstItem["rowspan"] = group.count;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Group</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-if="itemHasRowspan(item)" rowspan="{{ item.rowspan }}" valign="top">
{{ item.group }}
</td>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

AngularJS orderby with array of arrays and separate keys

I have tabular data that I'm returning from the server in the form of an array of arrays for the data, and an array of keys associated with that data. Then, I want to sort by a particular key. Now, I know I can pre-process the data and zip together an array of objects, but say I don't want to do that. Is there an easy, built-in way to do this?
Some code that doesn't actually sort but does display the data. CodePen.
JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
this.data = [
[ 'lol', 'wut' ],
[ '123', 'abc' ]
];
this.predicate = '';
});
HTML:
<table ng-app="helloworld" ng-controller="TestController as test">
<thead>
<tr>
<th ng-repeat="heading in test.headers" ng-click="test.predicate = heading">{{ heading }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>Predicate:</td>
<td>{{ test.predicate }}</td>
</tr>
<tr ng-repeat="row in test.data | orderBy: test.predicate">
<td ng-repeat="column in row">{{ column }}</td>
</tr>
</tbody>
</table>
You can accomplish this but I would suggest that you instead have your server return you data as a list of json objects.
To sort your multidimensional array you basically sort by the inner array's index.
Your predicate would hold the index of the column you want to sort on (either 0 or 1 in your case)
<th ng-repeat="heading in test.headers"
ng-click="test.predicate = $index">
{{ heading }}
</th>
Create a sorting function in your controller as below:
this.sorter = function(item){
return item[test.predicate];
}
Apply this sorter as your orderBy expression as below:
<tr ng-repeat="row in data | orderBy: test.sorter">
I've forked and updated your CodePen for you: http://codepen.io/anon/pen/qvcKD
For reference, the solution where the array is zipped together with standard JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
var data = [
[ 'lol', 'abc' ],
[ '123', 'wut' ]
];
this.data = [];
for (var i = 0, n = data.length; i < n; i++) {
this.data.push({});
for (var j = 0, m = this.headers.length; j < m; j++) {
this.data[i][this.headers[j]] = data[i][j];
}
}
this.predicate = '';
});
Or instead with LoDash as suggested by #Antiga:
_.each(data, function(item) {
this.data.push(_.zipObject(this.headers, item));
}, this);

Resources