Forest App — Skipping Ahead

Dutch Steak
16 min readMar 10

--

I would have covered a lot of the detail of what I’ve done up to this point but app development is fiddly and it would have made it more of a pain. Something I am interested in documenting, though, is the drag and drop features that I want to implement.

Imagine a list or a table, this is a web app in React by they way, where you want to reorder the entries just by dragging and dropping. In my case the data is stored in Redux and it’s that data that’s used to render the table, and in my case and the list since I want to do this in two places, one for each. I’m starting with an unordered list but I’ll look at tables afterwards. I thought I’d document my exploration as I go because somehow my own voice keeps me a little bit sane, so here it is.

My initial thoughts are to be concerned about state. The list is rendered using state so anything that updates the state will cause a rerender and I don’t want to change the state until the drag is complete. So a question I have for myself is: can I get away with applying the right CSS classes to block out the item being dragged and shift the items mid-drag, or am I going to need a model specifically for the UI, rather than just purely what comes out of the database? I honestly don’t know.

To separate this out from the main project I’ve started a basic project that just has within it a list. I have done drag and drop before but not within React and I believe from memory I did it laying everything out with position absolute from the start. This time I’d like to do things differently. I was thinking about setting the position of a list item from block to absolute on mouse click, but what about this: click on the list item, clone the element and block out the actual element with CSS? Then it would look like you’d grabbed it. I wonder if the other list elements could then just have the appropriate classes on them to shift them up or down mid-drag to simulate their positions where you to let go at that point. This is a bit of a concern this drag and drop stuff. I could just use arrows next to the images in my app but in this day and age we should have drag and drop, right? And I don’t want to just rely on some npm package — true though it is my solution almost certainly won’t operate on mobile, I don’t think it needs to as it’s an admin screen, not a public facing thing.

Another question I have for myself is how reusable can this be? All lists are lists, right? Getting it working is the start.

So how easy would it be to clone a list item? I won’t bore you with the details of the code as it is, well I’ll explain it but not share it; there’s the App component holding an ItemList component. Tell a lie, I will:

Item list has the data passed in. Here is what it looks like:

So it renders a list of ListItems, and I see I’m missing a key there, but then the list items are rendered like this:

I can see I’m not using ULs or LIs so I will change that now. The first thing I want to attempt to do is to clone the list item I click on and plonk it somewhere else on the screen. Just so you have a visual:

So now for cloning. How will that work? After a quick search there’s a cloneNode function which will clone your DOM element. If you pass true to it, it will give you a deep clone and copy all of its children. The one question I have about this is “Will this fuck anything up?” I don’t think it will. It might if the clone were to stay on the screen for any period of time, but it’s only there for the duration of the drag. So let’s attempt as a first goal to present a clone in the centre of the screen of any of the list items clicked on.

It worked!

So far so good. As a next step before I have a break — I’ve decided to try to do more creative things and since I rarely have much energy I should make the most of having any at all so my guitar is waiting — let’s try to position it so that it appears exactly where the mouse clicked. This isn’t what we’ll want eventually since when the mouse moves there should be an offset but then it wouldn’t be visible. This also seemed to work without a hitch:

That is the point on Item 3 where I clicked and Item 3’s clone arrived. Simple code too:

I was going to say “I’m not even going to plan the next step, it’s healthier to do something else” but I can’t help myself.

Somehow we need to keep track of the item that was grabbed. Initially this isn’t hard because we know which LI was clicked so that can be blocked out with a CSS style. However, on dragging over the item below it, that item will need to be shifted up to take the position of the blocked out item and the item below it will need to be blocked out. Then if you hover over the shifted item again it’ll need to be moved down. I think what it comes down to is shifting the thing you’re hovering over either up or down depending on some…things. As a small-next-goal, though, let’s get the clone dragging around. To be continued…

Okay I didn’t get to my guitar as I had to rest but, one change I want to make to the above is to fire off the event on mouse down rather than click. A drag cannot occur if it fires when you release the mouse button so that makes sense. That’s the next step, small as it is.

Thinking about it, I’m quite keen to keep the coordinates of the thing we want to drag out of state because I don’t see why we need to have the component re-render on every mouse move, so I will try it that way. But later because I’m tired.

Okay. I can make another change. What should it be? Maybe we don’t even need the current code in the onMouseDown event. What about onMouseMove? In there you get your coordinates and I think you can check for what button is down if any. So if you click, maybe we can do it all in there. Though we don’t want it cloning every time we move the mouse. Also should the clone be part of state? Let’s try it without.

Hopefully you can see what I tried to do here. The problem was that moving the mouse and firing the mouseMove event of the actual list item stops working if your pointer goes outside of the element. So it would probably make more sense to have it fire on the body. I don’t have direct access here, but maybe in a useEffect I could get a reference to it and assign the function.

That actually seemed to work. I hope it’ll work within the context of my modal. It’s not very Reacty, but if it works I don’t mind. So ultimately I have the same code as above but in an event for the document rather than the body or for the element itself. Does that make sense? I think so.

Let’s try and get it to let go on mouse up, which will be done in an event of the same name. I think that should be on the document as well since it’ll receive the same event regardless of whether the mouse is inside the element or not due to bubbling. I hope I’m not wasting my time here. That seemed to work. I can now let go of the item and it’ll stay put.

The next goal is to not have the element shift so I’m dragging it from the corner but dragging it from the position I grabbed it from. For this I’m going to need the local coordinates of the element clicked on. It looks like the following code will do it:

So what do I want to do with this? Those variables were obtainable because I clicked on the element and the element knows, relative to itself, or rather the event does, where I clicked. This can be used firstly in the click event where the clone is initially created. And it does work, showing the code in a second. The only reason I know it worked was because the clone is slightly larger than the original. I wonder if I could get away with just stealing the width. But it works. Those variables will need to be stashed, maybe in some more external variables. That worked! Besides the width issue, I can grab and drag a clone from any of my list items and put them wherever I want on the screen:

But I don’t want it to look like I’m dragging a clone. I want it to look like I’m dragging the original. I want to white up the list item. How? I’m tempted to be lazy and just write the code for it without CSS classes. I think that’s acceptable for something so dynamic and it’s something that should never be overridden by any CSS at all. I may change my mind if it starts to look gash but let’s see. It should white up as soon as the mouse button goes down. Ah! But of course! If you don’t do it in the right order, your clone will be all clear as well. But if you do, then it works. There are borders on the above which make it look less appealing but they can stay for now. Here’s a screenshot:

So what are the roles when it comes to displacement of the other list items? No items will ever have to move more than one place. If you grab the first item and hover over the second one, the second one should move up one. If you hover over the third item then the third item should take the place of the original. If I grab the third item and hover over the second one, then the second one should take the place of the third one. I think it’s actually just a case of swapping the one you’re dragging with the one you’re not when hovering. That is much simpler than what was in my own head which was just plain confusion if I’m honest. I thought writing it down might make it feel simpler and actually it has. So how do we make it do that?

My thinking now is that I should use classes instead of styling with JavaScript because it might be useful for the items you’re hovering over as well. Okay, so whatever you’re hovering over should be a) whited out and b) cloned to sit wherever the original was. This feels like too much cloning. Maybe I should actually just…oh I don’t know. Maybe there should be more state involved so that React can handle the redraws.

I wonder if…no. Let’s just experiment. Let’s start with the simple scenario that the user grabs item 1 and hovers over item 2. If I set item 2’s position to absolute, will it do anyting silly? If it doesn’t I might scrap this cloning idea altogether because Item 1 could have been set to absolute…that’s not a good idea. Who can say where the list will be seated or what styling will be going on in the background. The other problem with whiting it out if this is going to be reusable is that I won’t know how to unwhite it. Actually, if it’s a class it could just be removed.

Right, the next step is to white out whatever I’m hovering over. In doing it this way we can keep track of when we’re no longer hovering over the thing. So as a goal now, anything that gets hovered over should be whited out. I keep saying whited out, but I keep thinking of blacking up. This poses a new question: how will I know when I’m hovering over a specific element? Let’s check to see if we can find out. Okay, it doesn’t know if you hover over one element whilst dragging another.

I think this takes us back to the mousemove event on the document. Also, I think it’s going to come down to figuring out if the current clone of the dragged element is within the bounding box of another. I am so far outside of my React coding and into native UI coding at the moment. I wonder if there’s a better way. Since I don’t know any I will start by grabbing all of the list items. Then I’ll iterate through them and check to see if the top left corner of the clone intersects with one and if it does, I’ll white it out and break out of the loop. It can’t intersect with multiple elements after all.

Okay, grabbing them worked. I still need a function to work out if we’re inside one of them though. No function call but with the help of getBoundingClientRect, as expensive as it may be — there won’t be too many items in the list — I seem to be there. I’ve done it so that the elements are whiting up regardless of dragging anything and it does it using the cursor itself which isn’t what I wanted.

Let’s start by doing it by checking if the top left point of dolly, the name of the clone, is within the bounding box instead. Code reshuffle. Okay that works.

So if I clone the thing I’m hovering over at which point should I remove it? And I also only want to clone it once rather than on every mouse move. Maybe that won’t be so hard, maybe I can stash the original in a variable and check before cloning. The other rather large question is how the fuck will I know what order these things are in at the end? Let’s leave that to one side for now.

Urgh…this is going to need more thought. Do we need clones? Could we actually make the initial list item absolutely positioned after a click, move it around and if hovering over something literally swap them? The data could be put back together and the table redrawn since we still have the original data. The problem with that is that the other table elements shift up because the one you grab is taken outside of the normal flow of things so no longer pushes the others down. If we don’t have a clone, and we’re not going to make everything absolutely positioned, we still need something there.

This is confusing. I need to calculate this in the body because it won’t get picked up on the elements themselves if I am dragging over them. So I grab all elements that have the class list-item on them. If I add a class to the clone, and the clone I want to create then I can exclude those when I do my loop.

Okay, now I feel like I’m getting somewhere. We are excluding anything with the class clone on it and I’ve extended that to mean the whited out original version of the item. Which isn’t a clone but the logic works, I can make it make more sense in the future.

The only thing now is that all of the items are “jumping” to the bounding box of the very original.

Okay. Time to walk away for a bit. I’m not sure I’m a fan of all this cloning. Actually before I do go, let’s comment everything out and see if I can grab the original item, make it absolute and see if that’s workable. If it is…this isn’t going to work with state is it. Maybe it will. Right. So. The absolute thing.

Okay, I couldn’t leave it alone. Rather than creating clones and confusing myself I have decided to create a dummy list element. The dummy list element is given the same class as the other items so that it’s a fully paid up member and looks basically the same. So when the drag starts on mouse down, the item clicked on becomes absolutely positioned and the dummy is dynamically created and put in its place by swapping them in the DOM. The next part will be to swap whatever’s being hovered over with the dummy, but let’s look at it later on at around ten if I’m still awake. That’ll give me a break and you some time away from me.

Kind regards and until ten.

Okay it’s not ten, it’s not even eight but I believe I got there. If I can I’d like to make this more react friendly but as it is, it’s working. I am a bit concerned about how it’s going to function inside a modal as I say, but I hope that I can make it work.

So on mouse down on any of the list items, I grab the element clicked from the event object and stash it a global. I also grab the relative position of the click, relative to the item that was clicked. This is so that when the item is dragged about it can not jump to have its top left hand corner positioned where the cursor is. I stash these coordinates away and I will assume I need to because I want to stop. Tidies later.

I then make the item clicked position absolute so it can move. Then I check if we have a dummy element already, this dummy is to take the place of any list items we hover over or initially the place of the first item grabbed. So if we don’t have one already, I create one dynamically, I give it the same class as all the other list items along with a dummy class so I can identify it later. Then the dummy and the element grabbed are swapped. Some styling is applied to adapt the once static element into a draggable convincing thing and it’s stashed in a global.

When the page loads, a mousemove event is added on to the document itself. We grab a relative position, those are confusing me now as to which is which, we grab the point at which we want to position the moving thing. Then, if we are dragging, we reposition the item.

Then grab all of the list items that have a class of list-item so we can do individual checks as the mouse moves to see if we’re inside one of them. We iterate over them, ensuring the thing we’re inside of is neither the element we’re dragging nor the dummy. If not, it must be something we can potentially swap with. If it is we get its bounding box, then we check to see if we’re in it and if we are, we swap that element with the dummy element.

Finally, when the mouse button is released we swap the element being dragged with the dummy element. The element being dragged has its styling set to be static and have no width so it can just listen to its UL parent for that, the dummy element is removed and the element being dragged is now not being dragged and is set to null.

Bob is your Uncle. Is this a shit way to do this? It beats the shit out of the original cloning idea, right? I don’t hate how I’ve done it, but I do wonder if there might be some efficiency changes that could be made. If I could I’d rather have all of my events attached to elements within the JSX rather than sneakily sticking this mousemove one onto the document from a useEffect.

I did nick a function for swapping elements too, which doesn’t seem to work in one instance so I should look more into that as well as tidy up a bit. If the code gets messy and it works, then I can live with that. It’s better than not being able to reorder things, right? In reality, of course, I should then update the state.

Not quite done yet; tomorrow I need to figure out how I’m going to work out which element to switch with that. I guess if you take the index of the thing you’ve just grabbed. That’s a start. Actually I’ll do that now. That was probably written badly: find out which LI in the list was initially clicked on by number, like an index. This line actually works for that:

There’s something I don’t like about all this hacking stuff though. I think it’s because my UI, well my UL, was made, as was React, to just render itself using the state. Sticking UI code in there, such as a dummy row, feels yuk, but so do these hacks. I will stop now, but maybe if I don’t get too precious about the Redux state, I could update the state in there and let the UL render as it will. Or maybe transform the data into some local state and do it that way. I’ll see what it looks like tomorrow and after I’ve cleaned it up. The solution is evolving though and however this ends up being done, as long as I’m happy with it, I can reuse the same principles for the table dragging and dropping. I’m beginning to like the idea of “local UI state” though rather than messing with what’s in Redux but also sticking with the React state way of doing things. Especially if the list isn’t going to be that long anyway. That’s the word of the day whenever I get to it, local UI state. That way I’d have the regular state but I could also incorporate a dummy row and maybe even have it all done within the React world. But the theory is there, it works. And having to put events on the document…not sure I can keep that in the React world can I.

Aw well fuck it…

--

--

Dutch Steak

A coder, a rambler...and now wondering if maybe design and actual art, very different, should form my future...