Add another handler for "SyntheticEvent" - reactjs

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

Related

Angular , how to handle when user click button or submit multiple times?

How can I prevent users from submitting a form multiple times? My current issue right now is when the user clicked the submit button multiple times it will create multiple users. It should create a user only once and wait before creating another user.
Here is what I have:
<button mat-flat-button color="primary" [disabled]="userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT'" (click)="createUser()">Create
User</button>
TypeScript:
createUser() {
this.accountService.create(this.modelForm.value).pipe(
finalize(() => {
this.isInProgress = false;
})
).subscribe({next: (res) => { this.notificationService.showSuccess('User has been created successfully.');
this._router.navigate(['settings/user']);
},
error: (err) => {this.notificationService.showError('Something went wrong, Try again later.');
this.isInProgress = false;
},
complete: () => {
this.isInProgress = false;
},
});
}
I have slightly updated your code,
1 - We will have to create a User button in the template And
<button #createUserBtn mat-flat-button color="primary" [disabled]="userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT'"> CreateUser </button>
2 - Access Create User button in .ts file
#ViewChild('createUserBtn', {static:true}) button;
3 - Create variable clicks$ to store click events
clicks$: Observable<any>;
4 - In ngOnInit: Initialize clicks$ variable to listen click events
this.clicks$ = fromEvent(this.button.nativeElement, 'click');
5 - In ngOnInit: On every click event i.e from click$ we will pass our event to exhaustMap
The beauty of exhaustMap is once the first (outer observable) event is triggered it will stop
listening to events(Outer Observable) untill it completes its inner observable
So in our case when the user clicks on the button the first time(event), the exhaustMap will stop listening to the button click events until it completes our API call createUser(). This API call observable we will handle using the handleResponse() method.
ngOnInit() {
this.clicks$ = fromEvent(this.button.nativeElement, 'click');
const result$ = this.clicks$.pipe(
tap(x => console.log('clicked.')),
exhaustMap(ev => {
console.log(`processing API call`);
return this.createUser();
})
);
result$.subscribe(this.handleResponse());
}
Create User API Call
createUser(): Observable<any> {
return this.accountService.create(this.modelForm.value).pipe(
finalize(() => (this.isInProgress = false))
);
}
To handle response
handleResponse(): any {
return {
next: res => {
this.notificationService.showSuccess('User has been created successfully.');
this._router.navigate(['settings/user']);
},
error: err => {
this.notificationService.showError('Something went wrong, Try again later.');
this.isInProgress = false;
}
complete: () => this.isInProgress = false;
};
}
Demo
If you can't access button you can move ngOnit Code to AfterViewInit
Let me know if there is any error because i have not fully tested your code.
ngAfterViewInit(): void {
fromEvent(this.button.nativeElement, 'click')
.pipe(
tap(x => console.log('clicked.')),
exhaustMap(ev => {
console.log(`processing API call`);
return this.createUser();
})
)
.pipe(tap(x => console.log('Api call completed....')))
.subscribe(this.handleResponse());
}
If the functionality you want is that the user should be restricted from clicking the button again till the API responds with a value for the prior click event, you can do the following,
In your component.html file,
<button mat-flat-button color="primary" [disabled]="isButtonDisabled()" (click)="createUser()">Create User </button>
In your component.ts file,
Create a boolean type variable, with initial value set to false.
disableUserCreation: boolean = false;
Create the following function,
isButtonDisabled(): boolean {
if (this.userStatus == 'USER_EXISTS_ON_CURRENT_ACCOUNT' || this.disableUserCreation) {
return true;
}
return false;
}
Then,
createUser() {
this.disableUserCreation = true;
this.accountService.create(this.modelForm.value).pipe(
finalize(() => {
this.isInProgress = false;
})
).subscribe({next: (res) => { this.notificationService.showSuccess('User has been created successfully.');
this._router.navigate(['settings/user']);
},
error: (err) => {this.notificationService.showError('Something went wrong, Try again later.');
this.isInProgress = false;
},
complete: () => {
this.isInProgress = false;
this.disableUserCreation = false;
},
});
}

How to redraw specific DOM asynchronously in Mithril

I have sub-components that are updated by injecting state from the parent component.
I need to populate the model using an asynchronous function when the value of the parent component changes.
And I want to draw a new subcomponent after the asynchronous operation is finished.
I checked the change of the parent component value in onbeforeupdate, executed the asynchronous function, and then executed the redraw function, but this gets stuck in an infinite loop.
...
async onbeforeupdate((vnode)) => {
if (this.prev !== vnode.attrs.after) {
// Update model data
await asyncRequest();
m.redraw();
}
}
view() {
return (...)
}
...
As far as I can tell onbeforeupdate does not seem to support async calling style, which makes sense because it would hold up rendering. The use-case for onbeforeupdate is when you have a table with 1000s of rows. In which case you'd want to perform that "diff" manually. Say by comparing items length to the last length or some other simple computation.
This sort of change detection should happen in your model when the parent model changes, trigger something that changes the child model. Then the child view will return a new subtree which will be rendered.
In this small example the list of items are passed directly from the parent to the child component. When the list of items increases, by pressing the load button, the updated list of items is passed to the child and the DOM is updated during the redraw. There is another button to toggle if the decision to take a diff in the view should be done manually or not.
You can see when the views are called in the console.
The second example is the more common/normal Mithril usage (with classes).
Manual Diff Decision Handling
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class AppModel {
/* Encapsulate our model. */
constructor() {
this.child = {
items: [],
manualDiff: true,
};
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.child.items[this.child.items.length] = values[i];
}
}
getChildItems() {
return this.child.items;
}
toggleManualDiff() {
this.child.manualDiff = !this.child.manualDiff;
}
getManualDiffFlag() {
return this.child.manualDiff;
}
}
function setupApp(model) {
/* Set our app up in a namespace. */
class App {
constructor(vnode) {
this.model = model;
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
manualDiff: this.model.getManualDiffFlag(),
items: this.model.getChildItems(),
}),
m('button[type=button]', {
onclick: (e) => {
this.model.toggleManualDiff();
}
}, 'Toggle Manual Diff Flag'),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.model.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
constructor(vnode) {
this.lastLength = vnode.attrs.items.length;
}
onbeforeupdate(vnode, old) {
if (vnode.attrs.manualDiff) {
// Only perform the diff if the list of items has grown.
// THIS ONLY WORKS FOR AN APPEND ONLY LIST AND SHOULD ONLY
// BE DONE WHEN DEALING WITH HUGE SUBTREES, LIKE 1000s OF
// TABLE ROWS. THIS IS NOT SMART ENOUGH TO TELL IF THE
// ITEM CONTENT HAS CHANGED.
let changed = vnode.attrs.items.length > this.lastLength;
if (changed) {
this.lastLength = vnode.attrs.items.length;
}
console.log("changed=" + changed + (changed ? ", Peforming diff..." : ", Skipping diff..."));
return changed;
} else {
// Always take diff, default behaviour.
return true;
}
}
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
}
// Inject our model.
setupApp(new AppModel());
</script>
</body>
</html>
Normal Usuage
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class App {
constructor(vnode) {
this.items = [];
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.items[this.items.length] = values[i];
}
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
items: this.items
}),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
</script>
</body>
</html>
It should work. Maybe something is wrong with updating this.prev

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

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>
);
}
}

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: '√'; }

Cannot read property 'superclass' of undefined / Object #<Object> has no method 'addCls'

--------------------- Solution ---------------------------
I did a workaround of having to callParent inside my code,
var expandFieldOverride = function(event)
{
//event: collapse, false / expand, true
var fieldset = this;
var arguments = [event];
if(!fieldset.readyToExpand){
Ext.each(profilesPanel.items.items, function(panel, i)
{
if (panel.isProfilePanel)
{
console.log(event);
var field = panel.down('profileform[title=Standard Configuration]').down('fieldset[name='+fieldset.name+']');
field.readyToExpand = true;
field.setExpanded(event);
}
});
}
this.callParent(arguments);
fieldset.readyToExpand = false;
}
-------------------------Initial Problem-------------------------------
I am using ExtJS 4.2.1 and I am trying to override the collapse and expand events of fieldsets. Using collapse and expand didn't work, so I had to directly override setExpanded(). I am trying to achieve the event that when one fieldset is collapsed in a profile panel, so is the other in the other profile panel, and vice versa.
Ext.define('EcoCentral.Configuration.ThermostatProfiles.ProfileOptionsFieldSet',
{
extend: 'Ext.form.FieldSet',
setExpanded: expandFieldOverride,
//expand: expandFieldOverride,
//collapse: collapseFieldOverride,
alias: 'widget.profilefieldset'
});
var expandFieldOverride = function(event)
{
//this.callParent(arguments);
//event: collapse, false / expand, true
var fieldset = this;
var arguments = [event];
Ext.each(profilesPanel.items.items, function(panel, i)
{
if (panel.isProfilePanel)
{
var field = panel.down('profileform[title=Standard Configuration]').down('fieldset[name='+fieldset.name+']');
console.log(field);
//field.callParent(arguments);
field.self.superclass.setExpanded.call(arguments);
}
//this.callParent(arguments);
});
}
If I use 'this.callParent(arguments)' inside the code, I recieve
'Uncaught TypeError: Cannot read property 'superclass' of undefined '
I did some research and tried out this line of code
'field.self.superclass.setExpanded.call(arguments);'
from which I recieve :
'Uncaught TypeError: Object # has no method 'addCls''
Which is a call inside of the setExpanded function in the source.
setExpanded: function(expanded) {
var me = this,
checkboxCmp = me.checkboxCmp,
operation = expanded ? 'expand' : 'collapse';
if (!me.rendered || me.fireEvent('before' + operation, me) !== false) {
expanded = !!expanded;
if (checkboxCmp) {
checkboxCmp.setValue(expanded);
}
if (expanded) {
me.removeCls(me.baseCls + '-collapsed');
} else {
me.addCls(me.baseCls + '-collapsed');
}
me.collapsed = !expanded;
if (expanded) {
delete me.getHierarchyState().collapsed;
} else {
me.getHierarchyState().collapsed = true;
}
if (me.rendered) {
// say explicitly we are not root because when we have a fixed/configured height
// our ownerLayout would say we are root and so would not have it's height
// updated since it's not included in the layout cycle
me.updateLayout({ isRoot: false });
me.fireEvent(operation, me);
}
}
return me;
},
My fieldset is defined by xtype:
You have to use apply. Call is the wrong function. Have a look at this:
What is the difference between call and apply?

Resources