React美丽的DnD拖出位置问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React美丽的DnD拖出位置问题相关的知识,希望对你有一定的参考价值。

我用可拖动的行创建了一个可拖动的拖放表。 我正在使用react beautiful-dnd。 当我拖动一行时,该行不在位置而不是我的光标位置。 当我拖动一行时,该行获得position: fixed和一些topleft样式。 我怀疑这个问题,但为什么它会得到错误的数字,以致它导致不显示在正确的位置? 这个GIF将显示问题。 enter image description here

这是我的完整代码:

import update from "immutability-helper";
import * as React from "react";
import * as ReactDnD from "react-dnd";
import  WithNamespaces, withNamespaces  from "react-i18next";
import  toastr  from "react-redux-toastr";
import * as HttpHelper from "../../httpHelper";
import  FormState  from "../common/ValidatedForm";
import Addtagmodal from "../common/AttributeModal";
import AttributeModal from "./AttributeModal";
import PreviewModal from "./PreviewModal";
import  DragDropContext, Droppable, Draggable  from "react-beautiful-dnd";
/* import locale from "react-json-editor-ajrm/locale/en"; */
type Props = WithNamespaces & 
  id: number;
  displayName: string;
;

interface Fields 
  columns: any;


type State = FormState<Fields> & 
  isLoading: boolean,
  canSave: boolean,
  isSaving: boolean,
  possibleTags: any,
  configTagModalActive: boolean,
  previewModalActive: boolean,
  activeTag: any
;
const getItemStyle = (draggableStyle: any) => (
  ...draggableStyle
);
const Card = (props: any) => 
  const opacitys = props.isDragging ? 0.3 : 1;

  function findindex(val: any) 
    return props.tags.some((item: any) => val === item.name);
  
  let select;
  let selectStyle = ;
  let tagInputStyle = ;
  if (props.tags.length == 0 || props.tags.length > 3) 
    selectStyle =  border: "0px", outline: "none", width: "100%", height: "20px", backgroundColor: "transparent", zIndex: 0, float: "left", position: "relative" ;
    tagInputStyle = border: "1px solid #ced4da", height: "auto", width: "400px", padding: "8px", minHeight: "38px", background: "white";
  
  else 
    selectStyle =  border: "0px", outline: "none", width: "100%", height: "20px", backgroundColor: "transparent", zIndex: 0, float: "left", top: "-20px", position: "relative" ;
    tagInputStyle = border: "1px solid #ced4da", height: "auto", width: "400px", padding: "8px", minHeight: "38px", background: "white", marginTop: "10px";
  
  if (props.tags.length < 4) 
    select =
  <select value="" className="autocomplete-select" style=selectStyle id=props.index onChange=props.onaddtag>
    <option value="" disabled ></option>
    props.possibleTags.map((i: any) =>

      <option value=i.name disabled=i.uses == 0 || findindex(i.name) == true ? true : false>i.name</option>

    )
  </select>;
  
  else 
    select = undefined;
  
  return (
        <tr ref=props.provided.innerRef
        ...props.provided.draggableProps style=getItemStyle(props.provided.draggableProps.style) className=(props.indexnr % 2 ? "whiterow" : "grayrow") key=props.indexnr data-id=props.indexnr >
          <td ...props.provided.dragHandleProps style=width: "50px", textAlign: "center"><i className="fa fa-bars" style=lineHeight: "40px", fontSize: "24px"></i></td>
          <td style= textAlign: "center", width: "80px" >
            <input
              type="checkbox"
              className="flipswitch"
              id=props.index
              checked=props.export
              onChange=props.oncheck
            />
          </td>
          <td>
            <input
              type="text"
              name="caption"
              id=props.index
              className="form-control"
              value=props.caption
              onChange=props.ontextupdate
            />
          </td>
          <td>
            <input
              type="text"
              name="fieldname"
              id=props.index
              className="form-control"
              value=props.fieldname
              onChange=props.ontextupdate
            />
          </td>
          <td style=width: "400px">
            <div className="tags-input" style=tagInputStyle>
            Object.keys(props.tags).map((key, i) =>
              <div key=key style=backgroundColor: "#0753ad", height: "20px", borderRadius: "3px", display: "inline-block", padding: "5px", lineHeight: "12px", float: "left", color: "white", marginRight: "5px", fontSize: "10px", width: "90px", position: "relative", zIndex: 20>
                props.tags[i].name <i className="fa fa-trash" id=props.index data-key=i data-name=props.tags[i].name onClick=props.ondeletetag style=float: "right" ></i><i className="fa fa-cog" data-id=i data-parent=props.index style=float: "right", marginRight: "5px" onClick=props.onConfigButtonClicked></i>
              </div>
            )
            select
            </div>
           </td>
          <td style= textAlign: "center", width: "80px" >
          <button onClick=() => props.ondeleterow(props.index) type="button" style=padding : "8px 16px"  className="btn btn-danger btn-rounded"><i className="fa fa-trash"></i></button>
          </td>
        </tr>
  );
;
const reorder = (list: any, startIndex: any, endIndex: any) => 
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  console.log(startIndex, endIndex, removed);
  result.splice(endIndex, 0, removed);

  return result;
;
interface SetColumnsResponse extends HttpHelper.ResponseData  columns: any; 

class CrmConnectorColumns extends React.Component<Props, State> 

  constructor(props: Props) 
    super(props);
    this.moveCard = this.moveCard.bind(this);
    this.oncheck = this.oncheck.bind(this);
    this.ontextupdate = this.ontextupdate.bind(this);
    this.ondeleterow = this.ondeleterow.bind(this);
    this.onaddnewrow = this.onaddnewrow.bind(this);
    this.ondeletetag = this.ondeletetag.bind(this);
    this.onaddtag = this.onaddtag.bind(this);
    this.onConfigButtonClicked = this.onConfigButtonClicked.bind(this);
    this.onPreviewButtonClicked = this.onPreviewButtonClicked.bind(this);
    this.onClosePreview = this.onClosePreview.bind(this);
    this.state = 
      isLoading: true,
      isSaving: false,
      canSave: false,
      errorColor: "danger",
      fields:  columns:  ,
      deleteModalActive: false,
      configTagModalActive: false,
      previewModalActive: false,
      activeTag: name: "", attributes: [name: "", value: ""],
      possibleTags: [
        name: "SUBTITLE", status: "new", helptexts: [language: "nl", helptext: "Dit is de subtitel van een record"], attributes: [], uses: 1,
        name: "URL", status: "new", helptexts: [language: "nl", helptext: "De waarde wordt gezien als html link."], attributes: [name: "link", status: "new", helptexts: [language: "nl", helptext: "De link is deze waarde. Voorbeeld waarde is \"http://www.google.nl?search=[naam]\". op de plaats van \"[naam]\" wordt de waarde van het veld \"naam\" ingevuld."], uses: undefined],
        name: "TITLE", status: "new", helptexts: [language: "nl", helptext: "Dit is de hoofdtitel van een record"], attributes: [], uses: 1,
        name: "PHONE", status: "new", helptexts: [language: "nl", helptext: "De waarde wordt gezien telefoonnummer"], attributes: [], uses: undefined,
        name: "BUTTON", status: "new", helptexts: [language: "nl", helptext: "Uiterlijk van een knop"], attributes: [], uses: undefined,
        name: "EMAIL", status: "new", helptexts: [language: "nl", helptext: "De waarde wordt gezien e-mail adres"], attributes: [], uses: undefined,
        name: "IMAGE", status: "new", helptexts: [language: "nl", helptext: "De waarde wordt als afbeelding weergegeven"], attributes: [], uses: undefined,
        name: "HTML", status: "new", helptexts: [language: "nl", helptext: "De waarde wordt gezien als HTML"], attributes: [name: "HTML code", status: "new", helptexts: [language: "nl", helptext: "Vul hier je custom HTML code in. De waarde tussen de [] word vervangen door de data."], uses: undefined]
      ]
    ;
    this.onDragEnd = this.onDragEnd.bind(this);
  
  onDragEnd(result: any) 
    // dropped outside the list
    if (!result.destination) 
      return;
    
    let newlist = [...this.state.fields.columns];
    newlist = reorder(
      newlist,
      result.source.index,
      result.destination.index
    );
    Object.keys(newlist).forEach((nr) => 
      newlist[parseInt(nr, 10)].index = parseInt(nr, 10);
      );
    this.setState( fields:  columns: newlist  );
    console.log(this.state.fields.columns);
    this.setState( canSave: true );




  
  async componentDidMount() 
    console.log("Start select columns");

    const fields = await HttpHelper.getJson<Fields>(`/connectortypes/$this.props.id/columns`);
    this.setState(prevState => 
      return update(prevState, 
        fields:  $set: fields ,
        isLoading:  $set: false ,
      );
    );
    for (let i = 0; i < fields.columns.length; i++) 
      fields.columns[i].index = i;
    
    this.setState( fields:  columns: fields.columns  );
    const newlist = [...this.state.possibleTags];
    console.log(newlist);
    for (const column of fields.columns) 
      for (const tags of column.tags) 
        const index = newlist.findIndex(item => item.name == tags.name);
        if (newlist[index].uses > 0) 
          newlist[index].uses = 0;
        
      
    
    this.setState( possibleTags: newlist );
    console.log(this.state.possibleTags);

  
  moveCard (index: any, indexnr: any) 
    const cards = this.state.fields.columns;
    const sourceCard = cards.find((card: any) => card.index === index);
    const sortCards = cards.filter((card: any) => card.index !== index);
    sortCards.splice(indexnr, 0, sourceCard);
     Object.keys(sortCards).forEach((nr) => 
    sortCards[nr].index = parseInt(nr, 10);
    );
    this.setState( fields:  columns: sortCards  );
    console.log(this.state.fields.columns);
    this.setState( canSave: true );
  
  oncheck(e: any) 
    const cards = this.state.fields.columns;
    cards[e.target.id].export = e.target.checked;
    this.setState( fields:  columns: cards  );
    console.log(this.state.fields.columns);
    this.setState( canSave: true );
  
  ondeleterow(nr: any) 
    console.log(nr);
    const array = [...this.state.fields.columns]; // make a separate copy of the array
    const arrayCopy = array.filter((row: any) => row.index !== nr);
    this.setState( fields:  columns: arrayCopy );
    console.log(this.state.fields.columns);
    this.setState( canSave: true );
  
  ontextupdate(e: any) 
    const cards = this.state.fields.columns;
    cards[e.target.id][e.target.name] = e.target.value;
    this.setState( fields:  columns: cards  );
    this.setState( canSave: true );
  
  onaddnewrow() 
    const columnsCopy = this.state.fields.columns;
    columnsCopy.push(index: this.state.fields.columns.length, export: true, editable: false, fieldname: "", caption: "", tags: [] );
    this.setState( fields:  columns: columnsCopy  );
    this.setState( canSave: true );
  
  onDragStart = (e: any) => 
    e.dataTransfer.effectAllowed = "move";
    e.dataTransfer.setData("text/html", e.target.parentNode);
    e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);
  
  ondragOver(e: any) 
    e.preventDefault();
    const columnsCopy = this.state.fields.columns;
    columnsCopy.pop();
    columnsCopy.push(index: e.target.dataset.id, export: true, editable: false, fieldname: "", caption: "", tags: [] );
    this.setState( fields:  columns: columnsCopy  );
  
  onaddtag(e: any) 
    function findindex(element: any) 
      return element.name == e.target.value;
    
    const index = this.state.possibleTags.findIndex(findindex);
    const array = this.state.fields.columns;

    for (const column of array) 

      if (column.index == e.target.id) 
         const newArray = [ ...array[e.target.id].tags, name: this.state.possibleTags[index].name, attributes: []  ];
         array[e.target.id].tags = newArray;
      
      else 
        const newArray = [...column.tags];
        column.tags = newArray;
      
      this.setState( fields:  columns: array  );
    
    this.setState( canSave: true );
    const tags = this.state.possibleTags;
    if (tags[index].uses > 0) 
      tags[index].uses = 0;
    
    this.setState( possibleTags: tags );
  
  ondeletetag(e: any) 
    const array = this.state.fields.columns;
    for (const column of array) 
      if (column.index == e.target.id) 
        const newlist = [].concat(array[e.target.id].tags); // Clone array with concat or slice(0)
        newlist.splice(e.target.dataset.key, 1);
        array[e.target.id].tags = newlist;
      
      else 
        const newArray = [...column.tags];
        column.tags = newArray;
      
    
    this.setState( fields:  columns: array  );
    this.setState( canSave: true );
    function findindex(element: any) 
      return element.name == e.target.dataset.name;
    
    const index = this.state.possibleTags.findIndex(findindex);
    const tags = this.state.possibleTags;
    if (tags[index].uses == 0) 
      tags[index].uses = 1;
    
    this.setState( possibleTags: tags );
  
  onUpdateAttribute() 
    this.setState( configTagModalActive: false );
    this.setState( canSave: true );
  
  onPreviewButtonClicked() 
    this.setState( previewModalActive: true );
  
  onClosePreview() 
    this.setState( previewModalActive: false );
  
  onCancelUpdateAttribute() 
    this.setState( configTagModalActive: false );
  
  onConfigButtonClicked(e: any) 
    e.preventDefault();
    this.setState( activeTag: this.state.fields.columns[e.target.dataset.parent].tags[e.target.dataset.id]);
    this.setState( configTagModalActive: true, errorMessage: undefined );
    console.log(this.state.activeTag);
  
  onSubmit = (e: any) => 
    e.preventDefault();
    console.log("Start saving changes");
    this.setState( isSaving: true , () => 
      if (this.state.fields) 
        HttpHelper.postJson<SetColumnsResponse>(`/connectortypes/$this.props.id/columns/`,  columns: this.state.fields.columns ).then((responseData) => 
          if (responseData.responseStatus !== undefined && responseData.responseStatus !== null && responseData.responseStatus.message !== null) 
            this.setState( isSaving: false, errorMessage: responseData.responseStatus.message );
          
          else 
            this.setState( canSave: false, isSaving: false, fields:  columns: responseData.columns  , () => 
              toastr.success(this.props.displayName, this.props.t("columnsUpdated"));
            );
          
        );
      
    );
  
  public render() 
    const columns = this.state.fields.columns || [] ;
    const  t  = this.props;
    return (
    <form>
      <div className="App">
        <main>
          <button onClick=this.onSubmit className="btn btn-primary" type="submit" style=float: "right" disabled=!this.state.canSave || this.state.isSaving>this.state.isSaving ? <i className="fa fa-spinner fa-spin"></i> : "" this.props.t("update")</button><br/><br/>
          <DragDropContext onDragEnd=this.onDragEnd>
          <Droppable droppableId="droppable">
          (provided: any) => (
          <table ref=provided.innerRef className="col-8 table columns" style=border: "1px solid #dee2e6" >
          <thead className="thead-dark" style=border: "1px solid #1b2847">
          <tr>
          <th colSpan=2>
            <button onClick=this.onaddnewrow type="button" style=padding : "8px 16px"  className="btn btn-primary btn-rounded"><i className="fa fa-plus"></i> </button>
          </th>
           <th>t("displayname")</th>
           <th>Element</th>
           <th>Tags</th>
           <th>
             <button onClick=this.onPreviewButtonClicked type="button" className="btn btn-primary"  style=float: "right" >Preview</button>
          </th>
          </tr>
        </thead>
        <tbody>
            Object.keys(columns).map((key, i) => (
              <Draggable key=i draggableId=key index=i>
              (provided) => (
             <Card
             key=columns[i].index
             indexnr=i
             oncheck=this.oncheck
             ontextupdate=this.ontextupdate
             ondeleterow=this.ondeleterow
             ondeletetag=this.ondeletetag
             onaddtag=this.onaddtag
             possibleTags=this.state.possibleTags
             onConfigButtonClicked=this.onConfigButtonClicked
             onPreviewButtonClicked=this.onPreviewButtonClicked
             onClosePreview=this.onClosePreview
             provided=provided
             ...columns[i]
           />
           )
           </Draggable>
           ))
            </tbody>
          </table>
          )
         </Droppable>
      </DragDropContext>
        </main>
      </div>
      <AttributeModal
        startAction=this.onUpdateAttribute.bind(this)
        isOpen=this.state.configTagModalActive
        headerText=t("header")
        activeTag=this.state.activeTag
        addText=t("close")
        possibleTags=this.state.possibleTags >
      </AttributeModal>

      <PreviewModal
        startAction=this.onClosePreview.bind(this)
        isOpen=this.state.previewModalActive
        headerText="Preview"
        addText=t("close")
        columns=this.state.fields.columns >
      </PreviewModal>
    </form>
    );
  


export default withNamespaces("crmConnectorColumns")(CrmConnectorColumns);

有谁知道为什么我的可拖动物品不合适? 我使用的唯一的css是bootstrap和我的代码中的那些。

答案

我有同样的问题,我想出来了! :-)

解决方案可以在这里找到:https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/using-a-portal.md

基本上,当库使用position: fixed作为OP时,有时会出现一些意想不到的后果 - 在这种情况下,您需要使用门户网站。

我通过查看这里的门户示例得到了它:https://github.com/atlassian/react-beautiful-dnd/blob/master/stories/src/portal/portal-app.jsx

由于这个评论找到了解决方案:https://github.com/atlassian/react-beautiful-dnd/issues/485#issuecomment-385816391

另一答案

在使用react-beautiful-dnd时,我发生了类似的事情。在我的情况下,原因是我有两个具有相同ID的项目。我希望这对你有所帮助。

另一答案

在我的例子中,问题是Draggable的一个父元素在“关键帧”动画中具有“transform”css属性。删除它解决了这个问题。

它在文档中描述:https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/using-a-portal.md

以上是关于React美丽的DnD拖出位置问题的主要内容,如果未能解决你的问题,请参考以下文章

滚动后反应Dnd不在位置

如何将项目拖放到 react-dnd 中的空白部分?

更改 react-beautiful-dnd 可拖动点击区域

React Dnd 实现横向和纵向拖拽排序

拖动移动时不更新 React-dnd 元素样式

使用 ReactJs 和 react-dnd 拖放可排序列表的问题