Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A way to drag outside to iframe #1496

Open
xiaoshuangLi opened this issue Aug 1, 2019 · 10 comments
Open

A way to drag outside to iframe #1496

xiaoshuangLi opened this issue Aug 1, 2019 · 10 comments
Assignees
Labels
design decisions enhancement pinned won't become stale from stalebot

Comments

@xiaoshuangLi
Copy link

xiaoshuangLi commented Aug 1, 2019

Is your feature request related to a problem? Please describe.
React limits that drag-and-drop within a single HTML document. Somehow we need drag something outside to iframe inside.

Describe the solution you'd like
I find that dnd HTML5backend just trigger drag callbacks in window. So if we can make the components inside iframe trigger those callback.And React.createPortal make components inside iframe have same DndProvider with outside.It works, luckily. But i worry about may some problem i didn't notice.And the code is not so convenient with HTML5Backend.

import React, {
  useRef,
  useEffect,
  useContext,
  forwardRef,
} from 'react';
import classnames from 'classnames';
import { DndContext } from 'react-dnd';

// Frame base on createPortal inside iframe dom.
import Frame from 'react-frame-component';

const events = [
  ['dragstart', 'handleTopDragStart', false],
  ['dragstart', 'handleTopDragStartCapture', true],
  ['dragend', 'handleTopDragEndCapture', true],
  ['dragenter', 'handleTopDragEnter', false],
  ['dragenter', 'handleTopDragEnterCapture', true],
  ['dragleave', 'handleTopDragLeaveCapture', true],
  ['dragover', 'handleTopDragOver', false],
  ['dragover', 'handleTopDragOverCapture', true],
  ['drop', 'handleTopDrop', false],
  ['drop', 'handleTopDropCapture', true],
];

export const DndFrame = forwardRef((props = {}, ref) => {
  const container = useRef(null);
  const dndValue = useContext(DndContext) || {};

  const { dragDropManager: { backend = {} } = {} } = dndValue;
  const { children, className, containerProps = {}, ...others } = props;

  const cls = classnames({
    'dnd-frame': true,
    [className]: !!className,
  });

  useEffect(() => {
    const { current } = container;

    if (!current) {
      return;
    }

    // this make callback run in outside window
    events.forEach((eventArgs = []) => {
      const [name, callbackName, capture = false] = eventArgs;

      const callback = backend[callbackName] && backend[callbackName].bind(backend);

      current.addEventListener(name, (...args) => {
        callback && callback(...args);
      }, capture);
    });
  }, [container.current]);

  return (
    <Frame className={cls} ref={ref} {...others}>
      <div className="dnd-frame-container" ref={container} {...containerProps}>
        { children }
      </div>
    </Frame>
  );
});

Describe alternatives you've considered
May you can export some options with HTML5Backend.

@HsuTing
Copy link

HsuTing commented Aug 7, 2019

I found an easier solution to simplify this code, but I am not sure if this one is a better solution. Hope to help you to fix this issue.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

@xiaoshuangLi
Copy link
Author

@HsuTing It's easier and better. But still bound to HTML5Backend interface. If HTML5Backend changes addEventListeners => addListeners, our web would be broke down

@darthtrevino
Copy link
Member

darthtrevino commented Aug 7, 2019 via email

@humayunkabir
Copy link

I found an easier solution to simplify this code, but I am not sure if this one is a better solution. Hope to help you to fix this issue.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

It's a great approach and works for me.

@shankyms
Copy link

I found an easier solution to simplify this code, but I am not sure if this one is a better solution. Hope to help you to fix this issue.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

When trying with typescript I get an error Property 'addEventListeners' does not exist on type 'Backend'

is there any way to fix this in TS ?

@darthtrevino
Copy link
Member

addEventListeners isn't exposed on the Backend interface; and I don't want to expose it because it's DOM/Browser specific. I think what we should probably do is something like:

// detect if SSR mode
const DEFAULT_GLOBAL_CONTEXT = typeof window !== 'undefined' ? window : global

const DndFrame = ({ children, globalContext = DEFAULT_GLOBAL_CONTEXT}) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);
  const backend = useMemo(() => dragDropManager.getBackend(), [dragDropManager])

  useEffect(() => {
     // This will required adding an initialize() method to the Backend interface.
    // Backend constructors will have to thunk over to it. It could replace constructors, but that would be semver major.
    backend.initialize(dragDropManager, globalContext)
    backend.setup()
  }, [globalContext]);

  return children;
};

@xiaoshuangLi
Copy link
Author

Create a repo for this. And resolve the style problem.

react-mc-dnd-frame

@drmzio
Copy link

drmzio commented Mar 17, 2021

I found an easier solution to simplify this code, but I am not sure if this one is a better solution. Hope to help you to fix this issue.

import React, { useContext, useEffect } from 'react';
import DndProvider, { DndContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import Frame, { FrameContext } from 'react-frame-component';

const DndFrame = ({ children }) => {
  const { dragDropManager } = useContext(DndContext);
  const { window } = useContext(FrameContext);

  useEffect(() => {
    dragDropManager.getBackend().addEventListeners(window);
  });

  return children;
};

const Example = () => (
  <DndProvider backend={HTML5Backend}>
    <Frame>
       <DndFrame>
          <div>...</div>
       </DndFrame>
    </Frame>
  </DndProvider>
);

When trying with typescript I get an error Property 'addEventListeners' does not exist on type 'Backend'

is there any way to fix this in TS ?

In case anyone comes across this, I just added the //@ts-ignore above the addEventListeners line and it worked for me.

@monkeyphysics
Copy link

For future visitors, there's a simple solution: https://react-dnd.github.io/react-dnd/examples/dustbin/iframe

@armedi
Copy link

armedi commented Sep 23, 2022

For future visitors, there's a simple solution: https://react-dnd.github.io/react-dnd/examples/dustbin/iframe

This solution doesn't work for me. The example shows dragging item within the iframe, not from outside to inside iframe.

I tried this solution from @HsuTing and it still works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design decisions enhancement pinned won't become stale from stalebot
Projects
None yet
Development

No branches or pull requests

8 participants