Tuesday, April 28, 2009

Javascript custom right click (context) menu

Today I will cover another aspect of creating web pages without using browsers' standard behavior and capabilities. I will show you how to make your own right mouse button menu and use it instead of this, that the browser draws by default. This allows you to adapt this menus according to you site's needs and can be very useful for some projects. To achieve that I use the oncontextmenu event trigger, which may not be familiar for some of you, cause it is rarely used. It catches the right mouse button clicks, just like the onclick event works for left mouse button.

This script will use my mouse position capture script to draw the menus at the proper coordinates.

The object constructor will accept three parameters:
  • id - HTML id of the menu)
  • menuItems - an array containing all the menu items which will be drawn
  • parent - optional parameter which defines the element to which the menu will be applied. If this is not set, document.body will be used.
The second parameter needs further explanation. The keys of the array menuItems will be used as the text, which will be displayed in the menu and the values of that array will be the functions that will be executed, when the corresponding menu item is clicked. Values can be either "function() {...}" code or a predefined function name. Maybe this seems a bit blurry, but read on and you will see how it's used.

Here is the object constructor:
function contextMenu(id, menuItems, parent)
{
 //First we need to make sure that we have the obligatory parameters present
 if(typeof id == 'undefined' || typeof menuItems == 'undefined')
  return;
 
 //Remember menu's id cause we will need it in the object's prototypes
 this.id = id;
 
 //If this menu already exists - call a prototype that destroys it, so it can be created again on it's new place.
 //This is executed when we already have the menu drawn on the page and the user has clicked on a new location
 if(document.getElementById(this.id))
  this.destroy();
 
 
 //Create the main menu item - an UL element. I use some styles to make it look pretty much like every browsers' menu, 
 //which will be more familiar to the user
 var menu = document.createElement('ul');
 menu.id = id;
 menu.style.listStyleType = 'none';
 menu.style.margin = 0;
 menu.style.padding = 0;
 menu.style.position = 'absolute';
 menu.style.left = xMousePos;
 menu.style.top = yMousePos;
 menu.style.border = '1px solid #000;';
 menu.style.background = '#eee';
 menu.onmouseover = function() { this.style.cursor = 'pointer'; }
 menu.oncontextmenu = function() { return false; }
 
 //We have the menu created, now we must append it's items.
 for(k in menuItems)
 {
  //Sometimes we need to divide the menus in sections. To do so, I'm using a keyword - 'separator'. 
  //If this keyword is used in any menu item, it will be drawn as a separating line (hr) between 
  //the elements above and beneath it...
  if(menuItems[k] == "separator")
  {
   var delim = document.createElement('hr');
   delim.style.height = '1px';
   delim.style.width = '90%';
   delim.style.margin = 0;
   delim.style.marginLeft = '5%';
   delim.style.padding = 0;
   
   menu.appendChild(delim);
  }
  else //...otherwise we use the text and code from the menuItems array
  {
   //if this is not a valid function - don't create the menu item
   if(typeof menuItems[k] != "function")
    continue;
   
   var menuItem = document.createElement('li');
   menuItem.style.textAlign = 'left';
   menuItem.style.margin = 0;
   menuItem.style.marginLeft = '5px';
   menuItem.style.marginRight = '5px';
   menuItem.style.padding = 0;
   menuItem.innerHTML = k;
   menuItem.onclick = menuItems[k];
   
   menu.appendChild(menuItem);
  }  
 }
 
 
 //Now we have the menu ready we need to append it to it's parent element. 
 //If the third parameter (parent) is omitted - we use the body of the page.
 //Note that the check 'if(typeof parent == "object")' will also fail if we have provided a parent, 
 //but it is not a valid HTML element, thus the document.body will be used instead.  
 if(typeof parent == "object")
  parent.appendChild(menu);
 else
  document.body.appendChild(menu);
 
 //The last thing we need to do is add an event listener to the parent. 
 //We add an onclick event, so when the user clicks outside the menu it will disappear.
 //WARNING! This code overwrites the parent's onclick event listener (if any)! This means 
 //that if you already have a listener defined, it will stop working! Be careful when using 
 //this menu on elements the already have event listeners and always test carefully when implementing!
 menu.parentNode.onclick = methodize(this.destroy, this);
}
This is just a simple menu and the object has just one prototype, which removes the menu from the page:
//The prototype that removes the menu from the page.
contextMenu.prototype.destroy = function()
{
 document.getElementById(this.id).parentNode.removeChild(document.getElementById(this.id));
}
I will extend this object in the future, allowing customization, submenus and other useful functionality. To use the code we need to append an event listener to the oncontextmenu event to each element where the menu will be displayed. We can create a custom function that defines our menu items and creates the menu:
//This is an example function that creates a custom right-click menu
function createContextMenu(id, parent)
{
 var menuItems = new Array();
 
 //You can define a new function using a "function() {...}" code as the value of the array.
 menuItems["Go Back"] = function() { history.go(-1) };
 //You can add a separator by defining a value "separator" for the array element.
 menuItems[0] = "separator";
 menuItems["Reload"] = function() { location.reload(); };
 //You can also use a predefined function - in this case reload(), which is defined in the code that fallows.
 menuItems["My Own Reload"] = reload;
 menuItems[1] = "separator";
 menuItems["Another Function"] = anotherFunction;
 
 new contextMenu(id, menuItems, parent);
 
 return false;
}

//An example custom function that is called when a menu item is clicked
function reload()
{
 location.reload();
}

//An example custom function that is called when a menu item is clicked
function anotherFunction()
{
 alert('Another Function Code...');
}
This function defines an array, which will be used to draw the menu. Some of the functions inside the menu are created with the code "function() {...}" and others are predefined functions, which are also shown in the code above. Note that the keyword 'separator' is a string, so you can still have a function named separator() and use it within the menu. In order for the menu to be working propery, all values of the array must be valid functions. If you misspell a function name, or set a value that is not a function - the menu item will not be displayed.

Finally, a sample HTML that shows you how to use the object:
<div style='width: 250px; height: 250px; border: 5px solid #333;' oncontextmenu="createContextMenu('myFirstContextMenu', this); return false;">&nbsp;</div>
With this code, a menu will appear at the mouse position when you right click on the div. If you click anywhere outside that div, the page will be working normally. WARNING! When you create a menu with this code it overwrites the parent's onclick event listner (if any)! This means that if you already have a listener defined, it will stop working, which can lead to strange page behavior or brake other scripts' usage! Be careful when using this menu on elements the already have event listeners and always test carefully when implementing!

This is pretty much the simplest menu example. In the future fallow ups to this post I will extend the object to be able to create a menu with sub menus, add the ability to set a custom style for the menu, etc. You can see a demo page here. You can use the script directly from this url.

P.S. I've received a bug report about this script - it isn't working on Internet Explorer! Unfortunately I had no Windows OS around me when I was creating it and haven't tested on IE. I will fix it soon and advice you to wait for the fix before using the script.

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.