Skip to content

TomasHubelbauer/rbc-dnd-auto-select

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React Big Calendar Drag and Drop Auto-Select

LIVE

The demo app of RBC (remotely or locally when building from source) does not suffer from the problem I experienced. In it, dragging and resizing can happen immediately, without having to first click a slot to select it.

In my repository, this behavior is not preserved and click to select is needed, but only every other time.

My suspicion is that I am either using the calendar in a wrong way, which I cannot identify though, as my use and the use of it in the DnD demo looks the same to me, or the artifacts on NPM differ from the repository and this difference causes the difference in the functionality.

I have captured this for discussing in an issue in the RBC repository:

jquense/react-big-calendar#1227

Exploring

This is a stream-of-consciousness sandbox repository where I explore whether the React Big Calendar component utilizing the drag and drop addon can be made to auto select the event before it is being dragged or resized so that the amount of clicks needed goes from two (select, drag / resize) to one (immediate action).

If you aren't here for the journey and only care about the solution, go to the end.

This work relates to work on my other sandbox repository, rbc-rbc-resizable-event-wrapper. In that repository I have found that when using the drag and drop addon, it is best to use the event component instead of the eventWrapper component as an activation zone for specifically Ant Popover, but more generally any content which needs to stay on the are of the event.

This is because when using just the eventWrapper component, the event wrapper stays around the even. When using it in conjunction with the drag and drop addon, it instead stacks on top of the day column and has a zero height.

Let's prove that.

npx create-react-app . --typescript
npm install react-big-calendar moment --save
npm install @types/react-big-calendar --save-dev

Follow the setup instructions to get a basic calendar going and set it to week view.

Next up hook up the components property and set up a component for the eventWrapper field. You can just wrap the props.children in a div and see that since the rbc-event div - what we're wrapping - is absolutely positioned in CSS, it will jump out of this wrapper div and it will stack on top of the day column having zero height.

class EventWrapperComponent extends React.Component<EventWrapperProps, never> {
  public render() {
    return (
      <div data-info="This container does not wrap the event but instead stacks at the top of the day column container">
        {this.props.children}
      </div>
    );
  }
}

This cannot be fixed by making the wrapper div relative. That would squash the rbc-event container. Unless we could also get the rbc-event container's dynamic style.top property value to mirror in the event wrapper (we can't), the event wrapper won't help us.

Next up we could try using the event component, but that one will only fill up the area not occupied by the time range line or the drag and drop addon resizing handles. Our goal is to auto-select as event before the user starts dragging or resizing so we absolutely cannot miss a mouse event over the drag and drop addon resizing handles.

class EventComponent extends React.Component<EventWrapperProps, never> {
  public render() {
    return (
      <div data-info="This container doesn't fill the whole area of the event, importantly the time range line and the resize handles" style={{ height: '100%' }}>
        {this.props.children}
      </div>
    );
  }
}

What could work is to use an event wrapper after all but instead of actually wrapping the children, clone the sole top-level subtree root child and add the event handlers on it.

It would look like this:

function BoundEventWrapperComponent(app: App) {
  return class EventWrapperComponent extends React.Component<EventWrapperProps, never> {
    public render() {
      return React.cloneElement(React.Children.only(this.props.children) as ReactElement, { onMouseOver: this.onEventDivMouseOver });
    }
  
    private readonly onEventDivMouseOver: React.MouseEventHandler<HTMLDivElement> = event => {
      console.log(this.props);
    };
  };
}

However putting this together turns out we can access the style of the event! It's right there in the props. :-) So we can set the same style to the event wrapper and just put a mouse handler on it. We can introduce this immediate div without any issues. Do notice however that in the style object pass in, the values are in percents but are bare numbers so they get interpreted as pixels. This needs to be fixed otherwise the style would be off.

function BoundEventWrapperComponent(app: App) {
  return class EventWrapperComponent extends React.Component<EventWrapperProps, never> {
    public render() {
      // The left, top, width & height values are in percentages but come as bare numbers so we need to help that
      const style = {
        left: this.props.style!.left + '%',
        top: this.props.style!.top + '%',
        width: this.props.style!.width + '%',
        height: this.props.style!.height + '%',
        position: 'absolute' as 'absolute',
      };

      return (
        <div onMouseOver={this.onEventWrapperDivMouseOver} style={style}>
          {this.props.children}
        </div>
      );
    }
  
    private readonly onEventWrapperDivMouseOver: React.MouseEventHandler<HTMLDivElement> = _event => {
      app.setState({ selected: this.props.event });
    };
  };
}

By positioning the wrapper, the event needs to be reset to be placed at 0,0 and fill the absolute wrapper container area.

<style>{`.rbc-event { left: initial !important; top: initial !important; width: 100% !important; height: 100% !important; }`}</style>

But now we finally have a container to hook mouse events handlers up to. We propagate the event from the wrapped to the state of the application so that it finds its way back to the calendar's selected prop and now hovering over a set of events in the calendar makes it so that they become selected as soon as they get hovered-over.

The next step is to see if this still works with the DnD addon implemented. To implement it, follow the DnD addon starter here:

https://github.com/arecvlohe/rbc-with-dnd-starter/blob/master/src/App.js

Now when using the drag or resize features, during the action, the event fill up the entire day column, so I guess the React.cloneElement approach is better. Let's try that.

function BoundEventWrapperComponent(app: App) {
  return class EventWrapperComponent extends React.Component<EventWrapperProps, never> {
    public render() {
      const stockEventWrapper = React.Children.only(this.props.children) as React.ReactElement;
      const oldEventDiv = React.Children.only(stockEventWrapper.props.children) as React.ReactElement;
      const newEventDiv = React.cloneElement(oldEventDiv, { onMouseOver: this.onEventDivMouseOver });
      return React.cloneElement(stockEventWrapper, { children: newEventDiv });
    }
  
    private readonly onEventDivMouseOver: React.MouseEventHandler<HTMLDivElement> = _event => {
      app.setState({ selected: this.props.event });
    };
  };
}

Welp, turns out the selection state of the event doesn't affect the DnD behavior at all, events need to be clicked once before being actioned no matter what. I requested this changes:

jquense/react-big-calendar#1227

The above I have since found does not reproduce on the demo site of the calendar and even in the RBC source code when checked out locally, installed and ran using npm run examples. However the demo code is exactly the same as what I have in this repo.

I figured maybe there are fixes in trunk which have not made it to NPM yet so I tried to link to the local instance of the RBC source code, which I attempted by changing its build script to use set for environment variables and then running npm run build. Afterwards, I pointed into the local RBC source directory by running npm install ../react-big-calendar however this didn't work because it was looking for some files which existed in lib in dist and I don't know why.

Deploying

  • npm run build
  • Delete docs if it exists
  • Rename build to docs
  • Push to the origin