updating a row in a table in angular 2 - angular2-template

Hi I'm working on the angular 2 inserting updating and deleting a row in angular 2.
In ngFor I am binding the data to the table.
I had created the update button in the ngFor loop.
On click of the particular rows "Update" button, I needs only that row to get with textboxes instead of all rows.
Unfortunately i'm getting for all records.
I knew its because of the property binded to all rows.
But how can I overcome to make sure that only particlular clicked row to get with edit mode like text boxes.
My code was like below :
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public enableEdit = true;
showcreate: boolean=false;
public items=[];
public FirstName="";
public LastName="";
public MobileNumber="";
public PinCode="";
public City="";
public CollageName="";
public Percent="";
public conformdelete;
public edit = false;
public btncreate =false;
public indexVal:any;
constructor(){
if(localStorage.getItem("items"))
this.items = JSON.parse(localStorage.getItem("items"))
}
delete(index){
if(confirm("Are you sure you want to delete this item?") == true){
this.items.splice(index,1);
localStorage.setItem("items",JSON.stringify(this.items))
}
}
update(event, index){
debugger;
console.log(event);
console.log(index);
this.enableEdit = false;
}
save(index){
// console.log("save",i)
// this.indexVal = i;
this.enableEdit = true;
}
cancel(){
this.enableEdit = true;
}
btnsubmit(){
this.items.push({
"FirstName":this.FirstName,
"LastName":this.LastName,
"MobileNumber":this.MobileNumber,
"PinCode":this.PinCode,
"City":this.City,
"CollageName":this.CollageName,
"Percent":this.Percent
})
localStorage.setItem("items",JSON.stringify(this.items))
}
}
app.component.html :
<table border="2">
<thead>
<tr>
<th>FirstName</th>
<th>LastName</th>
<th>MobileNumber</th>
<th>PinCode</th>
<th>City</th>
<th>CollageName</th>
<th>Percent</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let i of items; let index = index">
<td><input *ngIf="!enableEdit" [(ngModel)]="i.FirstName"> <span *ngIf="enableEdit">{{i.FirstName}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.LastName"> <span *ngIf="enableEdit">{{i.LastName}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.MobileNumber"> <span *ngIf="enableEdit">{{i.MobileNumber}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.PinCode"> <span *ngIf="enableEdit">{{i.PinCode}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.City"> <span *ngIf="enableEdit">{{i.City}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.CollageName"> <span *ngIf="enableEdit">{{i.CollageName}}</span></td>
<td><input *ngIf="!enableEdit" [(ngModel)]="i.Percent"> <span *ngIf="enableEdit">{{i.Percent}}</span></td>
<td>
<button *ngIf="enableEdit" (click)="delete(index)">Delete</button>
<button *ngIf="enableEdit" (click)="update($event,index)" class="update">Update</button>
<button *ngIf="!enableEdit" (click)="save(index)">save</button>
<button *ngIf="!enableEdit" (click)="cancel(index)" >cancle</button>
</td>
</tr>
</tbody>
</table>

The issue is, when one of your row button is clicked, since the "enableEdit" condition is universal for all rows, it gets reflected to all rows. One of the possible solution is to add an extra key-value pair to your table array, so that you can make use of each row , by using its index.
Example :
in your component.ts,
constructor(){
if(localStorage.getItem("items"))
this.items = JSON.parse(localStorage.getItem("items"));
/* add an extra key value pair named "edit", and initially set it to false. So all the rows will be showing "Delete" and "Update" buttons initially */
this.items.forEach(function (eachItem){
eachItem.edit = false;
});
}
/* function for update or cancel functionalities */
updateCancel(event, index,action:string){
this.items[index].edit = true; /* selects the items with index number and swaps the buttons*/
if(action == "cancel"){
this.items[index].edit = false;
}
}
/* function for save or delete functionalities */
saveDelete(index, action:string){
this.items[index].edit = false;
if(action == "delete"){
if(confirm("Are you sure you want to delete this item?") == true)
{
this.items.splice(index,1);
this.items[index].edit = true;
localStorage.setItem("items",JSON.stringify(this.items))
}
}
}
In your app.component.html file, change the button area td with new function names and if condition
<td>
<button *ngIf="!i.edit" (click)="saveDelete(index,'delete')">Delete</button>
<button *ngIf="!i.edit" (click)="updateCancel($event,index,'update')" class="update">Update</button>
<button *ngIf="i.edit" (click)="saveDelete(index,'save')">Save</button>
<button *ngIf="i.edit" (click)="updateCancel($event,index,'cancel')">cancel</button>
</td>
This solution worked for me. Thanks.

Related

Why is getter returning a proxy in Vuex 4 how do I access the actual data? [duplicate]

I'm working with Laravel and Vue to make a single page web application. I've used Vue before to get the data from a database using a controller with no problem, but for some reason I'm now only getting a seemingly infinitely nested JS object that has getter and setter methods stored in each parent object instead of the data I queried. I've seen other people with similar issues, but the solutions that worked for them didn't work for me. For example, some people used JSON.parse(JSON.stringify(response.data)); to get just the raw data, but this doesn't work when I attempt to store it in this.actions. Here is my index method in my ActionLogController
public function index($url)
{
$companyName = explode("/", $url);
if(Auth::check())
{
$company = Company::where('name', '=', strtolower($companyName[count($companyName) - 1]))->first();
// If sortby not empty
$sortby = "created_at";
//assume desc (most recent)
$sortdirection = 'desc';
if(request()->has('sortdirection') && request()->sortdirection == 'asc')
{
$sortdirection = 'asc';
}
// if sortby is set
if(request()->has('sortby'))
{
$sortby = request()->sortby;
switch($sortby)
{
case "date":
$sortby = "string_date";
break;
case "company":
$sortby = "company_name";
break;
case "name":
// do nothing
break;
case "communication-type":
$sortby = "communication_type";
break;
case "contact":
// do nothing
break;
case "subject":
$sortby = "status";
break;
case "assigned-to":
$sortby = "assigned_to";
break;
case "action":
$sortby = "action_item";
break;
case "assigned-to":
$sortby = "assigned_to";
break;
default:
$sortby = 'created_at';
break;
}
}
}
if($sortdirection == 'asc') {
return Auth::user()->actionLogs
->where('activity_key', '=', '1,' . $company->id)
->sortBy($sortby);
}
return Auth::user()->actionLogs
->where('activity_key', '=', '1,' . $company->id)
->sortByDesc($sortby);
}
This is my Vue component to get the data from the controller. I know the template code works, because it worked fine when I sent it dummy data before pulling the data from the controller.
<style scoped>
.action-link {
cursor: pointer;
}
.m-b-none {
margin-bottom: 0;
}
</style>
<template>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th><a id="sortby-date" class="action-nav" href="?sortby=date&sortdirection=desc">Date</a></th>
<th><a id="sortby-company" class="action-nav" href="?sortby=company&sortdirection=desc">Company</a></th>
<th><a id="sortby-name" class="action-nav" href="?sortby=name&sortdirection=desc">Name</a></th>
<th><a id="sortby-communication-type" class="action-nav" href="?sortby=communication-type&sortdirection=desc">Communication Type</a></th>
<th><a id="sortby-contact" class="action-nav" href="?sortby=contact&sortdirection=desc">Contact</a></th>
<th><a id="sortby-subject" class="action-nav" href="?sortby=subject&sortdirection=desc">Subject</a></th>
<th><a id="sortby-action" class="action-nav" href="?sortby=action&sortdirection=desc">Comment/Action Item</a></th>
<th>Archive</th>
<!-- check if admin?? -->
<th><a id="sortby-assigned-to" class="action-nav" href="?sortby=date&sortdirection=desc">Assigned To</a></th>
<!-- /check if admin?? -->
</tr>
</thead>
<tbody v-if="actions.length > 0">
<tr v-for="action in actions">
<td>
{{ action.string_date }}
</td>
<td>
{{ action.company_name }}
</td>
<td>
{{ action.name }}
</td>
<td>
{{ action.communication_type }}
</td>
<td>
{{ action.contact }}
</td>
<td>
{{ action.status }}
</td>
<td>
{{ action.action_item }}
</td>
<td>
<input type="checkbox" :id="'archive-' + action.id" class="archive" :name="'archive-' + action.id">
</td>
<td :id="'record-' + action.id" class="assigned-to">
{{ action.assigned_to }}
</td>
</tr>
</tbody>
</table>
<p id="add-action" style="text-align: center;">
<button id="action-log-add" class="btn btn-sm btn-primary edit">Add Item</button>
<button id="action-log-edit" class="btn btn-sm btn-danger edit">Edit Items</button>
</p>
</div>
</template>
<script>
export default {
data() {
return {
actions: []
}
},
methods: {
getActionLogs(location) {
var company = location.split("/");
company = company[company.length - 1];
axios.get('/action-log/' + company)
.then(response => {
this.actions = response.data;
console.log(this.actions);
})
.catch(error => {
console.log('error! ' + error);
});
}
},
mounted() {
this.getActionLogs(window.location.href);
}
}
</script>
This is the output I get in the browser console
{…}
​
1: Getter & Setter
​
2: Getter & Setter
​
3: Getter & Setter
​
4: Getter & Setter
​
5: Getter & Setter
​
6: Getter & Setter
​
7: Getter & Setter
​
8: Getter & Setter
​
9: Getter & Setter
​
10: Getter & Setter
​
__ob__: Object { value: {…}, dep: {…}, vmCount: 0 }
​
<prototype>: Object { … }
I was expecting to see the normal array of data that gets returned, but this is what shows up instead and then won't update the component with the data. I'm new to Vue, so maybe there's something really easy I missing, but I can't seem to figure this out.
Writing up my comments above as a sort of canonical answer to this as it keeps coming up...
What you're looking at is how Vue proxies your data to make it reactive. This is because you're using console.log() on a Vue instance data property.
When you assign values to a data property, it is transformed to an observable so Vue can treat it reactively. I suggest you forget about trying to console.log() anything assigned to this and use the Vue Devtools browser extension to inspect your components and their data if you're having trouble rendering the response.
Please note, there is nothing wrong here.

Clear data after update data in datatables for angular 8

i'm using this plugin https://l-lin.github.io/angular-datatables/#/advanced/rerender for could use datatable in angular, i have a form for search data, it'll be show in table, the problem is when i do many search, the table update with the new data but also show data of searches before.
Some idea to clear data before update with the new data.
I'm using this code for my dattables.
#ViewChild(DataTableDirective, { static: false })
dtElement: DataTableDirective;
dtOptions: DataTables.Settings = {};
dtTrigger: Subject<DataTableDirective> = new Subject();
ngAfterViewInit(): void {
this.dtTrigger.next();
}
ngOnDestroy(): void {
// Hay que dessuscribirse del evento dtTrigger, para poder recrear la tabla.
this.dtTrigger.unsubscribe();
}
//----------------------------------------------------
// ReDraw Datatable
reDraw(): void {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
// Destruimos la tabla
dtInstance.destroy();
// dtTrigger la reconstruye
this.dtTrigger.next();
});
}
Here, i call the function to update datatable with de new data.
this._investigadorGrupoService.find(this.InvestigadorGrupo).subscribe((result: any) => {
this.reDraw();
this.ListInvestigadores = result;
this.bSearchActive = true;
$('#processing').addClass("escondido");
});
this is my HTML
<table id="dtDataTable" datatable [dtOptions]="dtOptions" class="row-border hover" [dtTrigger]="dtTrigger" style="width:100%">
<thead>
<tr>
<th>Acciones</th>
<th>Nombre</th>
<th>Facultad</th>
<th>Nombre Grupo</th>
<th>Es Lider</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let i of ListInvestigadores">
<td>
<a class="btn btn-warning text-white" (click)="onEdit(i.investigador)">
<span [innerHTML]="Tools.GetIconEdit() | safe: 'html'"></span>
</a>
</td>
<td>{{i.investigador.nombres}} {{i.investigador.apellidos}}</td>
<td>{{i.grupo.facultad.nombre}}</td>
<td>{{i.grupo.nombre}}</td>
<td>{{i.esLiderGrupo ? 'Si': 'No'}}</td>
<td>{{i.investigador.email}}</td>
</tr>
</tbody>
</table>
Thanks for your support
reDraw(): void {
this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
dtInstance.clear().draw(); // Add this line to clear all rows..
dtInstance.destroy();
// dtTrigger la reconstruye
this.dtTrigger.next();
});
}

Getting part of the page to display updated data in vue

I'm using vue to create a page where I list all users and if I click on the edit button the details of that user then gets shown
next to the list.
What I'm trying to do is, if I update a user and click save then the user details in the list needs to change.
The problem I'm having is that I'm not able to get the details to change in the list after I've saved.
My vue
<template>
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-md-7">
<table class="table table-striped table-sm mt-2">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="user in displayAllUsers">
<td>{{ user.name }}</td>
<td>
<button class="btn btn-sm btn-success" #click="manageUser(user)">Edit</button>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5" v-if="user != null">
<div class="card">
<div class="card-header">
<h4 class="card-title mb-0">Manage {{ user.name }}</h4>
</div>
<div class="card-body">
<table class="table">
<tr>
<th>Name</th>
<td>
<input type="text" v-model="user.name">
</td>
</tr>
</table>
</div>
<div class="card-footer">
<button #click="updateUser()"class="btn btn-success"><i class="fa fa-save"></i> Save</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {
},
data: function () {
return {
users: [],
user: null
}
},
computed: {
displayAllUsers(){
return this.users;
}
},
methods: {
manageUser(user){
axios.get('/admin/user/'+user.id).then((response) => {
this.user = response.data.user;
});
},
updateUser(){
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
this.users = response.data.user;
});
}
},
mounted() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
}
</script>
There are two possible solutions.
The first is to run this code at the end of the updateUser method:
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
The second is to use a state manager like Vuex.
The first scenario will fetch again your users data from the remote API and will update your view with all your users.
With the second scenario, you will handle your application state way much better than just using the data attribute of your page module, but in the background, it is more or less the same as the first solution I suggest.
To update the current user only in the table you could do something like that at the end of the updateUser method:
let userIdx = -1;
for(let idx = 0, l = this.users.length; idx < l; idx++) {
if ( this.user.id === this.users[idx].id ) {
userIdx = idx;
break;
}
}
if ( -1 !== userIdx ) {
this.users[userIdx] = this.user;
this.user = {};
}
Other than your problem, it seems like you don't need this code:
computed: {
displayAllUsers(){
return this.users;
}
},
You could remove this code, and instead use this code in the HTML part:
<tr v-for="user in users">
For your updateUser function you could just return the modified user in the same format that you have for all the users in you user list and update the user by index. This is presuming that the user you want to update is in the users array to start with.
updateUser() {
axios.put('/admin/user/'+this.user.id, {
name: this.user.name
}).then((response) => {
const updatedUser = response.data.user;
// Find the index of the updated user in the users list
const index = this.users.findIndex(user => user.id === updatedUser.id);
// If the user was found in the users list update it
if (index >= 0) {
// Use vue set to update the array by index and force an update on the page
this.$set(this.users, index, updatedUser);
}
});
}
This could be a good starting point.
Unrelated Note:
You can add your mounted function code to its own method, for example
getUsers() {
axios.get('/admin/users').then((response) => {
this.users = response.data.users;
});
}
then
mounted() {
this.getUsers()
}
this makes it a little cleaner and easier if you ever need to get the users again (example: if you start having filters the user can change)
As it could get more complex vuex would be a great addition.

Form collection validation with dates and string - Vuelidate

I am trying to validate series of dates with something like this.
const data = [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
... more values
];
// Elements inside data can be added or removed but will have at least one.
Here data[1][begin] should be more than or equal to data[0][begin] and data[1][place] should not equal to data[0][place]. Is there anyway to achieve this. Documentation talks about dynamic validation but I am not sure how I can achieve this with collection.
You can consider implementing a custom validation in the form submit event listener.
This can be achieved by looping through your array of objects and compare items in pairs.
HTML
<form
id="app"
#submit="checkForm"
action="/someurl"
method="post"
>
<table border="1">
<tr v-for="(item,index) in dates" :key="index">
<td>
{{index}}
</td>
<td>
{{formatDate(item.begin)}}
</td>
<td>
{{item.place}}
</td>
</tr>
</table>
<input type="date" v-model="dateEntry"/>
<input type="text" v-model="placeEntry"/>
<button type="button" #click="addEntry">Add</button>
<p>
<br>
<input
type="submit"
value="Submit"
>
</p>
<p v-for="error in errorList">
{{error}}
</p>
</form>
JS
new Vue({
el: "#app",
data: {
errorList: [],
dateEntry: null,
placeEntry: null,
dates: [
{begin: new Date('2019-12-01'), place: '2'},
{begin: new Date('2019-12-03'), place: '3'}
]
},
methods: {
addEntry: function(){
if(this.dateEntry == null || this.dateEntry == "")
return false;
if(this.placeEntry == "")
return false;
this.dates.push({
begin: new Date(this.dateEntry),
place: this.placeEntry
});
this.dateEntry = null;
this.placeEntry= "";
},
checkForm: function(e){
var isValid = true;
var index = 0;
var nextIndex = 1;
this.errorList = [];
while(nextIndex < this.dates.length){
if(nextIndex < this.dates.length){
var isValidDate = this.validDate(this.dates[nextIndex].begin,this.dates[index].begin);
var isValidPlace = this.validPlace(this.dates[nextIndex].place,this.dates[index].place);
if(!isValidDate){
this.errorList.push("Invalid date on index " + nextIndex);
}
if(!isValidPlace){
this.errorList.push("Invalid place on index " + nextIndex);
}
}
index++;
nextIndex++;
}
if(!this.errorList.length){
this.errorList.push("All dates are valid");
return true;
}
e.preventDefault();
},
formatDate: function(date){
return date.toDateString();
},
validPlace: function(curPlace, prevPlace){
return curPlace != prevPlace;
},
validDate: function(curDate,prevDate){
try{
return curDate.getTime() >= prevDate.getTime();
}catch(e){
return false;
}
}
}
})
Check out this JS Fiddle that I created to illustrate my suggestion.
On the other hand, if you are building the array during runtime, then you can apply the validation before it gets added into the array.

PrimeNg TurboTable filters are not working with LazyLoad

I am unable to use filters with lazy load option. Can anyone help me if i am missing anything here?
I read primeng documentation this and this and this with examples. I could find any solution.
When I use both [lazy] and filters together, filters are not working. If I just remove [lazy]="true" from HTML would work the filters. How can I achieve both?
import { Component, OnInit, Input } from '#angular/core';
import { SelectItem, LazyLoadEvent } from 'primeng/primeng';
import { MyService } from '../services/my.service';
import { ColumnHeaders } from '.';
#Component({
selector: 'myList-table',
templateUrl: 'myList-table.component.html',
styleUrls: ['myList-table.component.less']
})
export class myListTableComponent implements OnInit {
rowCount = { 'value': 5 };
totalRecords: number = 0;
pageNavLinks: number = 0;
myList: any = [];
columnHeaders = ColumnHeaders;
#Input() myListStatus = 'live';
constructor(private myService: MyService) { }
ngOnInit() { this.lazyLoad({ 'first': 0 }); }
lazyLoad(event: LazyLoadEvent) {
const pageNumber = Math.round(event.first / this.rowCount.value) + 1;
this.myService.getmyList(this.myListStatus, pageNumber, this.rowCount.value)
.subscribe(response => {
this.myList = response.batches;
this.totalRecords = response.batches[0].TotalRowCount;
this.pageNavLinks = Math.round(this.totalRecords / this.rowCount.value);
});
}
changeCount() {
this.totalRecords = 0;
this.pageNavLinks = 0;
this.lazyLoad({ 'first': 0 });
}
}
<div class="ui-g-12">
<p-table #dt [columns]="columnHeaders" [value]="myList" [paginator]="true" [rows]="rowCount?.value" [totalRecords]="totalRecords" [responsive]="true" (onLazyLoad)="lazyLoad($event)" [lazy]="true">
<ng-template pTemplate="header" let-columns>
<tr>
<th *ngFor="let col of columns">
{{col.header}}
</th>
</tr>
<tr>
<th *ngFor="let col of columns">
<div class="search-ip-table">
<i class="fa fa-search"></i>
<input pInputText type="text" (input)="dt.filter($event.target.value, col.field, col.filterMatchMode)">
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns">
<tr [pSelectableRow]="rowData">
<td>{{rowData.id}}</td>
<td>{{rowData.name}}</td>
<td>{{rowData.TName}}</td>
<td>{{rowData.Base}}</td>
<td>{{rowData.Date | date:'medium' }}</td>
</tr>
</ng-template>
<ng-template pTemplate="paginatorleft">
<span>Total Records: {{totalRecords}}</span>
</ng-template>
<ng-template pTemplate="paginatorright">
<p-dropdown [options]="pageRecordsCountOptions" [(ngModel)]="rowCount" optionLabel="value" (onChange)="changeCount()"></p-dropdown>
</ng-template>
</p-table>
</div>
When [lazy]="true", filter doesn't work in the front-end, but leave this work to the back-end by (onLazyLoad) event.
If you want filter your data in the front-end, you can into lazyLoad() method :
.subscribe(response =>. ...
using filter informations provided by event parameter.