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

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.

Related

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

In what circumstances would I need to use Protractor ExpectedConditions to wait for an element

I'm new to working with Protractor and I was wondering in what circumstances would you need to use ExpectedConditions (example below) when using Protractor. I thought that Protractor automatically determine when an AngularJS page is fully loaded.
let EC = ExpectedConditions;
let condition = EC.presenceOf(element(by.id("something")));
browser.wait(condition, 10000);
Thanks, Eric
From my experience working with Protractor, the use of ExpectedConditions depends on the behavior of the page you are automating. It's mostly used due to failing if the condition doesn't comply in the specified time.
These conditions will also return a promise that you can handle to your liking.
I'll give you a few scenarios so you can understand where to use them.
alertIsPresent(): This condition will wait till an alert appears.
e.g.: After clicking a button, there will be an alert appearance; however, there's an API call which makes the pop-up take longer and also a small animation, so we want to wait a few seconds and no more than that.
// will click on a button
element(by.id('button')).click();
// will wait for the condition
let EC = ExpectedConditions;
browser.wait(EC.alertIsPresent(), 5000);
The following code will wait for 5 seconds after clicking the button, to see if the alert is present, else it will throw an error.
invisibilityOf(): This condition will wait till the specified element is not being displayed.
e.g.: There's a loader that appears for every single action that is triggered in the page. For this we want to wait till this loader disappears so we can continue with the automation process. By business requirements, this loader shouldn't take longer than 10 seconds.
This loader locks the whole page, so other elements are not interactable while it is up.
// trigger random action on page so loader appears
element(by.id('button2')).click();
// will wait for the condition
let EC = ExpectedConditions;
browser.wait(EC.invisibilityOf(element(by.id('loader'))), 10000);
After clicking a button, we will give a 10 seconds grace for the loader to disappear, else the condition will throw an error.
elementToBeClickable(): This condition will wait till the specified element can be clicked.
e.g.: The button to the login form is disabled by default, so it can't be clicked unless we complete the username and password textfields. The button being enabled after filling the textfields has a fast animation, either way we want to give it 1 second to complete and check if we are able to click it.
// complete both textfields required for the button to be enabled
element(by.id('username')).sendKeys('User1234');
element(by.id('password')).sendKeys('Protractor');
// will wait for the condition and then will click the button
let EC = ExpectedConditions;
browser.wait(EC.elementToBeClickable(element(by.id('loginButton'))), 1000);
element(by.id('loginButton')).click();
After completing both textfields, the condition will wait for 1 second for the element to be clickable, if it is, it will procede with the next line and click it. On the other hand, if it doesn't, an error will be thrown.
presenceOf(): In this case, the condition will check if the element is present in the DOM (Document Object Model) but it won't check if the element is visible or not.
e.g.: On a page with a radio button group containing 3 flavors: chocolate, vanilla and strawberry. Depending on which you choose, you will be shown different questions. Developers mentioned that the questions are in the page at all moments, but are hidden due to which radio button is selected at the moment. In this situation, we just want to check that all questions exist in the DOM, whether or not they will be shown by a radio button being selected.
// check all questions directly, without selecting any radio buttons
let EC = ExpectedConditions;
browser.wait(EC.presenceOf(element(by.id('question-1'))), 1000);
browser.wait(EC.presenceOf(element(by.id('question-2'))), 1000);
browser.wait(EC.presenceOf(element(by.id('question-3'))), 1000);
The time is pretty irrelevant here; nonetheless, using this conditions we will be able to check that the questions, even though hidden, exist in the DOM. If one is missing, an error will cut the test immediately.
These were a few examples I've had to deal with in the past. The use of the conditions is situational and mostly they are useful when you want to use the existing conditions since they save you the time of building them yourself.
PD: More information can be found in the Protractor API.

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

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.

ag-grid: Using Drag drop to reorder rows within the grid

I need to re-order rows within ag-grid by using drag-drop. The drag drop methods are simple enough but when the drop occurs there seems no easy way to switch the rows locations.
I attempted a
gridOptions.api.removeItems(selectedNode);
to clear the current and then
gridOptions.api.insertItemsAtIndex(2, savedNode);
but the drop event appeared to re-fire preventing that approach. Plus the insertItems (when ran first) falls over in internal ag-grid looping.
I would rather not re-sort the gridRows manually and then reset the gridRow data which would be somewhat clunky. This seems a common request on most grids so i assume it can be done but have just missed the relevant documentation. Thanks for any help..
K have finally got an Angular 2 method working, though currently I have to switch off sorting and filtering on the grid.
This particular example is relying on row selection being enabled (due to how it is finding some records). Grid dragdrop should be disabled (as that drags the grid visually which sucks) Instead use a processRowPostCreate to set up draggable params at the row level. This sets the dragged option to the row which looks much nicer.
in gridOptions
processRowPostCreate: (params) => {
this.generateRowEvents(params);
},
which calls
private generateRowEvents(params) {
params.eRow.draggable = true;
params.eRow.ondragstart = this.onDrag(event);
params.eRow.ondragover = this.onDragOver(event);
params.eRow.ondrop = this.onDrop(event);
}
I track the source recrord in the onDrag method
var targetRowId: any = $event.target.attributes.row.value;
this.savedDragSourceData = targetRowId;
onDragOver as usual
On drop we have to protect against an infinite loop (ag-grid appears to recall live methods when items are added to the grid hence the ondrop occurs multiple times) and then insert, delete and splice over both the grid and its data source ( I will carry on looking at using the grid to populate the data as opposed to the source data as that would allow source/filter, currently doign that inserts blank rows). An event (in this case) is then emitted to ask the owning component to 'save' the adjusted data.
private onDrop($event) {
if ($event && !this.infiniteLoopCheck) {
if ($event.dataTransfer) {
if (this.enableInternalDragDrop) {
this.infiniteLoopCheck= true;
var draggedRows: any = this.gridRows[this.savedDragSourceData];
// get the destination row
var targetRowId: any = $event.target.offsetParent.attributes.row.value;
// remove from the current location
this.gridOptions.api.removeItems(this.gridOptions.api.getSelectedNodes());
// remove from source Data
this.gridRows.splice(this.savedDragSourceData, 1);
if (draggedRows) {
// insert into specified drop location
this.gridOptions.api.insertItemsAtIndex(targetRowId, [draggedRows]);
// re-add rows to source data..
this.gridRows.splice(targetRowId, 0, checkAdd);
this.saveRequestEvent.emit(this.gridRows);// shout out that a save is needed }
this.v= false;
}
else {
this.onDropEvent.emit($event);
}
}
}
}
NOTE we are wrapping the grid in our own class to allow us to write one set of grid methods and not replicate over the application whenever the grid is used.
I will be refining this over the next few days but as a basic this allows the ag-grid in angular 2 to drag drop rows to sort records.
If you don't find a solution within ag-grid then you can do this by adding one more directive("ngDraggable") and integrate it with ag-grid.
Please find the following working plnkr for this.
https://embed.plnkr.co/qLy0EX/
ngDraggable
Hope this helps..

GWT Detect DOM changes or modifications

What am I trying to do?
I have an existing page (generated by system automatically and I don't have any control on it) in which I am injecting GWT code to modify the behaviour of the page after it loads based on certain columns and augment the functionality of the page. For example after adding my GWT code, cells in one of the table columns become clickable and when the user clicks it, additional information is displayed to the user in a pop-up panel. All that is working fine.
What is the issue?
The generic page in which I am injecting my code has paginated table which shows 15 rows at a time. Now, when I load/refresh the page, my GWT code kicks in and sinks events in the specific column which adds functionality (described above) to the cells. However, when the user uses the left and right buttons to navigate the paginated result, the page does not refresh as it is an asynchronous call. The specific column in the new set of 15 rows is now without the sunk events as the GWT code does not know that the page changed.
I am trying to find a way to tell my GWT code that page has changed and it should sink events to the cells of specific column for the new 15 rows but unable to find any method or mechanism to help me capture a DOM/Document change event. I tried doing this but did not help:
new ChangeHandler(){
#Override
public void onChange(ChangeEvent event) {
Window.alert("Something Changed");
}
It is possible I am missing something very obvious. Posting this question to know if there is an easy way to figure out DOM changes in GWT. Have searched for DOM/Document change/mutation/ etc. without luck.
If anyone knows how to detect DOM changes in GWT would really appreciate otherwise would go ahead writing native code using native mutation observers.
You can try something like this:
First get the input elements with:
InputElement goOn = DOM.getElementById("IdOfGoOnButton").cast();
InputElement goBack = DOM.getElementById("IdOfGoBackButton").cast();
Next add a native EventHandler:
Event.addNativePreviewHandler(new Event.NativePreviewHandler() {
#Override
public void onPreviewNativeEvent(Event.NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONCLICK) {
if (event.getNativeEvent()
.getEventTarget() != null) {
Element as = Element.as(event.getNativeEvent()
.getEventTarget());
if (as.getTagName()
.toLowerCase()
.equals("input")) {
InputElement clickedElement = as.cast();
if (clickedElement.getId().equals(goOn.getId()) ||
clickedElement.getId().equals(goBack.getId())) {
// one of the paging button is pressed
}
}
}
}
}
});
Hope that helps.