How can I add loading ONLY to the clicked button in vuejs? - javascript

There is a table which loops and outputs the data which comes from API. I have added a button inside the table. When you click it, it should send the id of the clicked button and till it recieves the data of the function which needs to be printed, it should be in loading. Here is my code.
<table id="customers">
<tr>
<th>{{$t('message.numberReport')}}</th>
<th>{{$t('message.periodFrom')}}</th>
<th>{{$t('message.periodTo')}}</th>
<th>{{$t('message.printButton')}}</th>
</tr>
<tr v-for="(item,index) in getReportInfo" :key="index">
<td>{{ item.id }}</td>
<td>{{ item.periodFrom }}</td>
<td>{{ item.periodTo }}</td>
<td>
<v-btn
class="primary"
:loading="loading"
:disabled="loading"
#click="fetchGetReportDetailed(item)"
>{{ $t('message.printButton')}}</v-btn>
</td>
</tr>
</table>
But when I clicked the particular button, all the buttons are getting in loaded mode. How do I fix it? Any suggestion would be deeply appreciated.here is the visual example

Using index of item in list.
You can register a new variable in your data for example indexClicked .
data () {
return {
// Some predefined value, 0 isn't good because index can be 0.
indexClicked: undefined // Some predefined value
}
}
And when you click at button you can send index value:
<td>
<v-btn class="primary"
:loading="loading && indexClicked === index"
:disabled="loading && indexClicked === index"
#click="fetchGetReportDetailed(item, index)">
{{ $t('message.printButton') }}
</v-btn>
</td>
And in your fetchGetReportDetailed(item, index) method you need to assign index to this.indexClicked like:
fetchGetReportDetailed (item, index) {
// Your code
this.indexClicked = index;
}
This should work. But if you need more information please provide more code.
Note if you have a problem with multiple conditions in :disable you can create a method which will return true or false depends on condition loading && this.indexClicked === index.
Good luck!

You're using a single data property for all rows, so in mounted hook add a property called loading to each row like :
mounted(){
this.getReportInfo=this.getReportInfo.map(rep=>{
rep.loading=false;
return rep;
});
}
and the template do:
<tr v-for="(item,index) in getReportInfo" :key="index">
<td>{{ item.id }}</td>
<td>{{ item.periodFrom }}</td>
<td>{{ item.periodTo }}</td>
<td><v-btn class="primary" :loading="item.loading" :disabled="loading" #click="fetchGetReportDetailed(item,index)" >{{ $t('message.printButton')}}</v-btn></td>
</tr>
in fetchGetReportDetailed method :
fetchGetReportDetailed(item,index){
....
this.getReportInfo[index].loading=true;
this.$set(this.getReportInfo,index,this.getReportInfo[index])
}

You could separate the tr that is displaying the data into its own state-full component and call the function from within the component.
This way the state of loading for each item in the array will be local to its own component.

Related

Functions as Element Attributes and the Angular ExpressionChangedAfterItHasBeenCheckedError

I'm creating a summary row on a tree table that also has filtering (I'm using the ng Prime component library, but I don't think that's germane to the error or question). I want my summary row to only summarize the information currently in view, thus the summary should change when the tree is expanded or when a filter is applied.
I achieved this goal by adding row data to a map via a function I set as an element attribute [attr.summaryFunction]="summaryFunction(i,rowData[col.field])", and reinitializing the map with ngDoCheck. Without this step, reinitializing the map in ngDoCheck, totals accumulate with every repaint or change and result in incorrect values. (I tried reinitializing the map with several other lifecycle hooks ngOnInit, ngOnViewInit, and ngOnChanges, but the only lifecycle hook that effectively reinitialized the map was ngDoCheck).
My approach works perfectly (yay me!), but I get an ExpressionChangedAfterItHasBeenCheckedError in the browser, three times, for each column. To address this error, I tried all 4 approaches to fixing the error recommended in the documentation:
using different lifecycle hooks --> totals are incorrect
I added this.cd.detectChanges() to summary function --> infinite loop
I wrapped the if statement in setTimeout(() => if(statement){}) --> infinite loop
Promise.resolve().then(() => if(statement){}) --> infinite loop
Again, my approach currently works exactly as I want it to, I just want these annoying errors to go away. I'd also like to understand why the usual fixes cause an infinite loop.
Here's my TS layer;
export class PrimetreeComponent implements OnInit, DoCheck {
sampleData: TreeNode[] = []
columns: any[] = [];
summaryMap = new Map();
columnIndex = 0;
constructor(private nodeService: NodeService, private cd: ChangeDetectorRef) {}
ngOnInit(): void {
this.nodeService.getFilesystem().subscribe(data => {this.sampleData = data});
this.columns = [
{field: 'name', header: 'name'},
{field: 'size', header: 'size'},
{field: 'type', header: 'type'}]
}
ngDoCheck(): void {
this.summaryMap = new Map();
}
summaryFunction(columnIndex:number, rowData:any){
if(this.summaryMap.has(columnIndex)){
//if row data is a non-number string we just count the rows
if(Number(rowData)){
this.summaryMap.set(columnIndex, this.summaryMap.get(columnIndex)+Number(rowData));
} else {
this.summaryMap.set(columnIndex, this.summaryMap.get(columnIndex)+1)
}
} else {
if(Number(rowData)){
this.summaryMap.set(columnIndex, Number(rowData));
} else {
this.summaryMap.set(columnIndex, 1)
}
}
}
}
And here's my HTML Template:
<p-treeTable #tt [value]="sampleData" [columns]="columns">
<ng-template pTemplate="caption">
<div style="text-align: right">
<span class="p-input-icon-left">
<i class="pi pi-search"></i>
<input
pInputText
type="text"
size="50"
placeholder="Global Filter"
(input)="tt.filterGlobal($any($event.target).value, 'contains')"
style="width:auto"
/>
</span>
</div>
</ng-template>
<ng-template pTemplate="header" let-columns>
<tr>
<th *ngFor="let col of columns">
{{ col.header }}
</th>
</tr>
<tr>
<th *ngFor="let col of columns">
<input
pInputText
type="text"
class="w-full"
(input)="
tt.filter($any($event.target).value, col.field, col.filterMatchMode)
"
/>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowNode let-rowData="rowData" >
<tr>
<td *ngFor="let col of columns; let i = index;" [attr.summaryFunction]="summaryFunction(i,rowData[col.field])">
<p-treeTableToggler
[rowNode]="rowNode"
*ngIf="i == 0"
(columnIndex)="i"
></p-treeTableToggler>
{{ rowData[col.field] }}
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr>
<td [attr.colspan]="columns.length">No data found.</td>
</tr>
</ng-template>
<ng-template pTemplate="summary">
<tr>
<td *ngFor="let col of columns; let i = index;">
{{ col.header }}--sumMap--{{summaryMap.get(i) }}
</td>
</tr>
</ng-template>
I am not familiar with the approach of binding a function to an elements attribute, but it seems kind of hacky to me. Correct me if I'm wrong, but the reason for this is to have a way to trigger the function when the node is added to the dom?
I did not test this myself, but I think you could make use of the #ContentChildren decorator see docs. It exposes an observable that you can subscribe to to react to changes of the content dom. Ideally, you could run the summary creation and updating the summaryMap in one step.
Update:
I played around with the idea a bit. One solution for what you are trying to achieve is to use #ViewChildren decorator to get a QueryList of all the rows currently rendered. This can be achieved in the following way:
<ng-template pTemplate="body" let-rowNode let-rowData="rowData" >
<tr [ttRow]="rowNode" #tableRow [attr.file-size]="rowData.size">
<td>
<p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
{{ rowData.name }}
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
Note the template ref on <tr [ttRow]="rowNode" #tableRow [attr.file-size]="rowData.size"> along with binding the rowData.size to an attribute of the element.
You can then grab that list of rows rows that are currently mounted to the dom like so:
// inside your component
#ViewChildren('tableRow') view_children!: QueryList<ElementRef>;
// get the list, calculate initial sum and subscribe to changes.
// We do that in ngAfterViewInit lifecycle hook as the
// list is guaranteed to be available at that point of time:
ngAfterViewInit() {
this.calculateSum(this.view_children);
this.view_children.changes.subscribe(
(list: QueryList<ElementRef>) =>
this.calculateSum(list)
);
}
You can find a running example of this here.

How to add row number or Serial no in angular2 datatable

I am using angular2 -datatable.
I am not able to display correct row no.s in the corresponding rows.
The datatable starts counting from one again when the user moves to the next page using paginator.
My html code:
<tbody>
<tr *ngFor="let item of mf.data; let i =index">
<td>
{{i+1}}
</td>
<td>{{ item.name }}</td>
<td>{{item.department}}</td>
<td >{{item.category}}</td>
</tr>
</tbody>
try {{data.indexOf(item) + 1}}, here I suppose data is the variable you bind to <table> by <table [mfData]="data | filter "
this will get the original index from array of data.
plunker demo.
You need to calculate it with currentPage (current page number) and itemsPerPage (number of item to show per page):
{{ itemsPerPage * (currentPage - 1) + i + 1 }}

Angularjs -Updating individual values in ng-repeat from outside its scope using "this" keyword

I am trying to update quantity number of individual items in a table whenever the user clicks the item in another table.
For example, I have list of all items in Table A
<tr ng-repeat="item in items">
<td>{{item.fid}}</td>
<td{{ item.fname }}</td>
<td>{{ item.calorie }}</td>
<td>{{ item.protein }}</td>
<td>{{ item.carb }}</td>
<td>{{ item.fat }}</td>
<td><button ng-click=additem(values)>Add</button>
<tr>
Now when the user clicks this Add button, the selected item gets added to another table (Table B).
I have disabled duplicates in Table B and want that if the user is repeatedly adding same item then the quantity of the item in Table B should increase.
Table B
<tr ng-repeat="sitem in sitems>
<td>{{sitem.fname}}</td>
<td>{{sitem.calorie}}</td>
<td>{{sitem.protein}}</td>
<td>{{sitem.carb}}</td>
<td>{{sitem.fat}}</td>
<td>*</td>
<td><button ng-click="removeItem(values)">Remove</button></td>
</tr>
is the one where i want the increased quantity to be shown.
I have tried using "this" keyword but didn't worked, I am new to angular so i don't know what all are my options and got mixed up.
You have to keep track of two separate array to accomplish this.
This is the code snippet: `
$scope.additem = function(item) {
var index = $scope.sitems.indexOf(item);
if (index === -1) {
item.quantity = 1;
$scope.sitems.push(item);
return;
}
item.quantity++;
$scope.sitems[index] = item;
};
`
Complete Working fiddle: https://jsfiddle.net/nbakliwal18/4kbo7Lfo/2/
Also you check for quantity before adding.
You can simply pass item to the function addItem() :
<td><button ng-click=addItem(item)>Add</button>
and push this item to your array sitems in your function :
$scope.addItem = function(item) {
$scope.sitems.push(item);
}
A workaround for the duplicates in ng-repeat is to add track by $index to it :
<tr ng-repeat="sitem in sitems track by $index">
Update 1:
Updated my answer with working example in Plunker
Update 2:
Here is an example with lodash for quantity Plunker

how to get the result of two controller on ng-repeat

I want to get the values of the two object in thesame ng-repeat
$http.get('/api/PreviewPayroll').success(function (data){
//alert(data[0].empID);
$scope.allowance = data;
});
$http.get('/api/Deduction').success(function (data){
//alert(data[0].empID);
$scope.Deduction = data;
});
<tr ng-repeat="item in allowance && ng-repeat="value in Deduction">
<td>{{ item.empID }}</td>
<td>{{ value.empID }}</td>
</tr>
how can I get the two scope object on thesame ng-repeat
So you will want to combine the data.
You can use $q.all(promises):
var promise1 = $http.get('/api/PreviewPayroll');
var promise2 = $http.get('/api/Deduction');
$q.all([promise1, promise2]).then(function (results) {
var allowances = results[0];
var deductions = results[1];
var combinedList = /* some combination logic */;
});
By using $q.all() you are ensuring you have both lists of data before trying to combine anything. You can easily play around with this to get the desired effect. For example, if you don't care if the other list isn't available.
Then you can use the ng-repeat in order to iterate over that new combined list:
<tr ng-repeat="item in combinedList">
<td>{{ item.allowance.empID }}</td>
<td>{{ item.deduction.empID }}</td>
</tr>
The sub properties allowance and deduction are based on your combined list.
However
It is in my honest opinion that, the server side gives you the data in the format you need to display it in. (i.e. the business logic remains server side in a controlled environment). I believe the view should only deal with view logic, like button actions etc..
But this is my opinion, and is what I find easiest.
Another note
I prefer to also keep the view logic in the JavaScript, hence why I combine the data there. Rather than trying to do some overly complicated angular expression in the HTML.
You could either have a nested ng-repeat and also combine the two objects into one.
<table>
<tbody ng-repeat="row in mainCombinedObject">
<tr>
<th>{{row.empID}}</th>
</tr>
<tr ng-repeat="sub in row.subObject">
<td>{{sub.empID}}</td>
</tr>
</tbody>
</table>
Combine $scope.allowance and $scope.Deduction to one list of objects "combined" then do your ng-repeat:
<tr ng-repeat="c in combined">
<td>{{ c.someField }}</td>
<td>{{ c.someOtherField }}</td>
</tr>
You can't do that in such way! If your allowance and Deduction have the same size you have to mix them in the collection like this:
var array = [
{ allowance: value1, Deduction: value2},
{ allowance: value3, Deduction: value4},
...
];
and them use it in the view:
<tr ng-repeat="item in array">
<td>{{ item.allowance.empID }}</td>
<td>{{ item.Deduction.empID }}</td>
</tr>

Go to URL with ID onClick() Using Laravel

I am building a simple contact database with Laravel and a MySQL database. I have a basic HTML file with a data table that list a series of records. I would like to make it so that when I click on a row, I go to a page based on the ID number of the record in the row clicked.
I have the table populated using a #foreach loop as such:
<tbody>
#foreach ($people as $person)
<tr>
<td>
<a>{!! link_to_action('ContactController#show', $person->id, $person->id) !!}</a>
</td>
<td>{{ $person->name_first }}</td>
<td>{{ $person->name_middle }}</td>
<td>{{ $person->name_last }}</td>
</tr>
#endforeach
</tbody>
The controller (ContactController) shows this:
public function show($id)
{
$person = Contact::findOrFail($id);
return view('contacts.show', compact('person'));
}
Now, I have a JavaScript script on the page:
<script type="text/javascript">
$(document).ready(function() {
$('#contacts').dataTable();
$('#contacts tbody').on('click', 'tr', function () {
var name = $('td', this).eq(1).text();
alert( 'You clicked on '+name+'\'s row' );
window.location="";
} );
} );
</script>
I assume I only need to insert the proper link in:
window.location="";
How do I write the link? I tried writing a number of variations with the Laravel syntax, where I need to access a route in my ContactController that would go to contacts/{id}. I tried:
window.location="{{URL::to('contacts', $person->id)}}";
That doesn't work. Please let me know what link to use in order to go to contacts/1 or contacts2, for example.
try this.
$('#contacts tbody').on('click', 'tr', function () {
var name = $('td', this).eq(1).text();
var link = $('td', this).eq(0).find('a');
alert('You clicked on ' + name + '\'s row');
window.location=link.attr('href');
});
And the show method only accepts one argument.
<td>
{!! link_to_action('ContactController#show', $person->id) !!}
</td>
And no need to wrap it in "a" tags
Add your link to a data attribute on your tr element and then use it in your JavaScript.
HTML:
<tbody>
#foreach ($people as $person)
<tr data-link='{{ action('ContactController#show', $person->id) }}'>
<td>
<a>{!! link_to_action('ContactController#show', $person->id, $person->id) !!}</a>
</td>
<td>{{ $person->name_first }}</td>
<td>{{ $person->name_middle }}</td>
<td>{{ $person->name_last }}</td>
</tr>
#endforeach
</tbody>
JavaScript:
$('#contacts tbody').on('click', 'tr', function () {
var name = $('td', this).eq(1).text();
alert( 'You clicked on '+name+'\'s row' );
window.location = this.getAttribute('data-link');
} );

Categories

Resources