I'm trying to add the rows of an Angular 2 Data Table ( https://material.angular.io/components/table/overview) dynamically.
I got a service ("ListService") which gives me the columns("meta.attributes") to display and i can retrieve my data from it.
The problem is, if I change the displayed columns later, after I loaded the dataSource and and the meta.attributes array gets entries (so the rows should exist in the html), it gives me this error:
Error: cdk-table: Could not find column with id "id".
Looks like the header can't find the given rows. Any ideas to fix that?
.html file:
<md-table #table [dataSource]="dataSource" mdSort>
<ng-container *ngFor="let attr of meta.attributes">
<ng-container [cdkColumnDef]="attr.name">
<md-header-cell *cdkHeaderCellDef md-sort-header>{{attr.label}}</md-header-cell>
<md-cell *cdkCellDef="let row">
{{row[attr.name]}}
</md-cell>
</ng-container>
</ng-container>
<md-header-row *cdkHeaderRowDef="displayedColumns"></md-header-row>
<md-row *cdkRowDef="let row; columns: displayedColumns;"></md-row>
</md-table>
.ts file:
export class ListComponent implements OnInit {
displayedColumns = [];
exampleDatabase = new ExampleDatabase();
dataSource: ExampleDataSource | null;
meta: any = {
attributes: []
};
constructor(private service: ListService) {
//If i do it here it works
//this.meta.attributes.push({label: "ID", name: "id"});
}
ngOnInit() {
this.dataSource = new ExampleDataSource(this.exampleDatabase);
this.service.getMeta(this.name).subscribe(meta => {
//not here
this.meta.attributes.push({label: "ID", name: "id"});
this.service.getTableData(this.name).subscribe(data => {
this.exampleDatabase.loadData(data);
let cols = [];
for (let i = 0; i < this.meta.attributes.length; i++)
cols.push(this.meta.attributes[i].name);
this.displayedColumns = cols;
});
});
}
}
...exampleDatabase etc., same as from Angular Website
Thanks for help!
I was able to fix it by a workaround... I just added an *ngIf to the table and enable everything when service (meta) finished loading.
this.showTable = true;
console.log('table set exists');
setTimeout(() => { // necessary waiting for DOM
this.displayedColumns = ['id'];
console.log('nameCol set shown');
}, 1);
I had the same issue where it wasn't displaying. I solved it by adding an empty constructor: constructor(){} into the class or it won't set up the table properly
Related
I'm trying to figure out how to get the current changes in a 'contenteditable' and update it in the row that it was changed.
<tbody>
<!-- Loop through the list get the each data -->
<tr v-for="item in filteredList" :key="item">
<td v-for="field in fields" :key="field">
<p contenteditable="true" >{{ item[field] }}</p>
</td>
<button class="btn btn-info btn-lg" #click="UpdateRow(item)">Update</button>
<button class="btn btn-danger btn-lg" #click="DelteRow(item.id)">Delete</button>
</tr>
</tbody>
Then in the script, I want to essentially update the changes in 'UpdateRow':
setup (props) {
const sort = ref(false)
const updatedList = ref([])
const searchQuery = ref('')
// a function to sort the table
const sortTable = (col) => {
sort.value = true
// Use of _.sortBy() method
updatedList.value = sortBy(props.tableData, col)
}
const sortedList = computed(() => {
if (sort.value) {
return updatedList.value
} else {
return props.tableData
}
})
// Filter Search
const filteredList = computed(() => {
return sortedList.value.filter((product) => {
return (
product.recipient.toLowerCase().indexOf(searchQuery.value.toLowerCase()) != -1
)
})
})
const DelteRow = (rowId) => {
console.log(rowId)
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowId}`, {
method: 'DELETE'
})
.then((response) => {
// Error handeling
if (!response.ok) {
throw new Error('Something went wrong')
} else {
// Alert pop-up
alert('Delete successfull')
console.log(response)
}
})
.then((result) => {
// Do something with the response
if (result === 'fail') {
throw new Error(result.message)
}
})
.catch((err) => {
alert(err)
})
}
const UpdateRow = (rowid) => {
fetch(`${import.meta.env.VITE_APP_API_URL}/subscriptions/${rowid.id}`, {
method: 'PUT',
body: JSON.stringify({
id: rowid.id,
date: rowid.date,
recipient: rowid.recipient,
invoice: rowid.invoice,
total_ex: Number(rowid.total_ex),
total_incl: Number(rowid.total_incl),
duration: rowid.duration
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
})
})
}
return { sortedList, sortTable, searchQuery, filteredList, DelteRow, UpdateRow }
}
The commented lines work when I enter them manually:
// id: 331,
// date: rowid.date,
// recipient: 'new R',
// invoice: 'inv500',
// total_ex: Number(500),
// total_incl: Number(6000),
// duration: 'Monthly'
Each cell has content editable, I'm not sure how to update the changed event
The way these run-time js frontend frameworks work could be summarized as "content is the function of data". What I mean is the html renders the data that you send it. If you want the data to be updated when the user changes it, you need to explicitly tell it to do so. Some frameworks (like react) require you to setup 1-way data binding, so you have to explicitly define the data that is displayed in the template, as well as defining the event. Vue has added some syntactic sugar to abstract this through v-model to achieve 2-way binding. v-model works differently based on whichever input type you chose, since they have slightly different behaviour that needs to be handled differently. If you were to use a text input or a textarea with a v-model="item[field]", then your internal model would get updated and it would work. However, there is no v-model for non-input tags like h1 or p, so you need to setup the interaction in a 1-way databinding setup, meaning you have to define the content/value as well as the event to update the model when the html tag content changes.
have a look at this example:
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 contenteditable #input="({target})=>msg=target.innerHTML">{{ msg }}</h1>
<h2 contenteditable>{{ msg }}</h2>
<input v-model="msg">
</template>
If you change the h2 content, the model is not updated because vue is not tracking the changes. If you change through input or h1, the changes are tracked, which will also re-render the h2 and update its content.
TL;DR;
use this:
<p
contenteditable="true"
#input="({target})=>item[field]=target.innerHTML"
>{{ item[field] }}</p>
In my app, I have a list where the user can add to and delete elements from it. My problem is, when I click an element (it can be in the middle, at the end etc.), it deletes the first element of the list. And when I refresh the page, I can see the previously 'deleted' elements. Like I haven't deleted anything. Here is my code. What's wrong with it and how should I fix it?
HTML:
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row)">block</mat-icon>
</button>
TS:
deleteWorkItem(row: IProduct, index: number) {
let supplierProduct: ISupplierProduct = {
Supplier: {
SupplierId: this.SupplierId
},
Product: {
ProductId: row.ProductId
}
};
this.confirmDialogRef = this._dialog.open(FuseConfirmDialogComponent, {
disableClose: false
});
this.confirmDialogRef.componentInstance.confirmMessage = 'Ürünü silmek istiyor musunuz?';
this.confirmDialogRef.afterClosed().subscribe(result => {
if (result) {
this._service.update('Supplier/DeleteSupplierProduct', supplierProduct).subscribe(response => {
this._customNotificationService.Show('Ürün silindi', 'Tamam', 2);
});
let tempData = this.dataSource.data.slice(0);
tempData.splice(index, 1);
this.dataSource = new MatTableDataSource(tempData);
this.EditIndex = undefined;
this._products = this.dataSource.data;
this.ProductChange.emit(this._products);
}
});
}
You don't seem to pass index into deleteWorkItem method.
You need to declare a template variable within *ngFor as follows:
<div *ngFor="let row of data; let i = index">
...
<button mat-icon-button>
<mat-icon (click)="deleteWorkItem(row, i)">block</mat-icon>
</button>
</div>
I am trying to bind mat-table with data from backend api based on this Angular Material Table Dynamic Columns without model. Here is the .ts file content
technologyList = [];
listTechnology = function () {
this.http.get("https://localhost:44370/api/admin").subscribe(
(result: any[]) => {
this.technologyList = result;
//creating table begins here
var displayedColumns = Object.keys(this.technologyList[0]);
var displayedRows = Object.entries(this.technologyList[0]);
this.dataSource = new MatTableDataSource(this.technologyList);
}
I am getting response from backend as technologyListwhich is
>
Array(2)
0: {id: 1, technologyName: "Python", description: "Python123", commission: "20", imageURL: "https://cutt.ly/gePgUvn", …}
1: {id: 2, technologyName: "Ruby", description: "Python123", commission: "30", imageURL: "https://cutt.ly/gePgUvn", …}
length: 2
I am trying to bind this with html using .html file as
>
<div>
<mat-table #table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container *ngFor="let disCol of displayedColumns; let colIndex = index" matColumnDef="{{disCol}}">
<mat-header-cell *matHeaderCellDef>{{disCol}}</mat-header-cell>
<mat-cell *matCellDef="let element "> {{element[disCol]}}
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
The output is a blank white block. What am I doing wrong here? Any help would be much appreciated, thank you.
Response after the proposed change
output table
Try like this:
Working Demo
displayedColumns = [];
dataSource;
listTechnology = function () {
this.http.get("https://localhost:44370/api/admin").subscribe(
(result: any[]) => {
this.technologyList = result;
this.displayedColumns = Object.keys(this.technologyList[0]);
this.dataSource = new MatTableDataSource(this.technologyList);
})
}
I'm using Laravel 5.7 & VueJs 2.5.* ...
I did pagination for my invoice as well as vendor table. i have a create invoice form in which i have dropdown option for selecting vendors, i have more than 20 vendors, after doing pagination i see in my form i have only 10 vendors in my dropdown option... I don't know how to fix this issue.
Did Pagination Like this:
In HTML
<!--PAGINATION FOR VENDORS TABLE -->
<pagination :data="ticketInvoices" #pagination-change-page="getResults"></pagination>
<!--PAGINATION FOR VENDORS TABLE -->
<pagination :data="vendors" #pagination-change-page="getResults"></pagination>
methods:{} of both pagination
//METHOD FOR INVOICE
getResults(page = 1) {
axios.get("api/ticket-invoice?page=" + page).then(response => {
this.ticketInvoices = response.data;
});
},
//METHOD FOR VENDOR
getResults(page = 1) {
axios.get("api/vendor?page=" + page).then(response => {
this.vendors = response.data;
});
},
InvoiceController & VendorController
/*Invoice Controller*/
class TicketInvoiceController extends Controller
{
public function index()
{
$ticketInvoices = TicketInvoice::orderBy('created_at', 'desc')->paginate(10);
return $ticketInvoices;
}
/*Invoice Controller*/
class VendorController extends Controller
{
public function index()
{
$vendor = Vendor::paginate(10);
return $vendor;
}
Before Pagination
After Pagination
I have a kendo grid that is filtered by pushing values from a dropdownlist into the built in kendo filters. I can search the grid using the same method when I type values in a textbox and search. This is my kendo grid and the dropdown
#(Html.Kendo().DropDownListFor(model => model.MyObject.ID)
.Name("Objects").DataTextField("Value").DataValueField("Key")
.BindTo(#Model.MyObjectList).AutoBind(true)
.HtmlAttributes(new { id = "selectedObject" })
<a class="button" onclick="searchGrid()" id="search">Search</a>
#(Html.Kendo().Grid<MyViewModel>()
.Name("MyGrid").HtmlAttributes(new { style = " overflow-x:scroll;" })
.Columns(columns =>
{
columns.Bound(a => a.MyObject.Name).Title("Field 1");
columns.Bound(a => a.Column2).Title("Field 2");
}
.Pageable(page => page.PageSizes(true))
.Scrollable(src => src.Height("auto"))
.Sortable()
.Filterable()
.Reorderable(reorder => reorder.Columns(true))
.ColumnMenu()
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(10)
.Read(read => read.Action("GetList_Read", "MyController"))
)
)
<script>
function searchGrid()
{
selectedObject = $("#selectedObject").data("kendoDropDownList");
gridFilter = = { filters: [] };
if ($.trim(selectedRecipient).length > 0) {
gridListFilter.filters.push({ field: "Field 1", operator: "eq", value: selectedObject});
}
}
var grid = $("#MyGrid").data("kendoGrid");
grid.dataSource.filter(gridFilter);
</script>
My View model looks like
public class MyViewModel
{
public MyObject myObj {get;set;}
public string Column2 {get;set;}
}
The above function work when the search field is a textbox but it doesnt work when I am using a dropdown. I think it is because I am pushing the id of 'MyObject' into the grid filter while the grid is populated with the name of 'MyObject'. Can anyone show me how I can fix this. Thank you!!
There are two ways of handling this issue as I've found out. One is by pushing the selected values into the built in Kendo Filters or by passing a value to the controller action and filtering on the server side. First store the selected value of the dropdown on-change event to an object called 'selectedDropDownValue'
Filtering Client Side (Pushing values to kendo filters)
function searchGrid()
{
var gridListFilter = { filters: [] };
var gridDataSource = $("#MyGrid").data("kendoGrid").dataSource;
gridListFilter.logic = "and"; // a different logic 'or' can be selected
if ($.trim(selectedDropDownValue).length > 0) {
gridListFilter.filters.push({ field: "MyObject.MyObjectID", operator: "eq", value: parseInt(selectedDropDownValue) });
}
gridDataSource.filter(gridListFilter);
gridDataSource.read();
}
This pushes the selected value of the drop down to the built-in kendo grid filter
Filtering Server-side
Edit the DataSource read line by adding data
.Read(read => read.Action("GetApportionmentList_Read", "Apportionment").Data("AddFilter"))
Then create a javascript function to add the filter
function AddFilter()
{
return {filter:selectedDropDownValue};
}
Then inside the search grid JS function start with
function searchGrid()
{
var gridListFilter = { filters: [] };
var gridDataSource = $("#MyGrid").data("kendoGrid").dataSource;
gridDataSource.read();
}
After the read call you can still add client-side filters, apply the filter and then make the read recall afterwards.
The contoller signature should look like this
public JsonResult GetList_Read([DataSourceRequest] DataSourceRequest request, string filter)
filter will contain the value of the drop down selected
In your filter you are setting
value: selectedObject
but selectedObject is the actual Kendo DropDownList widget instance.
You need to get the value out of the widget using .value() or .text()
selectedObject = $("#selectedObject").data("kendoDropDownList").value();