While the jQuery UI library is quite powerful, and mighty impressive, its footprint is sometimes a bit too much for the project at hand. The UI library also tends to “take over” your HTML, moving it, adding classes, and sometimes even changing attributes around. This can make styling and customizing your UI elements a bit of a pain, especially when you need a custom solution. This is why sometimes you need a UI solution, using jQuery, but without the jQuery UI. Today we are going to work on a draggable popup.
Here is an example of what we hope to accomplish:
The first thing we will need is some HTML and CSS styling. For the sake of getting the ball rolling, I will give you what we will be working with:
<head>
<title>THIS. IS. JQUERY!</title>
<style type="text/css">
#popup
{
width: 400px;
height:400px;
background-color: #000;
color: #00ff00;
position: absolute;
top: 20px;
left: 100px;
}
</style>
</head>
<body>
<div id="main">
<div id="popup">
This is a popup…
</div>
</div>
<script type="text/javascript" src="jquery-1.5.min.js"></script>
</script>
</body>
</html>
As you can see, we are wasting no time here and we already have jQuery added to our page. Now we can get started with the real fun part, the plugin. I am putting all my JS in a file called jquery.popup.js
, but you are welcome to call the file whatever you like. To start with, we have the basic jQuery plugin structure:
(function($)
{
$.fn.popup = function(params)
{
};
})(jQuery);
As you can see, it’s not that complex. We are just adding a function called popup to the jquery object. The params we are passing in will be an object, which will allow us to pass in params like we normally would with a jquery call.
The first thing we need to do is move the popup from wherever it is to the very bottom of the body. This allows us to correctly gauge where the popup is at all times, without calculating a whole bunch of offsets. To do this, we will be using the detach
function that jQuery provides:
{
$.fn.popup = function(params)
{
/* Move the popup to the bottom of the document. */
var popupElm = this;
popupElm.detach();
$(‘body’).append(popupElm);
};
})(jQuery);
The real neat thing about the detach
function is that it removes the object from the dom, but keeps the jQuery object. This allows us to move the popup very easily. Now that we have our popup positioned in the dom, let’s get this thing moving.
The next thing we will be adding to our plugin is a mousedown
event on our popup. The this
keyword can be used to access the jquery object referenced by the call to popup
, so we will be setting up a variable to reference this
.
In this mousedown
event we need to capture the position of the mouse:
{
$.fn.popup = function(params)
{
var popupElm = this;
handle.mousedown(function(emd){
/* Get the mouses offset when the popup is clicked. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
});
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
};
})(jQuery);
We accomplish this task by getting the offset of the popup in relation to the document, then subtracting it from the mouse position. This gives us the mouse position inside the popup, which we will use to position to popup later on.
The next step in the process is to add a mousemove
event, which will update the position of the popup whenever the user moves the mouse. However, we only want this to happen when you press down. Thankfully we already have a mousedown
event to put this new event inside. So let’s get this new event put together:
{
$.fn.popup = function(params)
{
var popupElm = this;
popupElm.mousedown(function(emd){
/* Get the mouses offset when you click the popup. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
$("body").mousemove(function(emm){
/* Gets the x and y to set the
* popup to, taking into account
* the offset of the mouse position. */
var x = emm.pageX – mxOff;
var y = emm.pageY – myOff;
popupElm.css({
‘left’: x,
‘top’: y
});
});
});
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
};
})(jQuery);
Here we have the needed logic, which will move the popup whenever we move the mouse anywhere in the body. We have to add this even to the body because if you drag the mouse too fast, and it actually leave the popup. This would cause the event to stop firing if we added it to the popup as opposed to the body itself. With this, even if we drag at light-speed, the popup’s position will always get updated.
But wait, how do we stop the dragging? The way it is set up now, once we click, the popup will follow the mouse position forever. Well, all we have to do is unbind the mousemove
event on the mouseup
event:
{
$.fn.popup = function(params)
{
var popupElm = this;
popupElm.mousedown(function(emd){
/* Get the mouses offset when you click the popup. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
$("body").mousemove(function(emm){
/* Gets the x and y to set the
* popup to, taking into account
* the offset of the mouse position. */
var x = emm.pageX – mxOff;
var y = emm.pageY – myOff;
popupElm.css({
‘left’: x,
‘top’: y
});
});
})
.mouseup(function(e){
$(‘body’).unbind(‘mousemove’);
});
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
};
})(jQuery);
Thats it, just a few lines stops the movement event. But there is still something missing. Right now we have a draggable div, but not really a popup per se…
The first step to get the “popup” effect is to show the popup, which we can do with a simple call to show
:
{
$.fn.popup = function(params)
{
var popupElm = this;
popupElm.mousedown(function(emd){
/* Get the mouses offset when you click the popup. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
$("body").mousemove(function(emm){
/* Gets the x and y to set the
* popup to, taking into account
* the offset of the mouse position. */
var x = emm.pageX – mxOff;
var y = emm.pageY – myOff;
popupElm.css({
‘left’: x,
‘top’: y
});
});
})
.mouseup(function(e){
$(‘body’).unbind(‘mousemove’);
});
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
popupElm.show();
};
})(jQuery);
Now comes the tricky part, closing the popup. In this tutorial, we are just going to add a link that closes the popup. We will add this right before we detach the popup. It is actually not too complicated:
{
$.fn.popup = function(params)
{
var popupElm = this;
popupElm.mousedown(function(emd){
/* Get the mouses offset when you click the popup. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
$("body").mousemove(function(emm){
/* Gets the x and y to set the
* popup to, taking into account
* the offset of the mouse position. */
var x = emm.pageX – mxOff;
var y = emm.pageY – myOff;
popupElm.css({
‘left’: x,
‘top’: y
});
});
})
.mouseup(function(e){
$(‘body’).unbind(‘mousemove’);
});
/* Add a close anchor. */
$(‘<a id="popupClose" href="" style="color: #fff;">close</a>’).click(
function(e)
{
e.preventDefault();
$(this).parent().hide();
$(this).remove();
}).appendTo(popupElm);
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
popupElm.show();
};
})(jQuery);
As you can see, the first thing we do is prevent the default functionality of the anchor. Once that is taken care of we close the parent of the anchor, which is going to be the popup in this case. We also must make sure to remove the link when we close the dialog to prevent adding more than one link to the popup. I also added a little inline style to the close anchor that pops it out a bit on our green popup. This can be removed or moved to an external style as you see fit.
Before we wrap up this tutorial, there is one last thing we must account for…text selection. As funny as that sounds, when you drag the dialog around at crazy speeds and directions, you will select body text and unwarranted flickering will occur. If you have a decent sized webpage, this could mean the whole screen will flicker, which we don’t want of course.
To account for this we have to do two things to the document body: add some css and add an attribute. That’s right, an attribute. To start things off we have to create a css class with what may be the strangest css properties I have ever seen:
<head>
<title>THIS. IS. JQUERY!</title>
<style type="text/css">
#popup
{
width: 400px;
height:400px;
background-color: #000;
color: #00ff00;
position: absolute;
top: 20px;
left: 100px;
display: none;
}
.no-select
{
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-o-user-select: none;
user-select: none;
}
</style>
</head>
<body>
<div id="main">
<div id="popup">
<span id="handle">Something</span>
This is a popup…
</div>
</div>
<script type="text/javascript" src="jquery-1.5.min.js"></script>
<script type="text/javascript" src="jquery.popup.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$(‘#popup’).popup();
});
</script>
</body>
</html>
Thats right, five weird “user select” properties. The funny thing is, this prevents users from selecting things in almost every browser. This is the main way we are stopping the crazy selection flicker. The other way is adding an attribute to the body. For convenience, I will just show you all the changes to the plugin:
{
$.fn.popup = function(params)
{
var popupElm = this;
popupElm.mousedown(function(emd){
/* Get the mouses offset when you click the popup. */
var offset = popupElm.offset();
var mxOff = emd.pageX – offset.left;
var myOff = emd.pageY – offset.top;
$("body").mousemove(function(emm){
/* Gets the x and y to set the
* popup to, taking into account
* the offset of the mouse position. */
var x = emm.pageX – mxOff;
var y = emm.pageY – myOff;
popupElm.css({
‘left’: x,
‘top’: y
});
})
.addClass("no-select")
.attr(‘unselectable’, ‘on’);
})
.mouseup(function(e){
$(‘body’)
.unbind(‘mousemove’)
.removeClass("no-select")
.attr(‘unselectable’, ‘off’);
});
/* Add a close anchor. */
$(‘<a id="popupClose" href="" style="color: #fff;">close</a>’).click(
function(e)
{
e.preventDefault();
$(this).parent().hide();
$(this).remove();
}).appendTo(popupElm);
/* Move the popup to the bottom of the document. */
popupElm.detach();
$(‘body’).append(popupElm);
popupElm.show();
};
})(jQuery);
The careful observer will notice the first addition is an addClass
and an attr
call added to the body inside the mousedown
event. This adds the css class and set the attribute we need to stop object selection in just about every browser. The careful observer will also notice the removal of the css class and the disabling of the attribute. This combination will disable selecting when you click on the popup and enable selecting when you release it. This eradicates any “selection flickering” that we have, without disabling object selection all the time.
And this is our popup. We can drag it, show it, hide it, all without nasty flickering. That sounds like a popup to me. Of course you can mold this to your needs. The good news is that its a jQuery plugin, so all we have to do is include the script. Keep an eye out for round two though, where we will be using some parameters to set up a few more features. This concludes this tutorial, but just remember that when you need coding help, all you have to do is Switch On The Code.