13 reactjs react-beautiful-dnd
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);
Run Code Online (Sandbox Code Playgroud)
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.
ozg*_*zer 19
当我尝试显示react-beautiful-dnd
内部时,我遇到了相同的位置问题react-modal
,我通过将这些 CSS 添加到可拖动项目找到了解决方案。
.draggable {
top: auto !important;
left: auto !important;
}
Run Code Online (Sandbox Code Playgroud)
我有同样的问题,我想通了!:-)
解决方案可以在这里找到: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
归档时间: |
|
查看次数: |
19623 次 |
最近记录: |