How to add a horizontal scrollbar on top of the ag-grid - ag-grid

I have an ag-grid set up with a series of components in place for cell rendering. When my dataset loads the vertical scroll works well but the horizontal scroll isn't obvious unless using a trackpad or horizontal scroll enabled mouse.
I would like to be able to add a scroll bar to the top of the grid as well as the automatically generated one at the bottom?
Has anyone encountered this, come up with as solution?
Thanks in advance

This question is old but I struggled with the same issue and came up with something working.
💡 The Idea
The main idea behind my solution is to...
clone AgGrid scrollbar when grid is ready
insert the cloned scrollbar on top of the grid
add event listeners on both scrollbars to keep the scroll position synchronized
use MutationObserver to observe style attribute changes on original AgGrid scrollbar element (and child) to keep the size of the cloned scrollbar synchronized
⚡ The Code
The following code is for Angular but the concept is the same for Vanilla JS, React or Vue.
First, get a hook on gridReady event:
<ag-grid-angular
...
(gridReady)="onGridReady()">
</ag-grid-angular>
In the function associated to the event use the following code to clone the AgGrid scrollbar and keep the scrollbars synchronized:
// hold the `MutationObserver` to be disconnected when component is destroyed
private mutationObserver: MutationObserver;
onGridReady() {
// css class selectors
const headerSelector = '.ag-header';
const scrollSelector = '.ag-body-horizontal-scroll';
const scrollViewportSelector = '.ag-body-horizontal-scroll-viewport';
const scrollContainerSelector = '.ag-body-horizontal-scroll-container';
// get scrollbar elements
const scrollElement = document.querySelector(scrollSelector);
const scrollViewportElement = document.querySelector(scrollViewportSelector);
const scrollContainerElement = document.querySelector(scrollContainerSelector);
// create scrollbar clones
const cloneElement = scrollElement.cloneNode(true) as Element;
const cloneViewportElement = cloneElement.querySelector(scrollViewportSelector);
const cloneContainerElement = cloneElement.querySelector(scrollContainerSelector);
// insert scrollbar clone
const headerElement = document.querySelector(headerSelector);
headerElement.insertAdjacentElement('afterend', cloneElement);
// add event listeners to keep scroll position synchronized
scrollViewportElement.addEventListener('scroll', () => cloneViewportElement.scrollTo({ left: scrollViewportElement.scrollLeft }));
cloneViewportElement.addEventListener('scroll', () => scrollViewportElement.scrollTo({ left: cloneViewportElement.scrollLeft }));
// create a mutation observer to keep scroll size synchronized
this.mutationObserver = new MutationObserver(mutationList => {
for (const mutation of mutationList) {
switch (mutation.target) {
case scrollElement:
cloneElement.setAttribute('style', scrollElement.getAttribute('style'));
break;
case scrollViewportElement:
cloneViewportElement.setAttribute('style', scrollViewportElement.getAttribute('style'));
break;
case scrollContainerElement:
cloneContainerElement.setAttribute('style', scrollContainerElement.getAttribute('style'));
break;
}
}
});
// start observing the scroll elements for `style` attribute changes
this.mutationObserver.observe(scrollElement, { attributeFilter: ['style'], subtree: true });
}
When destroying the component, disconnect the MutationObserver to avoid memory leaks.
ngOnDestroy() {
// stop observing
this.mutationObserver.disconnect();
}
It's tricky and all based on keeping the cloned scrollbar synchronized with the original scrollbar but so far it works great for my use cases.
Good luck 😎

Update 2022
::ng-deep{
.ag-root-wrapper{
.ag-root-wrapper-body{
.ag-root{
.ag-body-horizontal-scroll{
order: 1;
}
.ag-header{
order: 2;
}
.ag-floating-top{
order: 3;
}
.ag-body-viewport{
order: 4;
}
.ag-floating-bottom{
order: 5;
}
.ag-overlay{
order: 6;
}
}
}
}
}

Related

Horizontal scrolling to identify hidden or dynamic element (with WDIO + Appium + JS)

I came across this interesting problem related to appium UI automation. In my app I have few UI elements that are loading dynamically after user horizontal scroll action. I tried to build my custom method to perform horizontal action on UI elements but no luck (attached screenshot for error). I see that the (x,y) position works pretty well with the vertically scrolling and would like to know if anyone has done horizontal scrolling to make hidden elements visible (dynamically loaded elements)? If you have any pointers on this please let me know. Thanks in advance.
Note: In the screenshot below you can see the last visible element is 16x20 but there is one more button “20x30” after that and I am trying to select that.
I tried with below options:
First:
browser.execute('mobile: scroll', {element: element, direction: 'left'});
Second:
screenAction.swipeLR(element,5);
The element i am passing in my custom method is last visible element i.e. 16x20:
{
console.log("Performing touch ops horizontally");
//element.touchMove(10,0);
//element.scrollIntoView();
const xLocation = element.getLocation('x')
console.log(xLocation); // outputs: 150
const yLocation = element.getLocation('y')
console.log(yLocation); // outputs: 20
const swipeStart = {
x:<Number>xLocation,
y: <Number>yLocation,
};
const swipeEnd = {
x: <Number>(xLocation+20),
y: <Number>yLocation,
};
// scroll up-to numberOfSwipes
let counter = 0;
while (counter < numberOfSwipes) {
console.log('Swipe process started');
this.swipe(swipeStart, swipeEnd);
counter += 1;
if (counter >= numberOfSwipes) {
break;
}
}
}````
[![enter image description here][1]][1]
[![enter image description here][2]][2]
[1]: https://i.stack.imgur.com/t893q.png
[2]: https://i.stack.imgur.com/YMh9Z.png

AG Grid: How to stick the scrolling at bottom

i'm using AG Grid Angular to create a log view, i will add a lot of entries per second to the grid, typically this type of view's has the newest entry at the bottom, so it would be great if there is a good way where the scrolling will stick at the bottom, so i can always see the latest entry. A bonus would be, if i can manually scroll up and the scrolling will stay at this position.
Here's one way to do it:
handle rowDataChanged in your markup:
<ag-grid-angular
class="ag-dark"
[rowData]="messages"
[columnDefs]="columnDefs"
[gridOptions]="gridOptions"
(bodyScroll)="handleScroll($event)"
(rowDataChanged)="handleRowDataChanged($event)">
</ag-grid-angular>
In the handler for rowDataChanged, call ensureVisibleIndex to scroll to the last row added:
gridOptions: GridOptions = {
suppressScrollOnNewData: true,
}
handleRowDataChanged(event) {
const index = this.messages.length - 1;
this.gridOptions.api.ensureIndexVisible(index, 'bottom');
}
Demo:
https://stackblitz.com/edit/angular-ag-grid-stuck-to-bottom
In that code there is also some logic around when to keep the table scrolled to the end or not based on the scroll position.

How to collapse the leaflet control

I start my application with expand Layer-Control:
L.control.layers(baseMaps, overlays, { collapsed:false } ).addTo(mymap);
I found no Mouse-Action to minimize the Layer-Control. I want to minimize the Layer-Control. But I don't know the handler. Could anybody give me a tip?
I had the same requirement for Leaflet. I needed to have the layer control expanded at first and then return to its normal hiding after someone realizes what it does.
I am using JQuery, but you could probably manipulate the DOM as well.
I have a function that instantiates the layer control object, and then I immediately reset the mouseenter and mouseleave events for the expanded control and the smaller toggle widget.
let layerControl = L.control.layers(basemap_items, { 'specialLayer': layer}, { collapsed: false }).addTo(map);
$('.leaflet-control-layers').on('mouseleave', () => {
layerControl.collapse();
});
$('.leaflet-control-layers-toggle').on('mouseenter', () => {
layerControl.expand();
});

Detect scrollHeight change with MutationObserver?

How can I detect when scrollHeight changes on a DOM element using MutationObserver? It's not an attribute and it isn't data either.
Background: I need to detect when a scrollbar appears on my content element, the overflow-y of which is set to auto. I figured that the instant the scrollbar appears the value of scrollHeight jumps from 0 to, say, 500, so the idea was to set up a MutationObserver to detect a change in this property.
What I've got so far:
HTML
<div class="body" #body>
CSS
.body {
overflow-y: auto;
}
TypeScript
export class MyWatchedContent implements AfterViewInit, OnDestroy {
#ViewChild('body', { read: ElementRef })
private body: ElementRef;
private observer: MutationObserver;
public ngAfterViewInit() {
this.observer = new MutationObserver(this.observerChanges);
this.observer.observe(this.body.nativeElement, {
attributes: true,
});
}
public ngOnDestroy() {
this.observer.disconnect();
}
private observerChanges(records: MutationRecord[], observer: MutationObserver) {
console.log('##### MUTATION');
records.forEach((_record) => {
console.log(_record);
});
}
}
If I, for example, change the background color in the developer window I can see the observer firing
MUTATION
my-content-watcher.component.ts?d0f4:233 MutationRecord {type: "attributes", target: div.body, addedNodes: NodeList(0), removedNodes: NodeList(0), previousSibling: null…}
If, however, I change the window size to make the scrollbar appear there's no mutation detected. Is this doable with MutationObserver at all and if so, how?
Here's the answer, for anyone still looking for the solution:
As of today, it's not possible to directly monitor scrollHeight changes of an element.
The MutationObserver detects changes in the DOM tree, which could indicate a scrollHeight change, but that's a wild guess.
The ResizeObserver detects changes in the outer height of an element, but not the scrollHeight (i.e. "inner" height).
There is no ScrollHeight-Observer (yet).
BUT the solution is very close:
The Solution
The ResizeObserver detects changes in the outer height of an element...
There's no point in observing the scroll-container because its outer height does not change. The element that changes their out height is any CHILD node of the container!
Once the height of a child node changes, it means, that the scrollHeight of the parent container changed.
Vanilla JS version
const container = document.querySelector('.scrollable-container');
const observer = new ResizeObserver(function() {
console.log('New scrollHeight', container.scrollHeight);
});
// This is the critical part: We observe the size of all children!
for (var i = 0; i < container.children.length; i++) {
observer.observe(container.children[i]);
})
jQuery version
const container = $('.scrollable-container');
const observer = new ResizeObserver(function() {
console.log('New scrollHeight', container[0].scrollHeight);
});
container.children().each(function(index, child) {
observer.observe(child);
});
Further steps
When children are added dynamically, you could add a MutationObserver to add new children to the ResizeObserver once they were added.
You can emulate this behavior by adding an internal wrapping element on the contenteditable element (eg a span) and then add the ResizeObserver listener on the internal element. The internal span has to be display:block otherwise it wont trigger the ResizeObserver.
HTML
<div contenteditable id="input"><span id="content">Some content</span></div>
CSS
#input {
max-height: 100px;
overflow: scroll;
white-space: pre-wrap;
}
#content {
display: block;
white-space: pre-wrap;
}
JS
const content = document.getElementById("content");
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
console.warn(entry);
}
});
observer.observe(content);

Avoid Ext.form validation scrolling to top

I have a Ext.form.Panel inside Ext.window. Form height is more than window height so I have vertical scroll on window.
On form fields validation (on validitychange event) scroll jumps to the top.
How to avoid this behaviour?
I tried to figure out, why one of my forms did scroll up and other did not. Turned out, that I have forgot to explicitly specify layout manager and that default layout manager (anchor) scrolled to top on validity change, while vbox layout did not. While everything looked exactly the same (vbox with align: 'stretch'), it behaved differently when the error was either shown or hidden.
I have the same problem :(
I made a creepy workaround (it works to 80%) Sometimes it still jumps to the top.
You should know, that I have a window with a layout of 'form'. If you have a window with (for example) a layout of 'fit' with an xtype of 'form' - you may have to change the code a little bit.
For example the line el.child(".x-window-body", fasle) wouldn't work.
init: function() {
this.control({
...
/** My Ext.window.Window is called reservationwindow **/
'reservationwindow': {
afterrender: function(comp) {
// comp is this Ext.Component == wrapper
var el = comp.getEl();
//extjs adds the scrollbar to the body element...
var elForm = el.child(".x-window-body", false);
// or el.child(".x-panel-body", false);
//we are listinig to the scroll-event now
this.myFormEl = elForm;
this.safeScroll = {top:0, left:0};
elForm.on('scroll', function() {
console.log("save");
this.safeScroll = this.myFormEl.getScroll();
}, this);
elForm.on('click', function() {
var resWin = this.getResWin();
resWin.scrollBy(0,this.safeScroll.top,false);
console.log("reset");
}, this);
elForm.on('keyup', function() {
var resWin = this.getResWin();
resWin.scrollBy(0, this.safeScroll.top, false);
console.log("reset");
}, this);
}
As you can see, I am listening to the scroll-event and safe and reset the scroll bar. Sometimes (especially if you are writing very quickly in a textbox) the events come in a different order and the page will still jump to the top. Sometimes you also see it flickering around (if it needs too long to set it back to the original position).
So.... As I said, its a creepy workaround.
If you find a better solution, please let me know.
EDIT
I also figured out, that the grow option on a textareafield was one of the troublemakers.
{
id: this.id + '-details',
xtype: 'textareafield',
// grow: true, now it isn't jumping
name: 'message',
fieldLabel: 'Zusätzliche Informationen',
labelAlign: 'top',
renderder: 'htmlEncode',
disabled: isDisabled,
anchor: '100%'
}