reactjs is it possible to get a notification after the DOM is updated? - dom

I have a component which renders a list of up to a thousand elements.
It takes 3/5 seconds to update the DOM, I mean after the component event componentDidUpdate, which is called after the changes have been flushed to the DOM, it takes 3/5 seconds to actually see the DOM updated.
I would like to show a spinning cog or something, but I don't know how, because I don't know how to get notified when the DOM updated is complete.
Anyone knows?

Javascript is single threaded, and all DOM operations are blocking. That means if the browser is busy adding to and updating the DOM, this will lock the Javascript thread until the DOM is updated. During the actual update, there's nothing you can do in code.
This is assuming the UI lockup is actually from a raw, very large amount of DOM manipulation, and not some other underlying culprit. #zerkms's comment is also accurate that animated gifs, CSS animations, etc, generally do not run while the browser is locked up performing large calculations.
If you anticipate browser lockup, the simplest solution is to show some spinner overlay, then run the command that updates your data. This way the spinner will already be in the DOM. When the update has completed, you can remove the spinner. It might look something like
render() {
return <div>
<div onClick={ this.performLongRunningAction }>click me</div>
{ this.state.spinnerVisible ? 'Loading' : null }
</div>
}
performLongRunningAction() {
// First show the spinner...
this.setState({ spinnerVisible: true }, () => {
// Then after state has been set and spinner rendered, start the
// long action
executeLongRunningActionNow();
});
}
// Then you need some mechanism to turn off the spinner state after the
// task has completed
componentWillReceiveProps( nextProps ) {
// Did a task execute? Turn off the spinner before the next render
if( nextProps.someCompletedFlag !== this.props.someCompletedFlag ) {
this.setState({ spinnerVisible: false });
}
}
Another solution is to break up the updating into chunks, so you update the DOM in intervals that are minimal enough not to lock up the Javascript thread. You have provided code, so fleshing out this solution is not possible.

Related

How to force a DOM element to update before others or show a waiting indicator in Svelte?

I have some checkboxes that when clicked will cause a lot of changes in the DOM, and this freezes up the application for several seconds. I'd like the checkboxes to update first, and/or display a waiting indicator. I've tried different things but for some reason nothing else in the DOM will update before the freeze. The changes are made to a large table, either removing or adding entire columns, and it acts as if this has higher priority over anything else because any other attempts to update the DOM after clicking the checkbox don't go through until the table has completed re-rendering. FWIW I can use console.log to display a message before the table updates, and also after it completes for some reason.
import {tick} from "svelte";
let checked = false;
$: applyChanges(checked);
async function applyChanges() {
messageVisible = true
await tick()
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// do the stuff that causes a lots of dom updates
})
})
}
The await tick() applies the messageVisible changes to the DOM.
The double raf will wait for the browser to draw the updated DOM.

How to prevent closing of cell editing in ag-grid on "Other Cell Focus"

I am working on an editable table in Angular application with ag-grid library. I would like to keep editing cells (in full row edit mode) until I finish with it and then close the editors manually via API. The problem is that the editor is closing on other cell click/focus (on some other line) as described here:
The grid will stop editing when any of the following happen:
Other Cell Focus: If focus in the grid goes to another cell, the editing will stop.
I cannot figure out how to disable this, if it is possible. Installing the onCellMouseDown() hook does not help, because the cellFocused event is fired before cellMouseDown. Therefore, the editing stops before I have a chance to intercept the mousedown event.
Here is my stackblitz little extract with related pieces of code.
The need for such scenario is that I want to validate the entry and not to allow a used to quit the editing if the form is not valid. The only workaround I found so far is that on any click outside of editing cells when the editor closing I reopen it right away in onRowEditingStopped() hook unless the editor has been closed via 'OK' button.
After all, I have managed to provide a custom solution that fits exactly into this problem which I was facing also.
First thing is to disable pointer events to non edited rows when a specific row is currently being edited. On Ag-grid's 'cellEditingStarted' callback I have added the following code:
public cellEditingStarted(event: any): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row:not(.ag-row-selected):not(.ag-row-editing):not(.pointer-events-none)');
forEach(nonSelectedGridRows, row => {
row.classList.add("pointer-events-none");
});
}
Because not all rows exist on dom (Ag-grid creates and destroys while you are scrolling )when a specific cell is being edited, I have also added a rowClassRule which is applied when rows are being created:
this.rowClassRules = {
'pointer-events-none': params => {
if (params.api.getEditingCells().length > 0) {
return true;
}
return false;
}
};
scss:
.pointer-events-none {
pointer-events: none
}
By disabling pointer events, when you click on a non edited cell the cell won't get focus and thus the currently edited cell will stil remain on edit mode. You can provide your own custom validation solution and close the editor manually through API. When you are done, you have to enable pointer events to all grid rows back again:
private enablePointerEvents(): void {
//not all rows are on dom ag-grid takes care of it
const nonSelectedGridRows = document.querySelectorAll('.ag-grid-custom-row.pointer-events-none');
forEach(nonSelectedGridRows, row => {
row.classList.remove("pointer-events-none");
});
}
I implemented the same above approach in Ag-Grid React.
I used getRowStyle callback for adding the css pointerEvents: none on dynemic basis.
It seems to be working for me fine.
Please refer the below code
const getRowStyle = (params) => {
// this is not initialized in read mode
// condition for me ==> currentEditRowIndex.current !== null && params.node.rowIndex !== currentEditRowIndex.current
if (someCondition for Row other than inline edit row) {
return { pointerEvents: "none" };
}
return null;
};
After adding this whenver you start the editing..You will need to call redrawRows so that css changes can be applied.
Hope this will help. Thank You!!
Thought I would share another solution that has been working out okay for me so far.
Using 'pointer-events-none' as suggested in the other answer is flawed because the Enter key can also close the editor.
In my case, I want to prevent the editor from closing when client side validation has failed and the data is invalid. When my conditions are met, I call stopPropagation() on the events to prevent the editor close from happening in the first place. It still has potential problems:
It cancels mousedown, dblclick, keydown, focusout and click for all elements that have a class name starting with ag- so if you happen to use this class prefix for other controls on the page, it could interfere. It also means any controls within the grid (sorting, resizing, etc.) don't work while the condition is met.
Calling stopPropagation() could potentially interfere with your own custom controls. So far I've been okay if I dont use the ag- prefix within the markup from my own custom cell editors and renderers
I hope they can add a proper API function to cancel the row/cell stopEditing function in the future.
["mousedown", "dblclick", "keydown", "focusout", "click"].forEach(function (eventName) {
document.addEventListener(eventName, function (e) {
if ( conditionForCancelingIsMet() ) {
// this appears to cancel some events in agGrid, it works for
// preventing editor closing on clicking other cells for example.
// It would be ideal if this worked for all ag-grid specific events
// and had a proper public API to use!
e["__ag_Grid_Stop_Propagation"] = true;
}
// convert element classList to normal js array so we can use some()
var classArray = [].slice.apply(e.target.classList);
if ( conditionForCancelingIsMet() && classArray.some(c => c.startsWith("ag-")) ) {
// unfortunately some events like pressing the 'enter' key still
// require stopPropagation() and could potentially interfere with unrelated controls
e.stopPropagation();
}
}, true);
});

How to Set Initial Focus in a View?

I have a Detail view in an SAPUI5 app which contains a single input field with ID "bestellmenge_tu". Whenever this view is called, the focus should be on that input field. Unfortunately, when setting the focus on the field in the controller's onInit method, the focus will be set but some milliseconds later the UI5 takes it away and transfers it to the "navigation back" button of the detail view.
By putting a log.trace() on the input field's blur event, I found out that the focus is taken away by a method called sap.ui.define.NavContainer._afterTransitionCallback which is called asynchronously (some window.setTimeouts between trigger and execution). The function simply looks for the first focusable element in the view and brutally switches the focus on it.
My workaround was to redefine the method jQuery.fn.firstFocusableDomRef which is used to find this "first focusable element":
// DIESE KANONE FUNKTIONIERT
jQuery.fn.firstFocusableDomRef = (function() {
var _default = jQuery.fn.firstFocusableDomRef;
return function() {
var bestellmenge_tu = document.querySelector("input[id$='bestellmenge_tu-inner']");
if (bestellmenge_tu &&
bestellmenge_tu.style.display !="none" &&
bestellmenge_tu.style.visibility != "hidden") return bestellmenge_tu;
else return _default.apply(this);
}
})();
But this could be a performance issue (querySelector called during DOM transversal at any page load from there on), and it is too much coding for the desired effect.
Is there an easier method to achieve this?
I thought of something like
<mvc:View controllerName="zrt_dispo.view.Detail"...>
<Page id="detailPage" initialFocus="bestellmenge_tu"> <!-- ID of the element to carry the focus -->
</Page>
</mvc:view>
Here is a working example: https://embed.plnkr.co/wp6yes/
<App autoFocus="false" xmlns="sap.m"> <!-- AND/OR -->
<f:FlexibleColumnLayout autoFocus="false" xmlns:f="sap.f" /> <!-- autoFocus in FCL available since 1.76 -->
{ // Controller of the target view:
onInit: function() {
this.attachAfterShow(this.onAfterShow);
},
attachAfterShow: function(onAfterShow) {
this._afterShowDelegate = { onAfterShow };
this.getView().addEventDelegate(this._afterShowDelegate, this);
},
onAfterShow: function() {
this.byId("thatControl").focus();
},
onExit: function() { // detach delegates
this.getView().removeEventDelegate(this._afterShowDelegate);
this._afterShowDelegate = null;
}
}
If you have a sap.m.NavContainer (I.e. sap.m.App or in sap.f.FlexibleColumnLayout), its direct child, aka. NavContainerChild which is usually a View, can react to navigation related events. Add a delegate to the NavContainerChild event afterShow according to the API reference of NavContainer:
The afterShow event can be used to focus another element, only if autoFocus is set to false.
The event handler is fired after the animation is finished. And most importantly:
This event is fired every time (in contrast to onAfterRendering) when the NavContainer has made this child control visible.
Since sap.ui.core.Control extends sap.ui.core.Element, every control can receive the focus via focus().
Known Issues in Older UI5 Versions
SAP Fiori launchpad (FLP) running with older SAPUI5 versions might take the control of the initial focus for the first app launch: FLP: Setting Custom Initial Focus on App Launch Fails
However, it's no longer reproducible (Let me know if otherwise).
In UI5 1.62 and below, calling focus() in onAfterShow alone would still make the app set the focus on the first focusable element when the user navigates back, even with autoFocus="false" (See this GitHub issue #2306). In that case, an additional setTimeout with 0 ms (or requestAnimationFrame) is needed in order to let the browser know that the element should be focused at the end of the call stack.
onAfterShow: function() {
// Only if UI5 version < 1.63:
setTimeout(() => this.byId("thatControl").focus());
},
With commit:6d46cf0, the fix is available as of 1.63.
The NavContainer has a property autoFocus. App is a descendant of NavContainer so it has that property too.
The help (as linked above) states the following:
Determines whether the initial focus is set automatically on first rendering and after navigating to a new page. This is useful when on touch devices the keyboard pops out due to the focus being automatically set on an input field. If necessary the "afterShow" event can be used to focus another element.
Default value is true.

polymer 1.0 event when element appears on page

I can't understand how to make css animations to an element no sooner than it appears on page, like css3 transform rotate. when I set in attached(){performRotation()} when the element appears on page it's already rotated. I couldn't find in all documentation which event I need to subscribe to in order to start animation.
You can do that with the help of NeonAnimatableBehavior and NeonAnimationRunnerBehavior, you may configure your animations in animationConfig property and then play them with playAnimation('name') function which is implemented by NeonAnimationRunnerBehavior .
Please check lifecycle callbacks and neon-animation demos.
behaviors: [
Polymer.NeonAnimatableBehavior,
Polymer.NeonAnimationRunnerBehavior
],
properties: {
animationConfig: { ... },
}
...
ready: function() {
this.playAnimation('entry');
}
I've made you a simple example using the above elements to give you a quick start.
Update
You can modify each configured animation based on given properties in one of the lifecycle callbacks, e.g. ready callback:
var entryAnimation = this.animationConfig.entry[0];
entryAnimation.transformFrom = this.doSomethingTo(this.someProperty);
Please check the updated demo.

EffectQueue and chaining effects in IceFaces 1.8

I'm currently working with IceFaces 1.8, and have been trying to find an simple way to chain effects on UI Components. For example, I have a "Show Help" link at the top right of the page. When clicked, help text will appear below certain controls for users. I'd like this text to appear by sliding down, then highlighting.
I have a basic isRenderHelp() method on my bean that returns true or false, and use that to render effects using the fire attribute on the <ice:effect> tag, so it looks something like this:
<ice:effect effectType="slidedown" fire="#{myBean.renderHelp}">
<ice:effect effectType="slideup" fire="#{!myBean.renderHelp}">
This works causing the help section to slide in and out as the help link toggles the renderHelp flag in the bean. There is the small exception that renderHelp returns null before the link is clicked for the first time to prevent the slideup animation from firing on the first page render.
Now, I noticed looking through the showcase code for 1.8 that there is an EffectQueue class that extends Effect. This allows me to add mutliple Effects to the queue in my bean, and return the queue from a getEffect method that I can then assign to a panelGroup effect attribute. However, it does not execute the events in the queue, despite having their priorities set. I'm sure I'm not using it properly, and I'm wondering how it should be used.
Normally I'd use jQuery for this type of thing, but the UI uses many partial submits. Our page is displayed via a Liferay portlet, so on any partialSubmit the view is rerendered, undoing any modifications to the DOM by jQuery.
So is there any simple way to chain effects in IceFaces 1.8? Suggestions?
here is how I implemented the effectQueue to appear and fade the text.
private EffectQueue effectQueue;
public Effect getSaveSettingsEffect() {
return effectQueue;
}
public void fireEffect(ActionListener e) {
if(effectQueue == null) {
effectQueue = new EffectQueue("apperAndFade");
Appear appear = new Appear();
appear.setDuration(2);
effectQueue.add(appear);
Fade fade = new Fade();
fade.setDuration(3);
effectQueue.add(fade);
effectQueue.setTransitory(true);
}
effectQueue.setFired(false);
}
facelet:
<ice:commandButton value="fireEffect" action="#{bean.fireEffect}"/>
<ice:outputText value="text" style="display: none;" effect="#{bean.effectQueue}"/>