/*
  Copyright 2008 Galen Wright-Watson

version: 1.0a
release: 20080706
requires: stringext.js
optional: include.js

This library is really trivial.  Defines functions to manipulate HTMLElement.className.

Definitions:
  A class name is a string containing word characters (alphanumeric or '_').
  A list of class names is a string containing class names, separated by one or more spaces.  Note that a list of 1 class name is equivalent to a class name.

The main difference between class.js and classLite.js is that the functions defined in class.js can work on individual class names in a list, whereas here they do not work with individial class names within lists (they only work on the entire list, in the order given).

For example, consider the following fragment:
    elt.className = '';
    addClass(elt, "foo");
    removeClass(elt, "foo bar");
If you're using class.js, the call to "removeClass()" will remove class 'foo'.  With classLite.js, the call will not remove 'foo'.

For:
    elt.className = '';
    addClass(elt, "foo bar");
    removeClass(elt, "bar foo");
class.js will remove classes 'foo' and 'bar'; classLite.js will remove neither.  
For:
    elt.className = '';
    addClass(elt, "foo bar");
    removeClass(elt, "foo bar");
Both class.js and classLite.js will remove classes 'foo' and 'bar'.

For:
    elt.className = '';
    addClass(elt, "foo bar");
    removeClass(elt, "foo");
Both class.js and classLite.js will remove class 'foo'.


Public functions:
Common Arguments--
  className: a string; a class name or a list of class names, in which case function will be applied to the list as a whole.
  obj: can be either an HTMLElement or id.
  elt: an HTMLElement

  hasClass(elt, className):
Return true if element 'elt' has class 'className'.

  removeClass(obj, className):
Remove class/es from obj.

  addClass(obj, className):
Add class/es to obj, if not already there.

  replaceClass(obj, oldClass, newClass):
Remove class/es given by oldClass, add classes given by newClass.

  toggleClass(obj, className):
Remove class/es which is/are set and add class/es which aren't. 

 */


if (! String.prototype.cut) {
  if (window.include) {
	include("/js/stringext.js");
	include.register("/js/classLite.js");
  } else {
	var stringext = document.createElement('script');
	stringext.type="text/javascript";
	stringext.src = "/js/stringext.js";
	if (!document.head && !document.body) {
	    if (document.getElementsByTagName) {
		document.head = document.getElementsByTagName('head')[0];
	    } else {
	    }
	}
	if (document.head) {
	  document.head.appendChild(stringext);
	} else if (document.body) {
	  document.body.appendChild(stringext);
	} else {
	    document.writeln('<script type="text/javascript" src="/js/stringext.js"></script>');
	}
  }
}

var hasClass,
    removeClass,
    addClass,
    toggleClass,
    replaceClass;

/*

General strategy for implementation:  
  When given a space-separated list of class names, split the list and perform 
the requested operation for each name.
  To deal with a specific class name, find its index in an object's className
property.  (alternative: split obj.className and deal w/ resulting array)

 */


// hide private functions within the scope of an anon function
(function () {
    function getObj(obj) {
	if (typeof(obj) == "string") {
	    obj = document.getElementById(obj);
	}
	return obj;
    }

    hasClass = function (elt, name) {
	return indexOfClass(elt.className, name) > -1;
	//return classIsSet(elt.className, name);
    };

    //Add classes listed in 'className' to obj, if not already present.
    addClass = function(obj, className) {
	obj = getObj(obj);
	if (!hasClass(obj, className)) {
	    obj.className += ' '+className;
	}
    };

    //remove all classes from obj listed in 'className'
    removeClass = function (obj, className) {
	obj = getObj(obj);
	obj.className = obj.className.replace(new RegExp('\\b'+className+'\\b', 'g'), ' ');
    };

    toggleClass = function(obj, className) {
	obj = getObj(obj);
	if (!hasClass(obj, className)) {
	    obj.className += ' '+className;
	} else {
	    removeClass(obj, className);
	}
    };


    //replace class(es): remove classes, add classes.  Convenience function
    replaceClass = function(obj, oldClass, newClass) {
	obj = getObj(obj);
	removeClass(obj, oldClass);
	addClass(obj, newClass);
    }

    /* return true iff 'start' and 'end' are at word boundaries*/
    function wordBoundariesP(str, start, end) {
	/*
	  return str.charAt(start-1).match(/\W/);
	  && str.charAt(end+1).match(/\W/);
	*/
	//report('wordBoundariesP("'+str+'", '+start+', '+end+')');
	var bfore = str.charAt(start-1);
	var after = str.charAt(end+1);
	return ( (bfore == '' || bfore == ' ')
		 && (after == '' || after == ' '));
    }

    /* Return the index of class 'name' in classes 'classList'.
       Note class name in 'classList' must be bordered by WS or start/end 
       of string.  That is, 'classList.match(/\bname\b/)'
    */
    function indexOfClass(classList, name) {
	//strategy: find occurence of name.  Check that characters
	//at either end of substring are either spaces or non-existant
	//If so, name is present.  Otherwise, keep looking.
	//report('indexOfClass("'+classList+'", "'+name+'")');
	var idx = classList.indexOf(name);
	while (idx >= 0) {
	    if (wordBoundariesP(classList, idx, idx+name.length-1))
		return idx;
	    idx = classList.indexOf(name, idx+name.length);
	}
	return -1;
    }
})();


