React-dnd: "canDrop", comment savoir sur quel élément on est au dessus ?

Créé le 14 avr. 2016  ·  4Commentaires  ·  Source: react-dnd/react-dnd

Dans la méthode drop(props, monitor, component) , nous avons trois arguments. Mais dans le canDrop(props, monitor) il n'y a pas le component arg.

Je veux que lorsqu'un élément est glissé sur un autre composant, je "divise" ce composant en deux parties de 50% (hauteur). Si l'objet est déposé dans la partie supérieure, il sera placé au-dessus, s'il est déposé dans la partie inférieure, il sera placé après.

J'ai déjà le code qui détecte si le composant est déposé dans la partie supérieure/inférieure. Ce que je veux maintenant, c'est ajouter une classe selon que la souris est dans la partie supérieure/inférieure (mais pendant l'action de survol, pas l'action de chute).

Je ne sais pas si je peux utiliser la méthode hover(props, monitor, component) , car je ne vois pas comment je suis censé ajouter une propriété dans mon composant (qui ajouterait une classe CSS dans le DOM ou similaire) de car il n'appartient pas au composant lui-même et n'a pas accès à son contexte et ainsi de suite.

Je suppose qu'il me manque quelque chose. :)

Commentaire le plus utile

OK, donc l'idée est que vous voulez définir l'état dans votre composant, puis lorsque l'état est défini, vous ajoutez la classe.

Disons que vous faites glisser un Foo dans un bar :

import React, { Component } from "react";
import classNames from "classnames";

class Bar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      // Where a foo is hovering over the component.  One of null, "above", or "below"
      fooHover: null,
    };
  }

  render() {
    const { fooHover } = this.state;

    const classes = classNames("bar", {
      "bar-above": fooHover === "above",
      "bar-below": fooHover === "below",
    });
  }
}

La seule question est de savoir comment définir cet état !

import { findDOMNode } from "react-dom";

const barTarget = {
  ...
  hover(props, monitor, component) {
    if (!monitor.canDrop()) {
      return;
    }

    const rawComponent = undecorate(component); // undecorate described below

    const { y } = monitor.getClientOffset();
    const { top, height } = findDOMNode(component).getBoundingClientRect();

    if (y < top + height/2) {
      rawComponent.setFooHover("above"); // setFooHover described below
    } else {
      rawComponent.setFooHover("below");
    }
  },
  ...
}

Donc, en gros, vous voulez appeler une méthode d'instance de votre composant, setFooHover , qui appellera setState . Cela devrait ressembler à ceci pour des raisons de performances :

  // In Bar
  setFooHover(fooHover) {
    if (fooHover !== this.state.fooHover) {
      this.setState({ fooHover });
    }
  }

La raison pour laquelle j'appelle undecorate ci-dessus est que react-dnd utilise des composants d'ordre supérieur, et nous voulons les composants non emballés. C'est une complication laide de composants d'ordre supérieur à mon avis. Vous pourriez peut-être implémenter cela à la place en envoyant une action à votre magasin de flux ou quelque chose du genre, mais au cas où vous décideriez de le faire comme moi, voici mon undecorate :

function undecorate(component) {
  let curr = component;
  while (typeof curr.getDecoratedComponentInstance === "function") {
    curr = curr.getDecoratedComponentInstance();
  }

  return curr;
}

Maintenant, si vous implémentez cela, tout ira bien, à l'exception d'un petit problème. La classe ne s'en va jamais ! Personne n'appelle jamais setHoverState(null) (il n'y a pas de fonction endHover dans votre barTarget). La bonne façon de gérer cela est d'utiliser componentWillReceiveProps comme ceci :

  componentWillReceiveProps(nextProps) {
    if (this.props.isFooOver && !nextProps.isFooOver) {
      this.setState({ fooHover: null });
    }
  }

(N'oubliez pas d'ajouter { isFooOver: monitor.isOver() } dans votre fonction de collecte)

Faites-moi savoir comment ça se passe!

Tous les 4 commentaires

Hé, vous avez de la chance! J'ai déjà implémenté presque exactement cela, alors laissez-moi rassembler un peu de code.

OK, donc l'idée est que vous voulez définir l'état dans votre composant, puis lorsque l'état est défini, vous ajoutez la classe.

Disons que vous faites glisser un Foo dans un bar :

import React, { Component } from "react";
import classNames from "classnames";

class Bar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      // Where a foo is hovering over the component.  One of null, "above", or "below"
      fooHover: null,
    };
  }

  render() {
    const { fooHover } = this.state;

    const classes = classNames("bar", {
      "bar-above": fooHover === "above",
      "bar-below": fooHover === "below",
    });
  }
}

La seule question est de savoir comment définir cet état !

import { findDOMNode } from "react-dom";

const barTarget = {
  ...
  hover(props, monitor, component) {
    if (!monitor.canDrop()) {
      return;
    }

    const rawComponent = undecorate(component); // undecorate described below

    const { y } = monitor.getClientOffset();
    const { top, height } = findDOMNode(component).getBoundingClientRect();

    if (y < top + height/2) {
      rawComponent.setFooHover("above"); // setFooHover described below
    } else {
      rawComponent.setFooHover("below");
    }
  },
  ...
}

Donc, en gros, vous voulez appeler une méthode d'instance de votre composant, setFooHover , qui appellera setState . Cela devrait ressembler à ceci pour des raisons de performances :

  // In Bar
  setFooHover(fooHover) {
    if (fooHover !== this.state.fooHover) {
      this.setState({ fooHover });
    }
  }

La raison pour laquelle j'appelle undecorate ci-dessus est que react-dnd utilise des composants d'ordre supérieur, et nous voulons les composants non emballés. C'est une complication laide de composants d'ordre supérieur à mon avis. Vous pourriez peut-être implémenter cela à la place en envoyant une action à votre magasin de flux ou quelque chose du genre, mais au cas où vous décideriez de le faire comme moi, voici mon undecorate :

function undecorate(component) {
  let curr = component;
  while (typeof curr.getDecoratedComponentInstance === "function") {
    curr = curr.getDecoratedComponentInstance();
  }

  return curr;
}

Maintenant, si vous implémentez cela, tout ira bien, à l'exception d'un petit problème. La classe ne s'en va jamais ! Personne n'appelle jamais setHoverState(null) (il n'y a pas de fonction endHover dans votre barTarget). La bonne façon de gérer cela est d'utiliser componentWillReceiveProps comme ceci :

  componentWillReceiveProps(nextProps) {
    if (this.props.isFooOver && !nextProps.isFooOver) {
      this.setState({ fooHover: null });
    }
  }

(N'oubliez pas d'ajouter { isFooOver: monitor.isOver() } dans votre fonction de collecte)

Faites-moi savoir comment ça se passe!

Merci beaucoup pour l'explication détaillée ! je vous tiens au courant :)

Enfin, j'ai utilisé l'approche Redux, j'ai donc essentiellement envoyé un événement via un réducteur lorsque l'événement drop a été déclenché.

    drop: (propsTargetItem, monitor, targetItem) ->
        draggedItem = monitor.getItem()# Item that has been dragged and eventually dropped.
        coordsDrop = monitor.getClientOffset()# Coords of the drop action.
        diff = monitor.getDifferenceFromInitialOffset()# Comparison of the initial click (dragging start) against the end of the click (dropping start).

        patternId = draggedItem.patternId
        patternPosition = draggedItem.position
        targetId = propsTargetItem.context.patternId
        targetPosition = propsTargetItem.context.position
        isCursorAboveHalf = GeoService.isAboveHalf(ReactDOM.findDOMNode(targetItem), coordsDrop.y)

        # Extract positions in the array of children.
        oldPosition = draggedItem.position# Current position of the item, which will be the old position as soon as the item is moved. (by the server through sockets)
        newPosition = calcDropPosition(targetId, patternId, patternPosition, targetPosition, isCursorAboveHalf)# Position where we will move the dragged item.

        # Dispatch only if the position has changed and is set.
        if oldPosition isnt newPosition and newPosition?
            draggedItem.moveDnDPattern(Object.assign({}, draggedItem, {newPosition: newPosition, oldPosition: oldPosition}))

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