How do I create an ag-Grid cell editor using React and TypeScript? - reactjs

I see that the ag-grid-react repo has types, and I also see that the ag-grid-react-example repo has examples. But how do I put the two together and create a cell editor with React and Types?
I'm guessing it's something like this but I can't make TypeScript happy:
class MyCellEditor implements ICellEditorReactComp {
public getValue() {
// return something
}
public render() {
const { value } = this.props
// return something rendering value
}
}

I implemented ICellEditor and used ICellEditorParams for prop definitions. For example, this MyCellEditor example from their documentation:
// function to act as a class
function MyCellEditor () {}
// gets called once before the renderer is used
MyCellEditor.prototype.init = function(params) {
// create the cell
this.eInput = document.createElement('input');
this.eInput.value = params.value;
};
// gets called once when grid ready to insert the element
MyCellEditor.prototype.getGui = function() {
return this.eInput;
};
// focus and select can be done after the gui is attached
MyCellEditor.prototype.afterGuiAttached = function() {
this.eInput.focus();
this.eInput.select();
};
// returns the new value after editing
MyCellEditor.prototype.getValue = function() {
return this.eInput.value;
};
// any cleanup we need to be done here
MyCellEditor.prototype.destroy = function() {
// but this example is simple, no cleanup, we could
// even leave this method out as it's optional
};
// if true, then this editor will appear in a popup
MyCellEditor.prototype.isPopup = function() {
// and we could leave this method out also, false is the default
return false;
};
became this:
class MyCellEditor extends Component<ICellEditorParams,MyCellEditorState> implements ICellEditor {
constructor(props: ICellEditorParams) {
super(props);
this.state = {
value: this.props.eGridCell.innerText
};
}
// returns the new value after editing
getValue() {
// Ag-Grid will display this array as a string, with elements separated by commas, by default
return this.state.value;
};
// Not sure how to do afterGuiAttached()
// if true, then this editor will appear in a popup
isPopup() {
return true;
};
render() {
return (
<div>
hello
</div>
);
}
}

Related

Add another handler for "SyntheticEvent"

There's a 3rd-party library that creates table rows and buttons inside those rows. Both <tr> and <button> are constructed with an "onClick" property, but the authors forgot to call stopPropagation() and so when clicking a button it also triggers the <tr> handler:
render: (_text: any, record: TableRecord) => {
return createElement(
"button",
{
className: button.actionButtonClass,
onClick: () => {
onClickHandler(
record,
button.actionButtonOnClickAction,
button.actionButtonOnClickMf,
button.actionButtonOnClickNf,
button.actionButtonOnClickForm,
button.actionButtonOnClickOpenPageAs
);
}
I can't alter the code above, but I tried to add another handler:
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
if (!btn._my_have_stopPropagation) {
btn._my_have_stopPropagation = true;
btn.addEventListener("click", function(ev) {
ev.stopPropagation();
});
}
});
};
However, I learned that PointerEvent is not handled directly. It first bubbles to the container, then in the container's click handler it's wrapped with SyntheticEvent and that SyntheticEvent is passed back to the button. So calling stopPropagation() on the native event will prevent what we defined in the "onClick" property.
How do I subscribe to SynteticEvent so I can call stopPropagation on that event?
Upd This is what I came up with. It's full of hacks
// https://github.com/mendixlabs/mendix-tree-table/issues/35 BEGIN
function installOnClickHook(props) {
if (!props.onClick._my_have_stopPropagation) {
var oldOnClick = props.onClick;
props.onClick = function(ev) {
oldOnClick.apply(this, arguments);
ev.stopPropagation();
};
props.onClick._my_have_stopPropagation = true;
}
}
mxtreetable.componentDidUpdate = function() {
mxtreetabledom.querySelectorAll(".actionButton").forEach((btn) => {
var props = FindReactProps(btn);
installOnClickHook(props);
});
};
function FindReactProps(dom) {
const key = Object.keys(dom).find(key=>{
return key.startsWith("__reactProps$")
});
// Somebody assigns another __reactProps to <button> without componentDidUpdate() on parent component
dom._my_props = dom[key];
Object.defineProperty(dom, key, {configurable: true, get: function() {
return this._my_props;
}, set: function (x) {
this._my_props = x;
installOnClickHook(x);
} });
return dom[key];
}
// https://github.com/mendixlabs/mendix-tree-table/issues/35 END

Why Can't Iterate over an array in my model using the map() function

i have angular 7 component which is tied to a model and there is an array inside that model, the array was populated from a service. and it's populated.
the problem is i can't map over the array although it has elements there.
when i console it it shows the array has element. then i tried to console typeOf(array) it always gives object although it is an array !!.
i tried using this soluation but it didn't help either.
any help please?
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
Console.log(this.model); // the model has data at this point
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr); // nothing is displayed here
// this works why ??
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
As the request is asynchronous, you might need to place the logic within the subscribe,
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
const arr = this.model.bars.map(a=> {
// never comes here
return a;
});
console.log(arr);
});
subscription is asynchronous so while it is still working the next line operation in the execution stack will be performed in this case the map you have after the subscription meanwhile it is still being populated in the background. You can try mapping in another life cycle hook say viewChecked hopefully it works. #cheers
Please look at the comments
export class FooModel {
foo : Foo
bars: Bar[];
}
export class SomeComponent implements OnInit {
model: FooModel;
constructor(private service: ProjectService) {
this.model = new FooModel();
this.model.bars = [];
}
ngOnInit() {
this.service.getFoos().subscribe((result: any) => {
// data is populated fine
this.model= <FooModel>result.data;
});
// the following starts to execute even before the model is populated above.
const arr = this.model.bars.map(a=> {
// never comes here because this.model.bars is empty at here and the length is 0 and nothing inside map executes
return a;
});
console.log(arr); // nothing is displayed here because it has nothing inside
// this works why ?? because you are using on an array which has some items.
const arr2 = [1,2,3].map(s=> {
return s;
}
console.log(arr2); // it displays [1,2,3]
}
}
So as Sajeetharan suggested, you have keep it inside subscribe()

React function not recognized

In my react app, I have an onClick function that isn't being recognized (TypeError: _this2.click is not a function) when called from dynamically-generated components. I poked around for issues with functions not being bound correctly, but they seem to be. Here's the code:
class C extends React.Component {
constructor(props) {
super(props);
// Bind components
this.eventComponent = this.eventComponent.bind(this);
this.click = this.click(this);
}
/**
* Click function for when a user selects their choice
* #param {[int]} id [id of the event the user is selecting]
*/
click(id) {
console.log(id)
}
/**
* Draws an event component (dynamically generated)
* #param {[String]} name [name of the event]
* #param {[String]} summary [summary of event]
* #return {[Object]} [react element of an event]
*/
eventComponent(name, summary, id) {
if (name != null && summary != null) {
return (
<div >
<h1>{name}</h1>
<p>{summary}</p>
<button onClick={() => this.click(id)}>Here is a button!</button>
</div>
);
}
}
render() {
var event = this.state.event
var objArray = this.state.objArray
var eventMap;
if (event) {
eventMap = objArray.map(function(event) {
// Get first property
var firstProp;
var k;
for(var key in event) {
if(event.hasOwnProperty(key)) {
firstProp = event[key];
k = key;
break;
}
}
return this.eventComponent(firstProp.title, firstProp.summary, k);
}.bind(this))
} else {
eventMap = <p>No events found!</p>;
}
// Generate a default HTML object
var eventComponent = (
<div>
{eventMap}
</div>
);
return eventComponent;
}
}
in your constructor correct this this.click = this.click(this);
to this.click = this.click.bind(this);
The most easy and convenient way is to use arrow functions, so you don't need to do binding in constructor anymore, a lot easier, isn't it?
so just remove this from constructor:
this.click = this.click.bind(this);
and change your function to:
click = (id) => {
console.log(id)
}
As answered by Vikas,
either you can follow that approach or you can use arrow syntax for functions using which there will be no need to bind functions.
Eg.
Click = (Id) => {
} .

Adding glyphicon icons to form fields

I'm wanting to add icons to form fields like bootstrap has: http://getbootstrap.com/css/?#forms-control-validation
I was able to get the class to display properly on the form-group by adjusting the options:
successClass: 'has-success',
errorClass: 'has-error',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
but i'm unable to figure out the best way to add the error or checkmark glyphicon. I assume it may have something to do with the errorWrapper / errorContainer but there isn't one for successWrapper/container
I ended up coming up with something else:
var bootstrapParsleyOptions = {
successClass: 'has-success has-feedback',
errorClass: 'has-error has-feedback',
classHandler: function (_el) {
return _el.$element.closest('.form-group');
}
};
$.extend(true, ParsleyUI, {
enableBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
window.Parsley.on('form:init', function () {
$(this.$element).find(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
});
window.Parsley.on('field:validated', function () {
var element = this.$element;
if (this.validationResult == true) {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-remove').addClass('glyphicon-ok');
$(element).siblings(".sr-only").text("(success)");
} else {
$(element).siblings(".form-control-feedback").removeClass('glyphicon-ok').addClass('glyphicon-remove');
$(element).siblings(".sr-only").text("(error)");
}
});
},
clearBootstrap: function () {
$(".form-control-feedback").removeClass('glyphicon-ok').removeClass('glyphicon-remove');
}
});
To enable it:
$("#form").parsley(bootstrapParsleyOptions);
ParsleyUI.enableBootstrap();
To reset it:
$("#form").parsley(bootstrapParsleyOptions).reset();
ParsleyUI.enableBootstrap();
I imagine that you can obtain what you want with CSS, something like
.parsley-success::before { content: '√'; }

Can I create a TypeScript class within a function and refer to its parameters?

E.g. in angularJS I may use the following construction:
myApp.factory('MyFactory', function(injectable) {
return function(param) {
this.saySomething = function() {
alert("Param=" + param + " injectable=" +injectable);
}
};
});
This can later be used like this:
function(MyFactory) {
new MyFactory().saySomething();
}
When the function passed to the method factory gets invoked, the param injectable is caged and will further be available to new instances of MyFactory without any need to specify that parameter again.
Now I want to use TypeScript and obviously I want to specify that my MyFactory is newable, and has a function saySomething. How could I do this elegantly?
I could write something like this:
class MyFactory {
constructor(private injectable, private param) {}
saySomething() {
alert(...);
}
}
myApp.factory('myFactory', function(injectable) {
return function(param) {
return new MyFactory(injectable, param);
}
});
But this changes the API:
function(myFactory) {
myFactory().saySomething();
}
I wonder if it could be more elegant, because I like how the "new" expresses quite clearly that a new unique object is created and this object creation is the whole purpose of the factory.
** Edit: TypeScript >= 1.6 supports class expressions and you can now write things like:
myApp.factory(injectable: SomeService) {
class TodoItem {
...
}
}
** Original answer:
I have the same problem: with AngularJS and ES5, I enjoy dependency injection not polluting constructors and be able to use the new keyword.
With ES6 you can wrap a class inside a function, this is not yet supported by TypeScript (see https://github.com/Microsoft/TypeScript/issues/307).
Here what I do (MyFactory is now class TodoItem from a todo app to be more relevant):
class TodoItem {
title: string;
completed: boolean;
date: Date;
constructor(private injectable: SomeService) { }
doSomething() {
alert(this.injectable);
}
}
class TodoItemFactory() {
constructor(private injectable: SomeService) { }
create(): TodoItem {
return new TodoItem(this.injectable);
}
// JSON from the server
createFromJson(data: any): TodoItem {
var todoItem = new TodoItem(this.injectable);
todoItem.title = data.title;
todoItem.completed = data.completed;
todoItem.date = data.date;
return todoItem;
}
}
// In ES5: myApp.factory('TodoItem', function(injectable) { ... });
myApp.service('TodoItemFactory', TodoItemFactory);
class TodosCtrl {
// In ES5: myApp.controller('TodosCtrl', function(TodoItem) { ... });
constructor(private todoItemFactory: TodoItemFactory) { }
doSomething() {
// In ES5: var todoItem1 = new TodoItem();
var todoItem1 = this.todoItemFactory.create();
// In ES5: var todoItem2 = TodoItem.createFromJson(...)
var todoItem2 = this.todoItemFactory.createFromJson(
{title: "Meet with Alex", completed: false}
);
}
}
This is less elegant than with ES5 and functions (and not using classes with TypeScript is a no go) :-/
What I would like to write instead:
#Factory
#InjectServices(injectable: SomeService, ...)
class TodoItem {
title: string;
completed: boolean;
date: Date;
// No DI pollution
constructor() { }
saySomething() {
alert(this.injectable);
}
static createFromJson(data: string): TodoItem {
...
}
}
#Controller
#InjectFactories(TodoItem: TodoItem, ...)
class TodosCtrl {
constructor() { }
doSomething() {
var todoItem1 = new TodoItem();
var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
}
}
Or with functions:
myApp.factory(injectable: SomeService) {
class TodoItem {
title: string;
completed: boolean;
date: Date;
// No constructor pollution
constructor() { }
saySomething() {
alert(injectable);
}
static createFromJson(data: string): TodoItem {
...
}
}
}
myApp.controller(TodoItem: TodoItem) {
class TodosCtrl {
constructor() { }
doSomething() {
var todoItem1 = new TodoItem();
var todoItem2 = TodoItem.createFromJson({title: "Meet with Alex"});
}
}
}
I could write something like this
This is what I do
Can I create a TypeScript class within a function
No it needs to be at the top level of the file or in a module. Just FYI if were able to create it inside a function the information would be locked inside that function and at least the type info would be useless.
What's the reason for instantiating multiple instances of MyFactory? Would you not want a single instance of your factory to be injected into your dependent code?
I think using the class declaration you provided will actually look like this once injected:
function(myFactory) {
myFactory.saySomething();
}
If you are really needing to pass a constructor function into your dependent code, then I think you will have to ditch TypeScript classes, since they can't be defined inside of a function which means you would have no way to create a closure on a variable injected into such function.
You do always have the option of just using a function in TypeScript instead of a class. Still get the strong typing benefits and can call 'new' on it since it is still a .js function at the end of the day. Here's a slightly more TypeScriptiffied version:
myApp.factory('MyFactory', (injectable: ng.SomeService) => {
return (param: string) => {
return {
saySomething: () {
alert("Param=" + param + " injectable=" +injectable);
}
};
};
});

Resources