/*
   Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
   of Simon Willison (see comments by Simon below).

   Description:

   	Uses css selectors to apply javascript behaviours to enable
   	unobtrusive javascript in html documents.

   Usage:

	var myrules = {
		'b.someclass' : function(element){
			element.onclick = function(){
				alert(this.innerHTML);
			}
		},
		'#someid u' : function(element){
			element.onmouseover = function(){
				this.innerHTML = "BLAH!";
			}
		}
	};

	Behaviour.register(myrules);

	// Call Behaviour.apply() to re-apply the rules (if you
	// update the dom, etc).

   License:

   	My stuff is BSD licensed. Not sure about Simon's.

   More information:

   	http://ripcord.co.nz/behaviour/
*/

var checked_boxes = null;
var Behaviour = {
 rules : new Object, register : function(sheet) {
  for (selector in sheet) {
   var res = document.getCSSSelectionObject(selector, sheet[selector]);
   if (!Behaviour.rules[res.tagName]) {
    Behaviour.rules[res.tagName] = new Array();
   }
   Behaviour.rules[res.tagName].push(res);
  }
 }, start : function(){
  Behaviour.addLoadEvent(function(){
  Behaviour.apply();
 });
}, apply : function(){
 var select_time = 0;
 var execute_time = 0;
 var start_time = new Date();
 for (tagName in Behaviour.rules){
  var rule = Behaviour.rules[tagName];
  var has_non_id = false;
  // Start by handling any id selections
  for (var i = 0; selector = rule[i]; ++i) {
   if (selector.isSelection) {
    var element = document.getElementById(selector.id);
    if (element
        && (!selector.tagName || element.nodeName.toLowerCase()
        == selector.tagName)) {
     // We found an element by id match.
     selector.func(element);
    }
   } else {
    has_non_id = true;
   }
  }
  if (!has_non_id)
   continue;

  var elements;
  if (!tagName)
   elements = getAllChildren(document);
  else
   elements = document.getElementsByTagName(tagName);

  for (var i = 0; element = elements[i]; ++i) {
   // Now see which of the elements that match.
   for (var j = 0; selector = rule[j]; ++j) {
    if (selector.isSelection)
     continue;
    if (selector.matchFunction(element)) {
     if (selector.tokens) {
      // Find all the child elements.
      var children = document.getElementBySelector(element, selector.tokens)
      for (var k = 0; child = children[k]; ++k) {
       var execute_start = new Date();
       selector.func(child);
       var execute_end = new Date();
       selector.execute_time += execute_end - execute_start;
       execute_time += execute_end - execute_start;
      }
     } else {
      // No further tokens. So this element matches.
      var execute_start = new Date();
      selector.func(element);
      var execute_end = new Date();
      selector.execute_time += execute_end - execute_start;
      execute_time += execute_end - execute_start;
     }
    }
   }
  }
 }
 var select_end = new Date();
 select_time = select_end - start_time;
 // re-enable checkboxes
 if (checked_boxes) {
  for (var i=0;cbox=checked_boxes[i];i++){
   set_checked(cbox, true, false);
  }
 }
 if (("" + document.location).indexOf("behaviour_bench=42") >= 0) {
  var end_time = new Date();
  var diff = end_time - start_time;
  alert("Selecting items: " + (select_time - execute_time) + "\nExecuting function: "
   + execute_time + "\nTotal: " + select_time);
  var times = "selector: execute_time\n";
  for (tagName in Behaviour.rules) {
   var rule = Behaviour.rules[tagName];
   for (var i = 0; selector = rule[i]; i++)
    times += selector.selector + ": " + selector.execute_time + "\n";
   }
   alert(times);
  }
 }, addLoadEvent : function(func){
  var oldonload = window.onload;
  if (typeof window.onload != 'function' && typeof window.onload != 'object') {
   window.onload = func;
  } else {
   window.onload = function() {
    oldonload();
    func();
   }
  }
 }
}

//Behaviour.start();

/*
   The following code is Copyright (C) Simon Willison 2004.

   document.getElementsBySelector(selector)
   - returns an array of element objects from the current document
     matching the CSS selector. Selectors can contain element names,
     class names and ids and can be nested. For example:

       elements = document.getElementsBySelect('div#main p a.external')

     Will return an array of all 'a' elements with 'external' in their
     class attribute that are contained inside 'p' elements that are
     contained inside the 'div' element which has id="main"

   New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
   See http://www.w3.org/TR/css3-selectors/#attribute-selectors

   Version 0.4 - Simon Willison, March 25th 2003
   -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
   -- Opera 7 fails
*/

function getAllChildren(e) {
 // Returns all children of element. Workaround required for IE5/Windows. Ugh.
 return e.all ? e.all : e.getElementsByTagName('*');
}

document.getElementsBySelector = function(element, selector) {
 // Attempt to fail gracefully in lesser browsers
 if (!document.getElementsByTagName) {
  return new Array();
 }
 // Split selector in to tokens
 var tokens = selector.split(' ');
 var currentContext = new Array(element);
 for (var i = 0; i < tokens.length; i++) {
  token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
  if (token.indexOf('#') > -1) {
   // Token is an ID selector
   var bits = token.split('#');
   var tagName = bits[0];
   var id = bits[1];
   var element = document.getElementById(id);
   if (tagName && element.nodeName.toLowerCase() != tagName) {
    // tag with that ID not found, return false
    return new Array();
   }
   // Set currentContext to contain just this element
   currentContext = new Array(element);
   continue; // Skip to next token
  }
  if (token.indexOf('.') > -1) {
   // Token contains a class selector
   var bits = token.split('.');
   var tagName = bits[0];
   var className = bits[1];
   if (!tagName) {
    tagName = '*';
   }
   // Get elements matching tag, filter them for class selector
   var found = new Array;
   var foundCount = 0;
   for (var h = 0; h < currentContext.length; h++) {
    var elements;
    if (tagName == '*') {
     elements = getAllChildren(currentContext[h]);
    } else {
     elements = currentContext[h].getElementsByTagName(tagName);
    }
    for (var j = 0; j < elements.length; j++) {
     found[foundCount++] = elements[j];
    }
   }
   currentContext = new Array;
   var currentContextIndex = 0;
   for (var k = 0; k < found.length; k++) {
    if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
     currentContext[currentContextIndex++] = found[k];
    }
   }
   continue; // Skip to next token
  }
  // Code to deal with attribute selectors
  if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
   var tagName = RegExp.$1;
   var attrName = RegExp.$2;
   var attrOperator = RegExp.$3;
   var attrValue = RegExp.$4;
   if (!tagName) {
    tagName = '*';
   }
   // Grab all of the tagName elements within current context
   var found = new Array;
   var foundCount = 0;
   for (var h = 0; h < currentContext.length; h++) {
    var elements;
    if (tagName == '*') {
     elements = getAllChildren(currentContext[h]);
    } else {
     elements = currentContext[h].getElementsByTagName(tagName);
    }
    for (var j = 0; j < elements.length; j++) {
     found[foundCount++] = elements[j];
    }
   }
   currentContext = new Array;
   var currentContextIndex = 0;
   var checkFunction; // This function will be used to filter the elements
   switch (attrOperator) {
   case '=': // Equality
    checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
    break;
   case '~': // Match one of space seperated words
    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
    break;
   case '|': // Match start with value followed by optional hyphen
    checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
    break;
   case '^': // Match starts with value
    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
    break;
   case '$': // Match ends with value - fails with "Warning" in Opera 7
    checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
    break;
   case '*': // Match ends with value
    checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
    break;
   default :
    // Just test for existence of attribute
    checkFunction = function(e) { return e.getAttribute(attrName); };
   }
   currentContext = new Array;
   var currentContextIndex = 0;
   for (var k = 0; k < found.length; k++) {
    if (checkFunction(found[k])) {
     currentContext[currentContextIndex++] = found[k];
    }
   }
   // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
   continue; // Skip to next token
  }

  if (!currentContext[0]){
   return;
  }

  // If we get here, token is JUST an element (not a class or ID selector)
  tagName = token;
  var found = new Array;
  var foundCount = 0;
  for (var h = 0; h < currentContext.length; h++) {
   var elements = currentContext[h].getElementsByTagName(tagName);
   for (var j = 0; j < elements.length; j++) {
    found[foundCount++] = elements[j];
   }
  }
  currentContext = found;
 }
 return currentContext;
}

/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
  \---/  \---/\-------------/    \-------/
    |      |         |               |
    |      |         |           The value
    |      |    ~,|,^,$,* or =
    |   Attribute
   Tag
*/

document.getCSSSelectionObject = function(selector, func)
{
 var res = new Object();
 res.isSelection = false;
 res.func = func;
 res.selector = selector;
 var tokens = selector.split(' ');
 var token = tokens[0].replace(/^\s+/,'').replace(/\s+$/,'');

 if (token.indexOf('#') > -1) {
  // Token is an ID selector
  var bits = token.split('#');
  res.isSelection = true;
  res.tagName = bits[0];
  res.id = bits[1];
 } else if (token.indexOf('.') > -1) {
  // Token contains a class selector
  var bits = token.split('.');
  res.tagName = bits[0];
  res.className = bits[1];
  res.matchFunction = function(obj) {
   return obj.className
    && obj.className.match(new RegExp('\\b' + bits[1] + '\\b'));
  };
 } else if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
  var tagName = RegExp.$1;
  var attrName = RegExp.$2;
  var attrOperator = RegExp.$3;
  var attrValue = RegExp.$4;
  res.tagName = tagName;
  var checkFunction;
  switch (attrOperator) {
  case '=': // Equality
   checkFunction = function(e) {
    return (e.getAttribute(attrName) == attrValue);
   };
   break;
  case '~': // Match one of space seperated words
   checkFunction = function(e) {
    return (e.getAttribute(attrName).match
     (new RegExp('\\b'+attrValue+'\\b')));
   };
   break;
  case '|': // Match start with value followed by optional hyphen
   checkFunction = function(e) {
    return (e.getAttribute(attrName).match
     (new RegExp('^'+attrValue+'-?')));
   };
   break;
  case '^': // Match starts with value
   checkFunction = function(e) {
    return (e.getAttribute(attrName).indexOf(attrValue) == 0);
   };
   break;
  case '$': // Match ends with value - fails with "Warning" in Opera 7
   checkFunction = function(e) {
    return (e.getAttribute(attrName).lastIndexOf(attrValue)
     == e.getAttribute(attrName).length - attrValue.length);
   };
   break;
  case '*': // Match ends with value
   checkFunction = function(e) {
    return (e.getAttribute(attrName).indexOf(attrValue) > -1);
   };
   break;
  default :
   // Just test for existence of attribute
   checkFunction = function(e) { return e.getAttribute(attrName); };
   break;
  }
  res.matchFunction = checkFunction;
 } else {
  res.tagName = tokens[0];
  res.matchFunction = function(e) {
   return true;
  };
 }

 res.tokens = "";
 for (i = 1; i < tokens.length; ++i) {
 if (res.tokens)
  res.tokens += " ";
  res.tokens += tokens[i];
 }
 res.execute_time = 0;
 return res;
}

