I'm pretty new to Vue and I love it so far. However, I've encountered the following problem and I'll be thankful for any suggestions on how to get around it.
I would like to iterate recursively on an objects to get a nested table.
I have this template:
<script type="text/x-template">
<tr>
<td><a v-on:click="toggle">[+]</a></td>
<td>...</td>
</tr>
<tr is="row-item" v-show="open" ...></tr>
</script>
It's recursive, so each line tr has an other hidden line tr below.
When the user click on the [+] on the parent line, the children lines will appear.
I already try to wrap the content within a tbody tag, but then I get tbody inside tbody, which is still an illegal table layout and breaks it.
<script type="text/x-template">
<tbody>
<tr>
<td>...</td>
<td>...</td>
</tr>
<tr is="row-item" ...></tr>
</tbody>
</script>
Fiddle: https://jsfiddle.net/rafi16d/puwcs9ay/
Vue#1.x doesn't require exactly one root element. How can I do without ?
Has anyone run into anything similar?
Thanks.
(Sadly) Vue2 imperatively needs a real root element for each component.
So from that I think you don't have so many possibilities:
Do two components instead of a single one, and iter both of them with a virtual node:
<table>
<tbody>
<template for="row in someRows">
<tr>mainRow</tr>
<tr>subRow</tr>
</template>
</tbody>
</table>
Forget the idea to do a real table. Emulate the layout by using <div> and the related display css properties instead.
Alright i managed to do it. Red borders are for you to see the structure, you may want to adjust the padding.
https://jsfiddle.net/guanzo/puwcs9ay/9/
Several hacks, and ideas i borrowed from how jQuery Datatables does nested rows:
You can put pretty much anything in a td. So make the root element a tr, nest a td with colspan 100%, then you can nest a table. Since you can put tr inside of table, the recursion begins.
tr -> td -> table -> tr -> td -> table ad infinitum
<td colspan="42">
42, or any number that's definitely higher than your column count, is basically equivalent to colspan="100%".
You also can - and probably should - use the length of your array to give the colspan the correct number of columns, i was just lazy.
Inner trs have their default css changed.
.inner-tr{
display: table;
width:100%;
}
If you flatten the tree (normalize it) in a computed property, then you can render the HTML table without recursion within the component itself, and thus avoid hacking or generating invalid HTML.
Vue.component('tree-table-row', {
data() {
return {
indent: 20,
left_padding: 5
};
},
props: ['node'],
template: '#tree-table-row'
});
var app = new Vue({
el: '#tree-as-table',
data: {
tree: {
id: 0,
value: "fruits",
name: "root",
children: [
{
id: 1,
name: "A",
children: [{
id: 2,
name: "B",
value: "banana"
}],
value: "apple"
},
{
id: 3,
name: "C",
children: [{
id: 4,
name: "D",
value: "durian"
}],
value: "cherry"
}
]
}
},
computed: {
tree_array() {
return this.normalizedTree(this.tree, 0);
}
},
methods: {
normalizedTree(node, level) {
var _this = this;
var normal_element = {id: node.id, name: node.name, value: node.value, level: level};
var array_fragment = [normal_element];
if (node.children && node.children.length > 0) {
var i;
for (i=0; i < node.children.length; i++) {
array_fragment = array_fragment.concat(_this.normalizedTree(node.children[i], level+1));
};
}
return array_fragment;
}
}
});
.vue-table {
border-collapse: collapse;
font-family: sans-serif;
}
td {
border: 1px solid lightgrey;
padding: 2px 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script type="text/x-template" id="tree-table-row">
<tr>
<td :style="{'padding-left': node.level * indent + left_padding + 'px'}">{{node.name}}</td>
<td>{{node.value}}</td>
</tr>
</script>
<div id="tree-as-table">
<table class="vue-table">
<tr is="tree-table-row" v-for="(node, index) in tree_array" :node="node" :key="node.id"/>
</table>
</div>
Related
I am trying to select all table cells that have colSpan or rowSpan bigger than 1. I know that you can do querySelectorAll('td[colspan="3"]') to select cells that meet a narrower condition.
But I need something like querySelectorAll('td[colspan>"1"]').
It is not possible to put conditional operator inside the querySelector. In your case, if you want to achieve it using only CSS selector then you can ignore the particular colspan condition and select the remaining using :not operator.
td[colspan]:not(td[colspan = "1"]) {
background : red;
}
table, th, td {
border: 1px solid black;
}
td[colspan]:not(td[colspan = "1"]) {
background : red;
}
<table>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
<tr>
<td colspan="1">January</td>
<td>$100</td>
</tr>
<tr>
<td>February</td>
<td>$80</td>
</tr>
<tr>
<td colspan="2">Sum: $180</td>
</tr>
</table>
According to this link : https://bobbyhadz.com/blog/javascript-get-all-elements-by-data-attribute#get-all-dom-elements-by-partial-match-of-a-data-attribute
you can get elements with data-attribute starts with, ends with or contains with something like below:
// ✅ Get all where value of data-id starts with `bo`
const elements1 = document.querySelectorAll('[data-id^="bo"]');
console.log(elements1); // 👉️ [div, div]
// ✅ Get all where value of data-id ends with `ox1`
const elements2 = document.querySelectorAll('[data-id$="ox1"]');
console.log(elements2); // 👉️ [div]
// ✅ Get all where value of data-id contains with `box`
const elements3 = document.querySelectorAll('[data-id*="box"]');
console.log(elements3); // 👉️ [div, div]
in your case you can do something like that:
const getAllTdColspanBiggerThanOne= (a) => {
for (let i in a) if (a.hasOwnProperty(i)) {
if (a[i].getAttribute('colspan') > 1) {
alert(a[i].getAttribute('colspan'));
}
}
}
const td = document.querySelectorAll("td[colspan]") // or document.querySelectorAll("[colspan]")
const tdColspanBiggerThanOne = getAllTdColspanBiggerThanOne(td)
I am running into issues in getting the alternate rows with 2 different colors. I have a JSON data coming from backend, which is a dynamic array of objects. I need to show the data in the table as in the example here:
https://stackblitz.com/edit/create-xpkbjm?file=app/app.component.html
The odd, even rule that I find in most Stackoverflow questions doesn't seem to work here.
I would like to show the rows with alternate colors(the rows come as groups). The data should be presented similar to the link in the above stackblitz, but each rows should have alternate color. The odd, even rule only work for each group, but doesn't alternate the color in the table as a whole.
You can do this with plain old css,
tr:nth-child(2n) {
background: lightgray;
}
You can update the object that you're using to loop over and create the table.
Currently, you're using two loops,
one over tbody with groups
and one over tr with group.events
Instead, you can create another variable that is a list of events,
// app.component.ts
events = this.groups.map(g => g.events).flat();
Which simplifies your HTML to,
<!-- app.component.html -->
<tr *ngFor="let event of events; index as i">
<td>
{{event.event}}
</td>
</tr>
Check on stackblitz
UPDATE:
You can track total elements using a group/count property on each event across the whole object like so,
//app.component.ts
constructor() {
this.createGroups();
}
createGroups() {
let count = 0;
this.groups.forEach(g => {
g.events.forEach(e => {
e.group = count++;
});
});
console.log(this.groups);
}
And then use [ngClass] to bind a css class based on this property,
<tbody>
<tr *ngFor="let group of groups; index as i">
<table>
<tbody>
<tr *ngIf="i!==0">
<td></td>
</tr>
<tr *ngFor="let event of group.events;" [ngClass]="{'even': event.group % 2 !== 0}">
<td>{{event.event}} {{event.group}}</td>
</tr>
</tbody>
</table>
</tr>
</tbody>
Updated stackblitz
You can do this using pure CSS without the need to modify the angular.
tbody tr
{
background-color: #FFFFFF;
color: #fff;
}
tbody tr:nth-child(odd)
{
background-color: #000000;
color: #fff;
}
table with a tr in it. Now I would like if you click on that table row the backgroundcolor changes. Why I want this is because I am using a tree table to open up this table row. And I would like it if you click on this table row that the background color of the whole row changes. Now if I close this table row (by clicking on it again) I would like that the backgroundcolor goes back to normal
<ng-template pTemplate="header">
<tr>
<th>Bedrijsnaam</th>
<th>Bedrijfstype</th>
<th>Status</th>
<th>Bank</th>
</tr>
</ng-template>
This is the tr I am talking about. I tried using ng-class but without any luck. Does someone know how to do this?
You can use (click) and [ngClass] on the tr.
Ex :
<tr (click)="toggleClass()" [ngClass]="{active: className}">
....
</tr>
in ts
let className = "";
toggleClass(){
if(this.className === "active"){
this.className = ""
}else{
this.className = "active";
}
}
in csss
.active{
background-color: yellow;
}
Note this implemenation is for one tr you may need to attach this className for each row.
This another solution and you don't need to create a property in the component
<tr #tr (click)="tr.classList.toggle('active')" >
....
</tr>
demo
Table.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-table',
templateUrl: './table.component.html',
styleUrls: ['./table.component.css']
})
export class TableComponent {
tableRowClicked = false;
toggleTableRowClicked() {
this.tableRowClicked = !this.tableRowClicked;
}
}
Create an instance variable tableRowClicked to manage whether the table row has been clicked or not.
Create a toggle function to toggle the variable true or false.
Table.component.html
<div>
<table>
<tr (click)="toggleTableRowClicked()" [class.table__row--clicked]="tableRowClicked">
<th>Bedrijsnaam</th>
<th>Bedrijfstype</th>
<th>Status</th>
<th>Bank</th>
</tr>
</table>
</div>
Invoke the toggle function using the click handler on the <tr>
Apply the table__row--clicked class when tableRowClicked=true
Table.component.css
.table {
&__row {
&--clicked {
background-color: red;
}
}
}
CSS to make the background-color: red; when the tableRowClicked = true
I have an Angular 2 App using the Kendo UI Grid. There I have a Grid showing some data (integer values). Is it possible, to colorize each cell according to it's type? Maybe adding css class to each cell based on the type?
right now, the data looks like this [{"a":4,"b"=35,...},{...},....] I also have types for each element but not yet saved in the data grid.
I have a suggestion it's still in form of pure js kendo (but you should be able to do it in angular 2 kendo), by using schema.parse or in angular 2 : after getting data from backend you could add additionals field in the after retrieving your data from rest endpoint. add your logic inside the looping in my case i just assign color at random
schema: {
parse : function(response){
var colors = [
'red',
'green',
'blue',
'yellow'
];
//loop through all you data, add adding aditional field.
//also here i randomize the color for each cell
for(var i = 0; i< response.d.results.length; i++){
response.d.results[i].cell1 = colors[ Math.floor(Math.random()*colors.length)];
response.d.results[i].cell2 = colors[ Math.floor(Math.random()*colors.length)];
response.d.results[i].cell3 = colors[ Math.floor(Math.random()*colors.length)];
response.d.results[i].cell4 = colors[ Math.floor(Math.random()*colors.length)];
}
return response
}
}
Then on the row template you could use it as class like this (look at the cell1,cell2,cell3,cell4 attribute) in kendo-angular2 reference detail row template :
<script id="rowTemplate" type="text/x-kendo-tmpl">
<tr data-uid="#: uid #">
<td class="photo #=data.cell1#">
<img src="../content/web/Employees/#:data.EmployeeID#.jpg" alt="#: data.EmployeeID #" />
</td>
<td class="details #=data.cell2#">
<span class="name">#: FirstName# #: LastName# </span>
<span class="title">Title: #: Title #</span>
</td>
<td class="country #=data.cell3#">
#: Country #
</td>
<td class="employeeID #=data.cell4#">
#: EmployeeID #
</td>
</tr>
</script>
then add the css
<style>
.red {
background-color: red;
}
.green {
background-color: green;
}
.blue {
background-color: blue;
}
.yellow {
background-color: yellow;
}
</style>
Working example in dojo
I am working on an asp.net mvc-4 web application. and inside my view i have the following WebGrid inside my Razor view:-
#{
var gridcolumns = new List<WebGridColumn>();
gridcolumns.Add(new WebGridColumn()
{
ColumnName="OrderID",
Header = "",
Style="hidden-phone",
CanSort = false,
Format =
#<text>
#Html.ActionLink("Edit","Edit", "Order",new { id = item.OrderID },null)
</text>
});
//other columns goes here..
var grid = new WebGrid(
canPage: true,
rowsPerPage: Model.PageSize,
canSort: true,
ajaxUpdateContainerId: "grid");
grid.Bind(Model.Content, rowCount: Model.TotalRecords, autoSortAndPage: false);
grid.Pager(WebGridPagerModes.All);
#grid.GetHtml(htmlAttributes: new { id = "grid" }, // id for ajaxUpdateContainerId parameter
fillEmptyRows: false,
tableStyle: "table table-bordered table-hover",
mode: WebGridPagerModes.All,
columns: gridcolumns
);
}
now i want to hide specif WebGridColumns on small devices. so i have defined the following inside the web grid as shown in the above code:-
Style="hidden-phone",
now this will hide the column content inside small devices (phones) , but still the column header will be shown. and i ended up having different number of headers compared to the body columns when i am viewing the WebGrid on small screens (phone).
so can anyone adivce how i can force the Style="hidden-phone", to apply to the column content and the column header. in other word to hide the specified column/s when i am viewing my WebGrid inside small sized screens ? as seems the WebGrid's Style property will NOT be applied to specified column header ?
The simplest solution would be to hide header at all by using:
displayHeader:false
This is the option of WebGrid object, but if you need it then the only solution would be to hide column header by cell number:
$(function () {
$('#grid thead tr th:eq(0)').addClass('hidden-phone');
})
UPDATE
Ok, so lets assume that you add to column class hidden-phone like in your example then you can find corresponding header like that:
$(function () {
$('#grid tbody tr:first td.hidden-phone').each(function(index, td){
$('#grid thead tr th:eq(' + index + ')').addClass('hidden-phone');
});
})
Provided function will look through first row of a table and search for cells with class hidden-phone. Then for each cell it will add to it's corresponding header the same class.
UPDATE #2
If you use ajax paging and sorting then you need to slightly modify my example. Firstly move my js code to separate function:
function hideHeaders() {
$('#grid tbody tr:first td.hidden-phone').each(function (index, td) {
$('#grid thead tr th:eq(' + index + ')').addClass('hidden-phone');
});
};
$(function () {
hideHeaders();
})
Then pass that function name as a callback to ajax request:
var grid = new WebGrid(
canPage: true,
rowsPerPage: 1,
canSort: true,
ajaxUpdateContainerId: "grid",
ajaxUpdateCallback: "hideHeaders");
Notice ajaxUpdateCallback: "hideHeaders" - its the line that let now WebGrid which function needs to be called after ajax request has been completed.
I have made one code snippet please check it.
I have disabled the full 3rd column of table.
I think it will help you.
Thanks
$(function () {
debugger;
$('#grid tr').each(function(){
$(this).find('th:eq(2)').hide();
$(this).find('td:eq(2)').hide();
});
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="grid">
<tr>
<th>H1</th>
<th>H2</th>
<th>H3</th>
<th>H4</th>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
<td>d4</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
<td>d4</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
<td>d4</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
<td>d4</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
<td>d3</td>
<td>d4</td>
</tr>
</table>