React 漂亮的 DnD 拖出位置问题 [英] React beautiful DnD drag out of position problem

查看:45
本文介绍了React 漂亮的 DnD 拖出位置问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了一个带有可拖动行的可拖动拖放表.
为此,我正在使用 react beautiful-dnd.
当我拖动一行时,该行会偏离位置而不是光标所在的位置.
当我拖动一行时,该行会获得 position: fixed 和一些 topleft 样式.
我怀疑这就是问题所在,但为什么它会得到错误的数字,从而导致无法显示在正确的位置?
GIF 将显示问题.

这是我的完整代码:

从immutability-helper"导入更新;import * as React from "react";从react-dnd"导入 * 作为 ReactDnD;import { WithNamespaces, withNamespaces } from "react-i18next";import { toastr } from "react-redux-toastr";import * as HttpHelper from "../../httpHelper";import { FormState } from "../common/ValidatedForm";从../common/AttributeModal"导入Addtagmodal;从./AttributeModal"导入 AttributeModal;从./PreviewModal"导入PreviewModal;import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";/* 从react-json-editor-ajrm/locale/en"导入语言环境;*/类型道具 = WithNamespaces &{身份证号码;显示名称:字符串;};接口字段{列:任何;}type State = FormState&{isLoading:布尔值,canSave:布尔值,isSaving:布尔值,可能的标签:任何,configTagModalActive:布尔值,previewModalActive:布尔值,活动标签:任何};const getItemStyle = (draggableStyle: any) =>({...draggableStyle});const Card = (props: any) =>{const opacitys = props.isDragging ?0.3 : 1;函数 findindex(val: any) {return props.tags.some((item: any) => val === item.name);}让我们选择;让 selectStyle = {};让 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"};}别的 {selectStyle = { border: "0px", outline: "none", width: "100%", height: "20px", backgroundColor: "transparent", zIndex: 0, float: "left", top: "-20px",位置:相对" };tagInputStyle = {border: "1px solid #ced4da", height: "auto", width: "400px", padding: "8px", minHeight: "38px", background: "white", marginTop: "10px"};}如果 (props.tags.length < 4) {选择 =<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 ?真:假}>{i.name})}</选择>;}别的 {选择 = 未定义;}返回 (<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" }}><输入类型=复选框"类名=翻转开关"id={道具.索引}选中={props.export}onChange={props.oncheck}/></td><td><输入类型=文本"名称=标题"id={道具.索引}className="表单控件"值={props.caption}onChange={props.ontextupdate}/></td><td><输入类型=文本"名称=字段名称"id={道具.索引}className="表单控件"值={props.fieldname}onChange={props.ontextupdate}/></td><td style={{width: "400px"}}><div className="tags-input" style={tagInputStyle}>{Object.keys(props.tags).m​​ap((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>

)}{选择}

</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></按钮></td></tr>);};const reorder = (list: any, startIndex: any, endIndex: any) =>{const 结果 = Array.from(list);const [已删除] = result.splice(startIndex, 1);控制台日志(开始索引,结束索引,删除);result.splice(endIndex, 0, 移除);返回结果;};接口 SetColumnsResponse 扩展 HttpHelper.ResponseData { columns: any;}CrmConnectorColumns 类扩展了 React.Component{构造函数(道具:道具){超级(道具);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: 真,isSaving: 假,可以保存:假,错误颜色:危险",字段:{ 列:{}},deleteModalActive: 假,configTagModalActive: 假,previewModalActive: 假,activeTag: {name: "", 属性: [{name: "", value: ""}]},可能的标签:[{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."}],使用:未定义}]},{名称:标题",状态:新",帮助文本:[{语言:nl",帮助文本:Dit is de hoofdtitel van een record"}],属性:[],用途:1},{name: "PHONE", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien telefoonnummer"}], 属性: [], uses: undefined},{名称:按钮",状态:新",帮助文本:[{语言:nl",帮助文本:Uiterlijk van een knop"}],属性:[],用途:未定义},{name: "EMAIL", status: "new", helptexts: [{language: "nl", helptext: "De waarde wordt gezien e-mail adres"}], attributes: [], uses: undefined},{名称:图像",状态:新",帮助文本:[{语言:nl",帮助文本:De waarde wordt als afbeelding weergegeven"}],属性:[],用途:未定义},{名称:HTML",状态:新",帮助文本:[{语言:nl",帮助文本:De waarde wordt gezien als HTML"}],属性:[{名称:HTML 代码",状态:"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(结果:任何){//被删除到列表之外如果(!结果.目的地){返回;}让 newlist = [...this.state.fields.columns];新列表 = 重新排序(新名单,result.source.index,结果.目的地.索引);Object.keys(newlist).forEach((nr) => {newlist[parseInt(nr, 10)].index = parseInt(nr, 10);});this.setState({ fields: { columns: newlist } });控制台日志(this.state.fields.columns);this.setState({ canSave: true });}异步 componentDidMount() {console.log("开始选择列");const fields = await HttpHelper.getJson(`/connectortypes/${this.props.id}/columns`);this.setState(prevState => {返回更新(上一个状态,{字段:{ $set:字段},isLoading: { $set: false },});});for (let i = 0; i item.name == tags.name);if (newlist[index].uses > 0) {新列表[索引].uses = 0;}}}this.setState({ possibleTags: newlist });控制台日志(this.state.possibleTags);}移动卡(索引:任意,索引号:任意){const 卡片 = this.state.fields.columns;const sourceCard = card.find((card: any) => card.index === index);const sortCards = card.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 } });控制台日志(this.state.fields.columns);this.setState({ canSave: true });}oncheck(e: 任何) {const 卡片 = this.state.fields.columns;卡片[e.target.id].export = e.target.checked;this.setState({ 字段: { 列: 卡片 } });控制台日志(this.state.fields.columns);this.setState({ canSave: true });}ondeleterow(nr: 任何) {控制台日志(nr);const array = [...this.state.fields.columns];//创建一个单独的数组副本const arrayCopy = array.filter((row: any) => row.index !== nr);this.setState({ fields: { columns: arrayCopy }});控制台日志(this.state.fields.columns);this.setState({ canSave: true });}ontextupdate(e: 任何) {const 卡片 = this.state.fields.columns;卡片[e.target.id][e.target.name] = e.target.value;this.setState({ 字段: { 列: 卡片 } });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 = "移动";e.dataTransfer.setData("text/html", e.target.parentNode);e.dataTransfer.setDragImage(e.target.parentNode, 20, 20);}ondragOver(e: 任何) {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: 任何) {函数findindex(元素:任何){返回 element.name == e.target.value;}const index = this.state.possibleTags.findIndex(findindex);const 数组 = this.state.fields.columns;for(数组的常量列){if (column.index == e.target.id) {const newArray = [ ...array[e.target.id].tags, {name: this.state.possibleTags[index].name, attributes: [] } ];数组[e.target.id].tags = newArray;}别的 {const newArray = [...column.tags];column.tags = newArray;}this.setState({ fields: { columns: array } });}this.setState({ canSave: true });const 标签 = this.state.possibleTags;if (tags[index].uses > 0) {标签[索引].uses = 0;}this.setState({ possibleTags: tags });}ondeletetag(e: 任何) {const 数组 = this.state.fields.columns;for(数组的常量列){if (column.index == e.target.id) {const newlist = [].concat(array[e.target.id].tags);//使用 concat 或 slice(0) 克隆数组newlist.splice(e.target.dataset.key, 1);数组[e.target.id].tags = newlist;}别的 {const newArray = [...column.tags];column.tags = newArray;}}this.setState({ fields: { columns: array } });this.setState({ canSave: true });函数findindex(元素:任何){返回 element.name == e.target.dataset.name;}const index = this.state.possibleTags.findIndex(findindex);const 标签 = this.state.possibleTags;如果(标签[索引].uses == 0){标签[索引].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 });控制台日志(this.state.activeTag);}onSubmit = (e: any) =>{e.preventDefault();console.log("开始保存更改");this.setState({ isSaving: true }, () => {如果(this.state.fields){HttpHelper.postJson(`/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 });}别的 {this.setState({ canSave: false, isSaving: false, fields: { columns: responseData.columns } }, () => {toastr.success(this.props.displayName, this.props.t("columnsUpdated"));});}});}});}公共渲染(){const 列 = this.state.fields.columns ||[];const { t } = this.props;返回 (<表格><div className="应用程序"><主要><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">{(提供:任何)=>(<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></th><th>{t("displayname")}</th><th>元素</th><th>标签</th><th><button onClick={this.onPreviewButtonClicked} type="button" className="btn btn-primary" style={{float: "right"}} >预览</button></th></tr></thead>{Object.keys(columns).map((key, i) => (<Draggable key={i} draggableId={key} index={i}>{(提供)=>(<卡片key={columns[i].index}indexnr={i}oncheck={this.oncheck}ontextupdate={this.ontextupdate}ondeleterow={this.ondeleterow}ondeletetag={this.ondeletetag}onaddtag={this.onaddtag}可能的标签={this.state.possibleTags}onConfigButtonClicked={this.onConfigButtonClicked}onPreviewButtonClicked={this.onPreviewButtonClicked}onClosePreview={this.onClosePreview}提供={提供}{...列[i]}/>)}</可拖动>))}</tbody>)}</Droppable></DragDropContext></main>

<属性模态startAction={this.onUpdateAttribute.bind(this)}isOpen={this.state.configTagModalActive}headerText={t("header")}activeTag={this.state.activeTag}addText={t("close")}possibleTags={this.state.possibleTags} ></AttributeModal><预览模式startAction={this.onClosePreview.bind(this)}isOpen={this.state.previewModalActive}headerText="预览"addText={t("close")}列={this.state.fields.columns} ></PreviewModal></表单>);}}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

I created a draggable drag and drop table with draggable rows.
I'm using react beautiful-dnd for this.
When I drag a row, the row gets out of position instead on the position of my cursor.
When I drag a row, the row gets position: fixed and some top and left styling.
I suspect thats the issue, but why does it get the wrong numbers, so that its causing to not show on the right position?
This GIF will show the problem.

This is my full code:

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);

Does anyone know why my draggable item gets out of position?
The only css I'm using is bootstrap and the ones in my code.

解决方案

I had the same issue and I figured it out! :-)

The solution can be found here: https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/patterns/using-a-portal.md

Basically when the library is using position: fixed as OP mentioned, there are are some unintended consequences sometimes - and in those cases you need to use portal.

I got it to work by looking at the portal example here: https://github.com/atlassian/react-beautiful-dnd/blob/master/stories/src/portal/portal-app.jsx

solution found thanks to this comment: https://github.com/atlassian/react-beautiful-dnd/issues/485#issuecomment-385816391

这篇关于React 漂亮的 DnD 拖出位置问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆