I am using jointjs to create an interactive flowcharting application, is there a way to prevent elements from being dragged over the top of one another?
You can revert the position of an element when the user finishes dragging and overlap is found.
paper.on({
'element:pointerdown': (elementView, evt) => {
// store the position before the user starts dragging
evt.data = { startPosition: elementView.model.position() };
},
'element:pointerup': (elementView, evt) => {
const { model: element } = elementView;
const { model: graph } = paper;
const elementsUnder = graph.findModelsInArea(element.getBBox()).filter(el => el !== element);
if (elementsUnder.length > 0) {
// an overlap found, revert the position
const { x, y } = evt.data.startPosition;
element.position(x, y);
}
}
});
Related
I'm trying to get Drag and Drop to work correctly
But in the image gallery, it only changes the position of the photo when dragging it under the other image
As if it only works vertically
Demo
Javascript Vanilla
const enableDragSort = className => {
// Gets all child elements of the .gallery or .datasheet class
[...document.getElementsByClassName(className)].forEach(enableDragList);
}
const enableDragList = className => {
// For each child of the class, add the draggable attribute
[...className.children].forEach(enableDragItem);
}
const enableDragItem = item => {
item.setAttribute('draggable', true);
item.ondrag = handleDrag;
item.ondragend = handleDrop;
}
const handleDrag = event => {
const item = event.target.closest('[draggable]');
item.classList.add('drag-sort-active');
// .gallery or .datasheet
const className = item.parentElement;
const x = event.clientX;
const y = event.clientY;
let swap = document.elementFromPoint(x, y) ?? item;
if (className === swap.parentElement) {
swap = (swap !== item.nextSibling) ? swap : swap.nextSibling;
className.insertBefore(item, swap);
}
}
const handleDrop = ({ target }) => {
const item = target.closest('[draggable]');
item.classList.remove('drag-sort-active');
}
// Drag Drop
enableDragSort('gallery'); // It does not work properly
enableDragSort('datasheet'); // It works
Hi i'm facing the very challenging & interesting problem of scroll during selection of items with mouse drag in both direction i,e up and down
here is a screen shot
Here is my code : https://codesandbox.io/s/select-ivwq8j?file=/src/overridden/Drag-select.vue
Drag-select.vue is the file where drag selection logic is written.
which fires change when files selection gets changed.
I receive those change event here <drag-select-container #change="dragSelect($event)">
Edit 1: after IVO GELO comment
I have added inside drag() function
try{
let containerEl = document.querySelector('#wrapping_container');
let container = containerEl.getBoundingClientRect();
if(box.top > (container.top )){
containerEl.scrollTop = box.top - 50;
return true;
}
}catch(e){
console.log(e);
}
Edit code here: https://codesandbox.io/s/select-ivwq8j?file=/src/overridden/Drag-select.vue
It is very interesting and challenging problem so
Please help me thanks in advance!!
I recommend you use DragSelect js library.
Working Demo
https://codesandbox.io/s/select-forked-tnmnwk?file=/src/components/HelloWorld.vue
mounted() {
const vm = this;
const ds = new DragSelect({
selectables: document.querySelectorAll(".selectable-nodes"),
area: document.getElementById("area"),
draggability: false,
});
ds.subscribe("elementselect", function ({ item }) {
vm.selectedItems.push();
});
ds.subscribe("elementunselect", function ({ item }) {
const index = vm.selectedItems.indexOf(item.getAttribute("customAttribute"));
if (index > -1) {
vm.selectedItems.splice(index, 1);
}
});
}
I found a solution for your question. I rewrite your code in a completely different way. Here is a demo that you can test it. Actually it contains two main component. Parent component that is called "HelloCompo" and its code comes here:
HelloCompo:
<template>
<!-- This is a component that uses "MyDrag" component. -->
<div id="wrapping_container" class="hello">
<!-- Here we insert "MyDrag" component that emits custom "change" event when the selection of element is changed according to user drag. -->
<my-drag #change="dragSelect">
<div
class="item"
:class="{ selected: ( index >= minMax[1] && index <= minMax[0] ) }"
:key="item"
v-for="(item, index) in [1, 2, 3, 4,5,6,7,8,9,10,11,12,13,14,15,16]"
>
{{ item }}
</div>
</my-drag>
</div>
</template>
<script>
import MyDrag from "./MyDrag";
export default {
name: "HelloCompo",
components: {MyDrag},
data() {
return {
selectedItems: [],
};
},
computed: {
minMax: function () {
/* This computed property uses data returned by "MyDrag" component to define the maximum and minimum range to accept "selected" class. */
let max = -1;
let min = -1;
if (this.selectedItems.length > 0) {
max = Math.max(...this.selectedItems);
min = Math.min(...this.selectedItems);
}
return [max-1, min-1]
}
},
methods: {
dragSelect: function (selectedList) {
// console.log(selectedList);
/* this Method is used to set "selectedItems" data after each change in selected drag. */
this.selectedItems = selectedList;
}
},
}
</script>
<style scoped>
.item {
display: block;
width: 230px;
height: 130px;
background: orange;
margin-top: 9px;
line-height: 23px;
color: #fff;
}
.selected {
background: red !important;
}
#wrapping_container{
background:#e7e7e7;
}
</style>
And child component that is called "MyDrag":
MyDrag:
<template>
<section id="parentAll">
<!-- I used "#mousedown" and ... for calling methods instead of using all functions in mounted hook. -->
<div class="minHr" ref="container" #mousedown="startDrag" #mouseup="endDrag" #mousemove="whileDrag">
<slot></slot>
</div>
<!-- This canvas is shown only when the user is dragging on the page. -->
<canvas ref="myCanvas" v-if="showCanvas" #mouseup="endDrag" #mousemove="whileDrag"></canvas>
</section>
</template>
<script>
export default {
name: "MyDrag",
data() {
return {
dragStatus: false, // used for detecting mouse drag
childrenArr: [], // used to store the information of children of 'ref="container"' that comes from "slot"
startEvent: null, // used to detect mouse position on mousedown
endEvent: null, // used to detect mouse position on mouseup
direction: "topBottom", // used to detect the direction of dragging
selectedArr: [], // used to store the selected "divs" after dragging
heightContainer: null, // used to detect the height of 'ref="container"' dynamically
widthContainer: null, // used to detect the width of 'ref="container"' dynamically
/* These data used to draw rectangle on canvas while the user is dragging */
rect: {
startX: null,
startY: null,
w: null,
h: null
},
startDragData: {
x: null,
y: null
},
whileDragData: {
x: null,
y: null,
CLY: null
},
showCanvas: false // used to show or hide <canvas></canvas>
}
},
methods: {
childrenInfo: function () {
/* This method is called on "mounted()" hook to gather information about children of 'ref="container"' that comes from <slot></slot> */
const { container } = this.$refs;
const stylesDiv = window.getComputedStyle(container, null);
this.widthContainer = parseFloat( stylesDiv.getPropertyValue("width") );
this.heightContainer = parseFloat( stylesDiv.getPropertyValue("height") );
let children = container.childNodes;
children.forEach((item, index) => {
let childObj = {
offsetTop: item.offsetParent.offsetTop + item.offsetTop,
offsetHeight: item.offsetHeight
}
this.childrenArr.push(childObj);
})
},
startDrag: function (event) {
/* This method is called at mousedown and detect the click or right click. after that it sets some data like "showCanvas". */
if(event.button === 0) {
this.dragStatus = true;
this.startEvent = event.pageY;
this.startDragData.x = event.pageX;
this.startDragData.y = event.pageY;
this.showCanvas = false;
}
},
whileDrag: async function (event) {
/* This method is called when the user is dragging. Because I want to be confident about showing <canvas> before doing other parts of code, I used "async" function for this method. */
if (this.dragStatus) {
await this.showMethod();
console.log("dragging");
this.whileDragData.x = event.pageX;
this.whileDragData.y = event.pageY;
this.whileDragData.CLY = event.clientY
await this.canvasMethod();
} else {
this.showCanvas = false;
}
},
endDrag: function (event) {
/* This method is called at mouseup. After that it calls other methods to calculate the "divs" that were selected by user. */
if(event.button === 0) {
console.log("end drag");
this.dragStatus = false;
this.showCanvas = false;
this.endEvent = event.pageY;
this.calculateDirection();
this.calculateSelected();
}
},
showMethod: function () {
/* This method is used to set "showCanvas" data at proper time. */
this.showCanvas = true;
},
calculateDirection: function () {
/* This method is used to detect the direction of dragging. */
if (this.startEvent <= this.endEvent) {
this.direction = "topBottom";
} else {
this.direction = "bottomTop";
}
},
calculateSelected: function () {
/* This method is responsible to find out which "divs" were selected while the user was dragging. After that it emits "this.selectedArr" data to the parent component. */
this.selectedArr = [];
let endIndex = null;
let startIndex = null;
this.childrenArr.forEach( (item, index) => {
if ( (item.offsetTop < this.endEvent) && ( (item.offsetTop + item.offsetHeight) > this.endEvent) ) {
endIndex = index;
console.log(endIndex);
}
if ( (item.offsetTop < this.startEvent) && ( (item.offsetTop + item.offsetHeight) > this.startEvent) ) {
startIndex = index;
console.log(startIndex);
}
});
if( endIndex !== null ) {
if (this.direction === "topBottom") {
for (let i = startIndex; i <= endIndex; i++ ) {
this.selectedArr.push(i+1);
}
} else {
for (let i = startIndex; i >= endIndex; i-- ) {
this.selectedArr.push(i+1);
}
}
}
this.$emit("change", this.selectedArr);
},
canvasMethod: function () {
/* This method is used to show a rectangle when user drags on page. It also could understand that the user is near the top or bottom of page, and then it scrolls the page when the user is dragging. */
const { myCanvas } = this.$refs;
myCanvas.width = this.widthContainer;
myCanvas.height = this.heightContainer;
const html = document.documentElement;
let ctx = myCanvas.getContext('2d');
this.rect.startX = this.startDragData.x - myCanvas.offsetParent.offsetLeft;
this.rect.startY = this.startDragData.y - myCanvas.offsetParent.offsetTop;
this.rect.w = (this.whileDragData.x - myCanvas.offsetParent.offsetLeft) - this.rect.startX;
this.rect.h = (this.whileDragData.y - myCanvas.offsetParent.offsetTop) - this.rect.startY ;
if ( Math.abs(this.whileDragData.CLY - window.innerHeight) < 12) {
console.log("near");
html.scrollTop += 25;
}
if ( Math.abs(this.whileDragData.CLY) < 12 ) {
html.scrollTop -= 25;
}
if ( (this.whileDragData.y > (myCanvas.offsetParent.offsetTop + myCanvas.offsetHeight) - 25) || (this.whileDragData.y < myCanvas.offsetParent.offsetTop + 25) ) {
ctx.clearRect(0,0,myCanvas.width,myCanvas.height);
}
ctx.clearRect(0,0,myCanvas.width,myCanvas.height);
ctx.setLineDash([6]);
ctx.strokeRect(this.rect.startX, this.rect.startY, this.rect.w, this.rect.h);
},
},
mounted() {
this.childrenInfo();
}
}
</script>
<style scoped>
.minHr {
min-height: 900px;
}
#parentAll {
position: relative;
}
#parentAll canvas {
position: absolute;
top: 0;
left: 0;
}
</style>
I used a <canvas> to draw rectangle when the user is dragging. The main difference of my code with your is that it shows the selected items after the dragging process was finished. It works in both upward dragging and downward dragging and also when the user is want to continue dragging beyond the window area (scrolling).
I have a question, currently i am trying to save a image location in the localstorage but i have no idea how to do that. I want it in localstorage because once you refresh the page i still want the image that you dragged, in the same spot as where you dragged the image to before the page refresh/reload. The script is like a inventory with items that are images.
This is the code:
const fill1 = document.querySelector('#item1');
const empties1 = document.querySelectorAll('#block1');
const empties2 = document.querySelectorAll('#block2');
const spot1 = document.getElementById("block1")
const spot2 = document.getElementById("block2")
checkSpot1()
checkSpot2()
localStorage.spot1 = 1
localStorage.spot2 = 0
// Fill listeners
fill1.addEventListener('dragstart', dragStart);
fill1.addEventListener('dragend', dragEnd);
// Loop through empty boxes and add listeners
for (const empty of empties1) {
empty.addEventListener('dragover', dragOver);
empty.addEventListener('dragenter', dragEnter);
empty.addEventListener('dragleave', dragLeave);
empty.addEventListener('drop', dragDrop);
}
for (const empty of empties2) {
empty.addEventListener('dragover', dragOver);
empty.addEventListener('dragenter', dragEnter);
empty.addEventListener('dragleave', dragLeave);
empty.addEventListener('drop', dragDrop);
}
// Drag Functions
function dragStart() {
this.idName += ' hold';
setTimeout(() => (this.idName = 'invisible'), 0);
}
function dragEnd() {
this.idName = 'fill1';
}
function dragOver(e) {
e.preventDefault();
}
function dragEnter(e) {
e.preventDefault();
this.idName += ' hovered';
}
function dragLeave() {
this.idName = 'empties1';
this.idName = 'empties2';
}
function dragDrop() {
this.idName = 'empties1';
this.idName = 'empties2';
this.append(fill1);;
}
function checkSpot1() {
if (localStorage.getItem("spot1") == 1) {
}
}
function checkSpot2() {
if (localStorage.getItem("spot2") == 1) {
}
}
spot1.addEventListener('dragend', setspot1)
spot2.addEventListener('dragend', setspot2)
function setspot1(){
localStorage.spot1 = 1
localStorage.spot2 = 0
}
function setspot2(){
localStorage.spot1 = 0
localStorage.spot2 = 1
}
But like i said i have no idea how i could store this in localstorage correctly, and make it display were the image was dragged to before the page reload.
If you want to save something to localStorage, it has to be in string format.
So, if you wanted to save spot one, it would look like:
localStorage.setItem("spot1", "0")
where "spot1" is the key and "0" would be the value.
To retrieve from localStorage:
localStorage.getItem("spot1")
This should return "0"
I would implement it like this: (this way you can get x and y coordinates of the dragged element):
// this is for storing -->
function dragOver(e) {
e.preventDefault();
// Get the x and y coordinates
var x = e.clientX;
var y = e.clientY;
// store the value in localStorage
window.localStorage.setItem('x',x);
window.localStorage.setItem('y',y);
}
// => same as JQuerys document.ready();
document.addEventListener("DOMContentLoaded", function(event) {
// read the data from localstorage
var x = window.localStorage.getItem('x');
var y = window.localStorage.getItem('y');
// Now you have to set the element position to the stored values
// I am assuming you want to set it for block1, and that block1 has absolute
// positioning
// only set the position if there is already a value stored
if( x && y )
{
// I found a pure Javascript solution now
document.getElementById("block1").style.left = x+"px";
document.getElementById("block1").style.top = y+"px";
}
});
Well i have this SVG canvas element, i've got to the point so far that once a user clicks and drags the canvas is moved about and off-screen elements become on screen etc....
However i have this is issue in which when ever the user then goes and click and drags again then the translate co-ords reset to 0, which makes the canvas jump back to 0,0.
Here is the code that i've Got for those of you whio don't wanna use JS fiddle
Here is the JSfiddle demo - https://jsfiddle.net/2cu2jvbp/2/
edit: Got the solution - here is a JSfiddle DEMO https://jsfiddle.net/hsqnzh5w/
Any and all sugesstion will really help.
var states = '', stateOrigin;
var root = document.getElementById("svgCanvas");
var viewport = root.getElementById("viewport");
var storeCo =[];
function setAttributes(element, attribute)
{
for(var n in attribute) //rool through all attributes that have been created.
{
element.setAttributeNS(null, n, attribute[n]);
}
}
function setupEventHandlers() //self explanatory;
{
setAttributes(root, {
"onmousedown": "mouseDown(evt)", //do function
"onmouseup": "mouseUp(evt)",
"onmousemove": "mouseMove(evt)",
});
}
setupEventHandlers();
function setTranslate(element, x,y,scale) {
var m = "translate(" + x + "," + y+")"+ "scale"+"("+scale+")";
element.setAttribute("transform", m);
}
function getMousePoint(evt) { //this creates an SVG point object with the co-ords of where the mouse has been clicked.
var points = root.createSVGPoint();
points.x = evt.clientX;
points.Y = evt.clientY;
return points;
}
function mouseDown(evt)
{
var value;
if(evt.target == root || viewport)
{
states = "pan";
stateOrigin = getMousePoint(evt);
console.log(value);
}
}
function mouseMove(evt)
{
var pointsLive = getMousePoint(evt);
if(states == "pan")
{
setTranslate(viewport,pointsLive.x - stateOrigin.x, pointsLive.Y - stateOrigin.Y, 1.0); //is this re-intializing every turn?
storeCo[0] = pointsLive.x - stateOrigin.x
storeCo[1] = pointsLive.Y - stateOrigin.Y;
}
else if(states == "store")
{
setTranslate(viewport,storeCo[0],storeCo[1],1); // store the co-ords!!!
stateOrigin = pointsLive; //replaces the old stateOrigin with the new state
states = "stop";
}
}
function mouseUp(evt)
{
if(states == "pan")
{
states = "store";
if(states == "stop")
{
states ='';
}
}
}
In your mousedown function, you are not accounting for the fact that the element might already have a transform and you are just overwriting it.
You are going to need to either look for, and parse, any existing transform. Or an easier approach would be to keep a record of the old x and y offsets and when a new mousedown happens add them to the new offset.
I want to move a specific node (say i have the node reference), to a desired location (say to the top left corner of the canvas). I tried doing something similar to one in the sample project, where they move nodes while dragging with the mouse. But it doesn't seem to work. I am not seeing the node move as i expected. This is the code i have.
$("#someElement").click(function() {
sys.eachNode(function(node, pt) {
if (node.name === "specificNode") {
// moveToOrigin
var s = arbor.Point(1, 1);
var p = sys.fromScreen(s);
node.fixed = true;
node.p = p;
node.fixed = false;
node.tempMass = 1000;
}
}
});
To move a node to a desired position get the desired position relative to the canvas and set it via particle system's fromScreen(...) function:
var point = point;
var pos = canvas.offset();
var s = arbor.Point(point.x-pos.left, point.x-pos.top);
node.p = particleSystem.fromScreen(s);
You need to set position of node first, then again iterate each loop for nodes.
redraw: function () {
gfx.clear()
particleSystem.eachNode(function (node, pt) {
//var node = particleSystem.getNode("Carrol Wahi")
if (node.data.color == "yellow") {
var pos = $(canvas).offset();
var point = particleSystem.fromScreen(arbor.Point(pos.left + 150, pos.top));
node._fixed = true;
node._p = point;
//console.log("x=" + point.x + ", y=" + point.y);
node.tempMass = .1
}
});
particleSystem.eachEdge(function (edge, pt1, pt2) {
// your code goes here
}
particleSystem.eachNode(function (node, pt) {
//your code goes here
})
}