React-dnd: La connexion de monitor.getClientOffset à un DropTarget n'actualise pas les accessoires

Créé le 3 juin 2015  ·  22Commentaires  ·  Source: react-dnd/react-dnd

J'ai créé un DropTarget et connecté getClientOffset comme suit:

targetCollect = function (connect, monitor) {
  return {
    connectDropTarget: connect.dropTarget(),
    isOver: monitor.isOver(),
    clientOffset: monitor.getClientOffset()
  }
}

Cependant, lors du déplacement du glisser dans le composant, la cible ne reçoit pas d'accessoires mis à jour à chaque mouvement de la souris (uniquement à l'entrée et à la sortie). Le survol de la spécification cible est cependant appelé à plusieurs reprises. Y a-t-il une raison pour laquelle la prop clientOffset n'est pas injectée chaque fois qu'elle est modifiée?

bug wontfix

Commentaire le plus utile

+1. Se heurter à cela et perdre beaucoup de temps à essayer de comprendre pourquoi getClientOffset () fonctionne bien dans canDrop () - qui est appelé à plusieurs reprises ... - mais aucun moyen de transmettre à mon contrôle `` où '' l'élément que nous sommes. J'ai besoin de pouvoir insérer au-dessus et au-dessous d'un treenode, pas seulement "on". canDrop () n'a aucun moyen que je puisse trouver qui me permette de dire à mon composant lequel des nombreux "types" de gouttes du nœud a afin que mon contrôle puisse réafficher en conséquence. La modification de l'accessoire dans canDrop a entraîné une erreur. L'abonnement à onMouseMove ne fonctionne pas pendant les opérations de glisser-déposer. Cela a été une énorme perte de temps jusqu'à présent ... toute aide serait appréciée.

@ibash ,

Tous les 22 commentaires

Oh, bon point. C'est vrai, je n'avais pas prévu cet usage. Actuellement, le seul moyen d'activer le suivi du décalage client est d'utiliser DragLayer . Sinon, pour des raisons de performances, il ne met à jour les accessoires que si quelque chose concernant la _drop target elle-même_ a changé.

Je dirais que c'est un problème d'API, et il peut y avoir plusieurs solutions:

  • Interdire d'atteindre getClientOffset() et des méthodes similaires depuis l'intérieur de la fonction collect et dites d'utiliser DragLayer place (facile mais stupide);
  • Déterminez automatiquement que l'utilisateur souhaite suivre le décalage et s'abonner aux modifications de décalage si tel est le cas (plus difficile mais plus cohérent).

Je pense que j'accepterais un PR faisant la deuxième option. Vous pouvez vérifier la mise en œuvre de DragLayer pour voir comment elle s'abonne aux modifications de décalage.

J'ai examiné la deuxième option, mais elle est délicate car la collecte est une fonction, il n'est donc pas possible / facile de savoir quelles fonctions du moniteur peuvent être appelées.

Mon cas d'utilisation est un glissement de widgets qui sont accrochés à l'emplacement, déplaçant éventuellement d'autres widgets d'une manière relativement complexe (pensez à faire glisser des icônes de programme sur un Android). C'est vraiment la cible qui doit se réorganiser en fonction de l'emplacement exact du survol, tandis que la traînée elle-même est parfaitement correcte en tant qu'instantané HTML5. Je peux le faire avec le survol de la spécification cible mettant à jour l'état du composant cible. Je vais donc laisser ça pour l'instant et contourner le problème. Merci pour une bibliothèque incroyable; J'ai quelque chose de décent qui travaille en très peu d'heures de travail.

Gardons cela ouvert pour le moment. C'est la même chose que # 172: je n'avais pas prévu que les gens utilisent des deltas dans canDrop ou la fonction de collecte. Je ne vais pas résoudre ce problème tout de suite, mais peut-être après avoir libéré dans un mois environ.

J'ai également rencontré ce problème. Que pensez-vous de la possibilité pour les clients de passer un indicateur dans les spécifications DropTarget pour s'abonner aux modifications de compensation? C'est un autre «piège» pour l'utilisateur, mais c'est plus simple que de s'abonner / se désabonner manuellement aux offsets.

Vous ne pourriez peut-être pas utiliser RxJS-DOM pour observer l'événement dragover sur l'élément et mettre à jour l'état avec les coordonnées de la souris? Solution Hacky peut-être.

  componentDidMount() {
    this._events.dragOver = DOM.fromEvent(findDOMNode(this.refs.grid), 'dragover')
                            .throttle(200)
                            .subscribe(this.getMouseCoords)
  }

J'ai fini par saisir le moniteur et m'abonner à des changements de décalage comme le DragLayer. J'ai également fini par puiser dans le registre interne par endroits. Je ne pense pas que ce que je veux faire soit trop inhabituel, donc l'OMI pourrait y remédier par de simples changements d'API.

Quelque chose est-il arrivé? Je souhaite surveiller le décalage du client dans mon DropTarget plutôt que dans le calque de glissement personnalisé, car j'ai l'impression qu'il est plus facile de modifier la structure de DropTarget lorsque je peux accéder à l'emplacement de survol depuis le DropTarget au lieu du calque de glissement personnalisé.

+1 Je voudrais faire exactement ce que @jchristman essaie de faire.

+1. Se heurter à cela et perdre beaucoup de temps à essayer de comprendre pourquoi getClientOffset () fonctionne bien dans canDrop () - qui est appelé à plusieurs reprises ... - mais aucun moyen de transmettre à mon contrôle `` où '' l'élément que nous sommes. J'ai besoin de pouvoir insérer au-dessus et au-dessous d'un treenode, pas seulement "on". canDrop () n'a aucun moyen que je puisse trouver qui me permette de dire à mon composant lequel des nombreux "types" de gouttes du nœud a afin que mon contrôle puisse réafficher en conséquence. La modification de l'accessoire dans canDrop a entraîné une erreur. L'abonnement à onMouseMove ne fonctionne pas pendant les opérations de glisser-déposer. Cela a été une énorme perte de temps jusqu'à présent ... toute aide serait appréciée.

@ibash ,

Le passage à l'utilisation du survol l'a finalement fait pour moi.

`hover: (accessoires: MyProps, moniteur: DropTargetMonitor, composant: ReportItemRow) => {
if (composant! = null) {
const clientOffset = monitor.getClientOffset ();

        // Is the dragging node dragged above/below us as opposed to "on" us?

        const elementRect = document.elementFromPoint(clientOffset.x, clientOffset.y).getBoundingClientRect();

        const percentageY = (clientOffset.y - elementRect.top) / elementRect.height;

        // Divide the box up into .25 .5 .25
        const insertDragAndDropMagnetPercent = .25;

        if (insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "above" });
        }
        else if (1 - insertDragAndDropMagnetPercent >= percentageY) {
            component.setState({ dropTarget: "inside" });
        }
        else {
            component.setState({ dropTarget: "below" });
        }
    }
},`

@ noah79 , en attendant que quelque chose se

         _____
         _____| <-- drop div1 (above treenode1/below top of list)
treenode1_____| <-- drop div2 (on treenode1)
         _____| <-- drop div3 (below treenode1/above treenode2)
treenode2_____| <-- drop div4 (on treenode 2)
         _____| <-- drop div5 (below treenode2/above bottom of list)

Vous vous retrouvez avec 2n + 1 cibles de dépôt, où n est le nombre d'éléments de votre liste. Ensuite, en fonction de laquelle des divs est survolée, vous pouvez modifier l'apparence de la liste arborescente. J'ai fait quelque chose de très similaire à celui-ci pour éviter de ne pas pouvoir accéder à getClientOffset () pour le moment, et je n'ai pas remarqué de perte de performances. La hauteur de chaque div de chute doit être égale à 1/2 de la hauteur de la ligne treenode1 et la première div de chute doit être située à 1/4 de la hauteur de la ligne treenode1 plus haute que la première ligne. Autrement dit, votre CSS devrait ressembler à ceci:

.dropdiv-container {
    position: absolute;
    top: -0.25em; /* If the top of this container is aligned with the top of the treenode list initially */
}
.dropdiv {
    height: 0.5em; /* If the height of treenode1 line is 1em with no padding */
}
<div className='dropdiv-container'>
    { renderDropTargets(2 * listLength + 1) }
</div>

Cela a-t-il du sens?

Voici un aperçu de la façon dont j'ai résolu ce problème. L'astuce consiste simplement à atteindre
react-dnd internals et utilisez directement le moniteur global. Jetez un oeil au
DragDropMonitor.js source pour avoir une idée des méthodes disponibles.
réf: https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

Vous devrez excuser le coffeescript :)

  # in the component being dragged, get access to the dragDropManager by adding
  # it to the contextTypes
  #
  # dragDropManager is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropManager.js

  <strong i="11">@contextTypes</strong>: {
    dragDropManager: React.PropTypes.object
  }

  # because we can receive events after the component is unmounted, we need to
  # keep track of whether the component is mounted manually.
  #
  # <strong i="12">@_monitor</strong> is what lets us access all the nice internal state - it is an instance of this:
  # https://github.com/gaearon/dnd-core/blob/master/src/DragDropMonitor.js

  componentWillMount: () =>
    <strong i="13">@_isMounted</strong> = true
    <strong i="14">@_monitor</strong> = @context.dragDropManager.getMonitor()

    <strong i="15">@_unsubscribeToStateChange</strong> = @_monitor.subscribeToStateChange(@_onStateChange)
    <strong i="16">@_unsubscribeToOffsetChange</strong> = @_monitor.subscribeToOffsetChange(@_onOffsetChange)

  componentWillUnmount: () =>
    <strong i="17">@_isMounted</strong> = false
    <strong i="18">@_monitor</strong> = null

    @_unsubscribeToStateChange()
    @_unsubscribeToOffsetChange()

  # we can access dragging / dropping state by accessing the monitor 

  _onStateChange: () =>
    return unless <strong i="19">@_isMounted</strong>

    # When we stop dragging reset the counter state and hide all cursors.
    if <strong i="20">@_previousIsDragging</strong> && !@_monitor.isDragging()
      console.log('no longer dragging')
    <strong i="21">@_previousIsDragging</strong> = @_monitor.isDragging()

  _onOffsetChange: () =>
    return unless <strong i="22">@_isMounted</strong>

    # mouse is the x/y coordinates
    mouse = @_monitor.getClientOffset()

    # item is the drag item
    item = @_monitor.getItem()

    # if you want to check if a dragged item is over a target, you need the
    # targetId -- in the DropTarget wrapper you can pass it in like:
    #
    #   (connect, monitor) ->
    #     {
    #       targetId: monitor.targetId,
    #       connectDropTarget: connect.dropTarget()
    #     }
    #
    # and then you can use it like below

    @_monitor.isOverTarget(@props.targetId))

Est-ce que ça a du sens? Sinon je peux ajouter plus de détails

J'étais également confronté à cela aujourd'hui et j'ai perdu quelques heures. Pourquoi ne pas laisser une note à ce sujet dans la documentation sous DropTarget -> La fonction de collecte? Cela épargnerait au moins quelques autres pour les frustrations.

Btw. un autre hack vraiment laid autour de ce problème peut être d'envoyer les coordonnées _as state_ à partir de dropTarget.hover() :

const dropTarget = {
    hover(props, monitor, component) {
        // HACK! Since there is an open bug in react-dnd, making it impossible
        // to get the current client offset through the collect function as the
        // user moves the mouse, we do this awful hack and set the state (!!)
        // on the component from here outside the component.
        component.setState({
            currentDragOffset: monitor.getClientOffset(),
        });
    },
    drop() { /* ... */ },
};

Bien sûr, il existe des tonnes de façons plus correctes de faire cela qui éviteraient d'abuser de setState comme ça. Mais au moins, ce petit hack est très condensé et peut faire le travail jusqu'à ce que ce problème soit finalement résolu. Il vous permet de contourner le bogue sans changer d'autres composants et / ou fichiers et sans compter sur les composants internes de la bibliothèque.

Edit: c'est fondamentalement la même chose que noah79, je n'ai tout simplement pas lu son code avant maintenant.

N'y a-t-il vraiment aucun moyen d'obtenir la position de la souris en cas de chute? Mon composant cible n'a pas besoin de le savoir, donc setState n'est pas une option pour moi. J'ai juste besoin des coordonnées pour déclencher une action redux.

Veuillez ignorer mon dernier message. Je vois maintenant que drop et endDrag sont deux fonctionnalités différentes, et obtenir le positionnement du curseur dans drop fonctionne comme prévu. :)

D'après mes tests, react-dnd consomme les événements de déplacement de la souris dès qu'un élément est glissé sur la cible. S'il était possible que le backend html ne consomme pas cet événement, le composant cible pourrait simplement placer un écouteur mousemove normal sur son dom de manière conditionnelle lorsque la propriété isOver est définie sur true. Par la suite, cet auditeur pourrait prendre la position de la souris (de manière normale) lorsque quelque chose est glissé dessus. J'ai fait fonctionner cela temporairement en utilisant setState de la méthode drag () react donne des avertissements sur la définition de l'état au milieu d'une transition de rendu.

Cela m'a fait trébucher aussi. +1 pour une solution API ou au moins quelque chose sur la FAQ.

Y a-t-il un plan pour soutenir cela?
J'ai une idée pour une solution de contournement sans piratage: créer un wrapper pour DropTarget qui enveloppe également hover pour déclencher un autre appel à la fonction de collecte et un nouveau rendu si la fonction de collecte renvoie de nouvelles valeurs .

+1 pour ce numéro. le cas d'utilisation que j'ai est que j'ai plusieurs cibles de dépôt qui sont à proximité les unes des autres. Il semble étrange qu'en survolant, la cible de largage appropriée soit mise en surbrillance, mais en fait, tomber sur une cible inférieure en fonction de la direction depuis laquelle vous avez survolé.

Je suis tombé sur ce problème en recherchant pourquoi les compensations ne sont pas mises à jour, il semble qu'il ait encore besoin d'une solution de contournement.
J'ai repris l'idée de @ jedwards1211 et voici ce que j'ai trouvé. Le HOC a principalement la même interface que vanilla DropTarget , mais accepte collectRapidly dans ses options qui est une liste d'accessoires à interroger via la fonction de collecte + transmettre à chaque événement de survol. Passer tous les accessoires sans discernement de la fonction de collecte a causé une certaine bizarrerie et nous ne pouvons pas passer du tout connect au collecteur, il y a donc une protection contre l'interrogation de connectDropTarget "rapidement".

Donc au lieu de DropTarget(types, target, collect) vous utiliseriez RapidFireDropTarget(types, target, collect, {collectRapidly: ['offset']}) , où offset est probablement quelque chose recevant sa valeur de la famille de fonctions monitor.getOffset .

import React from 'react';
import {DropTarget} from 'react-dnd';
import pick from 'lodash/pick';

function RapidFireDropTarget(types, spec, collect, options={}) {
  let collectProps = {};
  const prevHover = spec.hover;
  const {collectRapidly = []} = options;

  const dummyConnect = {
    dropTarget: () => () => {
      throw new Error('Rapidly collected props cannot include results from `connect`');
    }
  };

  const WrappedComponent = Component => {
    return class extends React.Component {
      render() {
        return <Component {...this.props} {...collectProps} />;
      }
    };
  };

  spec.hover = (props, monitor, component) => {
    const allCollectProps = collect(dummyConnect, monitor);

    collectProps = pick(allCollectProps, collectRapidly);
    component.forceUpdate();

    if (prevHover instanceof Function) {
      prevHover(props, monitor, component);
    }
  }

  return (Component) => {
    return DropTarget(types, spec, collect, options)(WrappedComponent(Component));
  };
}

export default RapidFireDropTarget;

Ce problème a été automatiquement marqué comme obsolète car il n'a pas eu d'activité récente. Il sera fermé si aucune autre activité n'a lieu. Merci pour vos contributions.

Cette page vous a été utile?
0 / 5 - 0 notes