I need to span some cells if next column contains "null" or ""; As on picture.
I have transposed input data, so i need to transform it before creating table.
And i dont know how to span columns in dynamic generated table.
I provide a demo-code on stackblitz;
I use angular-material package;
https://stackblitz.com/edit/angular-ivy-hknwsw?file=src%2Fapp%2Fapp.component.ts
You should just add colspan to your <td> tags and render them with ngIf when row data has information about this column. Smth like this.
https://stackblitz.com/edit/angular-ivy-evfxpi?file=src%2Fapp%2Fapp.component.ts
You can also check this thread How colSpan and row Span added to material table Header Angular 7?
Just fix your template like this
<mat-card>
<table mat-table [dataSource]="rowsInfo" class="mat-elevation-z4">
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<ng-container
[matColumnDef]="column"
*ngFor="let column of columnDefs"
>
<th mat-header-cell *matHeaderCellDef>{{ column }}</th>
<ng-container
*matCellDef="let element"
>
<td
mat-cell
*ngIf="element[column] !== undefined"
[attr.colspan]="element[column].colspan"
[ngStyle]="{ 'text-align': column === 'name' ? 'left' : '' }"
>
{{ element[column].value }}
</td>
</ng-container>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="columnDefs"
></tr>
<tr mat-row *matRowDef="let row; columns: columnDefs"></tr>
</table>
</mat-card>
and the function that generates rows
public mapRows(datas: ColumnData[]): {}[] {
const result = [
{
name: {
value: "row1",
colspan: 1
}
},
{
name: {
value: "row2",
colspan: 1
}
},
{
name: {
value: "row3",
colspan: 1
}
},
];
for (let index = 0; index < datas.length; index++) {
const element = datas[index];
const propName = `prop${index}`;
const prevPropName = `prop${index - 1}`;
const hasPrevProp = index > 0;
if (element.field1 || !hasPrevProp) {
result[0][propName] = {
value: element.field1,
colspan: 1
};
} else {
result[0][prevPropName].colspan++;
}
if (element.field2 || !hasPrevProp) {
result[1][propName] = {
value: element.field2,
colspan: 1
};
} else {
result[1][prevPropName].colspan++;
}
if (element.field3 || !hasPrevProp) {
result[2][propName] = {
value: element.field3,
colspan: 1
};
} else {
result[2][prevPropName].colspan++;
}
}
return result;
}
Related
I have a nested array of objects , so i am trying to display in table row only first 3 elements in array and after i am displaying remaining elements in array as a count (i.e +2).Now if i click on remain count i need to display all the elements in array on particular row click.
I am attaching the stack blitz URL for reference :- https://stackblitz.com/edit/primeng-chip-demo-agf8ey?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
Please help me on these issue.
Thanks in advance
try this:
<p-chip
*ngFor="let cc of slice(c); let i = index"
[label]="cc.name"
></p-chip>
in .ts file
onChip(val: any) {
this.chips[val].extand = true;
}
slice(cc: any, index: number) {
if(cc?.extand) {
return cc.values;
}
return cc.values.slice(1,3);
}
also add to every object extand: false
chips = [
{
id: 1,
values: [
{
name: 'one',
},
{
name: 'two',
},
{
name: 'three',
},
{
name: 'four',
},
{
name: 'five',
},
],
extand: false
},]
Create a template variable:
<h5>Basic</h5>
<div class="p-d-flex p-ai-center">
<table>
<tr>
<th>Id</th>
<th>Chips</th>
</tr>
<tr *ngFor="let c of chips; let val = index">
<td>{{ c.id }}</td>
<td #myCell>
<ng-container *ngIf="myCell.showAll">
<p-chip *ngFor="let cc of c.values" [label]="cc.name"></p-chip>
</ng-container>
<ng-container *ngIf="!myCell.showAll">
<p-chip
*ngFor="let cc of c.values | slice: 0:3"
[label]="cc.name"
></p-chip>
<p-chip
styleClass="chipMore"
*ngIf="c.values.length >= 3"
(click)="myCell.showAll = !myCell.showAll"
>+{{ c.values.length - 3 }}</p-chip
>
</ng-container>
</td>
</tr>
</table>
</div>
Play at edited stackblitz: https://stackblitz.com/edit/primeng-chip-demo-ykmg3t?file=src/app/app.component.html
I am fetching the JSON data(Orders) from REST API and displaying in a dynamic HTML table using Vue js. I have a "Print" button for each row in the table. The purpose of the button is printing the data of the row in a structure, basically a bill.
For that, I want to highlight the newly added row until the Print button is clicked by the user. How do I achieve this?
I'm refreshing the table every minute.
This is my code.
<tr v-for="orders, index in orders">
<th scope="row">{{ index + 1 }}</th>
<td>{{ orders.id }}</td>
<td>{{ orders.billing.first_name + " " +orders.billing.last_name }}</td>
<td>{{ orders.date_created }}</td>
<td>{{ orders.billing.phone}}</td>
<td>{{ orders.billing.address_1 + ", " + orders.billing.address_2 + ", " + orders.billing.city + orders.billing.postcode }}</td>
<td>{{ orders.line_items.name}}</td>
<td>{{ orders.total}}</td>
<td><button class="btn btn-primary" (click)="printBill(data)">Print</button></td>
</tr>
</tbody>
</table>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
orders: []
},
mounted: function() {
axios.get('https://localhost/Site/wp-json/wc/v3/orders?consumer_key=KEY&consumer_secret=KEY1')
.then(response => {
this.orders = response.data;
console.log(response);
})
.catch(error => {
console.log(error);
});
},
})
</script>
I wrote a small example, have a look:
<template>
<div id="app">*
<tr
v-for="(order, index) in orders"
:key="index"
:class="{highlight: orders[index].isPrinted === undefined}"
>
<th scope="row">{{ index + 1 }}</th>
<td>{{ order.name }}</td>
<td>{{ order.something}}</td>
<td>
<button class="btn btn-primary" #click="printBill(index)">Print</button>
</td>
</tr>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
orders: []
};
},
methods: {
printBill(index) {
//code to print the bill
//change flag
this.$set(this.orders[index], "isPrinted", true);
}
},
mounted() {
//axios request - data sample
this.orders = [
{
name: "first",
something: "whatever"
},
{
name: "second",
something: "whatever"
},
{
name: "third",
something: "whatever"
},
{
name: "fourth",
something: "whatever"
},
{
name: "fifth",
something: "whatever"
}
];
}
};
</script>
<style>
.highlight {
background-color: blue;
color: white;
}
th {
width: 20%;
}
td {
width: 20%;
}
</style>
You can run it here.
As you can see that I am adding a flag to elements in orders array whenever printBill method runs.
By tracking newly added property we can conditionally display highlight class.
Add an isPrinted flag to each row of data, making sure you retain this if rows had been previously flagged. Also, call the API every minute.
mounted: function() {
// Call the API the first time
this.refreshData()
// Then call the API every minute
this.setIntervalId = setInterval(this.refreshData, 60000)
},
beforeDestroy: function() {
// Stop refreshing data after the component is destroyed!
clearInterval(this.setIntervalId)
}
methods: {
// Extract refresh logic into a method
refreshData () {
axios.get('https://localhost/Site/wp-json/wc/v3/orders?consumer_key=KEY&consumer_secret=KEY1')
.then(response => {
// Note the orders we previously flagged as printed, so we can reapply the flag after refreshing
const previouslyFlaggedIds = this.orders.filter(x => x.is_printed).map(x => x.id);
this.orders = response.data.map(x => ({...x, is_printed: previouslyFlaggedIds.find(y => y === x.id) != null}));
})
.catch(error => {
console.log(error);
});
}
}
Use this to style the rows
<tr
v-for="(order, index) in orders"
:key="order.id"
:class="{highlight: !order.is_printed}"
>
Set is_printed when rows are printed.
<td><button class="btn btn-primary" #click="printBill(order)">Print</button></td>
methods: {
printBill(order) {
order.is_printed = true
}
}
Using Vue, I have displayed table with dynamic data pulled from external JSON.
I want to target the last column in the table body to replace its value with a fixed value for every row.
How would I do this?
Note that my script uses the initial value from the JSON data for that column to determine which class to put on that td.
Here is my code:
var dataURL = 'inc/data.json.php'
Vue.component('demo-grid', {
template: '#grid-template',
replace: true,
props: ['data', 'columns', 'filter-key'],
data: function() {
return {
data: null,
columns: null,
sortKey: '',
filterKey: '',
reversed: {}
}
},
compiled: function() {
// initialize reverse state
var self = this
this.columns.forEach(function(key) {
self.reversed.$add(key, false)
})
},
methods: {
sortBy: function(key) {
this.sortKey = key
this.reversed[key] = !this.reversed[key]
}
}
})
var demo = new Vue({
el: '#app',
data: {
searchQuery: '',
gridColumns: [...],
gridData: []
},
ready: function() {
this.fetchData()
},
methods: {
fetchData: function() {
var xhr = new XMLHttpRequest(),
self = this
xhr.open('GET', programsURL)
xhr.onload = function() {
self.gridData = JSON.parse(xhr.responseText)
}
xhr.send()
}
}
})
<table>
<thead>
<tr>
<th v-repeat="key: columns" v-on="click:sortBy(key)" v-class="active: sortKey == key">
{{key | capitalize}}
<span class="arrow" v-class="reversed[key] ? 'dsc' : 'asc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-repeat="
entry: data
| filterBy filterKey
| orderBy sortKey reversed[sortKey]">
<!-- here is where I wish to target the 5th in this row to change its value -->
<td v-repeat="key: columns" v-class="lvl-1 : entry[key] === '1', lvl-2 : entry[key] === '2', lvl-3 : entry[key] === '3'>
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
Compare the special $index property with the length of the array (or computed property), and then use a template fragment so you can switch out the <td>
<template v-repeat="column in columns">
<td v-show="$index < columns.length-1">All other columns...</td>
<td v-show="$index === columns.length-1">Last Column</td>
</template>
Solved it with:
<div v-if="$index === 4">
...
I'm binding JSON data to ng-table using Angular.js.
If any value is null then positions for all columns gets disturb. How can I fix the data with column header?
See this Plunker: http://plnkr.co/edit/Ixvp8B0dRwOBDHflmu2j?p=preview
Description should be null but all values shifted to left.
Or, if all values are null for any property hide that particular column.
In order to determine if a column is empty you need some sort of column configuration that gets created by iterating the data to see if all rows contain data for any of the headings (object keys).
Then you can use that column configuration array as the repeater for the <th> and <td>.
Example config:
[
{
"heading": "Id",
"display": true
},
{
"heading": "Title",
"display": true
},
{
"heading": "Description",
"display": true
},
{
"heading": "Test",
"display": false
}
]
HTML
<thead>
<tr>
<th ng-repeat="col in colConfig" ng-if="col.display">{{col.heading}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-repeat="col in colConfig" ng-if="col.display">
{{item[col.heading]}}
</td>
</tr>
</tbody>
Example config create
var keys = Object.keys(data[0]);
function createConfig() {
var validKeyCounts = {};
var colConfig;
keys.forEach(function (key) {
validKeyCounts[key] = 0;
})
data.forEach(function (row, idx) {
keys.forEach(function (key) {
if (row.hasOwnProperty(key) && row[key] !== null) {
validKeyCounts[key]++;
}
})
});
colConfig = keys.map(function (key) {
return {
heading: key,
display: validKeyCounts[key] > 0
}
});
return colConfig
}
I'm sure this could be optimized but is just a way to get started with functionality wanted
DEMO
I would like to render dynamically rows and columns using knockout. The idea is that I would like to populate each row with some cells and dynamically add more rows if needed.
lets assume that totall number of cells equals 4*number of rows, then I tried:
<table>
<tbody data-bind="foreach: model">
<!--ko if: $index() % 4 == 0--><tr><!--/ko-->
<td>
<label data-bind="text: Value"></label>
</td>
<td>
<input type="checkbox" data-bind="checked: IsChecked"/>
</td>
<!--ko if: $index() % 4 == 0--></tr><!--/ko-->
</tbody>
</table>
but it works like it was:
<table>
<tbody data-bind="foreach: model">
<!--ko if: $index() % 4 == 0-->
<td>
<label data-bind="text: Value"></label>
</td>
<td>
<input type="checkbox" data-bind="checked: IsChecked"/>
</td>
</tr><!--/ko-->
</tbody>
</table>
by not rendering whole row with content, is it possible with knockout to render all cells and add rows only when needed?
As a workaround I thinking about nested foreach, but it would require my model to change from single dimensional to two dimensional which seems odd.
Add another computed property that structures your data into rows:
<table>
<tbody data-bind="foreach: rows">
<tr>
<!-- ko foreach: $data -->
<td data-bind="text:$index"></td>
<td data-bind="text:fname"></td>
<td data-bind="text:lname"></td>
<!-- /ko -->
</tr>
</tbody>
</table>
with code:
var vm = {
people: ko.observableArray([
{ fname: 'fname', lname: 'lname' },
{ fname: 'fname', lname: 'lname' },
{ fname: 'fname', lname: 'lname' },
{ fname: 'fname', lname: 'lname' }
])
};
vm.rows = ko.computed(function () {
var itemsPerRow = 3, rowIndex = 0, rows = [];
var people = vm.people();
for (var index = 0; index < people.length; index++) {
if (!rows[rowIndex])
rows[rowIndex] = [];
rows[rowIndex].push(people[index]);
if (rows[rowIndex].length == itemsPerRow)
rowIndex++;
}
return rows;
}, vm);
$(function () {
ko.applyBindings(vm);
});
Your syntax will not work with knockout default templating engine just because it uses DOM.
If you need to do this, use string-based external templating engine (it will treat your template as string and will use regex and string manipulations, so you will be able to do this trick with conditional rendering of start/end tag).
Your example using underscore js:
http://jsfiddle.net/2QKd3/5/
HTML
<h1>Table breaking</h1>
<ul data-bind="template: { name: 'peopleList' }"></ul>
<script type="text/html" id="peopleList">
<table>
<tbody>
{{ _.each(model(), function(m, idx) { }}
{{ if (idx % 4 == 0) { }}
<tr>
{{ } }}
<td>
<label>{{= m.Value }}</label>
</td>
<td>
<input type="checkbox" data-bind="checked: m.IsChecked"/>
</td>
{{ if (idx % 4 == 3) { }}
</tr>
{{ } }}
{{ }) }}
</tbody>
</table>
</script>
Javascript (this includes underscore integration decribed here - http://knockoutjs.com/documentation/template-binding.html
_.templateSettings = {
interpolate: /\{\{\=(.+?)\}\}/g,
evaluate: /\{\{(.+?)\}\}/g
};
/* ---- Begin integration of Underscore template engine with Knockout. Could go in a separate file of course. ---- */
ko.underscoreTemplateEngine = function () { }
ko.underscoreTemplateEngine.prototype = ko.utils.extend(new ko.templateEngine(), {
renderTemplateSource: function (templateSource, bindingContext, options) {
// Precompile and cache the templates for efficiency
var precompiled = templateSource['data']('precompiled');
if (!precompiled) {
precompiled = _.template("{{ with($data) { }} " + templateSource.text() + " {{ } }}");
templateSource['data']('precompiled', precompiled);
}
// Run the template and parse its output into an array of DOM elements
var renderedMarkup = precompiled(bindingContext).replace(/\s+/g, " ");
return ko.utils.parseHtmlFragment(renderedMarkup);
},
createJavaScriptEvaluatorBlock: function(script) {
return "{{ " + script + " }}";
}
});
ko.setTemplateEngine(new ko.underscoreTemplateEngine());
/* ---- End integration of Underscore template engine with Knockout ---- */
var viewModel = {
model: ko.observableArray([
{ Value: '1', IsChecked: 1 },
{ Value: '2', IsChecked: 0 },
{ Value: '3', IsChecked: 1 },
{ Value: '4', IsChecked: 0 },
{ Value: '5', IsChecked: 1 },
])
};
ko.applyBindings(viewModel);
P.S.: but better avoid using tables for html layout. Your example can be rendered using inline-block elements with much cleaner code.