Premium support for our pure JavaScript UI components


Post by Qwerty »

For editing date columns in Gantt, we have a custom date editor making use of react-day-picker.

We are encountering an issue where when selecting the month or the year in the date picker dropdown, the cell editor is dismissed immediately, without calling finalizeCellEdit.

I dug through the Gantt code a bit, and found that commenting out the super.onFocusOut(event); call in the Editor.js method onFocusOut(event) leads to improved behaviour, where the date picker is no longer incorrectly dismissed, but is still dismissed at the proper time. The only problem with that solution, after dismissing the date picker, is the cell looks like a text box until clicking on another cell. But at least now the date picker is usable.

Do you have any suggestions for a better solution to this problem? This is on Gantt 4.2.6, which I realise is not the most recent version, but it was the most recent when we started the upgrade process. We aim to upgrade again soon after this upgrade is complete.


Post by Maxim Gorkovsky »

Hello.
As you may have noticed from DOM markup editor element and popup window live in different parts of the DOM and their common acestor is document.body. When you click on the editor focus inevitably goes out of the input element and browser is triggering blur event. That is a special case for blur event when we do not want to close editor. But we also want to close editor when real blur happens, e.g. when user clicks outside the cell.
For our widgets we have a code which checks if focus really goes out of the editor-popup system, smth like:

const fromWidget = Widget.fromElement(fromElement); // FocusEvent.relatedTarget
const toWidget = Widget.fromElement(toElement); // FocusEvent.target
const commonAncestor = DomHelper.getCommonAncestor(fromWidget, toWidget);

for (let target = fromWidget; target && target !== commonAncestor; target = target.owner) {
  // propagate focusout through component tree
}

In case user clicks inside our standard date picker commonAncestor is calculated to be a DateField instance, which is also a fromWidget, this is how we know focus is still inside the system and we should not stop editing.

What you need to do is to avoid going inside that for loop which calls onFocusOut on every widget in the tree. You can try overriding DomHelper.getCommonAncestor:

Override.apply(class DomHelperOverride {
  static get target() { return { class : DomHelper } }

  static getCommonAncestor(from, to) {
    // check if to is your react picker
    if (isReactDatePicker(to)) {
      // check if to widget is actually owned by the from widget, i.e. to is a picker for from
      return from;
    }
    else {
      return this._overridden.getCommonAncestor.call(this, from, to);
  }
});

Post by Qwerty »

Hi, we're revisiting this in the process of upgrading to Gantt 5.1.1.

I tried the override, but even when isReactDatePicker returns true, the cell still loses focus. Additionally, when clicking on the date picker, isReactDatePicker is never called with the element that was actually clicked on, so I'm really not sure what to do here.

Please find a runnable example attached of what I am trying to do (including the override), although I deleted node-modules and .npmrc before zipping. The Date component here is a modified copy of our own app's component, and the app itself is modified from the react basic demo.

Attachments
basic-day-picker-zipped.zip
(2.44 MiB) Downloaded 32 times

Post by Maxim Gorkovsky »

Hello.
Widget gets hidden because focus goes to the different tree of the DOM. It can be fixed with another override:

import { Override, DomHelper, Editor } from '@bryntum/gantt';

// elem could be null (if Bryntum widget cannot be resolved from the element) or it would be a node within
// the date picker.
// Use better selector to properly distinguish react date picker elements
const isReactDatePicker = (elem) => !elem || !!elem.closest('.rdp');

Override.apply(class DomHelperOverride {
  static get target() { return { class : DomHelper }; }

  static getCommonAncestor(from, to) {
    // check if to is your react picker
    if (isReactDatePicker(to)) {
      // if widget is a react date picker, `to` would be null. return from widget instead
      return from;
    }
    else {
      return this._overridden.getCommonAncestor.call(this, from, to);
    }
  }
});

Override.apply(class {
    static target = {
        class: Editor
    }

    owns(elementOrWidget) {
        let result = this._overridden.owns.call(this, elementOrWidget);

        if (!result && elementOrWidget instanceof HTMLElement) {
          // check if element is within date picker element
          result = !!elementOrWidget.closest('.rdp');
        }

        return result;
    }
});

Post by Qwerty »

Hi Maxim,

Thanks, the override works well.

However, there is one issue we noticed - it seems to make HeaderMenus impossible to dismiss. TaskMenus are not affected. It seems to be specifically the DomHelperOverride causing this. Obviously though that override is needed.


Post by alex.l »

Hi Qwerty,

So you are unblocked now with overrides provided?

All the best,
Alex


Post by Qwerty »

Hi Alex,

Kind of, but the issue with the HeaderMenus not dismissing is there. We are running on Gantt 5.1.5 on the upgrade branch now, and the issue still occurs.

Is there a change to the overrides that would fix this issue, or some alternative method of achieving this?


Post by Maxim Gorkovsky »

You need to make isReactDatePicker more aware of the surroundings:

const isReactDatePicker = (from, to) => {
  // if focus goes from cell editor to no widget or to the special element
  if (from?.isWidget && from.element.querySelector('.foo') && (!to || !!to.closest('.rdp'))) {
    return true;
  }
};

This also requires putting some class to the editor to tell if focus is going out of the custom cell editor:

<span className='foo' data-tip={this.state.errors}>

This solves menu problem for me


Post Reply