Angular2 - DOM events lost on re-render when using Redux and uni-directional data flow - events

I wasn't sure of the best way to word the title, but hopefully the description will clarify.
I have an Angular2 component that uses Redux for state management.
That component uses *ngFor to render an array of small inputs with buttons like this. The "state" is something like this...
// glossing over how I'd get this from the Redux store,
// but assume we have an Immutable.js List of values, like this...
let items = Immutable.fromJS([{value: foo}, {value: bar}, /*...etc*/ })
And the template renders that like so...
<input *ngFor="let item of items, let i = index"
type="text"
value="item.get('value')"
(blur)="onBlur($event, i)">
<button (click)="onClick($event)">Add New Input</button>
When an input is focused and edited, then focus is moved away, the onBlur($event) callback is called, a redux action (ie: "UPDATE_VALUE") is dispatched with the new value.
onBlur($event, index) {
let newValue = $event.target.value;
this.ngRedux.dispatch({
type: "UPDATE_VALUE",
value: {index, newValue}
});
}
And the reducer updates the value (using Immutable.js):
case "UPDATE_VALUE": {
let index = getIndex(action.value.index); // <-- just a helper function to get the index of the current value.
return state.updateIn(["values", index], value => action.value.newValue);
}
The state is updated, so the component is re-rendered with the updated value.
When the button next to the input is clicked, the onClick($event) callback is fired which dispatches a different redux action (ie: "ADD_VALUE"), updates the state, and the component is re-rendered with a new blank input & button.
The problem comes up when the input is focused (editing) and the user clicks the button. The user intended to click the button, but because they happened to be focused on the field, it doesn't behave as expected. The (blur) event is fired first, so the input onBlur callback is fired, redux action dispatched, state updated, component re-rendered. BUT, the button (click) is lost, so the new input isn't created.
Question:
Is there a way to keep track of the click event and trigger the second action after the re-render? Or, is there a way to somehow chain the redux actions so they happen in sequence before the re-render?
Side-note - I've tried changing the (click) event to use (mousedown) which is triggered before the (blur) but that caused Angular (in devMode) to complain that the #inputs to the components were changed after being checked (the state changed during the change detection cycle). I didn't dig into that too deeply yet though. I'm hoping to find a solution using click and blur as is.
Thanks!

Related

Making focus works inside a CK Editor 5 createUIElement

So I've a custom widget which renders a custom component.
conversion.for('editingDowncast').elementToElement({
model: 'modelName',
view: (modelElement, viewWriter) => {
const modelName = modelElement.getAttribute('modelName');
const modelNameView = viewWriter.createContainerElement('span', {
class: 'modelName',
'data-modelName': modelName,
});
const reactWrapper = viewWriter.createUIElement(
'span',
{
class: 'modelName__react-wrapper',
},
function (this, domDocument) {
const domElement = this.toDomElement(domDocument);
rendermodelName(modelName, domElement);
return domElement;
},
);
viewWriter.insert(
viewWriter.createPositionAt(modelNameView, 0),
reactWrapper,
);
return toWidgetEditable(modelNameView, viewWriter);
},
});
Where rendermodelName will give back a React component with a simple input box as
return (
<div>
<input type="text" />
</div>
);
https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/frameworks/react.html.
But the problem is, whenever I tried to add some content inside the input, the focus is lost from the field and automatically moved to the surrounding editor. What am I missing. Tried creating a focushandler and adding the modelNameView to it.
Should I go with the new createRawElement? My current CK5 is 20.0.0 So I don't want any breaking changes coming now.
EDIT:
I researched a little bit more. seems like createRawElement may not work here. I think this doesn't have a simple solution. I tried with allowContentOf: '$block' which also not letting me focus. But these values are explicitly for normal CK widget, not for a react component.
I had the same issue and solved it by adding this tag to the parent div that wraps my Vue component.
https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/ui/widget-internals.html#exclude-dom-events-from-default-handlers
Adding from CKE Docs:
Sometimes it can be useful to prevent processing of events by default handlers, for example using React component inside an UIElement in the widget where, by default, widget itself wants to control everything. To make it possible the only thing to do is to add a data-cke-ignore-events attribute to an element or to its ancestor and then all events triggered by any of children from that element will be ignored in default handlers.
Let’s see it in an short example:
<div data-cke-ignore-events="true">
<button>Click!</button>
</div>
In the above template events dispatched from the button, which is placed inside containing data-cke-ignore-events attribute, will be ignored by default event handlers.
I faced the similar issue.
CKEditor will takes all the events on React component which you hosted on Widget.
The work around is to stop propagation of events to CKEditor which are fired from your DOM element(domElement) where your React component hosted.
Here is the sample code:
https://github.com/ckeditor/ckeditor5-core/compare/proto/input-widget#diff-44ca1561ce575490eac0d660407d5144R239
You should stop all required events. Also you can't paste any content inside the input field of React component. That will also listened by clipboardInput event of CKEditor.

How to get DetailsList/Selection component on React Fabric UI to work with ReactHooks?

I'm having issue getting the React Fabric UI DetailsList to work with React Hooks. It renders, but the selection part does not. Whenever, you select a row, I expect the count of the selection to be updated. However, i'm not seeing that. It looks like the selection component items never get updated even thou the UI shows it being selected. I'm also seeing the onSelectionChange being triggered when you select a row. Now sure if its because i'm using react hooks or what
I took the provided class example which works:
[Codepen]https://codepen.io/pen/?&editable=true (Example)
Same as the original but stripped down
[Codepen]https://codepen.io/darewreckk/pen/NQRWEd?editors=1111
converted it to a react hook component. I would expect the same thing, but i'm not and can't figure out what's different.
Modified Example with Hooks
[Codepen]https://codepen.io/darewreckk/pen/GVjRNb?editors=1111
Any advice appreciated,
Thanks,
Derek
The selection count is stored on the Selection object that you are creating. You could log it out like so:
const _selection = new Selection({
onSelectionChanged: () => {
console.log('Selected count:' + _selection.count);
}
});
You can set the value of selectionCount and selectionDetails whenever onSelectionChanged is called:
const _selection = new Selection({
onSelectionChanged: () => {
setSelectionCount(_selection.count);
setSelectionDetails(`${_selection.count} items selected`)
}
});
As a side note: If you want selectionDetails to update when selectionCount updates, you can use the useEffect hook:
React.useEffect(() => {
setSelectionDetails(`${selectionCount} items selected`);
}, [selectionCount]);
The above effect will run whenever selectionCount updates, and so in turn update selectionDetails.

Delaying Kendo Validation until Submitting the form

I have marked my controls with the Required attribute.
This has caused them to not only error on the lost focus event ( which is good ) but the problem is it won't even let me continue entering values in OTHER columns and controls of the grid on the form until I first type something for the required control.
Is there a way to delay this required validation to when I submit the form?
You can bind the .getKendoValidator(); method to your form which allow you to delay your validation.
You need to set button type ="Submit"
Refer the following line of code :-
var form_validator = $("#form_reg").kendoValidator({
rules: {
/*..*/
},
messages: {
/*..*/
}
}).getKendoValidator();
Also note that as the Validator is attached to a form element,
the validation will be executed automatically when the form is submitted, which in this case is when the button is clicked. Therefore, there is no need to manually call the validate method.
Note:-execute some custom logic, like alert to the user, when form is validated you may use
validate event.

Fullcalendar Problems w/ filtering Events in agendaDay-View (works only one time, then no more events exist)

I have a problem with filtering events:
I use fullcalender in agendaDay-View
I use a drop-Down list to select a driver
I compare the name (it is a property of event-object) with the selected value
(this part is ok)
Then,
I remove all Events (.fullCalendar('removeEvents');)
Add the specific events
(add with renderEvents doesn't work proberly at the moment)
And now my problem appears:
For the first time it works, however, when I select another 'driver', the events are gone, because of the 'removeEvents'-Action before.
So, how can I solve this problem, that I can only display the filtered events and keep the other (actualley all events) to filter again for second, third n- time?
$('#' + id).fullCalendar('refetchEvents');
was the first idea, however, its brings all back and selected Issues were doubled (and so on).
I'm thankful for every hint.
Ok I think you can solve your problem with fullCalendar's eventRender function.
The function can also return false to completely cancel the rendering of the event.
Your event from events.json should have a property like 'driver_id'.
I would have select field instead of dropdown with option keys being driver ids, values - names or whatever. Lets say it's id would be 'driver_select'.
Then render events like this:
$('#calendar').fullCalendar({
//...
eventRender: function(event, element) {
//driver select should have default value assigned or else no events will be rendered
if ($("#driver_select").val() !== event.driver_id) {
return false; //do not render other driver events
}
}
//...
});
Handle select changes to update
$("#driver_select").off('change').on('change', function() {
$("#calendar").fullCalendar( 'refetchEvents' );
});
Another approach would be to show all drivers in a timeline. For that you can use FullCalendar Scheduler.

react-bootstrap ModalTrigger doesn't hide when the parent element is unmounted

We encountered a strange behavior when using react-bootstrap's ModalTrigger with an array of items, in that it doesn't go away when the parent/owner item is unmounted. We suspect this has to do with React's virtual DOM and the diffing mechanism, and/or our own misuse of the ModalTrigger.
The setup is simple: a Content react component has a state that holds an array of item names. It also has an onClick(name) function that removes that name from the array via setState. In the render, it uses _.map to create a bunch of Item react components.
Each Item component displays its name and a ModalTrigger that holds a button labeled "delete me". Click on the button and it opens the modal; click "OK" in the modal and it executes the callback to the Content remove function.
When deleting the last item it works fine: the final Item component is unmounted, and with it, the ModalTrigger and its corresponding modal.
The problematic behavior we see is when deleting any item other than the last one. The item is removed but the modal stays open, whereas I would naively expect the modal to disappear since the parent ModalTrigger is gone. Not only that, but when clicking "ok" again, the next item on the list is removed, and so on until the modal happens to be associated with the last item, at which point clicking "ok" will finally hide it.
Our collective hunch is that this is caused by the overlayMixin's _overlayTarget being an anonymous element in the document, so that different ModalTriggers don't differentiate between them. Therefore, when a parent unmounts and React looks for the DOM diff, it sees the previous trigger's and says "hey, that could work".
This whole issue can easily be addressed by adding a hide() call in the Item's inner _onClick() function as is commented out in the code, and we finally arrive at my question:
Am I using ModalTrigger correctly, in that expecting it to go away when the parent is unmounting? This is kind of how I expect React to work in general, which means a bug in react-bootstrap.
Or should I be explicitly calling hide() because that's they way this component was designed?
Following is a piece of code that reproduces this.
Thanks!
var DeleteModal = React.createClass({
render:function() {
return (
<ReactBootstrap.Modal onRequestHide = {this.props.onRequestHide} title = "delete this?">
<div className="modal-body">
Are you sure?
</div>
<div className="modal-footer">
<button onClick={this.props.onOkClick}>ok</button>
<button onClick={this.props.onRequestHide}>cancel</button>
</div>
</ReactBootstrap.Modal>
);
}
});
var Item = React.createClass({
_onClick:function() {
//this.refs.theTrigger.hide();
this.props.onClick(this.props.name);
},
render:function() {
return (
<div>
<span>{this.props.name}</span>
<ModalTrigger modal={<DeleteModal onOkClick={this._onClick}/>} ref="theTrigger">
<button>delete me!</button>
</ModalTrigger>
</div>
);
}
});
var Content = React.createClass({
onClick:function(name) {
this.setState({items:_.reject(this.state.items, function(item) {return item === name;})});
},
getInitialState:function() {
return {items : ["first", "secondth", "thirdst"]};
},
render:function() {
return (
<div>
{_.map(this.state.items, function(item, i) {
return (
<Item name={item} onClick={this.onClick} key={i}/>
)}.bind(this)
)}
</div>
);
}
});
React.render(<Content/>, document.getElementById("mydiv"));
Turns out it was a misuse of React's "key" property. We gave the mapped objects integer keys, so when the render was called again, the same initial keys were given, which is why React thought it should reuse the same DOM element.
If instead we give it key={item} (where item is a simple string) it solves it in our case; however, this introduces a subtle bug whereby if there are 2 identical strings, React will display only one.
Trying to outsmart it by giving it key={item + i} introduces an even subtler bug, where duplicate items are displayed but are delete en mass, but in this case the bug is in the onClick method which would need to be modified to accept an index of some sort.
Therefore my take-away is that the keys must be a unique string, and callbacks should take these keys into consideration when performing any modifications.

Resources