Saturday, February 28, 2009

Javascript movable custom popup(hint) / alert window

Often we want to display additional information to the users when they do something particular on our pages. A good example of that is displaying a help/hint window when the user places the mouse over some element on the page. Both javascript and HTML have some innate means of doing this, but they are often inconvenient, i.e. javascript's alert() which takes the control of the page until closed. This is often undesired as we might want the user to see the alert message, but still be able to work with the page. I've made a small object that creates 'windows' that can be used for all the purposes mentioned above. You can create as many windows as you like, and they can also be moved within the page for users' convenience. I will start with the main function that creates the object:
function msgWin(id, title, msg, buttonText, parent, xPos, yPos)
{
 this.id = id; //ID of the html element (div) that will contain our window. This is used within the object, and can also be used for some custom changes outside the object 
 this.title = title; //Title of the window
 this.msg = msg; //Message that will be displayed within the window
 this.parent = parent; //Parent element. This is the id of the HTML element to which we want the window to be attached. If it's left blank, the window will be attached to the body of the page 
 this.left = xPos; //Abscissa (property left) in pixels of the window. If this is not set the window will be positioned in the center of the screen
 this.top = yPos; //Ordinate (property top) in pixels of the window
 
 this.buttonText = buttonText; //text of the button that closes the window
 
 //The fallowing are used within the object to handle moving of the window on the page
 this.moving = 0;
 this.offsetLeft = 0;
 
 this.create(); //Constructor of the object.
}
Now lets see how the window is created:
msgWin.prototype.create = function()
{
 //Create the main element that will contain the window.
 var w = document.createElement('div');
 w.id = this.id;
 w.style.width = '400px';
 w.style.border = '2px solid #000';
 w.style.position = 'absolute';
 
 if(!this.left) //If we haven't set coordinates for the object, the window will be centered in the middle of the page 
 {
  w.style.left = '50%';
  w.style.top = '50%';
  w.style.marginLeft = '-200px';
  w.style.marginTop = '-50px';
 }
 else //We have set coordinates, we shall position the element according to them
 {
  w.style.left = this.left+'px';
  w.style.top = this.top+'px';
 }
 
 //Create the window title. The essential thing here is that we set an id and some event hanlers that will allow us to update the content and movement of the window
 var t = document.createElement('div');
 t.id = this.id+'_title';
 t.style.borderBottom = '1px dashed #000';
 t.style.textAlign = 'center';
 t.style.fontWeight = 'bold';
 t.style.fontSize = '16px';
 t.innerHTML = this.title;
 t.style.background = '#628db4';
 t.onmouseover = function() {this.style.cursor = 'move';};
 //The fallowing event handlers are set in order to move the element on the page. The properties startMove and stopMoving will be explained later.
 t.onmousedown = methodize(this.startMove, this);
 t.onmouseup = methodize(this.stopMoving, this);
 
 w.appendChild(t); //Append the title to the window.
 
 //Create the window content element. The only interesting thing here is that we set an ID so we can update the content later. 
 var c = document.createElement('div');
 c.id = this.id+'_content';
 c.style.minHeight = '75px';
 c.style.fontSize = '13px';
 c.style.padding = '3px';
 c.innerHTML = this.msg;
 c.style.background = '#dfe3e8'; 
 
 w.appendChild(c);
 
 //Create a dummy div that will allow us to position the close button without interfering with the other layout of the window
 var tmp = document.createElement('div');
 tmp.style.background = '#dfe3e8';
 tmp.style.textAlign = 'center';
 tmp.style.padding = '3px 0 3px 0';
 
 //Now create the close button.
 var b = document.createElement('input');
 b.type = 'button';
 b.value = this.buttonText;
 b.style.width = '100px';
 b.style.border = '1px solid #000';
 b.onmouseover = function() {this.style.cursor = 'pointer';};
 //Add event listener for clicks that will change the display property of the window to 'none', that hides it, so it looks closed  
 b.onclick = function() 
 {
  //The interesting thing here is that we don't need to create another prototype to the msgWin object and use methodize() or any other method to close the window.
  //We have access to the window from the close button itself without depending on an ID or anything else that we used on the other event handlers.
  this.parentNode.parentNode.style.display = 'none';
 };
 
 tmp.appendChild(b); //Append the button to the dummy div..
 
 w.appendChild(tmp); //..and then append them both to the window.
 
 if(this.parent) //If we have set a parent element to contain the window - append the window to it 
  document.getElementById(this.parent).appendChild(w);
 else //otherwise - append it to the body of our page
  document.body.appendChild(w);
 
 return;
}
Now that we have the window created we need to create the prototypes of the object that will handle moving it on the page. We will start with the function that inits the movement:
msgWin.prototype.startMove = function()
{
 this.moving = 1; //raise a flag that tells us that the window is being moved 
 
 //This is a tricky part. In order to move the window smoothly, without interruption, we need to do it on every mouse move, even outside of the window (its parent element),
 //because if we track mouse movement only within it, when the user moves the mouse quickly, it will not trigger every mousemove event and move the window, but the mouse will
 //go outside of the window and it will stop moving.
 //WARNING!!! The fallowing couple of lines will set two event listeners to the parent element of the window. This will result in overwriting the original ones (if any). 
 //If you have set any other event handlers on that element, their functionality will be lost, which may result in failure or strange behavior of another function on your 
 //page! If you don't understand exactly what this code does be careful when using this object. I advice you to test it very carefully and add this object to your page 
 //only when you are sure it will not brake something else! If you have some issues, you can contact me and I will try to help you with a solution to your particular 
 //problem. 
 document.getElementById(this.id).parentNode.onmousemove = methodize(this.stopMove, this);
 document.getElementById(this.id).parentNode.onmouseup = methodize(this.stopMove, this); //add the same handler on mouse up, just to be sure it will be executed properly 
 
 var el = document.getElementById(this.id);
 
 el.style.zIndex = 2; //Move the window in front of the other elements on the page (including another windows created with this object)
 
 //Calculate the offset of the mouse from the left border of the window. 
 if(el.style.left.indexOf("%") != -1) //If the window has been positioned in the middle of the page, we need to convert the % to pixel value.
  var l = screen.width/2+parseInt(el.style.marginLeft);
 else //otherwise just get the integer value of the position
  var l = parseInt(el.style.left); 
 
 this.offsetLeft = xMousePos - l; //Calculate the offset
 
 return;
}
The next prototype - the one that moves the window on the page:
msgWin.prototype.move = function()
{
 if(!this.moving) //if the flag is not raised we know that the window is not being moved, therefor we must stop the function execution
  return;
 
 var el = document.getElementById(this.id);
 
 //Unset any margins set to the element and move it to it's new location, minding the offset of the mouse from it's left border
 el.style.margin = '0';
 el.style.left = (xMousePos - this.offsetLeft)+"px"; //Use the mouse offset that we calculated within startMove, to position the window properly on it's new place
 el.style.top  = (yMousePos - 10)+"px"; //We need to set element's ordinate a bit above the mouse position, cause otherwise the mouse will always be outside the window and it will be impossible to stop window's movent.
 
 return;
}
Finally, we've got to the last prototype that stops the movement of the element:
msgWin.prototype.stopMoving = function()
{
 this.moving = 0; //Unset the flag, so the next time move() is called it will not change the position of the window.
 
 document.getElementById(this.id).style.zIndex = 1; //Set the z-index of the window back to a lower value, but still a positive one, cause this way it will be drawed over the other page elements, which is generally the desired position.
 
 return;
}
This is it. The code above gives us a pretty convenient way to alert the user for something or just show him a message, without interrupting whatever he is doing. There are several different ways of using this object, depending on where you want it to pop up. If you want to use it as an alert to the user, use something like this:
new msgWin('myId', 'Alert', 'Message', 'Close');
This will create a window in the center of the screen, with title "Alert" and content "Message". It is very useful instead of javascrpit's alert(), cause the user can keep the window open (probably move it) and still be able to work with the rest of the page.
Another convenient place to pop it up is at the mouse position. This is easily done using my mouse position capturing script. You should add this script to your page and then create the window as fallows:
new msgWin('myId', 'Popup', 'Message', 'Close', '', xMousePos, yMousePos);
This will create the window at the mouse position, which is usable for hints and help messages.
You can also show it at a fixed location with a code like this:
new msgWin('myId', 'Popup', 'Message', 'Close', '', 400, 200);
This will draw the window on the 400th pixel from the left of the screen and the 200th from it's top.
No matter where you show the window, don't forget that it can be moved by the user and can also be closed. "Closing" a window actually just hides it, so if you want to show it again, you can do it without creating it from scratch. If you have a window, created with the code:
var myMsgWin = new msgWin(...);
and the user closes it, you can later show it again by calling it's prototype show():
myMsgWin.show();
Experiment with the object as it's functionality can be used in a lot of different ways and occasions. Remember to use it with caution when appending it to elements that already have event listeners for mouse move and mouse up, cause it overwrites them!
Here you can see an example page for creating several windows with the object.

11 comments:

Jason said...

Really great work. The comments throughout the code are very helpful.

I have been trying to learn how to add input fields (textbox, dropdownlist) into the popup window and then transfer inputted user data from those input fields to VB varibles...any chance you might be able to do a follow up that shows this?

Any help would be greatly appreciated. Thanks.

Nikoloff said...

Hi! Thanks for the comment :)
Maybe I will do a fallow up, but I must warn you that it will be in javascript. VBscript is MS-only technology and the usability is much lower... Anyway, placing input fields, buttons and pretty much every HTML element inside the window and then using them outside the window is possible. I will try to find the time to write about this soon :)

Jason said...

Nikoloff,

I'm glad to hear you are considering a follow up and in fact I would enjoy that it would be in javascript. I'm a fairly new web designer and unfortunately I started using VB to connect to databases and such. I got lazy and began using it for everything I could and have come to realize the lack of usability you spoke of.

I'm pretty sure you would agree that placing input fields in your popups would be fairly simple, but I'm not sure how to pass the information to things such as a database or smtp email. For example, if I wanted to create a "request quote" form as a popup...

Thanks again and I'll keep an eye out for any follow ups you do.

JD said...

Hey Nickoloff.. i would like to know as to how i can call this method from within the HTML code.. so that an alert message is displayed in the center and on click of the button in the alert message the alert message disappears...

Nikoloff said...

Hi JD,
the solution to your problem is actually on my demo page, but here's some clarification. To create a window add an event listener (onclick I guess), on any element on the page, with this code:
new msgWin('myMsgWin'+Math.random(), 'Title', 'Message', 'Close');
Every time you click the button a window will be drawn in the center of the screen. Note that you must also include in your <head> section two of my scripts:
<script type='text/javascript' src='http://enikoloff.info/scripts/mousePos.js'></script>
<script type='text/javascript' src='http://enikoloff.info/scripts/msgWin.js'></script>

JD said...

Hey Nickoloff.

I tried using ur code but it didnt work...
I am calling the function on the onclick event of a button.

like this

input type='button' value='ALert' onClick="new msgWin('myMsgWin'+Math.random(), 'Alert', 'This is ur alert message', 'Close')"

but still the alert message box does not appear.

I have included the js files as well as told by u...

it shows methodize not defined
t.onmousedown = methodize(this.startMove, this);

i commented these 2 lines but still no alert message was displayed...

I want a customized alert message which appears at the center of the page.. just like the usual javascript alert message... and i also want to knw that can i call this function from any other javascript files.. if yes then how??..


I tried this way as well
var myMsgWin = new msgWin(...);
myMsgWin.show();

but it told that show() method not defined...

Please help...

thanks in advance..

Nikoloff said...

From what you are telling me I think that something is wrong with file inclusion... The function methodize() is the first thing defined in http://enikoloff.info/scripts/msgWin.js , so if the browser is saying it is not defined, this means the whole file is not included. Check again how and where you include the two scripts!

Once included properly you can call this scripts from anywhere on the page, including other scripts that are in other files.

Maybe if I see the page that you want to add the script to I can see what is wrong.

JD said...

Here is how i m doing it..

html
head
script language="javascript" type="text/javascript" src="CustomizedPopUp.js"
/script
/head
body

input type='button' value='ALert' onClick="new msgWin('myMsgWin'+Math.random(), 'Alert', 'This is ur alert message', 'Close')"

/body
/html


i copied the code to a js file.
the js file is in the same folder as the html file.


Now it is showing that this,create()is not a function...

JD said...

Thanks Nickoloff the code is working now...

But one thing that i want to do is that as long as the alert is not closed, the user should not be able to anything else.. just as in case of usual Javascript alert...

deblibran said...

Great Work.. I want to know what has to be done to keep keep usual alert() functionality.User should not be able to do any thing and control will also not go to anywhere till user clicks ok. Say in form tag onSubmit="alert('Hi')" if we put normat alert(), till we click ok control doesn't go anywhere. Can we achieve same thing?

Nikoloff said...

Well, the script in the post wasn't meant to represent a modal window (which the alert() is), but a window that can used for warning/informing the user about something, without stoping him from working with the page. Plus, I consider an advantage being able to show more than one window at a time. The "real" modal window is another thing.

As you might've noticed, I havent posted anything new in quite a while. Heck, I haven't event been able to fix the bug in the last post. I've got a lot of stuff that need to be done and never enought time for this blog... Anyway, I wrote a small example page on what could be done to make this a modal window. You can see it here. Now, at first glance it might look like it is a modal window, but there are some issues. If you click the "do the thing" button, the screen is covered with a semi transparent color, except for the window that is now being displayed in the center. If you click the mouse outside the window - nothing happens - till you click the OK button inside the window. The problem is that if you click TAB enough times you can get to the content of the page - i.e. go inside the two text fields I've inserted and write stuff, you can click buttons, etc.

This issues can be avoided, hopefully soon I'll be able to write a whole example script, merging this example and the custom window code. JD, this answers your question too, sorry for the immense delay with the answer...