Wednesday, August 26, 2009

JavaScript & DHTML Dropdown Animated Menu

Menu’s are the most user friendly form of navigation technique when it comes to grouping a large number of links on your website. I have created an easy to use JavaScript/DHTML Animated Menu script which can be used without any coding required at all. Just include the JavaScript code in the header and use the menu HTML code on your page any number of times. Mouse events for opening and closing the menu’s are automatically assigned to the div tags based upon the class name. The menu rolls down slowly when mouse is rolled over it. The menu drop down speed can be controlled from the JavaScript code.

I have designed the menu with minimal amount of CSS, so you can modify the menu CSS to suit the color and styling of your website. This is how the menu would look like:JavaScript, DHTML Dropdown Menu

Here is the JavaScript code for animated dropdown menu. You can place this code inside the head section of your page or put it in a separate (.js) file and link to it. To change the opening and closing speed of the dropdown menu you can change the value for speed variable inside dropdown class. You may have to tweak around with the JavaScript code sometimes if you include borders or add extra padding to the menu class to make it work. You can adjust the menucontent.offsetHeight or total_height values by adding some number to it in the start function to make it work on your styling.

JavaScript Code:
<script type="text/javascript">
// Programmer: Abhinay Rathore
// Blog: http://web3o.blogspot.com
// Drop Down Menu Class
var dropdown =
{
      speed: 5, // Menu opening speed
      init: function() { // Initialize drop down menu
            var divs = document.getElementsByTagName("div");
            for (var i = 0; i < divs.length; i++) {
                  if (divs[i].className == "dropdown_menu"){ // attach mouse events
                        divs[i].onmouseover = function() { dropdown.start(this, 1); };
                        divs[i].onmouseout = function() { dropdown.start(this, -1); };
                  }
            }
      },
      start: function(obj, dir) {
            var menuheading = getElementChildNode(obj, 0); // Get Menu Heading
            var menucontent = getElementChildNode(obj, 1); // Get Menu Content
            var total_height = menuheading.offsetHeight + menucontent.offsetHeight + 2;
            clearTimeout(obj.Timer);
            obj.Timer = setInterval(function(){dropdown.animate(obj, menuheading.offsetHeight, total_height, dir)}, 1);
      },
      animate: function(obj, min_height, total_height, dir) {
            var objHeight = obj.offsetHeight;
            if (dir > 0){ // Expand
                  if (objHeight < total_height){
                        objHeight = objHeight + dropdown.speed;
                        obj.style.height = objHeight + "px";
                  } else { clearTimeout(obj.Timer); }
            } else if (dir < 0){ // Collapse
                  if (objHeight > min_height){
                        objHeight = objHeight - (dropdown.speed * 4);
                        if (objHeight < min_height) objHeight = min_height;
                        obj.style.height = objHeight + "px";
                  } else { clearTimeout(obj.Timer); }
            }
      }
};

// Returns Element Child node at given index
function getElementChildNode(node, index) {
      var element_node;
      var element_node_index = 0;
      var children = node.childNodes;
      for (var i = 0; i < children.length; i++) {
            if (children[i].nodeType == 1) {
                  element_node = children[i];
                  if (element_node_index == index) break;
                  else element_node_index++;
            }
      }
      return element_node;
}
// Add events
var EventUtil = {
      addHandler: function(element, type, handler) {
            if (element.addEventListener) {
                  element.addEventListener(type, handler, false);
            } else if (element.attachEvent) {
                  element.attachEvent("on" + type, handler);
            } else {
                  element["on" + type] = handler;
            }
      }
};

// Initialize dropdown menu when page loads...
EventUtil.addHandler(window, "load", dropdown.init); 
</script>

Here is the CSS style sheet for the dropdown menu, you can modify the style to suit the color and styling of your website. I have kept the z-index to 100 for the drop_down menu class to stay on top of all the contents on you page. The menu width is adjusted automatically based upon the width of the menu entry with maximum length, to keep fixed width menu you can add width attribute to the dropdown_menu and menu_heading classes in CSS or use inline styling in your HTML code.

CSS Code:
<style type="text/css">
<!--
.dropdown_menu, .menu_heading {
      height: 25px;
}
.dropdown_menu {
      overflow: hidden; z-index: 100; float: left;
}
.menu_heading {
      background-color: #033;
      border-right-width: 1px;
      border-left-width: 1px;
      border-right-style: solid;
      border-left-style: solid;
      border-right-color: #FFF;
      border-left-color: #FFF;
      cursor: default;
}
.menu_heading p{
      font-family: Verdana, Geneva, sans-serif;
      font-size: small; font-weight: bold; color: #FFF;
      margin: 0px; padding: 4px 4px 0px 4px;
}
.menu_content {
      position: relative;
}
.menu_content ul{
      margin: 0px; padding: 0px;
}
.menu_content li{
      list-style-image: none; list-style-type: none;
}
.menu_content li a{
      font-family: Verdana, Geneva, sans-serif;
      font-size: small; color: #333;
      display: block; text-decoration: none;
      cursor: default; background-color: #9CF;
      padding: 2px 4px 2px 4px;
      margin: 2px 1px 0px 1px;
}
.menu_content li a:hover{
      background-color: #E4E4E4;
}
-->
</style>

Here is the HTML Code you need to use for each Menu you want to put up on your website, the structure in pretty simple with one outside container (dropdown_menu) which contains menu heading div (menu_heading) and menu links div (menu_content) which finally contains the unordered list (ul) of links you want to put under that menu.

HTML Code:
<div class="dropdown_menu">
<div class="menu_heading"><p>Menu Heading</p></div>
<div class="menu_content">
<ul>
    <li><a href="http://www.google.com/">Google</a></li>
    <li><a href="http://www.yahoo.com/">Yahoo</a></li>
    <li><a href="http://www.msn.com/">MSN</a></li>
    <li><a href="#">My Link 2</a></li>
    <li><a href="#">My Link 1</a></li>
    <li><a href="#">My Link 2</a></li>
</ul>
</div>
</div>

To use multiple menu’s on a single page, you can create a container at top of the page to hold the menu’s and place the above given HTML Code one after the other. The dropdown menu JavaScript class’s init() functions scans all the div tags automatically and assigns mouse over and mouse out events to div tags which contain the menu class (dropdown_menu).

The only thing this menu script does not support as of now is multiple levels of cascading menu’s. I’ll work on another script soon to implement a hierarchical menu with various levels.

Wednesday, August 5, 2009

JavaScript DOM Child Node navigator to fix FireFox childNodes problem.

While working on DOM tree navigation using JavaScript, I realized that FireFox browser has a nasty problem of treating empty white spaces as text nodes. If your page size is very big, it makes physically impossible for you to track down a particular type of DOM Node using the childNodes function in JavaScript.

DOM Level 1 provides functions to navigate through the DOM Node tree such as, parentNode, childNodes, firstChild, lastChild etc. All these functions works fine in all the the browsers but sometime childNodes function gives inconsistent results in FireFox because it treats empty spaces as Text Nodes. For that reason I have created an easy to use Child Nodes navigation function which allows you to choose the type of nodes you want to select directly without navigating through rest of the Child Nodes.

This function getChildNode() comes in handy for navigating to a particular type of child node under an element. It takes three arguments, first is an object to the HTML DOM Element, second is the DOM Node Type under that Element and the third one is the index of the Node type in the child nodes list. You can call the getChildNode() function from your JavaScript code to access the DOM Nodes. For example if you want only the DOM Element Node, you can use this function to get the first (index starts at 0) DOM Element Node as follows:

var myNode = getChildNode(element, Nodes.ELEMENT_NODE, 0)

The getChildNodeCount() function can be used to count any particular type of DOM Nodes under any element and can be used as shown in example below to navigate through all the DOM Element Nodes under the HTML Body element:

var myBody = document.body;
for (i = 0; i < getChildNodeCount(myBody, Nodes.ELEMENT_NODE); i++) {
    var ElementNode = getChildNode(myBody, Nodes.ELEMENT_NODE, i)
    alert(ElementNode.innerText);
}

JavaScript Code:
<script type="text/javascript">
function getChildNode(node, childType, index) {
    var childnode;
    var childnode_index = 0;
    var children = node.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (children[i].nodeType == childType) {
            childnode = children[i];
            if (childnode_index == index) break;
            else childnode_index++;
        }
    }
    return childnode;
}
function getChildNodeCount(node, childType) {
    var count = 0;
    var children = node.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (children[i].nodeType == childType)
            count++;
    }
    return count;
}
var Nodes =
{
      ELEMENT_NODE                :  1,
      ATTRIBUTE_NODE              :  2,
      TEXT_NODE                   :  3,
      CDATA_SECTION_NODE          :  4,
      ENTITY_REFERENCE_NODE       :  5,
      ENTITY_NODE                 :  6,
      PROCESSING_INSTRUCTION_NODE :  7,
      COMMENT_NODE                :  8,
      DOCUMENT_NODE               :  9,
      DOCUMENT_TYPE_NODE          : 10,
      DOCUMENT_FRAGMENT_NODE      : 11,
      NOTATION_NODE               : 12
};
</script>

Technorati Tags:

JavaScript and DHTML Text Scroller with progress bar

You can find a wide variety of DHTML text scrollers on internet but I have created an easy to use scroller with a lot of flexibility and a progress bar. This is cross-browser compatible scroller and you can use multiple scrollers on the same page without assigning separate id’s to them.

Here are some benefits of using this DHTML scroller:
  • Zero coding required.
  • You can use multiple scrollers on a single page without assigning id’s separately.
  • Easily modifiable CSS layout and works with different styles for different scrollers on the same page.
  • Works with any mouse event giving you the flexibility of automatic and manual scrolling.
  • Cross-Browser compatibility.

JavaScript DHTML Scroller

Here are the sample scrollbar images (right click and save): up.png down.png

The scrolling function can be called from mouse over event for automatic scrolling or mouse down event for manual scrolling. The scrolling function is passed an object to the main container of the scroller and rest of the elements are navigated from there. This scroller code follows a strict hierarchy of div tags, so if you want to modify this and use your own styling and node hierarchy then you will have to change the JavaScript code to reach for the proper child nodes. For testing you can always use alert(element.className) to verify if you are navigating to the correct nodes. Instead of using the firstChild or childNodes functions from DOM, I have created my own function to get the child element nodes because when navigating using the DOM functions FireFox browser treats white spaces as text nodes and gives inconsistent results, for example to get to the third Element Node under the container you can use getElementChildNode(container, 2) (index starts at 0).

JavaScript Code:
<script type="text/javascript">
//Developed by: Abhinay Rathore | web3o.blogspot.com
var SPEED = 2; //scroll speed in pixels
//Starts the scrolling
function startScroll(container, dir, timer) {
    var scroller = getElementChildNode(getElementChildNode(container, 0), 0);
    clearInterval(scroller.timer); //clear existing timer
    //get Progress Column div id
    var progress_column = getElementChildNode(container, 2);
    //alert(progress_column.className);
    var progress_style = getstyle(progress_column);
    var progress_bar = getElementChildNode(progress_column, 0); //get Progress bar id
    var progress_bar_style = getstyle(progress_bar); //get Progress bar style
    //calculate the Progress bar scrolling limit
    var progress_bar_limit = parseInt(progress_style.height.replace('px', '')) - parseInt(progress_bar_style.height.replace('px', ''));
    //get current top offset for the Progress bar (default 0px)
    progress_bar.style.top = progress_bar.style.top || '0px';
    var progress_bar_top = parseInt(progress_bar.style.top.replace('px', ''));
    //calculate Scroll limit
    var limit = scroller.offsetHeight - scroller.parentNode.offsetHeight;
    //calculate Progress bar speed
    var progress_speed = (progress_bar_limit / (limit / SPEED));
    //create new object of Progress class
    progress = new Progress(progress_bar_limit, progress_speed, progress_bar_top);
    //alert(progress_bar_top);
    if (dir == -1) { // UP
        limit = 0;
        progress.limit = 0;
        scroller.timer = setInterval(function() { scrollUp(scroller, limit, progress_bar, progress) }, timer);
    }
    else if (limit >= 0) // DOWN
        scroller.timer = setInterval(function() { scrollDown(scroller, limit, progress_bar, progress) }, timer);
}
function scrollUp(scroller, limit, progress_bar, progress) { // SCROLL UP
    var container = scroller.parentNode.parentNode;
    scroller.style.top = scroller.style.top || '0px';
    var top = scroller.style.top.replace('px', '');
    if (Math.abs(top) - limit <= SPEED) { //top end reached
        cancelScroll(container);
        scroller.style.top = limit + 'px';
        progress_bar.style.top = Math.round(progress.getLimit()) + 'px';
        progress.setValue(progress.getLimit());
    } else {
        scroller.style.top = parseInt(top) + SPEED + 'px';
        progress_bar.style.top = Math.round(progress.getValue()) + 'px';
        progress.setValue(progress.getValue() - progress.getSpeed());
    }
}
function scrollDown(scroller, limit, progress_bar, progress) { // SCROLL DOWN
    var container = scroller.parentNode.parentNode;
    scroller.style.top = scroller.style.top || '0px';
    var top = scroller.style.top.replace('px', '');
    if (limit - Math.abs(top) <= SPEED) { //botttom end reached
        cancelScroll(container);
        scroller.style.top = '-' + limit + 'px';
        progress_bar.style.top = Math.round(progress.getLimit()) + 'px';
        progress.setValue(progress.getLimit());
    } else {
        scroller.style.top = top - SPEED + 'px';
        progress_bar.style.top = Math.round(progress.getValue()) + 'px';
        progress.setValue(progress.getValue() + progress.getSpeed());
    }
}
// Cancel the scrolling on mouseout
function cancelScroll(container) {
    var scroller = getElementChildNode(getElementChildNode(container, 0), 0);
    clearTimeout(scroller.timer);
}
// Get Element Child node and specified index
// this is a fix for FireFox browser where firstChild and
// childNodes[] DOM functionalities don't work sometime
// as FireFox treats empty spaces as text nodes.
function getElementChildNode(node, index) {
    var element_node;
    var element_node_index = 0;
    var children = node.childNodes;
    for (var i = 0; i < children.length; i++) {
        if (children[i].nodeType == 1) {
            element_node = children[i];
            if (element_node_index == index) break;
            else element_node_index++;
        }
    }
    return element_node;
}
// Class for Progress bar
function Progress(limit, speed, value) {
    this.limit = limit;
    this.speed = speed;
    this.value = value;
    this.getLimit = function() { return this.limit; };
    this.getSpeed = function() { return this.speed; };
    this.getValue = function() { return this.value; };
    this.setValue = function(value) { this.value = value; };
}
// Get CSS Style properties
function getstyle(id) {
    var myStyle = id.currentStyle;
    if (navigator.appName == "Microsoft Internet Explorer")
        myStyle = id.currentStyle;
    else myStyle = document.defaultView.getComputedStyle(id, null);
    return myStyle;
}
</script>

I have used a very basic set of styling for the scrolling window. You can modify the CSS to match the color and styling for your website. Some of the things in the CSS should not be changed such as the relative positioning of the scroll_content and progress_bar div tags. If you wish to change the hierarchy under the scrolling container, make sure you pass the correct objects to the functions, for more help on the node hierarchy read the paragraph following the CSS code very carefully.

CSS Code:
<style type="text/css">
<!--
.container {
      height: 300px; width: 500px;
      border: 1px solid #000;
      padding: 5px;
}
.scroll_window {
      width: 475px; height: 300px;
      overflow: hidden; float: left;
      margin-right: 5px;
}
.scroll_content {
      position: relative;
      font-family: Verdana, Geneva, sans-serif;
      font-size: small; text-align: justify;
}
.up, .down {
      float: left; height: 20px; width: 20px;
}
.progress_column {
      float: left; height: 260px; width: 20px;
}
.progress_bar {
      height: 20px; width: 20px;
      position: relative;
      background-color: #CCC;
}
-->
</style>

This scroller follows a strict hierarchy of div tags to make it a zero-coding tool. On mouse events, three arguments are passed to the startScroll function. First is the object to the main container from the image nodes by using DOM navigation function parentNode as this.parentNode.parentNode, second argument is the scrolling direction (-1 = up, 1 = down) and third argument is to set the scrolling speed. If your scrolling content is very large then you can set the scrolling speed to to higher number.

Here is the hierarchy of nodes in Scroller HTML DOM:
    container
            |_____scroll_window
            |                 |____scroll_content
            |_____up
            |      |____img
            |_____progress_column
            |                   |____progress_bar
            |_____down
                     |____img

You can have multiple copies of this scroller HTML code on your page without worrying about the calling function. The content for the scroller goes under the  scroll_content div tag.

HTML Code:
<div class="container">
    <div class="scroll_window">
        <div class="scroll_content">
            Your Content goes here!
        </div>
    </div>
<div class="up"><img onmouseover="startScroll(this.parentNode.parentNode, -1, 1)" onmouseout="cancelScroll(this.parentNode.parentNode)" src="up.png" alt="UP" /></div>
<div class="progress_column"><div class="progress_bar"></div></div>
<div class="down"><img onmouseover="startScroll(this.parentNode.parentNode, 1, 1)" onmouseout="cancelScroll(this.parentNode.parentNode)" src="down.png" alt="UP" /></div>
</div>

Feel free to use this DHTML scroller and modify it as you wish. But please do include a reference to my blog.

Technorati Tags: ,,

Monday, August 3, 2009

Disable webpage scrolling using JavaScript

Sometimes you might want to disable scrolling on a webpage for certain reasons such as to display some pop-up div tag at fixed page center. Here is a simple CSS hack which can be used to disable the page scrolling very easily. The trick here is to use the CSS overflow property on document body as follows using JavaScript:

document.body.style.overflow = "hidden";

Using this trick alone would make the page scroll bars disappear but the page will be automatically scrolled to top-left corner. To fix this issue I have also included a hack to keep the page aligned to its current position when the scrolling is disabled. After the scrolling is disabled, it calculates the current page offsets and scrolls the window to current offsets to make it look as if it never moved.

Here are the JavaScript functions for disabling and enabling the page scrolling. You can trigger disablePageScroll() and enablePageScroll() functions from any DOM events:

<script type="text/javascript">
function disablePageScroll(){
      document.body.style.overflow = "hidden";
      setTimeout("scrollWindow(" + PageXoffset() + "," + PageYoffset() + ")", 0);
}
function enablePageScroll(){
      document.body.style.overflow = "auto";
      setTimeout("scrollWindow(" + PageXoffset() + "," + PageYoffset() + ")", 0);
}
function scrollWindow(X, Y) {
      window.scrollTo(X, Y);
}
function PageXoffset() {
      return window.pageXOffset ? window.pageXOffset : document[(document.compatMode == 'CSS1Compat') ? 'documentElement' : 'body'].scrollLeft;
}
function PageYoffset() {
      return window.pageYOffset ? window.pageYOffset : document[(document.compatMode == 'CSS1Compat') ? 'documentElement' : 'body'].scrollTop;
}
</script>

Technorati Tags: