Resources & Links
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Jason: Hello, everyone, and welcome to another episode of Learn with Jason. Today on the show we've got Zell. Thank you so much for joining us.
Zell: Hi. I'm glad to be here.
Jason: Yes. You have been doing education in the space for quite a while now. For those of us not familiar with your work, do you want to give us a little background on yourself.
Zell: Sure. I am Zell, based in Singapore. I have been a frontend developer for 7 years so far. Since 2014. And it's funny because I was in a finance background previously. And my penultimate year of school I decided to start learning how to code. And I literally went on full-in on development since then.
Zell: We are building a drag and drop today. I was building it earlier to make sure I know what I'm talking about in this show. And I managed to get it down to 77 lines.
Zell: So, that was a major development during that period that I think a lot of us were taught jQuery and all the frameworks from them. But, of course, ES6 alone is very much inspired by jQuery.
Zell: Yeah. I kind of felt the same way. And I'm really grate much to the people blazing trails with the different frameworks out there. They help me think about not just the browser -- not just developers building browsers themselves, but help me rethink how I am building things. Like right now, most recently, I'm trying to wrap my head around like the next thing that came out after Vue and frameworks and generally because of the state management issues. But in the past, when I was working with jQuery, I would not think about state management that way. I would just literally pop everything into the DOM and rely on the DOM as a source of truth and pop everything back. But right now we don't need that. We can just put it into object-oriented program, have a state variable and use it like VX state variable.
Zell: To the actual point where I'll bring in. There's so many things that I think it's impossible to build without frameworks can actually be built without frameworks. I'm trying in the process of re-looking at components and figuring out what can be built without, what can be built with. And whether --
Zell: And try to make sense.
Zell: The way I'm thinking about things right now, it's more of a -- well, if I have something complicated that required me to manage the more like a related thing, I'll go for frameworks first. I was looking through the location API and the API the other day, it's going to be really messy to figure that one out.
Zell: When I figure that one out, maybe my ideas will change.
Zell: There's one point that I could add to that.
Zell: It just came into my mind. At which point I switched the framework. It's really hard to render things without a framework. So far. You know, Vue and React, they give you this render function.
Zell: The HTML file.
Jason: Okay. So, let me make a new directory. GoDaddy Pro pure-js -- spell it out. Drag n drop. Do this, init, so the code behaves. And then open this thing up. So, we have an empty folder. I'm going to create an index.HTML. And do you want me to scaffold out the basic document structure?
Zell: Yes. Go ahead. Scaffold out this basic document structure.
Jason: Where are you at Emmett? It always chooses Nunjucks and I don't know why.
Zell: You can configure it for file.
Zell: Cool. So, I guess the first thing we wanted to do is to create some elements that we can, you know, drag and drop.
Zell: So, in this case, I want to scaffold a little bit further and let's create something called a pick zone and create something called a dropzone.
Jason: Okay. So, we want to make a pickzone and a dropzone.
Jason: All right. And do you want to do that with a div?
Zell: We can do that with a div, you can use classes or you can use the attributes. Div is fine. I kind of want to use data attributes.
Zell: I prefer data attributes --
Jason: So, like data-pickzone like that?
Jason: Okay. And then div data-dropzone.
Zell: Yep. At this point I want to mention that when it comes to drag-n-drop the first thing that came to mind was the native drag-n-drop API.
Jason: Wait, there's a native drag-n-drop API?
Zell: There is a native drag-n-drop API.
Jason: What? I had zero idea this existed.
Zell: Yes. But this doesn't work for the drag-n-drop we're going to do. The difference between this drag-n-drop and the one we're trying to build is for this one, it behaves more like if you're on a Mac and you drag the folders around. The item has transparency and is above other things.
Zell: You cannot change the angles and other stuff. It's not so flexible in this sense.
Zell: What we're going to do is rely on the mouse down, which is very, very old.
Jason: Nice, to quickly answer a question in the chat. Ben asks, what's the benefit of npx serve versus opening in the browser directly? In the instance we're using now, there's none. The reason I like doing it, I like having the URL. That's kind of a vanity thing. And also, if we wanted to use anything that was going to do asynchronous requests or anything like that. You can get weird cores issues or other browsers sandbox things in a way. To get predictable behavior, I like to have a URL. That's generally my reasoning. But there's no like, right now this would open in the browser the same way that it's opened right here. So, did you want me to like do just a script tag here?
Jason: Okay. So, I'll do a style tag up here. Okay.
Zell: Okay. So, first of all, we wanted to -- let's create some sort of style for each of the zones, right? Maybe let's have a minimum height of 100 pixels maybe.
Jason: Okay. Minimum height of 100 pixels. We could do that on both. Data-dropzone as well. Maybe so we can see them, background of light gray or I'll just use --
Zell: We can do a board we are three pixels dotted black. Right there.
Zell: I want to use the background to indicate the draggable elements themselves.
Jason: That's a good point. And then let's just do a little bit of margin at the top so that they are separated.
Zell: Yep. Cool. Next thing we want to do is to create the whys that we're going to drag and drop, right? Let's create some elements in the pickzone.
Jason: Do you want me to do these as a certain type of element?
Zell: A div would do for now. I haven't thought about the accessibility for all the parts so far.
Zell: I'm going to stick with div for now.
Jason: Do day had need anything other than a class?
Zell: Just a class. If you do not want to have content inside, we can go and add it as a box or something. Have a height and some background.
Jason: Okay. Yeah. So, maybe we can do like a -- do item and we'll make a display -- it's already a display block. So, we can make it a back ground of we'll do Rebecca purple, I like that one. And we'll do a width of 50, height of 50. And maybe we can make this one, like, display flex. And we'll --
Zell: We can do display grid.
Jason: Oh, yeah, display grid. That's a good one.
Zell: Display grid with autosense or something.
Jason: Is it like -- that's not right, is it?
Zell: Repeat -- put it inside a repeat.
Jason: That's right. I'm missing something. Repeat auto-fit --
Zell: And then there's a value. Send me back zero and 50 pixels.
Jason: Zero and 50 pixels?
Jason: I'm doing something wrong here.
Zell: It's kind of not like that, but --
Jason: I'm getting something very wrong and I don't remember what it is.
Zell: I don't know. Can I like throw a value inside of the chat or something and you pick it up from there.
Jason: Oh, here it is. I got it. There it is.
Jason: And then we can set the gap to like one REM. Got to finish my stuff. There it is.
Jason: You want to see some cool stuff? I'm going to vertically center in CSS. Line items center. And down here we can justify content, center. Grid is magic. I love how many problems that has solved for us.
Zell: I keep using grid. And there's this thing where people say like for one directional layouts and grids and for two-directional layouts, I come back to them because grid can be used for one-directional layouts as well.
Jason: Yeah, definitely. Okay. We have kind of a general setup here. I'm going to make the color on this white so that we can see. Good. And all right. So, this is, you know, we're probably gonna win design awards for this, I think.
Jason: Let's see. I'll collapse this down for now so that we can see what we're working with. Here is our items and here is our script. So, what's next?
Zell: Next is to be able to select the elements. So, let's look through all of the three items and add values on to it.
Jason: Okay. So, to get all the items, I'm going to use querySelecter all and we'll get our item. And this now is an iterable. Which is -- do you know -- this is something that I know how to use it, but I don't understand why. Do you know why it's an iterable and not like an array?
Zell: I'm not too sure, actually. But it's not really an iterable at this point.
Jason: Oh, no.
Zell: Because it just happens that some browsers have implemented for each method into the querySelecterAll and some not. I was trying it out here and there and I kind of realized that. If you want to do it, it's better to turn it into an array.
Jason: So, we can spread it.
Jason: And then we'll get an item. And then here we can do something to each element. So, what should I do?
Zell: Add event listener
Jason: So, item.addEventListener. And pointer down?
Jason: If I'm understanding, pointer is for your mouse and your finger. If you're on touch, your pointer is your finger, and on a typical browser, click is a down event.
Zell: It's for stylus and others. It's a forward-thinking event that the guys thought up. I don't know who thought this up. I think it's brilliant.
Jason: That's so much nicer than having to write. I remember having to write the mouse down and the touch down and all the different events and you just kind of have to duplicate that logic. So, this is really nice to have that abstraction. So, now whenever we click on one of these, what we should see is item clicked. So, if I open up my browser here, reload the page and then now whenever we click, we can see it's counting up. And that's that pointer down event. Okay. Excellent. So, then what should I do?
Zell: Then the first thing is, okay. We want to have some sort of style to know that we are actually trying to, you know, drag this thing. So, maybe we can go to CSS and add a cursor to move for each of these items. So, we know that they are actually draggable.
Jason: Cursor move.
Jason: Look at that.
Zell: While we move it, it's important to add a touch action and set it to none, I think.
Jason: Okay. Touch action. I'm not familiar with this one. What does this one do?
Zell: So, it basically mean it is a person touches the item that you're talking about, what do you want to do? By default, it pans the website. So, like, up, down, pan.
Jason: Oh, I gotcha.
Zell: Not to this specific thing I'm not going to pan the website. You can do whatever you want to do.
Jason: Gotcha. I never would have considered that. I would have waited for the bug report to come in.
Zell: If you want to support touch at the same time, you need to use that.
Jason: And there's a question in the chat about browser support. Is this something that we'll have to polyfill for Safari?
Zell: I don't think so. Touch action actually works on Safari if I remember correctly. Let me just check.
Jason: Let's see. So --
Zell: Touch action works -- oh, no the in Safari 14.
Jason: Okay. So --
Zell: This is something you might want to polyfill.
Jason: Okay. So, we would have to... okay. So, we would need a polyfill for this. I think for the purpose of learning we can leave that part out for now.
Zell: Yeah. I just realized that pointerdown needs a polyfill for Safari.
Jason: Okay, touch action --
Zell: It was strange. I was testing on Safari and it worked for pointer down for mouse.
Jason: That's interesting. Maybe that's something that's changing or was recently released. But, yeah. So, what we should see, though, is this is something that's being adopted but browsers and so, we should see seen that this will be, you know, universally adopted. But, yeah. So, this is -- so, now what we've got is we've got some basic styles here. So, that what I hover over this, I get the mover. And when I click on it, we're aware that it's been -- that I'm like down. And the reason that we're using pointer down is because that's the drag. Like I'm clicking and holding, right?
Zell: Not yet. Pointer down, it's a setup for the drag. With pointer down, you're telling the cursor that this is the down -- because if you're on the drag-n-drop, you have the pointer down. And then move the pointer.
Jason: Gotcha, okay.
Zell: There's three different events we have to set up. One is pointerdown, pointermove, and pointerup.
Zell: One at the same time. But the moment we set pointerdown, the item to say rotate minus 5 degrees.
Zell: You're actually rotating it and this is the style when we are dragging.
Jason: Okay. Should maybe I do this with like I'll add a class of dragging? So, that we can do the styles up here. Does that make sense?
Zell: Makes sense. That's possible.
Jason: And you wanted to transform: Rotate did you say minus 5 degrees?
Zell: Any amount you'd like.
Jason: Okay. I've done that, I'm clicking. And there we go. And when you click. I haven't removed that class. When I've pointed down, you can see that it rotates.
Zell: Yep. We need a pointer to remove that class in that case.
Jason: Okay. So, we can item.addEventListener and say pointerup.
Jason: Get our event. And say itemClassList.remove and just remove dragging. To me, this one felt like a superpower when I learned it. Because prior to that, I thought I would have to get the string of classes. Right now what we've got is we've got item, space, dragging. I thought I would have to get the classes, do a regX to replace dragging and put it back in. When I learned that you could use the list to add and remove the properties, it felt like a superpower.
Zell: It's easier.
Jason: Now, down up, down, up. Look at it go! And I can do a transition on it. And transition. And we'll just do for ease, all 200 milliseconds and go linear. Now when I reload, now it's animated. That's maybe too slow.
Zell: I would be careful about the transition, because the moment we add movement it's going to go away.
Jason: Okay. I'll leave that out. Okay. Fine.
Zell: Especially if you use -- maybe if you work on the transforming, then it's fine.
Jason: Okay. I got you. But, yeah, this is going to be good. Like, so, we've got a good -- as I'm clicking, we can see that things are reacting to that. So, now are we ready to move or is there any other setup that we want to do here?
Zell: At this point, what I'm going to talk about next is it's probably a little bit purist in a sense.
Zell: Because if we do pointerdown, then we don't need a pointerup event unless the pointerdown comes in first. So, we can actually shift the whole pointerup event into pointerdown and then remove the event whenever it's not needed.
Jason: Like this?
Zell: Yes. And we have to create a name callback for the pointerup.
Zell: Call it move or something.
Zell: I mean, up, yeah. It should be up because move will be for move.
Jason: Oh, yeah. That's right, that's right. So, we'll go up.
Jason: Up. Okay. And then when we want to remove this -- how --
Zell: I don't think we can use the anonymous function. So, no arrow -- oh, wait. This is fine.
Zell: We need to remove it, item, remove.listener. Inside up.
Jason: Inside up?
Zell: I don't think many people remove EventListeners, but it's a cleanup thing. Yep.
Jason: If I'm understanding here, what we're doing is saying when you start with the pointer down, we're going to add this class. And then when you let the pointer up, we're going to remove that class and then we're going to remove the EventListener. The only time we have the pointerup EventListener is with the event here. That's important because I could click here and move over here and let go and that would do something weird.
Jason: So, now what we're doing is we're only letting that EventListener exist in this case. And every time we add it, we're not stacking more EventListeners. In this case, that's probably harmless, but in a lot of cases, that can get us into trouble.
Zell: Yeah. I want to put this in as a way of -- not really an optimization. But it's a cleanup basis.
Zell: I think we're almost ready to drag and drop, right?
Jason: I think so, right?
Zell: Let's start with the dragging. To drag, we kind of need to have a move function and a move EventListener otherwise there's no dragging at all.
Jason: Okay. I'm going to follow the same pattern here. And in this one, I have no idea what I should do. So, I'm just going to watch.
Zell: Just leave it empty for now. We need to add the EventListener.
Jason: Okay. Go here.
Zell: Pointer move, yep. To clean it up. We need to move the pointer move event when we release the pointer. It's an up.
Jason: Oh, in up. Okay. Cool. So, we'll get rid of this. And okay.
Zell: That makes sense because you do not to want remove the event. Otherwise you move by one pixel and it will be a movement.
Jason: That's a great point. That's a thing that, again, I would have figured out when it broke on me.
Zell: It broke on me a few times from this point. The worst thing is you forget to release the pointer move. So, each time you click on it, you add a pointermove again.
Zell: And suddenly the thing goes like -- why is it so far away?
Jason: And so, that's actually a really good point. Because what you're talking about there, that's what we were just talking about with stacking EventListeners, if your move says move the div by that much difference, if we stacked two, doubled it, stacked three, and tripled it until it shoots off the screen completely.
Zell: And that reminds me of the past where I accidently stacked EventListeners and it goes click, open/close, open/close, yeah. So, yeah, cool. I think we can start the drag now. To drag the element, the most important thing is to be able to move it around. So, moving things around I think the easiest way to do it, you know, there are two different methods. One is to change the top/bottom/left/right position. And one is to change the transform position. In this case, I think changing the top/bottom/left/right is easier. Not looking at it too much from the performance standpoint at this point. Because otherwise it gets really hard to write and transform the code. It's really, really hard. So, change top and left, most primarily he will be changing the two, we need to set the position to absolute.
Jason: okay. And so --
Zell: And down. It's more like setup before we drag.
Jason: So can I put that in our dragging class?
Zell: You can.
Jason: So, I'm going to go position... absolute. Okay.
Zell: Yep. And then you can try clicking and see what happens at this point.
Jason: Oh. I got to refresh before that works. Okay. So, it immediately gets kind of weird. Check this out. This is where it's interesting, because I click, right? Now, I'm going to release my click. I've released my click, both hands up, you can see them. But in order to get this to remove, the pointerup has to fire inside of this div. So, I have to click in pointerup inside and that actually causes it to reset. That's the interesting thing about them, they are where they are in the div when they fire.
Zell: I'm going to jump ahead and add this thing that we should probably talk about.
Zell: Pointer events have set pointer capture and release pointer capture.
Zell: When you click, it goes all the way to the left. If you set pointer capture, it saves that -- it tells the browse their whether pointer event is going to happen, it's going to happen to this item that is being moved.
Zell: So, in that case, when you release it, everyone though it's not on the element itself, it will act as if it was on that element.
Jason: Oh, interesting. Okay. And so, where would I place that?
Zell: You have to place it inside down because we're doing a setup.
Zell: So, item.setPointerCapture. Yep, that one.
Zell: It takes event.pointer ID. It takes the ID for the event. ID with a capital I.
Jason: Like that.
Zell: And then in up to have to do a -- let me go to my up. You have to do a release pointer capture.
Jason: And same thing?
Zell: Same thing.
Zell: You can give it a try.
Jason: All right. So, I'm gonna refresh the page and I've clicked. It bounced out, I'm releasing and it's back. Cool. That's really nice. I didn't realize that was possible.
Zell: Yeah. This is the key to making all of this work. Because I noticed this when I did mouse move. And it's possible that, you know, mouse moves so much faster than what DOM can refresh. And so, you move out of the -- the item itself and then when you try to mouse over again, you are just pushing the brick around.
Zell: This solved the problem.
Jason: Nice. I got to be honest, like, this is really clean. Like this feels really good to use because everything that we're doing, it makes sense. As I'm reading this, you know, as long as I know the vocabulary, as long as I know what a pointer and EventListener is, this feels very like I can read what's happening. And that's really nice. It's kind of clear almost human readable as long as you've got some jargon.
Zell: Yep. So, the next thing to do is to -- well, if you think about position absolute. So, this another hygiene thing. There's layers of hygiene stuff inside of this drag-n-drop component. If you think about position absolute. It is only absolute to the containing element that has a position set to relative.
Zell: Right. So, if we want to set position to absolute, we must make sure the item actually gets removed from the current position in the DOM and add it to the body. Because the body will always be relative to the body itself.
Jason: Got it. Okay. So, then we want to on pointerdown -- we would like item remove?
Zell: We can simply do a document.body.append item. It will do both.
Jason: Append item like this.
Jason: And that will move it.
Jason: I want to watch this happen. I've never watched this before. I'm going to pull this up soc we can see the elements. And what we should see here. Refresh the page, see the items. Again. Item three should drop out. There it is. And it's now down in the bottom. Dang. I didn't realize append would do that. I thought I had to pull it out and put it back in the DOM. So, already worth the price of admission.
Zell: so, append does it, append child does it as well. That's the old version.
Zell: Before does it, after does it.
Jason: Nice. That is really, really cool.
Zell: Much cleaner.
Jason: And this is good because like you said, if we had put position relative on this div, we would never be able to drag outside. We would be stuck in this div. So, in order to drag from this one to this one, we're short circuiting that. We take it outside of everything. And you're saying when we get into our new div or drop it, you're going to say we put it in this one instead.
Jason: All right. Nice, I'm following.
Zell: We're going to do it step-by-step. The first thing to do is drag it around and then put it anywhere we want. Not worry about where to put the final thing on. leave it there for now. To drag, we kind of have to go into the mouse move, function move, because we want to move the thing around, right? There's this thing, movementX and movementY. Are you on Chrome?
Jason: I am on Chrome.
Zell: What I'm talking about now only works on Chrome.
Zell: Event.movementX and Y. So, this event.movementX with a capital X. So, movementX is the delta for the amount that was moved since the last pointer event.
Zell: And the last pointer event as well.
Zell: If you want to support Safari that doesn't support movementX and movementY even though it says it supports, I find that out the hard way. You have to use the old version which is screen X and screen Y and you have to create a new variable to track the previous screenX and screenY and do the delta I yourself.
Jason: Okay. Can I ask a question about -- so, using the browser, we can determine where the pointer is on the screen, right? And so, would it make sense to just put the object like at the mouse position so we don't have to calculate deltas at all?
Zell: Well, the thing is, if you put the position -- how would you put it on the mouse position?
Jason: Isn't it... so, if I was gonna do it, I would... can you just set up -- can you just set it to be like I want the top to be mouse position Y minus 10 and the left to be like mouse position X minus 10 so it looks like you're dragging it. Would that work?
Zell: That's possible. You're doing it in a forced way.
Jason: Yeah. It's definitely solving that problem with a hammer. That's for sure.
Zell: The way I'm envisioning, clicking on the bottom right-hand corner I want to say on the bottom right-hand.
Jason: I understand. That's exactly the reason I was looking for. That's perfect. Are you going to use movementX or use the other one?
Zell: Let's do this one because it's simpler. If you go to MDN for movementX, you'll find the old version. Chrome and Firefox, not Safari. So, current event equals current event.screenX = previous event.screenX. You can build that out.
Jason: That's our polyfill. To use this, what do we do?
Zell: Do you want to use the polyfill version
Jason: I think for learning purposes, use this, this is kind of fun. It's Chrome-only for now.
Zell: Okay. Cool. So, what we want to do is move the element based on the delta. So, we're gonna change the targets left and top position based on its current position plus the delta.
Jason: Equals event target left plus movementX. And then I can copy all of this.
Zell: It's going to be a little bit long because it's going to be event.target.style.left.
Jason: Oh, style. Okay. That's right, that's right.
Zell: We don't need event.target. That's the item. We're going to use item --
Jason: Oh. Perfect.
Zell: And the problem with this is we have to give it a pixel value. So, we have to do pass float to the item.style.left because we can potentially have a decimal point, right?
Zell: Yep. Then --
Jason: So, parseFloat and then --
Zell: Wrap the entire thing in the template and a pixel behind.
Jason: Okay. No! Okay. So, here, here, here, here.
Zell: Yeah. That should work.
Jason: Okay. It wrapped so weird. Okay. That's fine. But, no, this is great. This, then, we are parseFloat is a built-in browser function that will identify if this is a 25.12 whatever. Because the browser will give us really long repeating floats. And this makes sure we use it as that and not as a string, right?
Jason: If I'm correct in reading this, if I save and refresh the page right now, this will work.
Zell: This will work a little bit wonkily because we didn't set up the position.
Jason: It's going to be -- oh, wait. What just happened? Why aren't you doing your thing?
Zell: We're placing this thing into -- we can consoleLog and see what's item.style.left and item.style.top and see what's wrong here.
Jason: Okay. We'll do item.style.left and .top, and it's not firing at all. Which means I did something wrong. So, on pointer move -- did I mess something up? Should be captured...
Zell: We can log a hello or something just to make sure.
Zell: Because with long strings of code like that, usually we can get some problems with it.
Jason: What's going on? What don't you like? It doesn't like something that we're doing. So, it's doing this part. Okay. Does it get to... here? Is this like some really -- Okay. So, it gets to there. And then it should be calling move. But it doesn't actually call move. Hm.
Zell: Did you release your mouse pointer already?
Jason: Oh, oh.
Zell: You have to be on -- I forgot about that, you have to be on the element itself when mouse move happens. It's kind of --
Jason: I thought we captured the pointer.
Zell: Yeah, I kind of -- I think capture pointer does that, but I'm not so sure at this point. I probably need to look at it a little bit.
Jason: Interesting. Okay. So, something -- something is going wrong because we're not getting any value in item style.left. Do I need to like set them initially to be like zero.
Zell: Yeah. Rather than setting them to zero, set them to the current position instead.
Zell: Because we cannot know where these items are. And the easiest way to do that is to use get client left to get the bounding box of the element.
Jason: Okay. And we would do that in down, right?
Zell: In down because it's a setup.
Jason: So, item.style.left is going to equal item.getBoundingclient.rect. And is that left?
Zell: And then the same thing for top.
Zell: Yep. Again I think we need the pixel.
Jason: Oh, okay. Here. Oh, my god. Come on.
Zell: This is a fun way I'll start creating variables to hold this stuff because the code is getting a little bit too -- it's getting a little bit too long for me. But that's me.
Jason: Oh, wait. Ooo! Look at it go! Look at it run away!
Jason: No, this is good. We sort of have this working. It's not quite right. I cannot pull it left, I can only push it. But it's still cool because I can like nudge it around the page by doing this. We're getting closer. This is feeling more and more like what we want it to do. Okay. So, then...
Zell: Let's debug a little bit here. Because by the time you click on it, it should appear on the same position and not fly away. So, let's figure out what's wrong with that.
Jason: Oh, you know what it is? It's adding dragging first. So, maybe what we should do is --
Zell: I don't think that's the problem, though. We should probably --
Jason: It definitely is. So, that split second where it's position absolute before we apply the left and top gives it time to bounce off the screen. So, once we add that, it does what we want. And this is -- okay. So, here we go. Now we've got drag and drop. Because -- and notice what Zell was talking about, if I click down in the corner, it is stays stuck to that -- hello? No, come back.
Zell: I'm just wondering why the pointer ID capture thing is not working. It seems to be working on my side.
Jason: Yeah. Did I mess something up? It's event.pointerId. So, we set that here. And then in pointerup --
Zell: We remove it in pointerup. I don't think there's anything wrong here.
Jason: We release it. Maybe what I'll do is I'll release the pointer capture first. Naw. I'm still losing it. I don't know. I'm not sure why it's not working for me. But I think --
Zell: In my case I tried releasing the pointer capture after the removing of the bounding, I'm not sure if that does anything. I don't think it should.
Jason: No, I still lost it.
Zell: Interesting. I'm not sure why.
Jason: Who knows? And I think this -- this is where you start to look at like why frameworks get so big is we're gonna write code to handle this edge case, and then if we release this as a library, someone is going to find an edge case and write code to fix that edge case. We are going to discover why there's so much happening.
Zell: I'm just wondering. It's working on my side with Chrome. I'm not too sure. If I have to figure it out, it will probably take a while.
Jason: Sure. As long as I don't yank the cursor around, we're in good shape. This will be okay for demo purposes and we just know there's a little further that we can go. And, you know, this -- this maybe is a great case for like a follow-up article or something where we can dig a little deeper into, you know, how to really make this feel more like rock solid.
Zell: Sure. So, let's move on, then.
Zell: If we want to put the element out anywhere we want to, it's more of we do not remove the dragging class. Okay. If we -- let me rephrase it. If you want to put the element wherever we want to put the element, the position should still be set to absolute. Make sense?
Zell: Since you added the dragging class and we removed the position absolute from it. Skip that part of putting the element wherever we want to. Let's move into the part where we want to put the element back to where we want to get it to.
Zell: Am I making sense?
Jason: I think so. When we want this, put this at the end of the body. If we want to drag it into this div, we need to move it away from the body and back inside this div. Which also means that we don't want it to be position absolute anymore. Or, I guess it kind of doesn't matter if it's position absolute if this one is relative.
Zell: If you want to drag from the first box to the second box, it doesn't matter -- actually, if you want to move from the first box to the second box, we want the position to move back to static. So, what we have here is the correct code. But there's also an alternative drag-n-drop that's simpler. And most tutorials stop there. Have the position set to absolute still. If the position is set to absolute, you can drag and drop it and wherever you want.
Zell: But we removed the class at the same time. We kind of skipped that step.
Jason: I gotcha. Instead of putting it here, I put it here, then what we will see is that I can put this anywhere I want on the page and it will just stick there.
Jason: As long as I don't move too fast. So, yeah. This is find, kind of. But it's -- this is now completely decoupled from the rest of the -- the app. Like there's no structure anymore or hierarchy. The app is just like chaos.
Jason: So, now what we're doing is taking it out of the structure so that we can move it. And then we want to put it back into the structure when we drop it.
Zell: Correct. Let's start by creating a preview of where this item will be dropped.
Zell: To do that, we need clone the item so we have the exact same size and properties for it. We can use clone node.
Jason: And that's true, right?
Zell: We do not need to clone everything. Unless you want to.
Jason: I don't.
Zell: And then we need to -- before we remove the why from the document flow, we need to append the cloneNode before the item. So, before line 62 we do item.beforeClone.
Jason: Before... clone? yep.
Zell: We need to style the clone a little bit. Do a background of EEE or anything.
Jason: And then if we have that, we can set an item.clone. That will change the background color to here. Okay. So... yeah. Look at it go!
Jason: It did get the dragging class. So, we wouldn't want that. So, we would add that here. There we go. So, now we've got our item kind of stays behind and we're able to move this one around. And as we can see, it's kind of not cleaning up after itself right now. But that's fine.
Zell: We can remove it later. So, let's say, for example, we want to put it back to the same position at this point. Can we add a border around the clone?
Zell: Because it seems a little bit -- it's getting to me because it's not really accessible from a color perspective.
Jason: Yeah. We'll just make it -- oops.
Zell: Yeah, perfect.
Jason: Solid black. Now it's like pretty obvious.
Jason: A real designer would not let that fly. But that's fine.
Zell: We're not designers anyway. We are really designers, what are you saying?
Jason: Okay. So, now we can see when I drag, I see it moving around and we can see where it was. So, the next thing would be to identify where we can drop it.
Zell: Let's drop it to the same location first.
Zell: So, to drop it to the same location, we kind of want to go into up. Because that's where we drop.
Zell: And we want to say, if you're dropping it to -- we want to drop it to where the clone is. So, what we can do is clone.after -- item.
Jason: Okay. And then clone remove.
Zell: Yeah. Clone.remove.
Jason: Okay. And everything else stays the same, right?
Jason: Okay. So, now, move it, I drop it and it goes home. That is really slick. Yeah. This is cool. Okay. And we can see if we look in the DOM, we'll watch those elements change too. So, we'll be able to see here that we'll get a clone. So, there's our clone. And we can see at the bottom -- the very bottom, I can't move my house to highlight it. But do you see it highlighting at the very bottom of the elements panel. As we move around, we can see the left and top getting adjusted. And when we drop, that one's going to be dropped right back into the clone and the clone disappears. So, that is really cool. That's really nice.
Zell: If you want to clean things up totally, change the style, left and top back to nothing.
Jason: Oh, sure, yeah, we could just remove those entirely honestly. What would we do? We could just like delete...
Zell: I think the easier way is to set item.style to empty screen. Because empty screen is not valid. It removes it.
Jason: Okay. I got ya. I should actually look at this to see if it's working. Yep. There you go. Lever leaves us with an empty style.
Zell: I like to clean thing up and look at hygiene before I go on.
Jason: Make sense. This is powerful. I really -- I'm appreciating how straightforward this is. Like, you know, we're -- the browser inconsistencies are still there a little bit, you know? We've got the support issues with some of these terms like movementX and stuff. Be this is so much more pleasant than the back in the day of how you would have to kind of almost do like user sniffing to figure out which files or prefixes you had to apply. So, this is really nice.
Zell: Yes. The next thing is we need to use -- I think this is a little bit magical to me.
Zell: Because with need to start using a new property. And I did not know that this property exists all the way back. Like this property I'm gonna talk about is document.element --
Jason: Document.element what now?
Zell: Element from point.
Jason: Element from point. Okay.
Zell: So, element from point.
Jason: And where am I putting this in? In move?
Zell: In move. What we're essentially doing here is when we move the element into the dropzone, for example, we kind of wanted to know whether this element is in the dropzone or not.
Jason: Oh. I did not -- I had no idea that this was real. Okay.
Zell: I had no idea too. And apparently it supports all the way back to Internet Explorer 2.
Jason: Okay. That's wild. This is a function and takes X and Y.
Zell: So, pass in the item.style.top and the item.style.left.
Jason: Okay. Okay. And so, what is this telling us?
Zell: It returns a an element. So, let's say -- it turns the element that's coding it at the top-most level.
Jason: Okay. Which means?
Zell: You can do a consoleLog for the results of this one and give it a try.
Jason: Okay. So, this is in our move event. And so, if we go and look at our console and I move this around. Uh-oh. Double value is non-finite. Oh. ParseFloat. That's fine. We can do that. ParseFloat. That is the least-helpful error message. There. Okay. Okay. So, here's our -- hold up. I am -- wait a minute.
Zell: See that?
Jason: What? No way
Jason: So, this is determining the closest element to where our mouse is? Holy crap.
Zell: Not the mouse. To where the point is.
Jason: That is wild. I had no idea that existed. That is -- whoo -- wow. So, that's going to make a lot of things a lot easier. This is supported all the way back to Internet Explorer?
Jason: Oh, my god. Okay. I'm thinking of all the things I overcomplicated because I didn't know this existed.
Jason: Okay. So, basically we're able to get a drop target, or a drop target element, I should say.
Zell: Yep. I called it hit test to make it simple.
Jason: Hit test. Okay.
Zell: Just one more point, if you undo the rotate, it doesn't work anymore.
Jason: If you undo the rotate?
Zell: If you do not rotate the element, it doesn't work. But there's a way to make it work.
Jason: Is that because of like transformations in? It needs to be transformed?
Zell: No, the rotate creates, okay. Okay. So, this is the box, right?
Zell: And let's say we are hitting this point here. This point on my finger here.
Zell: When you rotate this element, the point is still there.
Jason: Oh, I see.
Zell: You have to pass this element and it goes beyond. So, if you do not rotate, it doesn't get past this element itself. And to make it work, we need to set pointer events to none so it passes through.
Jason: And we would do that only on dragging?
Zell: We can do that only on dragging, yes.
Zell: Or we can do it as a setup -- we can do that as a setup. That's fine too. Either way.
Jason: Okay. So, now, even if we don't rotate, this will still get to the element.
Zell: Yeah. I think you want to --
Jason: Okay. So, I'll just turn this off. Here's my transform. So, let's get that out of there. And then we'll pull this up and let's take a look. Oh, wait. I got to reload. Okay, so, it's no longer -- wait. Uh-oh.
Zell: You can't have pointer-events doing the dragging. That kills it off.
Jason: That's true. Our pointer loses track of it entirely.
Jason: Okay. And we add this at which point?
Jason: Okay. So, at pointer down, I'm going to add pointer.style? Like this?
Zell: Yes. Equals to none. You should add this before -- after dragging or before dragging. But definitely after the clone.
Jason: Like here?
Zell: Before the panel just to keep things a little bit cleaner.
Jason: Okay. So, here. No, I lost it again. Did I forget to save? Position absolute. What's going on? Why doesn't it want to work?
Zell: Interesting. This is why some things don't seem to work on your side.
Jason: Okay. So --
Zell: Item.style.pointerevents = none. Yeah, that's what I have.
Jason: hm. I wonder if --
Zell: Is it not moving at all right now?
Jason: It's not moving.
Zell: It's the same problem with the set capture. We're not having the events on the element itself. If set capture works, this will work at the same time. If set capture doesn't, this won't work.
Jason: Yeah. We're losing set capture. Are you calling it at the top? Where are you calling it in your --
Zell: I am calling it just before I add the eventListeners.
Jason: Let's do that and see if that makes any difference. It absolutely does.
Zell: Oh, wow.
Jason: So, what changed? Why did that happen? So, we set pointer capture after we --
Zell: Oh, oh. I understand why now. I think. It's because we shifted the element itself.
Jason: That's true, yeah. So, we --
Zell: Edited it somewhere.
Jason: That makes sense. Basically what we did was we were saying capture the pointer but then dropped it out of the DOM for a second to move it.
Jason: That totally makes sense. So, this part here is what was breaking our pointer capture. So, theoretically speaking, if I move this to right after that, it will still work.
Jason: And it does. Okay. So, that was the problem. We can't move something around in the DOM and expect that pointer capture to still work. Okay. I get it. I understand. Now if I come back and refresh, I can whip this around all I want and don't lose it.
Zell: You can even whip it out of the window and come back.
Jason: Oh, beautiful. That's really nice. What I did lose it now I can't click it again because we set the pointer events to none.
Zell: Yes. You have to reset it back to the initial one.
Jason: Okay. We'll just get rid of that. And we can probably just remove it entirely?
Zell: I think so.
Jason: There we go. Okay. Beautiful. So, that's doing what we want. All right. So, I think the next thing, then, we have our target node, our hit test. And so, we just need to append the element in that, right?
Zell: We were testing whether hit test would work when you rotate, remember?
Jason: Oh, that's right, that's right. Did I remove, not in console log anymore. It does.
Zell: Yeah. So, that's the trick if you're using on point.
Jason: Got it. Let's put that rotate back in because I do like that.
Zell: Yeah, likewise. But I want to mention that the version that doesn't rotate, it works as well.
Jason: Yeah, no, that's -- and it's such a good thing to call out too. Because that would have mystified me a little. Wait, it worked before. Not realizing it was because we rotated it and changed the DOM container would have been really, really confusing.
Zell: And then you go on a wild goose chase on the Internet.
Jason: Now that we have the hit test, do I need to track this let hit test.move and go up?
Zell: no, in every hit test, we are checking -- this is the thing. The reason why we have the preview, there's a trick to it. Because if the hit test is in dropzone, what we do is move the preview into the dropzone.
Jason: I get it. Then we would do hitTest.append clone.
Zell: No, if hitTest in dropzone, you have to find it first.
Jason: Isn't this the dropzone?
Jason: We need to check if it's the dropzone. Hit test, is it data? Attribute?
Zell: Hit test. We kind of have to do a closest -- hitTest.closest?
Jason: And then --
Jason: Okay. And the reason for that is we might be hovering over an item in the dropzone, but we don't care about children, we just care about that container.
Zell: And we want to make sure that there's a dropzone as an attribute. Otherwise this code should have worked.
Jason: Okay. Then because it might not be that element, we want to do a document.querySelecter -- data-dropzone-append clone, right?
Zell: I think in this case, if it's not hovering on the dropzone, this doesn't work. If it's hovering on the dropzone closest, it returns to dropzone element.
Zell: We can do hitTest.closest dropzone and set it as closest as the variable.
Jason: Got it. Would be more like if dropzone --
Zell: Everything else we required a dropzone. So, let's require a return.
Jason: Got it. So, if not dropzone, will return. If I can spell.
Jason: Okay. I'm with you.
Jason: Okay. So, then, if I did this right, what we should see is when I move this element over the dropzone, our clone should -- we're to the removing it yet -- but our clone should appear in the dropzone.
Jason: Okay. Do I need to refresh the page?
Zell: Refresh the page.
Jason: There it is. Nice. And, when I dropped -- wait. Okay. I just put some pieces together. So, because of the way that we did this and up, where we put the item after the clone, we don't have to track where the item goes. We just have to track where the clone goes.
Jason: Oh, that is slick. Okay. Okay. Perfect.
Zell: And you can move the other two items down which works.
Jason: And if I move the other item, not the same hit test, it works.
Zell: Yeah, because we're doing closest.
Jason: Oh, my goodness.
Zell: Just so we have a bit more fun, if you go back into the HTML and set the pickzone to have the dropzone as well. You can drop on to the same zone.
Jason: Look at that. That is really cool. Now we can move things back and forth. So, this is really, really nice. I love that. I'm super-excited about this.
Zell: The only problem right now is we're always appending it to the end.
Zell: We're going to fix that one. The one that is the most complicated part of the code so far.
Jason: Okay. Just to time check. We have 12 minutes left. Is that enough time to do the reordering?
Zell: We probably need 10 minutes, I guess.
Jason: Might have to rush through it. Let's blaze. What's next?
Zell: First, check if the clone is in the dropzone or not first. So, if the clone is not in the dropzone, we want to append the clone to the dropzone, make sense?
Zell: You want to do anything to it.
Jason: You have the dropzone, if there's no dropzone, we return. So, here, we want to say if dropzone.-- what am I doing? Contains?
Zell: If done.closest -- data-dropzone is dropzone -- so, triple equals.
Jason: Dropzone. Okay.
Zell: Dropzone.clone --
Jason: Oh, dropzone.append.
Zell: And then return here so nothing goes beyond this point.
Zell: Next we need to find the possible positions to put the clone in. And the easiest way to find that is to get a list of the dropzone children.
Jason: Okay. So, we'll say dropzone children equals dropzone.querySelecter all.
Zell: Dot children, that will be fine.
Jason: Oh, dot children. Just like this.
Zell: Just like this, correct. I will turn it into an array because I need to do a for each later. Okay. Cool. At this point what we're going to do is for each of the dropzone children, we want to look through it.
Zell: So, if the hit test is the clone, we don't want to do anything. We don't need to do anything. If hit test is clone, we return. We skip.
Zell: Then for the rest of the elements. Why do I have an element? It's not an element here. I need to talk through this a little bit. I can't remember why I'm writing the code this way. At this point we want to check on the opportunity index -- wait. Do I need to check -- I might be blazing off a little bit too fast. By that time. Okay. That's the step. Otherwise it will not make sense.
Jason: Oh, data dropzone has a typo. Yes, it does. Good call. Okay.
Zell: Dropzone -- I'm testing against -- the reason why I'm doing this, I'm testing against each of these elements and seeing if I'm hovering on to them. Which is why hitTest is clone. If hitTest is clone, we don't do anything. But if it isn't, we do something. We need to check every element to make sure that we are actually hovering on to something.
Jason: So, it hitTest equals child --
Zell: Then we do something.
Jason: We would need to swap the indexes, right?
Zell: So, let's just do it this way first. I'm going to show you some magic on this one.
Zell: You can try child.after clone.
Zell: And then go back, refresh, and drag item one. Drag only item one --
Jason: Wait, what did I mess up here? What did I do wrong? I missed... I missed something.
Zell: All right. Let's just chill and crash a little bit.
Jason: In clone.closest dropzone equals dropzone, dropzone.append clone.
Zell: Not equals to dropzone.
Jason: Not equals dropzone.
Jason: There we go. Okay. So, I got the one. Here.
Zell: Okay. Just put all them -- it's in one direction, but not in the other direction. You drag item 1 on to item 2.
Jason: Oh, I see. I see. So, it works here. Got it. Okay.
Zell: All right. So, that means -- this means that index of the dropzone with reference to the index of the element is important. So, we need to find the current index of the dropzone. The current index of the clone
Jason: Okay. So, would that be up above? We would need -- or, I guess const cloneIndex is like -- wait?
Zell: Dropzone --
Jason: Do I want this outside of the loop?
Zell: You can do it inside, it's fine. But let's do it inside the child condition. No? Because we do not want it so far up.
Jason: Okay. What I'm doing here is saying findIndex where c equals clone, right?
Jason: And this one can live out here, right?
Zell: It can live out there, that's fine.
Zell: And then we need the current element index. The one that -- the hitTest -- where the hitTest is the child.
Jason: Equals -- I'm just going to copy this. And instead of clone, we'll check child. Okay. So, now we have a clone index and current element index.
Zell: I'll put the current element index ideas the second conditional.
Jason: Inside here?
Zell: Yeah. Because we do not need that all the time. We just need it when we need it.
Jason: Right. Okay.
Zell: So, if the clone index is less than the current index... we do after which is what we are doing here.
Jason: Okay. So, if clone index is less than the --
Zell: Current element index --
Jason: The currentElementIndex, okay. Then we'll do this. And the chat brings up a good point that we don't even need this actually because we can just get index like this.
Zell: Yeah, yeah.
Jason: We can just drop that out and do index. Thank you for the heads up, Eco. And then here then we would return. And otherwise, we would do child before?
Zell: We can do that else.
Jason: Oh, do else.
Zell: Do and then child before, correct.
Jason: Okay. Clone. Oh, boy. All right. Is that gonna work? Are we good?
Zell: Now try moving things around.
Jason: Ha, ha, ha, beautiful. Okay. And then... yeah. Okay. Look at that! We did it, y'all! And with 4 minutes to spare. So, I want to make sure that we have time to mention a couple things. You mentioned you are also teaching this. Is it a workshop a course?
Zell: A course.
Jason: How would somebody find that?
Zell: You could go to my blog. I'm going to put it in the chat.
Jason: This one here or that --
Zell: Yep, that one.
Zell: I guess -- I had fun talking about this.
Jason: I'm glad you had fun. I had an absolute blast. That being said, we're going to call this one done. Chat, stay tuned. We're going to raid. Zell, thank you for hanging out with us and we will see you next time.