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.

Friday, February 27, 2009

AJAX requests class

The script I'm going to demonstrate is an object that creates, sends and handles AJAX requests easily. You will be able to send requests with a simple line of code, and those requests can be used to load virtually unlimited content into your site. You don't need any advanced javascript knowledge to use it, as I've made it as simple as it can be. First we create the object constructor:
//The parameters passed to the function are as fallows:
//url - the url of the script that we wanna request. You can use either full or relative to your server urls.
//elmnts - this is either the id of the element that will contain the response from our request, or can also be a whole array of elements
//loadingMsg - if this parameter is set, after the request has been send, the content of the changed element(s) will be set to whatever this variable is set, i.e. "Loading! Please wait...".
function ajaxObj(url, elmnts, loadingMsg)
{
 this.obj = new Object();
 
 this.url = url;
 
 this.loadInto = elmnts;
 
 if(loadingMsg) //if we have set a loading message here it will be put into the changed elemnt(s)
 {
  if(typeof(this.loadInto) != 'object') //if we wanna change just one element, simply do it using it's id
       document.getElementById(this.loadInto).innerHTML.innerHTML = loadingMsg;
    else //or if the 'elmnts' parameter is an array - change all the elements of the array
      {
       for(i in this.loadInto)
       {
        document.getElementById(this.loadInto[i]).innerHTML = loadingMsg;
       }
      }
 }
 
 //This prototype is used to create our request, send it and handle it
 this.init();
} 
Now, the prototype that creates the XMLHttpRequest that will be used for our script.
//This function tries if different objects are available, untill it finds one that works, cause all the major browsers use different techniques to send the request
ajaxObj.prototype.create = function()
{
 try
   {
    xmlHttp = new XMLHttpRequest();
   }
   catch(e)
   {
    try
    {
     xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
    }
    catch(e)
    {
     try
       {
        xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
       }
     catch(e)
      {
       return false;
       }
  }
   } 
 
 this.obj = xmlHttp;
}
Now that we have the request object, we need to make sure it will be handled properly before sending it:
ajaxObj.prototype.handle = function()
{
 var o = this.obj;
 var into = this.loadInto;
 
 o.onreadystatechange = function() //Set an event handler that is triggered everytime the readystate of the object has changed
   {
    if(o.readyState == 4) //If the readyState is 4, the request has been completed - we can proceed with using the response
      {
       if(typeof(into) != 'object') //if we want to change just one element - set it's innerHTML equal to the response 
        document.getElementById(into).innerHTML = o.responseText;
       else //otherwise - we have more than one elements to change. We must first split the data that is returned into parts for each one of the elements and then update them
       {
        temp = o.responseText.split("@@");
        
        for(i in into)
        {
         document.getElementById(into[i]).innerHTML = temp[i];
        }
       }
      }
 }
}
The important thing about the above prototype is that if we want to change more than one element, we need to put delimeters in the response. The delimeter I've chosen is "@@", as this symbols are very rarely used in the content of a web page. We have a request, we can handle it properly, the only thing left is to send it:
//This prototype simply sends the request to the desired url
ajaxObj.prototype.send = function()
{
   this.obj.open('GET', this.url, true);
   this.obj.send(null);
}
There's just one more prototype that's left, the one that initializes the object:
//This prototype calls all the other ones in the proper order. It's not really necessary, cause we can just call 
//this prototypes from within the object constructor, but I use it, cause I'm planning to extend this object in the future. 
ajaxObj.prototype.init = function()
{
 this.obj = null;
 
 this.create();
 
 this.handle();
 
 this.send();
}
To create a new request you just need to create a new ajaxObj object. Here is an example:
new ajaxObj('myscript.php', 'myTestElement', 'Loading! Please wait...');
This line of code will send a request to "myscript.php" and will put the response into and element on the page with the id "myTestElement". While the request has been processed "myTestElement" will contain the text "Loading! Please wait...". You can have anything in the .php file that is being requested, i.e. something extracted from a database/read from a file and formatted in HTML. There are two important things to remember when using the object - to use the delimeter "@@" when you send a request for changing more than one element and change only elements that have the innerHTML property (i.e. div, span, td), cause the script will not work with, say < input> elements.
This is pretty much the simplest way of dynamically loading content within a web page, yet it can be very usefull even for big and complicated pages. However, it is far from being good enough to meet everyone's needs. I will extend it in time, while keeping things simple and understandable.
You can see an example page here and the file that contains the object can be found here.

Javascript mouse position capture

Capturing mouse position on the screen is often needed in creating modern web pages, with better functionality. Here's a small script that captures the mouse position on every mouse move and stores it in a couple of global variables that can be used from anywhere within the page.
//Add an event listener for mouse move, depending on client browser. 
if(document.layers) 
{ 
 document.captureEvents(Event.MOUSEMOVE);
 document.onmousemove = captureMousePosition;
} 
else if(document.all) 
 document.onmousemove = captureMousePosition;
else if(document.getElementById)  
 document.onmousemove = captureMousePosition;

//Declare the variables that will store the coordinates
var xMousePos = 0; //abscissa
var yMousePos = 0; //ordinate

//Capture the position, again depending on client's browser
function captureMousePosition(e) 
{
 if(document.layers) 
 {
  xMousePos = e.pageX;
      yMousePos = e.pageY;
   } 
   else if(document.all) 
   {
  xMousePos = window.event.x+document.body.scrollLeft;
      yMousePos = window.event.y+document.body.scrollTop;
   } 
   else if(document.getElementById) 
   {
  xMousePos = e.pageX;
      yMousePos = e.pageY;
   }
}
This simple script allows us to know where the mouse is and do a lot of stuff according to it, most commonly - move elements around or knowing exactly where the user has clicked. I use this script oftenly and it will mantioned quite often from now on. Actually, I've already used it in some of the old scripts, but decided to separate it and make it available for you on http://enikoloff.info/scripts/mousePos.js You can see an example page here.