I am trying to implement myown cell renderer following this example https://www.ag-grid.com/javascript-data-grid/cell-rendering/ , but in typescript .
When I declare a class which implements ICellRenderer interface and try to implement the inteface methods I get a compile time error
"Property 'eGui' does not exist on type 'MessageCellRenderer'."
This is part of my ts file where I define the renderer as well as the view which contains the grid.
NOTE: The grid displays fine without the cell renderer but I need to style the cells based on displayed values , hence trying to use the cell render.
import {ErrorFilterView} from "./error-dialog/error-filter-view";
import {ICellRenderer} from 'ag-grid-community/main';
var agGrid = require('../../../../node_modules/ag-grid-community');
class MessageCellRenderer implements ICellRenderer{
init(params) {
this.eGui = document.createElement('span');
if (params.value !== '' || params.value !== undefined) {
this.eGui.innerHTML = `'<span class="error_read_#: params.value# error-item_#: Id#">#:Message#</span>'`;
}
}
// gets called once when grid ready to insert the element
public getGui() {
return this.eGui;
};
// gets called whenever the user gets the cell to refresh
public refresh(params) {
// set value into cell again
this.eValue.innerHTML = params.value;
};
// gets called when the cell is removed from the grid
public destroy() {
// do cleanup, remove event listener from button
};
}
export var NotificationsDialogView = (viewModel : ErrorDialogViewModel) => {
if (!viewModel.getIsInitialized()) return;
var messageGrid: any;
var config = (element : any, isInitialized : boolean, context : any) => {
mdlInit(element, isInitialized, context);
Figured this out - just needed to add the required variable definitions as the example was using javascript as opposed to typescript which is a strongly typed (kind of) languauge
Related
Simple: the computed value isn't updating when the observable it references changes.
import {observable,computed,action} from 'mobx';
export default class anObject {
// THESE WRITTEN CHARACTERISTICS ARE MANDATORY
#observable attributes = {}; // {attribute : [values]}
#observable attributeOrder = {}; // {attribute: order-index}
#observable attributeToggle = {}; // {attribute : bool}
#computed get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = attrName
);
return orderedAttributeKeys;
};
changeAttribute = (existingAttr, newAttr) => {
this.attributes[newAttr] = this.attributes[existingAttr].slice(0);
delete this.attributes[existingAttr];
this.attributeOrder[newAttr] = this.attributeOrder[existingAttr];
delete this.attributeOrder[existingAttr];
this.attributeToggle[newAttr] = this.attributeToggle[existingAttr];
delete this.attributeToggle[existingAttr];
console.log(this.orderedAttributeKeys)
};
}
After calling changeAttribute, this.orderedAttributeKeys does not return a new value. The node appears unchanged.
However, if I remove the #computed and make it a normal (non-getter) function, then for some reason this.orderedAttributeKeys does display the new values. Why is this?
EDIT: ADDED MORE INFORMATION
It updates judging by logs and debugging tools, but doesn't render on the screen (the below component has this code, but does NOT re-render). Why?
{/* ATTRIBUTES */}
<div>
<h5>Attributes</h5>
{
this.props.appStore.repo.canvas.pointerToObjectAboveInCanvas.orderedAttributeKeys.map((attr) => { return <Attribute node={node} attribute={attr} key={attr}/>})
}
</div>
pointerToObjectAboveInCanvas is a variable. It's been set to point to the object above.
The changeAttribute function in anObject is called in this pattern. It starts in the Attribute component with this method
handleAttrKeyChange = async (existingKey, newKey) => {
await this.canvas.updateNodeAttrKey(this.props.node, existingKey, newKey);
this.setState({attributeEdit: false}); // The Attribute component re-renders (we change from an Input holding the attribute prop, to a div. But the above component which calls Attribute doesn't re-render, so the attribute prop is the same
};
which calls this method in another object (this.canvas)
updateNodeAttrKey = (node, existingKey, newKey) => {
if (existingKey === newKey) { return { success: true } }
else if (newKey === "") { return { success: false, errors: [{msg: "If you'd like to delete this attribute, click on the red cross to the right!"}] } }
node.changeAttribute(existingKey, newKey);
return { success: true }
};
Why isn't the component that holds Attribute re-rendering? It's calling orderedAttributeKeys!!! Or am I asking the wrong question, and something else is the issue...
An interesting fact is this same set of calls happens for changing the attributeValue (attribute is the key in anObject's observable dictionary, attributeValue is the value), BUT it shows up (because the Attribute component re-renders and it pulls directly from the node's attribute dictionary to extract the values. Again, this is the issue, an attribute key changes but the component outside it doesn't re-render so the attribute prop doesn't change?!!!
It is because you have decorated changeAttribute with the #action decorator.
This means that all observable mutations within that function occur in a single transaction - e.g. after the console log.
If you remove the #action decorator you should see that those observables get updated on the line they are called and your console log should be as you expect it.
Further reading:
https://mobx.js.org/refguide/action.html
https://mobx.js.org/refguide/transaction.html
Try to simplify your code:
#computed
get orderedAttributeKeys() {
const orderedAttributeKeys = [];
Object.entries(this.attributeOrder).forEach(
([attrName, index]) => orderedAttributeKeys[index] = this.attributes[attrName])
);
return orderedAttributeKeys;
};
#action.bound
changeAttribute(existingAttr, newAttr) {
// ...
};
Also rename your Store name, Object is reserved export default class StoreName
I'm using ag-grid and I have a column definition as following :
{
headerName: "Color",
valueGetter: function (params) {
return JSON.parse(params.data.color).name;
},
field: 'color',
cellRenderer: function (params) {
if (angular.isDefined(params.data) && angular.isDefined(params.data.color)) {
var color = JSON.parse(params.data.color);
return '<div style="width: 50px; height: 18px; background-color:' + color.htmlValue + ';"></div>';
}
},
suppressMenu: true,
suppressSorting: true
}
When I export the grid in CSV format, I get undefined for the color column, which is a cell renderer, I searched for a solution for this and I found this in the official documentation :
The raw values, and not the result of cell renderer, will get used,
meaning:
Cell Renderers will NOT be used.
Value Getters will be used.
Cell Formatters will NOT be used (use processCellCallback instead).
As you can see I'm already using a valueGetter but I always get undefined in the exported data for the color column.
How can I solve this ?
You could solve it by using processCellCallback when exporting to CSV. This way you can see and control exactly what is going to be exported.
Besides the column definition, you can pass other parameters to your grid options.
From ag-grid docs: What gets exported
The raw values, and not the result of cell renderer, will get used,
meaning:
...
Cell Formatters will NOT be used (use processCellCallback instead).
So, let's say that you have your column definition in a variable called columnDefs. Now you pass that to your gridOptions.
const gridOptions = {
columnDefs: columnDefs,
}
The latter code should work. So, now you want to handle the clicking on CSV Export on the context menu (You can also do it with a custom button if you may).
Export to CSV:
Now you have to add the provided getContextMenuItems function to you gridOptions object. For more info: Configuring the Context Menu
const gridOptions = {
columnDefs: columnDefs,
getContextMenuItems() {
return [
{
name: 'CSV Export',
action: function(params) {
gridOptions.api.exportDataAsCsv({
processCellCallback: function(cell) {
// Manipulate the value however you need.
return cell.value;
},
});
},
},
];
},
};
The idea is to get the CSV Export and programmatically add what you need to happen in the action. On the action, what you need is to call the exportDataAsCsv function from the gridOptions.api. Now (I know this is a ton of information) one of the options you have is to include your processCellCallback function where you can make sure you pass the cell value. Doing so is very useful because you can manipulate the value however you may need (e.g. Adding a $ sign to a number that is supposed to be money).
Custom button:
There is not much to say in the case you need a custom button. The only thing you would need to do is make sure you call the exportDataAsCsv on the gridOptions.api when onclick event is fired.
Something like:
onClick() {
gridOptions.api.exportDataAsCsv({
processCellCallback: ...
});
}
As another answer mentions, the ag-grid docs specifically state "Cell Renderers will NOT be used":
https://www.ag-grid.com/javascript-grid-excel/#what-gets-exported
I did a workaround that calls the cellRenderer from the processCellCallback function, like this:
processCellCallback: function (cell) {
var cellVal = cell.value;
if(_.get(cell, 'column.colDef.cellRenderer')) {
cellVal = cell.column.colDef.cellRenderer({value: cell.value});
}
return cellVal;
}
A lil late to the party but I recently had the same inconvenience, missing the raw values because four columns needed the cellRenderer and as you know per the docs those values will not be used. In my example I needed to plug in the raw values for in_progress, complete, unanswered, & needs_review
gridApi.exportDataAsExcel({
...
processCellCallback: (params) => processCells(params)
});
const processCells = (cell) => {
let cellVal = cell.value;
const {
in_progress,
complete,
unanswered,
needs_review
} = cell.node.data.percentageStatus; // raw data source
if(!cell.value){ // for the 4 cols this was always undefined
switch(cell.column.colId){
case "in_progress":
cellVal = in_progress;
break;
case "completed":
cellVal = complete;
break;
case "unanswered":
cellVal = unanswered;
break;
case "needs_review":
cellVal = needs_review;
break;
default:
cellVal = "n/a";
}
}
return cellVal;
}
I've found these answers a little confusing. Hoping to help the next dev looking into this. Vue JS with Vux store.
const actions = {
exportPurchaseOrderLines({ state: stateObject }) {
//set gridApi in state
stateObject.gridApi.exportDataAsCsv({
// nice file name
fileName: `${stateObject.order.number}-export`,
// select only selected rows
onlySelectedAllPages: true,
processCellCallback: (params) => {
// use the header name to determine which column to process
if (params.column.colDef.headerName === 'Linked bills') {
// if there are linked bills, return the bill numbers
if (params.node.data.bills.length > 0) {
return params.node.data.bills.map((bill) => bill.number).join(', ');
}
}
// return all the other cells without issues
return params.value;
},
});
},
// more actions .....
};
This is how I solved this issue :
When I call the export function the params arg in the cellRenderer function is not the same when the grid is loaded, the params arg looks like this when we export :
{export:true, value: "{...}"}
the value is the value in the field key, so in the cellRenderer function I had to do like this :
if(angular.isDefined(params.export)){
return JSON.parse(params.value.slice(1, -1)).name;
}
Notice that double quotes are always added which looks weird, so when we have a string value in the field, params.value will look like this : ""theStringValue"", that's why I use params.value.slice(1, -1).
So I'm trying to build a dynamic form in Angular2 (and NativeScript)..
What i've done so far is:
Get JSON from REST API
Add Component (via a template) to the ViewContainerRef
Now I want to "submit" the form. To do this, I thought I could simply use the ViewContainerRef and get its "siblings" (which are dynamically created based on the before mentioned JSON) and loop through them and create a JSON object to post this.. But I don't understand how the Angular2 DOM works..
Here is the (partial) code I've got so far:
addComponent function
//... class DynamicComponent, has id and value properties
public addComponent(container: ViewContainerRef, template: string){
#Component({template: template})
class TemplateComponent{
public value = "test";
}
#NgModule({declarations: [TemplateComponent]})
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.filter((comp)=>
comp.componentType === TemplateComponent
);
const component = container.createComponent(factory[0]);
return TemplateComponent;
}
Create Components
#ViewChild("container",{ read: ViewContainerRef }) container : ViewContainerRef;
..
//foreach JSON element
let _component = <any>
this._dynamicComponent.addComponent(
this.container, elementObject.render()
);
//the render() returns things like : `<Label text="foo"></Label>`
..
_component.prototype.onTap = () => {
this.submit();
}
Submit Function
submit(){
let json=[];
for(let i=0;i<this.container.length;i++){
let component = this.container.get(i);
//Ideally I want to do this:
/*
json.push({
id:component.id,
val:component.value
});
*/
}
}
Basically, I get "component" in the submit function as a ViewRef object, but how do I continue from there?
Also, I'm fairly new to Angular2, so is this the right way? I've seen the form builder but I didn't get it to work properly with dynamically created elements..
#Vinay in this TypeScript + AngularJS 1: How to connect enum with select directive? question shows a relatively simple way to get a array for building a select drop-down in angular.
Unfortunately, I try to ape this code and I get errors ... first upon declaring the 'colors' array if I use var or let... (but it works if I don't). Unfortunately, that just moves the error to the next variable declaration in the setup of the for loop. Unfortunately, here, I can't not put in a let or a var.
I'm sure this is simple, but I'm just banging me head and missing it.
enum Color {
Green = <any>"Green",
Red = <any>"Red",
Blue = <any>"Blue"
}
export class ClassName {
colors: string[] = []; // <-- get error here if I declare var or let
for (var item in Color) { // <-- get error here
if (Color.hasOwnProperty(item)) {
this.colors.push(item);
}
}
}
Property declarations belong in the body, but executable code goes in the constructor:
export class ClassName {
colors: string[] = []; // <-- get error here if I declare var or let
constructor() {
for (var item in Color) { // <-- get error here
if (Color.hasOwnProperty(item)) {
this.colors.push(item);
}
}
}
}
I'm refactoring some of my React code for ease of use in places where I can't use Babel directly (such as in short embedded JavaScript on pages). To assist with this I'm setting up a short function that builds the components and passes props to them. This code works just fine:
components.js:
import ResponsiveMenu from './components/responsive-menu';
window.setupMenu = (items, ele) => {
ReactDOM.render(<ResponsiveMenu items={items}/>, ele);
};
static-js.html:
<div id="menu"></div>
<script>
setupMenu({ items: [] }, document.getElementById('menu');
</script>
However, when I attempt to turn it into something more generic to handle more components like so:
components.js:
import ResponsiveMenu from './components/responsive-menu';
import AnotherComp from './components/another-comp';
window.setupComponent = (selector, name, props) => {
let eles;
if (typeof selector == 'string') {
eles = [];
let nl = document.querySelectorAll(selector), node;
for (let i = 0; node = nl[i]; i++) { eles.push(node); }
} else {
eles = $.toArray(selector); // A helper function that converts any value to an array.
}
return eles.map (
(ele) => {
let passProps = typeof props == 'function' ? props(ele) : props;
return ReactDOM.render(React.createElement(name, passProps), ele);
}
);
};
static-js.html:
<div id="menu"></div>
<script>
setupComponent('#menu', 'ResponsiveMenu', { items: [] });
</script>
I then get this error: Warning: Unknown prop "items" on <ResponsiveMenu> tag. Remove this prop from the element. For details, see (really unhelpful shortened link that SO doesn't want me posting)
Please help me understand why this works for the JSX version and not for the more manual version of creating the component.
When you pass string parameter to React.createElement, it will create native DOM element and there is no valid html DOM ResponsiveMenu.
You can store element into hash and store it into window variable.
Example:
// store component into window variable
window.components = {
ResponsiveMenu: ResponsiveMenu
}
//extract component from window variable by name
React.createElement(window.components[name], passProps)