In the last article Native Draggable, I explained how to create a draggable interaction using native JavaScript. We learned how to write a well structured class and the logic behind draggable interactions. Next we are going to extend this class to give an option in the case we would like a draggable item confined around a container.
The End Result
New WindowContainer Math
The math in this case is very simple. On mouse move, calculate the position (see Native Draggable: Draggable Math). If the new box top position is less than the container top position, make the box top position the container top position. If the new box left position is less than the container left position, make the box left position the container left position. If the new box bottom position is greater than the container bottom position, make the box bottom position the container bottom position. If the new box right position is greater than the container right position, make the box right position the container right position.

With that said, I guess the only thing we need is to calculate the right and bottom position, which we can do by adding the width and height to the left and top position of the box. You will find that my _getCoordinates() method will do these calculations, but for the sake of finishing the idea I'll spell out the calculations.
//calculating the bottom right
box.bottom = box.top + height; //150 = 50 + 100
box.right = box.left + width; //400 = 100 + 300
//container logic
if(box.left < container.left) {
box.left = container.left;
} else if(box.right > container.right) {
box.right = container.right;
}
if(box.top < container.top) {
box.top = container.top;
} else if(box.bottom > container.bottom) {
box.bottom = container.bottom;
}
Building the Class
So now that we understand the container math we can now build out the class. For more information on OOP JS and why I build my classes like this please read OOP JavaScript: Design Reusable Code. This class will extend our original class as explained in Native Draggable
(function() {
var c = draggable, p = c.prototype;
/* Constants
-------------------------------*/
/* Public Properties
-------------------------------*/
/* Private Properties
-------------------------------*/
var _parentConstruct = p.__construct;
var _parentGetPosition = p.getPosition;
/* Get
-------------------------------*/
/* Magic
-------------------------------*/
p.__construct = function (target, options) {};
/* Public Methods
-------------------------------*/
p.getPosition = function(e) {};
/* Private Methods
-------------------------------*/
})();
The main thing here is how we retrieve the parent methods for later usage.
Constructor
In our construct we want to initialize all the variables we will be using to make the container work with the drag.
p.__construct = function (target, options) {
//call parent construct
_parentConstruct.call(this, target, options);
options = options || {};
//by default set the container to false
this.container = options.container;
};
This is our first example where we call one of the parent methods we saved earlier.
Get Position
This is where we will put the math logic and add the calculations for the container.
p.getPosition = function(e) {
var position = _parentGetPosition.call(this, e);
if(!this.container) {
return position;
}
var size = _getSize(this.target),
right = position.left + size.width,
bottom = position.top + size.height;
coordinates = _getCoordinates(this.container);
if(position.left < coordinates[0].left) {
position.left = coordinates[0].left;
} else if(right > coordinates[1].left) {
position.left -= right - coordinates[1].left;
}
if(position.top < coordinates[0].top) {
position.top = coordinates[0].top;
} else if(bottom > coordinates[1].top) {
position.top -= bottom - coordinates[1].top;
}
return position;
};
Private Methods
What I normally define as private methods are methods that either have no direct relation to the purpose of the class or methods that cannot be usable separately; that should not be used by anything other than the class.
/**
* Returns the absolute top left and bottom right
* of the node's position
*
* @param el
* @return array
*/
var _getCoordinates = function(el, relative) {
var position = _getPosition(el, relative),
size = _getSize(el);
return [{left: position.left, top: position.top}, {
top:position.top+size.height, left:position.left+size.width}];
};
/**
* Returns the offset top left and bottom right
* of the node's position
*
* @param el
* @param bool
* @return array
*/
var _getPosition = function(el, relative) {
var size = _getSize(el),
windowSize = _getWindowSize(),
curleft = 0,
curtop = 0,
i = 0;
do {
if(i > 0 && relative &&
el.style.position == 'relative') {
break;
}
curleft += el.offsetLeft;
curtop += el.offsetTop;
i++;
} while (el = el.offsetParent);
return {
left: curleft, top: curtop,
right: windowSize.width-(curleft+size.width),
bottom: windowSize.height-(curtop+size.height)};
};
/**
* Returns the absolute size of the node
*
* @param el
* @return array
*/
var _getSize = function(el) {
var display = el.style.display, originalWidth, originalHeight;
if (display != 'none' && display !== null) {
// Safari bug
originalHeight = el.offsetHeight;
originalWidth = el.offsetWidth;
if(originalWidth && originalHeight) {
return {width: originalWidth, height: originalHeight};
}
return {width: el.clientWidth, height: el.clientHeight};
}
// All *Width *Height properties give 0 on elements with display none,
// so enable the node temporarily
var originalVisibility = el.style.visibility;
var originalPosition = el.style.position;
var originalDisplay = el.style.display;
el.style.visibility = 'visible';
el.style.position = 'absolute';
el.style.display = 'block';
originalHeight = el.clientHeight;
originalWidth = el.clientWidth;
el.style.visibility = originalVisibility;
el.style.position = originalPosition;
el.style.display = originalDisplay;
return {width: originalWidth, height: originalHeight};
};
And that's pretty much it for draggable in a container. See the benefits of OOP JS !?! From this article you should take away how to extend a class. One major reason for not making one large super class is the case where another developer wants to extend the draggable class and not use certain added features or hey, they might not like the way you implemented a feature and want to code their own. It's important to know that the intent for writing this is to learn how to write JavaScript classes and learn the logic behind draggables in a container. One limitation made on purpose is that what if we want to contain using an array of positions. For a more interactive tutorial you should try to see if you can solve for that. Enjoy!
Labels: article


















