Versions:
vueJS: 3.0.0
bootstrap: 5.0.0-beta3
ag-grid-community: 26.1.0
ag-grid-vue3: 26.1.2
See screenshot below. In my columnDefs for ag-grid, I have specified checkboxSelection: true for the Pending column. I do not wish to render any text in that column. Rather, I wish for a single checkbox that is centered in each cell. Can this be done? As things stand now, the checkbox is left-aligned in the cell.
Here is the columnDef for Pending:
{ headerName: "Pending", field: "pending", sortable: true, editable: true, checkboxSelection: true, cellStyle: () => getPageStatus() },
And here is styling for column headers:
.ag-header-cell-label {
justify-content: center;
}
Thanks for looking into it.
I scratched up a general solution that explains how to render custom HTML elements within a grid cell. Most of this solution is based on pre-existing articles and blogs out there. Note the data model here (rendered to the grid) assumes a pending property.
<template>
<ag-grid-vue
class="ag-theme-alpine"
:frameworkComponents="frameworkComponents"
:columnDefs="columnDefs"
:rowData="getRowData"
rowSelection="multiple"
domLayout="autoHeight"
editType="fullRow"
#grid-ready="onGridReady">
</ag-grid-vue>
<script>
import CheckboxRenderer from '#/components/CheckboxRenderer.vue';
export default {
data() {
return {
frameworkComponents: {
checkboxRenderer: CheckboxRenderer
},
columnDefs: [
{ headerName: "Pending", field: "pending", sortable: true, editable: true,
cellStyle: () => getPageStatus(), cellRenderer: 'checkboxRenderer', valueGetter: params => params ? null : null },
]
}
}
};
CheckboxRenderer.vue
<template>
<input ref="eGui" #click="checkedHandler" type="checkbox" v-model="params.data.pending" class="ag-input-field-input ag-checkbox-input" />
</template>
<script>
export default {
name: "CheckboxRenderer",
props: {
params: Object
},
methods: {
getGui() {
return this.$refs.eGui;
},
checkedHandler(e) {
let checked = e.target.checked;
let colId = this.params.column.colId;
this.params.node.setDataValue(colId, checked);
}
},
};
</script>
<style>
.ag-checkbox-input {
width: 18px;
height: 18px;
vertical-align: middle;
}
</style>
Related
I'm working with ag-Grid to create both tables and charts. I've read through all of the documentation on editing the labels on the axes of grouped, stacked and normalised columns and bar graphs and although there's information on how to format labels on AG-Grid charts, there isn't a way (it seems) to edit them such that they don't overlap when there are multiple labels.
For example I have a line chart below:
The labels on the x-axis are clustered together because each point on the line chart is titled a lengthy word/phrase. I've tried to add padding on the label (second line)
this.options.axes = [
{ type: 'category', position: 'left' },
{ type: 'number', position: 'bottom', label: { padding: 10 } }
]
However it doesn't help the situtation. How can I make it so that either the label is removed altogether or whenever the labels on the axes are over a certain length then it narrows down to just a few letters and then show an ellipses?
For example in the image, the first label 'Business-as-usual' would be narrowed down to just 'Bu..'?
there are two ways to accomplish this. padding won't help you much in this case.
you can use label formatter function to narrow down the label to just a few letters and then show an ellipses.
you can use rotation property to rotate the label(if chart container has enough space) to desired degree so that labels don't overlap.
var options = {
container: document.getElementById('myChart'),
data: [{
os: 'this is pretty long word and will overlap with other labels Windows',
share: 88.07
},
{
os: 'macOS',
share: 9.44
},
{
os: 'Linux',
share: 1.87
},
{
os: 'Other hgyhtghygyh Linux ',
share: 1.87
},
],
series: [{
type: 'column',
xKey: 'os',
yKeys: ['share'],
}, ],
axes: [{
type: 'category',
position: 'bottom',
title: {
text: 'Desktop Operating Systems',
enabled: false,
},
label: {
formatter: function(params) {
if (params.value.length > 10) {
return params.value.substr(0, 6) + '...';
}
return params.value
},
}
},
{
type: 'number',
position: 'left',
title: {
text: 'Market Share (%)',
enabled: false,
},
label: {
formatter: function(params) {
return params.value + '%';
},
},
},
],
legend: {
enabled: false,
},
};
var options1 = {
container: document.getElementById('myChart1'),
data: [{
os: 'this is pretty long word and will overlap with other labels Windows',
share: 88.07
},
{
os: 'macOS',
share: 9.44
},
{
os: 'Linux',
share: 1.87
},
{
os: 'Other hgyhtghygyh Linux ',
share: 1.87
},
],
series: [{
type: 'column',
xKey: 'os',
yKeys: ['share'],
}, ],
axes: [{
type: 'category',
position: 'bottom',
title: {
text: 'Desktop Operating Systems',
enabled: false,
},
label: {
rotation: 90
}
},
{
type: 'number',
position: 'left',
title: {
text: 'Market Share (%)',
enabled: false,
},
label: {
formatter: function(params) {
return params.value + '%';
},
},
},
],
legend: {
enabled: false,
},
};
agCharts.AgChart.create(options);
agCharts.AgChart.create(options1);
<!DOCTYPE html>
<html lang="en">
<head>
<script>
var __basePath = './';
</script>
<style media="only screen">
html,
body {
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
html {
position: absolute;
top: 0;
left: 0;
padding: 0;
overflow: auto;
}
body {
padding: 1rem;
overflow: auto;
}
</style>
<script src="https://unpkg.com/ag-charts-community#2.1.0/dist/ag-charts-community.min.js"></script>
</head>
<body>
<div id="myChart" style="height: 100%;"></div>
<div id="myChart1" style="height: 100%;"></div>
</body>
</html>
I have created two custom components, i.e custom tooltip component and custom datepicker component. when im trying to declare in framework it is not working.
Its taking only datepicker custom component not tooltip component. If datepicker component is removed then its taking tooltip component Its considering only one custom component in frameworkComponent.
Please find the below code:
CustomTooltip :
import {Component, ViewEncapsulation} from '#angular/core';
import {ITooltipAngularComp} from "ag-grid-angular";
#Component({
selector: 'tooltip-component',
template: `
<div class="custom-tooltip" [style.background-color]="data.color">
<p>{{tooltipData}}</p>
</div>`,
styles: [
`
:host {
position: absolute;
width: 250px;
height: 60px;
border: 1px solid cornflowerblue;
overflow: hidden;
pointer-events: none;
transition: opacity 1s;
}
:host.ag-tooltip-hiding {
opacity: 0;
}
.custom-tooltip p {
margin: 5px;
white-space: nowrap;
}
`
],
styleUrls: ['./custom-tooltip.component.scss']
})
export class CustomTooltip implements ITooltipAngularComp {
params: any;
data: any;
tooltipData: any;
agInit(params): void {
console.log("params",params.value);
this.params = params;
this.tooltipData=params.value;
this.data = params.api.getRowNode(params.rowIndex).data;
this.data.color = this.params.color || 'white';
}
}
CustomDateComponent:
import {Component, ElementRef, ViewChild} from '#angular/core';
import flatpickr from 'flatpickr'
#Component({
selector: 'app-loading-overlay',
template: `
<div #flatpickrEl class="ag-input-text-wrapper custom-date-filter fa">
<input type='text' data-input />
<a class='input-button' title='clear' data-clear>
<i class='fa fa-times'></i>
</a>
<a class="input-button" title="toggle" data-toggle>
<i class="fa fa-calendar"></i>
</a>
</div>
`,
styles: [
`
.custom-date-filter a {
position: relative;
right: 34px;
color: rgba(0, 0, 0, 0.54);
cursor: pointer;
}
.custom-date-filter:after {
content: '\f073';
display: block;
font-weight: 400;
font-family: 'Font Awesome 5 Free';
position: relative;
right: 25px;
pointer-events: none;
color: rgba(0, 0, 0, 0.54);
}
`
]
})
export class CustomDateComponent {
#ViewChild("flatpickrEl", {read: ElementRef}) flatpickrEl: ElementRef;
private date: Date;
private params: any;
private picker: any;
agInit(params: any): void {
this.params = params;
}
ngAfterViewInit(): void {
// outputs `I am span`
this.picker = flatpickr(this.flatpickrEl.nativeElement, {
onChange: this.onDateChanged.bind(this),
wrap: true
});
this.picker.calendarContainer.classList.add('ag-custom-component-popup');
}
ngOnDestroy() {
console.log(`Destroying DateComponent`);
}
onDateChanged(selectedDates) {
this.date = selectedDates[0] || null;
this.params.onDateChanged();
}
getDate(): Date {
return this.date;
}
setDate(date: Date): void {
this.date = date || null;
this.picker.setDate(date);
}
}
Im trying to use both custom component in one grid i.e:
this.columnDefs = [
{
headerName: 'Request Number', field: 'request_no', sortable: true, filter: 'agNumberColumnFilter'
},
{
headerName: 'Request Date', field: 'created_at', sortable: true, width: 300,
filter: "agDateColumnFilter",
filterParams: {
comparator: function (filterLocalDateAtMidnight, cellValue) {
var dateAsString = cellValue;
var dateParts = dateAsString.split("/");
var cellDate = new Date(Number(dateParts[2]), Number(dateParts[1]) - 1, Number(dateParts[0]));
if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
return 0;
}
if (cellDate < filterLocalDateAtMidnight) {
return -1;
}
if (cellDate > filterLocalDateAtMidnight) {
return 1;
}
}
}
},
{ headerName: 'Requested By', field: 'user_name', sortable: true, filter: 'agTextColumnFilter', tooltipField: 'user_name' },
{
headerName: 'Client', field: 'borrower_name', sortable: true, filter: 'agTextColumnFilter',
tooltipField: 'borrower_name', tooltipComponentParams: { color: "#ececec" }, width: 200
},
{
headerName: 'Debtor', field: 'customer_name', sortable: true, filter: 'agTextColumnFilter',
cellStyle: { color: 'blue', cursor: 'pointer' }, tooltipField: 'customer_name', width: 200
},
{
headerName: 'Current Limit', field: 'current_limit', sortable: true, filter: 'agNumberColumnFilter',
cellStyle: { textAlign: 'right' },
cellRenderer: this.CurrencyCellRenderer
},
{
headerName: 'Requested Limit', field: 'requested_limit', sortable: true, filter: 'agNumberColumnFilter',
cellStyle: { textAlign: 'right' },
cellRenderer: this.CurrencyCellRenderer
},
{
headerName: 'Approved Limit', field: 'approved_limit', sortable: true, filter: 'agNumberColumnFilter',
cellStyle: { textAlign: 'right' },
cellRenderer: this.CurrencyCellRenderer
},
{ headerName: 'Status', field: 'status', sortable: true, filter: 'agTextColumnFilter', width: 120, },
{ headerName: 'Comment', field: 'comment', sortable: true, filter: 'agTextColumnFilter', tooltipField: 'comment', width: 200 },
{
headerName: "",
suppressMenu: true,
suppressSorting: false,
cellClass: 'action-class',
width: 120,
template:
`<i class="fa fa-pencil-square-o" aria-hidden="true" data-action-type="view" pTooltip="Edit Queue" tooltipPosition="top"></i>
<i class="fa fa-info-circle" aria-hidden="true" data-action-type="history" pTooltip="View Comment History" tooltipPosition="top"></i>`
}
];
this.defaultColDef = {
enableValue: true,
sortable: true,
tooltipComponent: "customTooltip",
resizable: true
};
this.frameworkComponents = {
customTooltip: CustomTooltip,
agDateInput: CustomDateComponent };
Seems it's a custom tooltip issue (somewhere inside ag-grid), will try to inform the team and describe the bug.
In your case, you can specify your component (CustomDateComponent) directly via cellEditorFramework rather than cellEditor, without internal declaration in frameworkComponents block.
{
headerName: "Date",
field: "date",
width: 190,
editable:true,
cellEditorFramework:CustomDateComponent
}
Just for info: your CustomDateComponent not yet ready to be used as cellEditor
I created a simple responsive HTML + JS chart with chart.js which worked well. I decided to do it within Vue CLI and so have tried to switch it to vue-chartjs but the same chart always renders about 33% taller than my window and so presents vertical scrollbars (the width is fine). I recreated the problem with a sample trivial graph which I render with:
import {Line} from 'vue-chartjs'
export default {
extends: Line,
mounted () {
this.renderChart(data, options)
}
}
Note the data is trivial and the options are {}.
If I use chart.js in my Vue component, instead of vue-chartjs then it works fine. I.e. I do nothing more than delete the above code from my component and change it to the following then it renders fine, just like my sample HTML + chart.js version.
import Chart from 'chart.js'
function mount(el) {
return new Chart(
document.getElementById(el).getContext('2d'), {
type: 'line',
data: data,
options: options,
})
}
export default {
template: '<canvas id="chart"></canvas>',
mounted () {
self.chart = mount('chart')
}
}
I am using the default responsive: true and maintainAspectRatio: false of course, and have no explicit CSS or size settings anywhere.
Why can I not get the chart to render the height correctly when I use vue-chartjs? I am using vue-chartjs version 3.4.2 but have also tried a few versions back. I have looked all over the github bug tracker but seen nothing related.
UPDATE:
You should pass the options as prop or locally. But it's needed to add:
responsive: true
maintainAspectRatio: false
the desired height as well as the options as you said. Here's how it worked for me:
options:
options: {
scales: {
yAxes: [
{
ticks: {
beginAtZero: true
}
}]
},
responsive: true,
maintainAspectRatio: false
}
In template:
<bin-graph-weight :chart-data="datacollection" :styles="myStyles" :options="datacollection.options"/>
graph-component.js:
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted () {
// this.chartData is created in the mixin.
this.renderChart(this.chartData, this.options)
},
// If you want to pass options please create a local options object
watch: {
chartData () {
this.$data._chart.update()
}
}
}
Also had problem with height overflow and responsiveness, fixed by introducing flex parent container that takes up 100% of the space. After setting responsive and ratio options (check out related chartjs doc):
options: {
// ..
responsive: true,
maintainAspectRatio: true
}
I used following css to fill 100% of the parent (where TheChart is vue-chartjs component. Basically need to make sure the chart's parent is always filling 100% of it's own parent):
vue template
<v-container class="chart-container">
<TheChart :chartdata="chartData" :options="chartOptions" />
</v-container>
scss:
.chart-container {
flex-grow: 1;
min-height: 0;
> div {
position: relative;
height: 100%;
}
}
With responsiveness the chart rerenders with promises and actually sets two times.
With a watcher in Vue.js you can rerender every time with changes in the chartData.
Chart component:
<script>
import { Bar, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Bar,
mixins: [reactiveProp],
props: ['chartOptions'],
mounted() {
this.renderChart(this.chartData, this.chartOptions);
},
watch: {
chartData() {
this.renderChart(this.chartData, this.chartOptions);
},
},
};
</script>
Use together with dynamic styles.
Chart properties:
<template>
<div style="height:300px;">
<bar-chart :styles="myStyles" :chart-data="dataCollection"
:chart-options="chartOptions"></bar-chart>
</div>
</template>
<script>
import BarChart from './ChartBar.vue';
export default {
components: {
BarChart,
},
props: ['dataCollection'],
data() {
return {
myStyles: {
height: '300px',
width: '100%',
position: 'relative',
},
chartOptions: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
},
gridLines: {
display: true,
},
}],
xAxes: [{
ticks: {
beginAtZero: true,
},
gridLines: {
display: false,
},
}],
},
legend: {
display: true,
},
tooltips: {
enabled: true,
mode: 'single',
callbacks: {
label(tooltipItems, data) {
const { datasetIndex, index } = tooltipItems;
const value = data.datasets[datasetIndex].data[index];
if (parseInt(value, 10) > 999) {
return ` ${value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
}
return ` ${value}`;
},
},
},
responsive: true,
maintainAspectRatio: false,
height: 300,
},
};
},
};
</script>
<style lang="scss" scoped>
</style>
Input button - Is used to send a parameter back to stored proc to
fetch new data. (In the demo you don't need to put any value in there)
Reload Button - Is used to fetch new data based on the parameter.
I need to select some rows based on some condition from the service data. Rows get selected for the first time but when I reload the service data the rows get selected and then gets unselected automatically. Please check in the below plunker.
First time - Click Reload Button - Grid Loads Fine.
Second time - Click the Reload Button - Rows gets selected and then gets unselected soon after.
#Component({
selector: "my-app",
template: `<div style="height: 100%; padding-top: 35px; box-sizing: border-box;">
<ag-grid-angular
#agGrid
style="width: 100%; height: 100%;"
id="myGrid"
[rowData]="rowData"
class="ag-theme-balham"
[columnDefs]="columnDefs"
[defaultColDef]="defaultColDef"
[enableColResize]="true"
[suppressRowClickSelection]="true"
[rowSelection]="rowSelection"
(gridReady)="onGridReady($event)"
></ag-grid-angular>
</div>
<div style="position: absolute; top: 0px; left: 0px;">
<input type="Text" placeholder="Param to SP"/>
<button (click)="reloadData()">ReloadData</button>
</div>`
})
export class AppComponent {
private gridApi;
private gridColumnApi;
private rowData: any[];
private columnDefs;
private defaultColDef;
private rowSelection;
constructor(private http: HttpClient) {
this.columnDefs = [
{
headerName: "Athlete",
field: "athlete",
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true,
checkboxSelection: true
},
{
headerName: "Age",
field: "age"
},
{
headerName: "Country",
field: "country"
},
{
headerName: "Year",
field: "year"
},
{
headerName: "Date",
field: "date"
},
{
headerName: "Sport",
field: "sport"
},
{
headerName: "Gold",
field: "gold"
},
{
headerName: "Silver",
field: "silver"
},
{
headerName: "Bronze",
field: "bronze"
},
{
headerName: "Total",
field: "total"
}
];
this.defaultColDef = { width: 100 };
this.rowSelection = "multiple";
}
reloadData() {
this.http
.get("https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/olympicWinnersSmall.json")
.subscribe(data => {
this.rowData = data;
});
this.gridApi.forEachNode(function (node) {
node.setSelected(true);
});
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
}
}
Plunker - https://plnkr.co/edit/fuJ4DBc7Slp8MUTEKolJ?p=preview
The issue was angular was trying to bind the properties columndefs and rowdata on its own in random order, thus columndefs was getting called after and all the selected rows were getting de-selected.
The solution to the problem is don't bind columndefs and rowdata directly as a property to ag-grid instead, use gridOptions and set columndefs and rowdata using the API.
#Component({
selector: "my-app",
template: `<div style="height: 100%; padding-top: 35px; box-sizing: border-box;">
<ag-grid-angular
#agGrid
style="width: 100%; height: 100%;"
id="myGrid"
[gridOptions]="gridOptions"
class="ag-theme-balham"
[enableColResize]="true"
[suppressRowClickSelection]="true"
[rowSelection]="rowSelection"
(gridReady)="onGridReady($event)"
></ag-grid-angular>
</div>
<div style="position: absolute; top: 0px; left: 0px;">
<input type="Text" placeholder="Param to SP"/>
<button (click)="reloadData()">ReloadData</button>
</div>`
})
this.gridOptions.api.setColumnDefs(this.createParentColumnDefs());
this.gridOptions.api.setRowData(this.createParentRowData());
can I do a conditional template on the first column below?
for example:
If my row has score property and I want to hide the input when my score is above 70?
let columns = [
{ width: 30, suppressSorting: true, suppressMenu: true, template: '<input type="checkbox">' },
{ headerName: "Score", filter: 'number', valueGetter: (params : any) =>
params.data.traces ? (<Alert> params.data.traces[0]).severity : params.data.severity, width:70},
{ headerName: "Behaviour tags" },
{ headerName: "Host", field: "host_name" },
{ headerName: "Group Id", cellRenderer: 'group', width:140 },
{ headerName: "Comments",width:290 }
];
Use cellRenderer property in your column object
let columns = [{ width: 30, suppressSorting: true, suppressMenu: true,
cellRenderer: function (params) {
var display = 'block';
if (params.data.score > 70) {
display = 'none';
}
var html = '<input type="checkbox" style="display: ' + display + '">';
return html;
}
}]
In params.data you have all row data