IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

React Discussion :

drag and drop pour un élément dans un composant ( lui-même dans un composant draggable )


Sujet :

React

  1. #1
    Membre régulier
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2020
    Messages
    275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Indre et Loire (Centre)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2020
    Messages : 275
    Points : 113
    Points
    113
    Par défaut drag and drop pour un élément dans un composant ( lui-même dans un composant draggable )
    Bonjour

    Le titre n'est pas très explicite mais je vais expliquer mon souci le mieux possible.

    Dans une application, j'ai un composant qui affiche plusieurs blocs de données ( ici des repas pour exemple ) et chaque bloc de repas peut être déplacé avec un drag and drop.
    J'ai essayé de faire le plus simple possible avec le moins de code, n'hésitez pas à me dire s'il y a une meilleurs façon de faire ( surtout que ça peut impacter la suite ).

    Je sais déjà que lorsqu'il y a des aliments dans le repas, le "glissé" est moins sympas...

    Voici donc ce composant:
    Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    import React, { useState } from "react";
    import FoodList from "./FoodList";
    import "../../css/mealComposition.css"
     
    const Home: React.FC = () => {
     
    	interface Meal {
    		name: string,
    		foods: Array<string>
    		nbInputs: number
    	}
     
    	const [mealList, setMealList] = useState<Meal[]>([
    		{name: "Repas 1", foods: ["banane", "tomate"]},
    		{name: "Repas 2", foods: []},
    		{name: "Repas 3", foods: ["pain", "viande"]},
    	])
     
    	const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
     
    		const targetElement = e.target as HTMLDivElement
    		setTimeout(() => targetElement.classList.add('dragging'), 0)
    	}
     
    	const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
     
    		const targetElement = e.target as HTMLDivElement
    		const zone = document.querySelector('.mealDayContent')
    		const draggedElement = document.querySelector('.dragging')
    		const enterElementPositionY = targetElement.getBoundingClientRect().top
    		const draggedElementPositionY = draggedElement.getBoundingClientRect().top
     
    		zone.addEventListener('dragover', (e) => {
    			e.preventDefault()
    		})
     
    		if (enterElementPositionY > draggedElementPositionY) {
    			zone.insertBefore(draggedElement, targetElement.nextSibling)
    		} else {
    			zone.insertBefore(draggedElement, targetElement)
    		}
    	}
     
    	const handleDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
    		const targetElement = e.target as HTMLDivElement
    		targetElement.classList.remove('dragging')
    	}
     
    	const addMeal = () => {
    		const newMeal = {name: `repas ${mealList.length + 1}`, foods: [], nbInputs: 0}
    		const updateMealList = [...mealList, newMeal]
    		setMealList(updateMealList)
    	}
     
    	return(
    		<>
    			<div className="mealDay">
    				Repas de la journée: 
    				<button onClick={addMeal}> + </button>
    			</div>
    			<div className="mealDayContent">
    				{mealList.map((meal: Meal, index: number) => (
    					<div 
    						key={index} 
    						className="meal"
    						draggable="true"
    						onDragStart={(e) => handleDragStart(e)}
    						onDragEnter={(e) => handleDragEnter(e)}
    						onDragEnd={(e) => handleDragEnd(e)}>
     
    						<div className="mealName">
    							{meal.name} 
    						</div>
     
    						<FoodList foodList={meal.foods}/>
    					</div>
    				))}
    			</div>
    		</>)
    }
     
    export default Home

    Pour l'instant, je fais simple car j'apprends le drag and drop ( jamais fait avant encore moins avec React... !)
    Il reste bien sûr à réorganiser l'objet mealList ( je me débrouillerai avec les index au moment du dragend )

    Le problème, le vrai, est le suivant: mon composant Foodlist affiche une liste d'ingrédients et je voudrais pouvoir déplacer ces ingrédients
    à l'intérieur d'un repas dans la foodList, mais aussi vers une foodList d'un autre repas.

    Par exemple: je dois pouvoir glisser tomate dans le repas 3 ( n'importe ou dans la liste, avant le pain, après la viande ou entre les deux )

    voici donc mon composant FoodList:
    Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React from "react";
     
    interface FoodListProps {
    	foodList: Array<string>
    }
     
    const FoodList: React.FC<FoodListProps> = ({foodList}: FoodListProps) => {
    	return (
    		<div className="foodList">
    			{foodList.map((food: string, index: number) => (
    				<div key={index} draggable="true" className="food">
    					{food}
    				</div>
    			))}
    		</div>
    	)
    }
     
    export default FoodList

    Quelles seraient les pistes ? Parce que pour le moment, le composant parent a un impact sur ce composant: ça déplace les aliments en dehors des repas !
    ( ce qui est tout à fait normal, vu que je n'ai rien codé! )
    Je pourrais m'en sortir si j'avais tout dans un composant parent ( mapping des aliments ), mais je voudrais justement savoir comment faire dans ce cas.
    De plus React, par principe, ça sert à faire des composants. Donc tout faire dans le même composant n'est pas correct.

    J'ai mis un peu de CSS (pas la priorité pour l'instant):
    Code css : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    input {
    	display: block;
    	margin-bottom: 10px;
    }
     
    .meal {
    	border: 2px solid red;
    	border-radius: 4px;
    	margin: 10px;
    	padding: 10px;
    	cursor: move;
    }
     
    .mealName {
    	margin-bottom: 10px;
    	font-size: 25px;
    }
     
    .dragging {
    	visibility: hidden;
    }
     
    .food {
    	border: 1px solid black;
    	border-radius: 4px;
    	padding: 5px;
    	margin-bottom: 4px;
    }
     
    .foodList {
    	display: flex;
    	justify-content: start;
    	align-items: start;
    	flex-direction: column;
    }

    Évidemment, une piste sans librairie, parce que je veux apprendre...

    Merci d'avance.

    Laurent.

  2. #2
    Membre éclairé
    Homme Profil pro
    Urbaniste
    Inscrit en
    Août 2023
    Messages
    386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : Urbaniste

    Informations forums :
    Inscription : Août 2023
    Messages : 386
    Points : 788
    Points
    788
    Par défaut
    Bonjour,

    A mon avis vous avez déconnecté le state et la vue lorsque vous faites "zone.insertBefore(draggedElement..."

    Normalement l'évènement dragstart doit permettre d'attacher
    une valeur identifiant "la chose" en cours de déplacement/copie.

    Ainsi, lors du drop, vous pouvez récupérer cette identifiant pour ré organiser le state,
    l'opération de rendu fera alors son travail automatiquement.

    Attention ici

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    zone.addEventListener('dragover', (e) => {
    			e.preventDefault()
    		})
    Vous attachez la fonction autant de fois que l'évènement handleDragEnter est déclenché.

    Voir la MDN, https://developer.mozilla.org/en-US/...rag_operations
    Et comme ce n'est pas du react, c'est plus simple à partager.

    Bonne journée.

  3. #3
    Membre régulier
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2020
    Messages
    275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Indre et Loire (Centre)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2020
    Messages : 275
    Points : 113
    Points
    113
    Par défaut
    Oui, j'ai déconnecté le state et la vue.

    Pour les effets pendant les déplacements: de cette façon, j'ai les blocs sur lesquels je passe qui se déplacent automatiquement.
    Et lors du drop, le bloc déplacé prends sa nouvelle place ( dans le trou prévu a cet effet par les déplacements )


    J'ai essayé en permutant les objets dans le tableau mealList et le rendu automatique: je n'arrive pas à obtenir l'effet voulu...

    C'est pour ça que j'ai choisi cette approche par la suite, récupérer des index et faire cette modification lors du drop.

    Par contre, vous avez raison, je dois changer la place de mon zone.addEventListener

  4. #4
    Membre éclairé
    Homme Profil pro
    Urbaniste
    Inscrit en
    Août 2023
    Messages
    386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : Urbaniste

    Informations forums :
    Inscription : Août 2023
    Messages : 386
    Points : 788
    Points
    788
    Par défaut
    Bonjour,

    Quand vous dites permuter, vous ne mettez pas à jour par copie le tableau ?

    https://react.dev/learn/updating-arrays-in-state

    Arrays are mutable in JavaScript, but you should treat them as immutable when you store them in state. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array.
    Votre description n'est pas tout à fait ce que j'ai pu lire en haut.

    Bonne journée.

  5. #5
    Membre régulier
    Homme Profil pro
    Étudiant
    Inscrit en
    Novembre 2020
    Messages
    275
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Indre et Loire (Centre)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Novembre 2020
    Messages : 275
    Points : 113
    Points
    113
    Par défaut
    Si, je permute comme ça:
    Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    let updatedMealList: Meal[] = [...mealList]
     
    		const temp = updatedMealList[draggedElement.current]
    		updatedMealList[draggedElement.current] = updatedMealList[index]
    		updatedMealList[index] = temp

    Si c'est bien ce dont vous parlez ?

  6. #6
    Membre éclairé
    Homme Profil pro
    Urbaniste
    Inscrit en
    Août 2023
    Messages
    386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : Urbaniste

    Informations forums :
    Inscription : Août 2023
    Messages : 386
    Points : 788
    Points
    788
    Par défaut
    Bonjour,

    je suppose que c'est suivi d'un setMealList(updatedMealList).

    Du coup là je vois pas trop.

    Si je pouvais le reproduire ici j'y verrais plus clair car dans le code présenté, le seul appel à setMealList est dans la fonction addMeal qui est elle même un écouteur de l'évènement Button.onclick.

    Bonne journée.

  7. #7
    Membre éclairé
    Homme Profil pro
    Urbaniste
    Inscrit en
    Août 2023
    Messages
    386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : Urbaniste

    Informations forums :
    Inscription : Août 2023
    Messages : 386
    Points : 788
    Points
    788
    Par défaut
    Bonjour,

    Pour reproduire,

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $ mkdir dnd
    $ cd dnd
    $ npm create vite@latest
    ✔ Project name: … vite-project
    ✔ Select a framework: › React
    ✔ Select a variant: › JavaScript
    $ cd vite-project
    $ npm install
    $ npm run dev
    # ... autre terminal
    $ xdg-open http://localhost:5173/
    J'ai fait à l'arrache avec du vanilla js via Vite https://vitejs.dev/guide/

    ./src/App.jsx
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import Meal from "./Meal"
    import './App.css'
     
    function App() {
     
      return (
        <>
        <Meal></Meal>
        </>
      )
    }
     
    export default App
    ./src/Foolist.jsx
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React from "react";
     
     
    const FoodList= ({foodList}) => {
    	return (
    		<div className="foodList">
    			{foodList.map((food, index) => (
    				<div key={index} draggable="true" className="food">
    					{food}
    				</div>
    			))}
    		</div>
    	)
    }
     
    export default FoodList
    ./src/Meal.jsx
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    import React, { useState } from "react";
    import FoodList from "./Foodlist";
    import "./mealComposition.css"
     
    const Home = () => {
     
    	const [mealList, setMealList] = useState([
    		{name: "Repas 1", foods: ["banane", "tomate"]},
    		{name: "Repas 2", foods: []},
    		{name: "Repas 3", foods: ["pain", "viande"]},
    	])
     
    	const handleDragStart = (e) => {
     
    		const targetElement = e.target
    		setTimeout(() => targetElement.classList.add('dragging'), 0)
    	}
     
    	const handleDragEnter = (e) => {
     
    		const targetElement = e.target
    		const zone = document.querySelector('.mealDayContent')
    		const draggedElement = document.querySelector('.dragging')
    		const enterElementPositionY = targetElement.getBoundingClientRect().top
    		const draggedElementPositionY = draggedElement.getBoundingClientRect().top
     
    		zone.addEventListener('dragover', (e) => {
    			e.preventDefault()
    		});
     
    		if (enterElementPositionY > draggedElementPositionY) {
    			zone.insertBefore(draggedElement, targetElement.nextSibling)
    		} else {
    			zone.insertBefore(draggedElement, targetElement)
    		}
    	}
     
    	const handleDragEnd = (e) => {
    		const targetElement = e.target 
    		targetElement.classList.remove('dragging')
    	}
     
    	const addMeal = () => {
    		const newMeal = {name: `repas ${mealList.length + 1}`, foods: [], nbInputs: 0}
    		const updateMealList = [...mealList, newMeal]
    		setMealList(updateMealList)
    	}
     
    	return(
    		<>
    			<div className="mealDay">
    				Repas de la journée: 
    				<button onClick={addMeal}> + </button>
    			</div>
    			<div className="mealDayContent">
    				{mealList.map((meal, index) => (
    					<div 
    						key={index} 
    						className="meal"
    						draggable="true"
    						onDragStart={(e) => handleDragStart(e)}
    						onDragEnter={(e) => handleDragEnter(e)}
    						onDragEnd={(e) => handleDragEnd(e)}>
     
    						<div className="mealName">
    							{meal.name} 
    						</div>
     
    						<FoodList foodList={meal.foods}/>
    					</div>
    				))}
    			</div>
    		</>)
    }
     
    export default Home
    ./src/mealComposition.css
    Code css : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    input {
    	display: block;
    	margin-bottom: 10px;
    }
     
    .meal {
    	border: 2px solid red;
    	border-radius: 4px;
    	margin: 10px;
    	padding: 10px;
    	cursor: move;
    }
     
    .mealName {
    	margin-bottom: 10px;
    	font-size: 25px;
    }
     
    .dragging {
    	visibility: hidden;
    }
     
    .food {
    	border: 1px solid black;
    	border-radius: 4px;
    	padding: 5px;
    	margin-bottom: 4px;
    }
     
    .foodList {
    	display: flex;
    	justify-content: start;
    	align-items: start;
    	flex-direction: column;
    }

    Qui donne
    Nom : Capture-20231219075014-327x334.png
Affichages : 51
Taille : 12,8 Ko

    Après un rapide drag and drop, on se retrouve avec
    Nom : Capture-20231219075101-735x643.png
Affichages : 45
Taille : 57,2 Ko

    Une grosse exception et des ingrédients qui apparaissent entre les repas.

    Comme quoi TS c'est pas magique.

    Bonne journée.

  8. #8
    Modérateur

    Avatar de NoSmoking
    Homme Profil pro
    Inscrit en
    Janvier 2011
    Messages
    16 959
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Isère (Rhône Alpes)

    Informations forums :
    Inscription : Janvier 2011
    Messages : 16 959
    Points : 44 112
    Points
    44 112
    Par défaut
    Bonojur,Comme quoi TS c'est pas magique.
    en quoi est-ce un soucis lié à TS, le message me semble plutôt explicite, mauvais ciblage des éléments manipulés !
    child to insert before is not a child of this node

  9. #9
    Membre éclairé
    Homme Profil pro
    Urbaniste
    Inscrit en
    Août 2023
    Messages
    386
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Autre

    Informations professionnelles :
    Activité : Urbaniste

    Informations forums :
    Inscription : Août 2023
    Messages : 386
    Points : 788
    Points
    788
    Par défaut
    Bonjour,

    J'ai presque envie de vous dire que c'était totalement gratuit,
    mais comme au fond de moi je pense que c'est un peu trop survendu le TS,
    mais qu'en même temps les language wars ça ne m'intéresse pas du tout,
    et que je sens bien que j'ai touché une corde sensible,
    je vous laisse choisir.

    D'un point de vu pratique,
    si le PO avait passé un peu plus de temps dans la console
    et un peu moins dans les annotations de type,
    peut être que son problème serait résolu.

    De trouver cette exception a séché mon intérêt d'aider le PO,
    je laisse des billes si ça intéresse quelqu'un d'autre.

    Bonne journée.

Discussions similaires

  1. Drag and drop pour l'API 8 avec Android Support Library
    Par hariman dans le forum Composants graphiques
    Réponses: 0
    Dernier message: 23/10/2012, 12h10
  2. Drag and Drop d'un élément dans un TreePanel
    Par major68 dans le forum Ext JS / Sencha
    Réponses: 0
    Dernier message: 01/03/2011, 10h20
  3. Réponses: 0
    Dernier message: 19/01/2011, 15h04
  4. Réponses: 10
    Dernier message: 27/05/2008, 15h09
  5. Drag and drop pour control en VBA
    Par cbleas dans le forum VBA Access
    Réponses: 2
    Dernier message: 10/03/2007, 10h30

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo