pseudocode + diagrams = ui interactions on a silver platter
I mentioned in a previous post how I’d been working on the drag and drop module of thud for about 2 weeks. It was definitely challenging, though now that I’ve done it, I think I could do it again in about 2 or 3 days.
Making something on a page draggable without using the new HTML5 attributes and events is the easy part. What are the tricky parts? Setting the coordinates of the drag image correctly when the drag element is in a positioned element, scrolling correctly and getting the snap to grid correct. It was a little frustrating, but I got there in the end. Thanks to pseudocode. This post is mostly to do with implementing drag functionality. However, it was pseudocode and drawing diagrams that saved me, hence the title.
I first “learnt” (about) pseudocode in high school — I use the term loosely, because I didn’t really learn much in high school, except about how to be a yes man and how everyone in the class was going to fail. I failed at both failing and being a yes man — computer studies class. I didn’t really get it then and thought it was a waste of time, never looking at it again until recently.
However, after failing to figure out some of the major errors I was encountering I needed to get back to basics and so I turned to pen and paper. I didn’t follow the exact pseudocode syntax, I don’t think you really need to. As long as you understand what is going on and can use what you write in your program. Mine is my own special recipe of pseudocode and JavaScript.
I thought I would add post about it, in case anyone else finds it useful.
Finding and setting the drag image’s current left and top positions
The logic for finding and setting the drag image’s current left and top position also includes the logic to snap the drag image to a grid, constrain it to specific dimensions, keep it a specific amount of pixels from the mouse pointer (offset) and/ or limit it to been dragged horizontally or vertically.
Glossary
I’ve put this at the top so it’s easier to see while reading the rest. Assuming you’re lucky enough to have a big monitor.
| property | description |
|---|---|
| x | The position the mouse cursor is at, relative to the page. i.e. the event’s pageX value. |
| coordinates | The bottom, height, left, offsetLeft, offsetTop, right, scrollHeight, scrollLeft, scrollTop, scrollWidth, top & width properties of an html element or the document. |
| orientation | A value, either horizontal, vertical or null, and if set is used to limit the drag_image to only moving in either direction but not both. |
| drag_element | The element we want to drag. |
| drag_image | The element that will be following the cursor while the drag operation is taking place. This could be the drag_element itself, a clone of the drag_element or a completely bespoke element. |
| scroll_element | The element to scroll if scrolling is allowed and the drag_image is at the boundary of the viewable_area. |
| viewable_area | The coordinates of the area currently visible in the scroll_element (this will generally be the document). |
| snap | As in snap to grid. The number of pixels to snap the drag_image to. If this is less than 2 it will be ignored. |
| grid | A predefined array of all the values the drag_image should snap to within the boundaries of the scroll_element. This is worked out as:( new Array( Math.ceil( scroll_element.scrollWidth / snap ) + 2 ) ).map( function( value, index ) { return index * snap; } );e.g. If the scrollWidth was 1024px and the snap is 16px, the grid array would be: [16, 32, 48, …, 1024, 1040] The reason the final grid value is larger than the scroll_element’s boundary is that we want to be able to position the drag_image at the scroll_element’s boundary. To figure out the vertical grid, we use scrollHeight instead of scrollWidth. |
| constrain_coordinates | the bottom, left, right, & top coordinates to limit the drag_image’s movements within. |
| offset | the number of pixels to keep the drag_image from the mouse pointer. |
| scrollX | the amount of pixels the viewable_area was scrolled left or right. |
| aggregateOffsetLeft | This is worked out as:var aggregateOffsetLeft = 0, el = drag_element; |
| aggregateOffsetTop | This is worked out as above, except instead of offsetLeft we aggregate offsetTop. |
Finding and setting the drag image’s left (x) position
IF orientation != vertical // if we are only allowing vertical movement, don't worry about calculating the horizontal
IF viewable_area WAS SCROLLED BY AMOUNT scrollX // check to see if the scrolling function has scrolled horizontally
viewable_area.left += scrollX // if so we need to adjust the viewable_area properties to reflect this
viewable_area.right += scrollX
x = x - drag_image.aggregateOffsetLeft // we don't want ot use the -= operator here as it will change the original x value
IF x >= viewable_area.right // this makes sure the drag_image is always visible to the user dragging it
IF viewable_area.right // 20 is the standard vertical scroll bar width
ELSE
x = scroll_element.scrollWidth - drag_image.width - |offset| // |offset| if you don't know is the absolute value of offset so |8| == |-8| == 8
ELSE IF x // closestGridPosition returns the number in the grid array that x is between.
// e.g. if snap == 16px and x is between 16 and 32 it will return 16,
// if x is between 32 and 48 it will return 32, etc.
IF CONSTRAIN TO COORDINATES
IF constrain_coordinates.left && x constrain_coordinates.right
x = constrain_coordinates.right
drag_image.left = x + offset
On line 5, we have the following code x = x - drag_image.aggregateOffsetLeft. This was a tricky part, it took me about 2 days to realise I was using the wrong property here.
As I mentioned before making something draggable is easy. In it’s simplest form you can set the left and top position of the drag_image to the mousemove event’s pageX and pageY values. Consider the following diagram:

If our drag_element is a child — and not a descendant of a positioned element — of the body tag and you don’t need to scroll the page, then setting the drag_image’s left and top styles to the event’s pageX and pageY values is ok. However, consider the next diagram:

Basically, the element we want to drag is a child of positioned_element in the diagram. Its left and top values are relative to this element. So setting the left and top values to the event’s pageX and pageY values, would position the drag_image in the wrong location on the page, as its position is relative to positioned_element, NOT the viewport.
If we want to drag the drag_image out of the positioned_element, then we need a way of figuring out the drag_image’s left and top values, relative to positioned_element.
In the example above, positioned_element is 100px from the left and top of the document, the drag_image is currently 100px from the left and top of the positioned_element and we want to drag the drag_image to 10px from the left and top of the document, the new coordinates for the drag_image would need to be set to -90px from the left and top of positioned_element.
This is worked out as, 10px from the left of the viewport minus the positioned_element’s 100px from the left of the viewport, equals -90px.
Finding and setting the drag image’s top (y) position
I was going to put the pseudocode for this in, but it’s exactly the same as the above, only we replace the left (x) properties, with the corresponding top (y) properties:
| left (x) property | top (y) property |
|---|---|
| x | y |
| vertical | horizontal |
| scrollX | scrollY |
| viewable_area.left | viewable_area.top |
| viewable_area.right | viewable_area.bottom |
| drag_image.height | drag_image.width |
| drag_image.left | drag_image.top |
| drag_image.aggregateOffsetLeft | drag_image.aggregateOffsetTop |
| scroll_element.scrollWidth | scroll_element.scrollHeight |
| constrain_coordinates.left | constrain_coordinates.top |
| constrain_coordinates.right | constrain_coordinates.bottom |
Scrolling when the drag image is at a boundary point
The tricky part here is working the scroll sensitivity into the equation. We could adjust the viewable_area boundary of the scroll_element to incorporate the scroll_sensitivity, so that when the drag_image reaches this new boundary we can start scrolling.
However, as the viewable area will then change we will need to readjust the boundary each time we scroll. We need to create a boundary — I like to think of it as a force field — around the drag_image. The easiest way to envision this, I’ve found, is using the following diagram:

Using this method, we can create a boundary — force field — around the drag_image the size of the scroll_sensitivity amount. When this drag_boundary’s bottom, left, right, top — or a combination of those — is greater — or less than, depending on the direction of the drag — the boundary of the viewable_area we can initiate scrolling in the specific direction. Figuring out the drag_boundary is defined in the glossary below, so I won’t repeat it here.
Glossary
Apart from what’s already above, we also have these properties:
| property | description |
|---|---|
| scroll_sensitivity | The distance from the scroll_element’s boundary points to initiate scrolling. |
| drag_boundary | This is an object representing the drag_image dimensions plus the scroll_sensitivity. This is worked out as:
|
| scroll_amount | The amount in pixels to scroll by if scrolling is required. |
| scrollX | If scrolling is required, then this will be set to either positive scroll_amount (to scroll down/ right) or negative scroll_amount (to scroll up/ left). |
Scrolling horizontally
IF orientation != vertical
IF drag_boundary.right > viewable_area.right && drag_boundary.right scroll_element.scrollLeft + scroll_sensitivity
scrollX = scroll_amount
ELSE IF drag_boundary.left = 0
scrollX = -scroll_amount
IF scrollX IS A NUMBER
scroll_element.scrollLeft += scrollX
Scrolling vertically
Again, apart from what is already above, we swap the corresponding properties:
| left (x) property | top (y) property |
|---|---|
| drag_boundary.left | drag_boundary.top |
| drag_boundary.right | drag_boundary.bottom |
| scroll_element.scrollLeft | scroll_element.scrollTop |
Conclusion
Using pseudocode and diagrams, we can clearly visualise user interface interactions and this in turn will help us write clean and efficient code.
Obviously, project time constraints are an issue and a lot of development teams are pushed to get something live rather than something — of quality — that works. This type of careful planning often takes a back seat to showing working code for your next sprint so you can rush through the next chunk of work.
Having the opportunity to take time off, to focus on development with an emphasis on quality is an amazing experience. It’s going to be hard if I have to go back!