/*********** Stuff from mochikit ********/

/* Short alias to get an element by its ID */
function $(id) {
  return (typeof (id)=="string") ? document.getElementById(id)
                                 : id;
}

/* Shallow copy-on-write object. */
function clone(obj) {
  var me = arguments.callee;
  
  if(arguments.length==1) {
    me.prototype=obj;
    return new me();
  }
}

/********************************************************************************
 * OO support code from                                                         *
 * http://truecode.blogspot.com/2006/08/object-oriented-super-class-method.html *
 *******************************************************************************/
function Class() { }
Class.prototype.construct = function() {};
Class.addStatic = function(name, fn) {
  Class.__statics[name] = Class[name] = fn;
}

Class.extend = function(def) {
  var classDef = function() {
    if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
  };
  
  var proto = new this(Class);
  var superClass = this.prototype;
  
  for (var n in def) {
    var item = def[n];                        
    if (item instanceof Function) item.$ = superClass;
    proto[n] = item;
  }
  
  classDef.prototype = proto;
  for(var s in this.__statics) {
    classDef[s] = this.__statics[s];
  }
  
  classDef.__statics = clone(this.__statics);
  
  //Give this new class the same static extend method    
  classDef.extend = this.extend;        
  return classDef;
}

Class.__statics = {addStatic : Class.addStatic, extend: Class.extend};

/**********************************************************
 * Standard Errors *
 **********************************************************/

function make_error_class(name) {
  var o = function(msg) { this.message = msg; };
  o.prototype.toString = function() {
    return name + ': ' + this.message;
  };
  
  return o;
}

var IllegalStateError = make_error_class('IllegalStateError');
var OperationNotSuppported = make_error_class('OperationNotSuppported');

/**************************************************************************
 * Browser detection                                                      *
 * Inspired by http://www.codingforums.com/archive/index.php?t-29428.html *
 **************************************************************************/

var BrowserSniffer = Class.extend({
    construct: function() {
      /*
       * Yes, user-agent based browser sniffing is evil; however, it is
       * essential in detecting some layout and box model bugs that
       * can't otherwise be detected without expensive element construction.
       */
      this.ua = navigator.userAgent.toLowerCase();
      this.ie = (typeof document.getElementById!="undefined"
                 && typeof document.all!="undefined"
                 && typeof window.opera=="undefined");
      
      this.ie5 = (this.ie && this.ua.indexOf("msie 5")!=-1);
      this.ie55 = (this.ie && this.ua.indexOf("msie 5.5")!=-1);
      this.ie50 = (this.ie5 && !this.ie55);
      this.ie6 = (this.ie && this.ua.indexOf("msie 6")!=-1);
      this.ie7 = (this.ie && this.ua.indexOf("msie 7")!=-1);
      this.opera = (this.ua.indexOf("opera") != -1);
      this.quirks = (this.ie5 || (this.ie6 && document.compatMode == 'BackCompat'));
    }
  });

//browser object
var browser = new BrowserSniffer();

var DRAG_HANDLER_DRAG = 0;
var DRAG_HANDLER_STOP = 1;

// Facility to globally capture mouse motion events
// for dragging in as painless a way as possible
function capture_dragging(handler_obj, handler_func) {
  if(document.drag_handler_func) {
    throw new IllegalStateError('Already have drag handler');
  }
  
  document.drag_handler_obj = handler_obj;
  document.drag_handler_func = handler_func;
  
  document.onmousemove = capture_dragging_onmousemove;
  document.onmouseup = capture_dragging_onmouseup;
}

function capture_dragging_onmousemove(e) {
  e = e || window.event;
  document.drag_handler_func.call(document.drag_handler_obj, DRAG_HANDLER_DRAG,
                                  e.clientX, e.clientY);
  return false;
}

function capture_dragging_onmouseup(e) {
  e = e || window.event;
  document.drag_handler_func.call(document.drag_handler_obj, DRAG_HANDLER_STOP,
                                  e.clientX, e.clientY);
  
  document.drag_handler_obj = document.drag_handler_func =
    document.onmousemove = document.onmouseup = null;
  
  return false;
}

/**********************************************************
 * Facility for executing arbitrary code on document load *
 **********************************************************/
var Hooks = Class.extend({
    construct: function() {
      this.hooks = [];
    },
    
    add: function(func) {
      this.hooks.push(func);
    },
    
    run: function() {
      var x;
      
      for(x in this.hooks) {
        this.hooks[x]();
      }
    }
  });

var onloadHooks = new Hooks();
var onunloadHooks = new Hooks();

/**********************************************************
 * Legacy support                                         *
 **********************************************************/

function MM_findObj(n, d) { //v3.0
  var p,i,x;  if(!d) d=document; if((p=n.indexOf("?"))>0&&parent.frames.length) {
    d=parent.frames[n.substring(p+1)].document; n=n.substring(0,p);}
  if(!(x=d[n])&&d.all) x=d.all[n]; for (i=0;!x&&i<d.forms.length;i++) x=d.forms[i][n];
  for(i=0;!x&&d.layers&&i<d.layers.length;i++) x=MM_findObj(n,d.layers[i].document); return x;
}

function MM_swapImage() { //v3.0
  var i,j=0,x,a=MM_swapImage.arguments; document.MM_sr=new Array;
  for(i=0;i<(a.length-2);i+=3)
    if ((x=MM_findObj(a[i]))!=null){document.MM_sr[j++]=x;
      if(!x.oSrc) x.oSrc=x.src; x.src=a[i+2];}
}
function MM_swapImgRestore() { //v3.0
  var i,x,a=document.MM_sr;
  for(i=0;a&&i<a.length&&(x=a[i])&&x.oSrc;i++) x.src=x.oSrc;
}

/**********************************************************
 * Miscellanea                                            *
 **********************************************************/

function basename(arg) {
    var idx = arg.lastIndexOf('/');
    if(idx != -1) {
        arg = arg.substring(0, idx);
    }

    return arg;
}

function fixupLoginForm() {
    var l = document.forms['login'].loginemail;
    var p = document.forms['login'].loginpassword;
    
    if(l.value) {
	p.focus();
    } else {
	l.focus();
    };
}

function minimax(min, max, value) {
  if(value < min) return min;
  if(value > max) return max;
  return value;
}

/* Return true IFF code refers to an ASCII digit */
function isdigit(code) {
  return (code >= 48 /* '0' */ && code <= 57 /* '9' */);
}

function on_coupon_keypress(e) {
  var e = get_event(e);
    
  if((e.charCode == 13) || (e.charCode == 0 && e.keyCode == 13)) {
    var form = get_event_target(e).form;
    var elements = form.elements;
        
    for(var i = 0; i < elements.length; ++i) {
      var btn = elements[i];
      if(btn.name == 'action#recalculate') {
        btn.click();
        break;
      }
    }
        
    return false;
  }
    
  return true;
}

function on_ship_meth_change(phone_required) {
  var div = $('shippingRequiredPhone');
  if(phone_required) {
    div.style.display = 'block';
  } else {
    div.style.display = 'none';
  }
    
  return true;
}

function on_ship_meth_change_closure(phone_required) {
  return function() {
    on_ship_meth_change(phone_required);
  };
}

/**
 * Work around a misfeature in javascript event handling where 'this'
 * is set to the element receiving an event rather than the 'this' of
 * a bound object method. Returns a function that, when called with
 * one argument, calls fcn with this set to obj.
 */
function make_event_handler(obj, fcn) {
  return function(e) {
    var e = get_event(e);
    return fcn.call(obj, e);
  };
}

/**********************************************************
 * Step widget                                            *
 **********************************************************/

function turn_on_steps() {
    var btn = $('hideShowStepsButton');
    btn.src = basename(btn.src) + '/hide-arrow.png';
    btn.onclick = turn_off_steps;
    
    var step_icon_row = $('stepsGraphics');
    var step_benefits_row = $('stepsBenefits') || step_icon_row;
    var step_tagline_row = $('stepsTagLine') || step_icon_row;
    step_icon_row.style.display = 
        step_benefits_row.style.display =
        step_tagline_row.style.display = '';

}

function turn_off_steps() {
    var btn = $('hideShowStepsButton');
    btn.src = basename(btn.src) + '/show-arrow.png';
    btn.onclick = turn_on_steps;

    var step_icon_row = $('stepsGraphics');
    var step_benefits_row = $('stepsBenefits') || step_icon_row;
    var step_tagline_row = $('stepsTagLine') || step_icon_row;
    
    step_icon_row.style.display = 
        step_benefits_row.style.display =
        step_tagline_row.style.display = 'none';
}

function on_search_focus() {
    var searchbox = $('stepsSearchBox');
    searchbox.style.color = 'black';
    searchbox.value = '';
    searchbox.onfocus = null;
}

function set_up_steps() {
    var btn = $('hideShowStepsButton');
    if(btn) {
        btn.style.display = 'block';
    
        var step_icon_row = $('stepsGraphics');
        
        if(step_icon_row.style.display == 'none') {
            turn_off_steps();
        } else {
            turn_on_steps();
        }

        var searchbox = $('stepsSearchBox');
        if(searchbox) {
            searchbox.onfocus = on_search_focus;
            searchbox.value = 'Looking for...';
        }
    }
}

/* Pragmatically click the "Upload different image" button.
 * HACK HACK HACK
 */
function hack_go_back_upload() {
    var form = document.forms[0];
    var elements = form.elements;
    for(var i = 0; i < elements.length; ++i) {
        var element = elements[i];
        if(element.value && element.value.indexOf('different image') != -1) {
            element.click();
        }
    }
}

/**********************************************************
 * Payment Widget                                         *
 **********************************************************/

/* Sets a form object up */
function paymentForm(form) {
  this.cc_types = Object();
  this.form = form;
  
  var num_entry = form.elements['ccnum'];
  num_entry.onkeypress = make_event_handler(
    this, this.on_entry_keypress);
  
  var n = form.elements['paytype'];
  
  for(var i = 0; i < n.length; ++i) {
    if(n[i].value == 'paypal') {
      this.paypal_checkbox = n[i];
      
      n[i].onclick = make_event_handler(this,
        function(e) {
          this.show_paypal(true);
        });
      
      if(n[i].checked) {
        this.show_paypal(true);
      }
      
    } else if(n[i].value == 'ccard') {
      this.ccard_checkbox = n[i];
      
      n[i].onclick = make_event_handler(this,
        function(e) {
          this.show_paypal(false);
        });
      
      if(n[i].checked) {
        this.show_paypal(false);
      }
    }
  }
}

paymentForm.prototype.add_cc_type = function(name, regexp) {
  this.cc_types[name] = regexp
}

paymentForm.prototype.on_entry_keypress = function(e) {
  var code;
  
  if(e.keyCode == 0) {
    code = e.charCode;
  } else if(e.keyCode > 0 && e.charCode != undefined) {
    return true;
  } else {
    code = e.keyCode;
  }
  
  if(code <= 31) {
    return true;
  }
  
  if (!isdigit(code) && code > 31) {
    return false;
  }
  
  var field = get_event_target(e);
  var form = field.form;
  
  var cctype = form.elements['cctype'];
  
  var val = field.value;
  
  for(name in this.cc_types) {
    var re = this.cc_types[name];
    if(re.test(val)) {
      for(var i = 0; i < cctype.options.length; ++i) {
        if(cctype.options[i].text == name) {
          cctype.value = cctype.options[i].value;
          return true;
        }
      }
    }
  }
  
  cctype.value = '';
  return true;
};

paymentForm.prototype.show_paypal = function(do_show_paypal) {
  var foo = this.form.getElementsByTagName('div');
  var credit_card_div;
  
  for(var i = 0; i < foo.length; ++i) {
    var elem = foo[i];
    if(elem.className.indexOf('creditCardInfo') != -1) {
      credit_card_div = elem;
    }
  }
    
  if(do_show_paypal) {
    credit_card_div.style.color = "#aaaaaa";
  } else {
    credit_card_div.style.color = "";
  }
  
  for(var i = 0; i < this.form.elements.length; ++i) {
    var elem = this.form.elements[i];
    if(elem.name.indexOf('cc') == 0) {
      elem.disabled = do_show_paypal;
    }
  }
  
  return true;
}

/**********************************************************
 * Suggestion box                                         *
 **********************************************************/

function suggestionBox(id, submit_url) {
  this.id = id;
  this.submit_url = submit_url;
  this.req = null;
    
  var btn = this.get_btn();
  btn.onclick = make_event_handler(this, this.on_btn_click);
    
  this.set_message('Using the submit button will not cause this page to reload', false);
}

suggestionBox.prototype.get_btn =
  function() {
    return $(this.id).getElementsByTagName('button')[0];
  }

suggestionBox.prototype.get_msg_elem =
  function() {
    return $(this.id).getElementsByTagName('span')[0];
  }

suggestionBox.prototype.get_loading_img =
  function() {
    return $(this.id).getElementsByTagName('img')[0];
  }

suggestionBox.prototype.get_suggestion =
  function() {
    return $(this.id).getElementsByTagName('textarea')[0];
  }

suggestionBox.prototype.get_email =
  function() {
    return $(this.id).getElementsByTagName('input')[0];
  }

suggestionBox.prototype.set_message =
  function(msg, show_loading) {
    var msge = this.get_msg_elem();
    var img = this.get_loading_img();
        
    msge.innerHTML = msg;

    if(show_loading) {
      img.style.visibility = 'visible';
    } else {
      img.style.visibility = 'hidden';
    }
  }

suggestionBox.prototype.on_btn_click =
  function(e) {
    if(this.req) {
      return false;
    }
        
    var btn = this.get_btn();
    var suggestion_box = this.get_suggestion();
    var suggestion = suggestion_box.value;
    var email_box = this.get_email();
    var email = email_box.value;
        
    btn.disabled = true;
    this.set_message('Sending...', true);
        
    this.req = make_XMLHttpRequest();
    this.req.open('POST', this.submit_url, true);
    this.req.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
    this.req.onreadystatechange = make_event_handler(this, this.on_req_readystate_changed);
    this.req.send('ajax=1&suggestion=' + escape(suggestion) + '&email=' + escape(email));
        
    return false;
  }

suggestionBox.prototype.on_req_readystate_changed =
  function(e) {
    if(this.req.readyState != 4) {
      return;
    }
        
    var btn = this.get_btn();
    btn.disabled = false;
        
    this.set_message('Your comment has been recorded. Thank you.', false);
    this.req = null;
  }

/**********************************************************
 * Category page functions                                *
 **********************************************************/

function category_widget_onload_closure(id) {
  return function() {
    var x = id + 'showOrientControls';
    var elems = $(x).getElementsByTagName('input');
    elems[0].onclick = elems[1].onclick = function() {
      window.location = this.parentNode.href;
    }
  }
}

function orient_horiz_clicked(e) {
  alert(this.parent);
}

/**********************************************************
 * Address form functions                                 *
 **********************************************************/

function addrForm(formname, prefix, info) {
  this.info = info;
  this.prefix = prefix;
  this.formname = formname;
    
  this.set_according_to_country(this.get_country());
  this.get_widget('country').onchange = make_event_handler(this, this.on_country_change);
}

addrForm.prototype.get_form =
  function() {
    return document.forms[this.formname];
  }

addrForm.prototype.on_country_change =
  function(e) {
    this.set_according_to_country(this.get_country());
    return true;
  }

addrForm.prototype.get_widget =
  function(name) {
    var form = this.get_form();
    return form.elements[this.prefix + name];
  }

addrForm.prototype.get_label_widget =
  function(name) {
    var widget = this.get_widget(name);
    while(widget && widget.nodeName != 'LABEL') {
      widget = widget.previousSibling;
    }
  
    if(widget == null) {
      throw new Error("Couldn't find label for widget: " + name);
    }
        
    return widget;
  }

addrForm.prototype.get_country =
  function() {
    return this.get_widget('country').value;
  }

addrForm.prototype.set_according_to_country =
  function(country) {
    var info = this.info[country];
    var partname = titlecase(info['partname']);
        
    this.get_label_widget('part').innerHTML = partname;
    this.get_label_widget('postcode').innerHTML = titlecase(info['postcode']);
        
    var state_widget = this.get_widget('part');
    var current_value = state_widget.value;
        
    var parts = [{
        name: '',
        value: 'Select a ' + partname,
        selected: false
      }];
        
    for(var x in info['parts']) {
      parts.push({
          name: x,
          value: info['parts'][x],
          selected: x == current_value});
    }
        
    this.set_widget_select_options('part', parts);
  }

addrForm.prototype.set_widget_select_options =
  function(name, options) {
    var widget = this.get_widget(name);
    var opt;
    widget.options.length = 0;
        
    for(i in options) {
      opt = options[i];
      widget.options[widget.options.length] =
        new Option(opt.value, opt.name, opt.selected, opt.selected);
    }
  }

// Returns a closure that initializes the given address form when
// called. (Probably by onloadHooks)
function addrFormInitHook(formname, prefix, addr_info) {
  return function() {
    var form = document.forms[formname];
    new addrForm(formname, prefix, addr_info);
  };
}

function set_bill_form() {
  var x = document.getElementById('billToggle');
  var billform = document.getElementById('billingAddr');
    
  if(!x.checked) {
    billform.style.display = "block";
  } else {
    billform.style.display = "none";
  }
    
  return true;
}

function bill_toggle_mousedown(e) {
  var e = get_event(e);
  var cb = document.getElementById('billToggle');
  cb.click();
  return false;
}

/**********************************************************
 * Geometry                                               *
 **********************************************************/

var Rect = Class.extend({
    construct: function(x, y, w, h) {
      this.x = x;
      this.y = y;
      this.w = w;
      this.h = h;
    },
    
    toString: function() {
      return "{x:" + this.x + " y:" + this.y + " w:" +
        this.w + " h:" + this.h + "}";
    }
  });

/**
 * Return a Rect structure indicating the dimensions of the given
 * element; includes only the content width, not borders, padding or
 * margins; i.e., the client area of the element.
 *
 * @param elem Element or ID
 * @return Dimensions
 *
 */
function getElementInnerDimensions(elem) {
  /* Some feature detection logic to forward
     code to feature-specific functions, below */
  
  if(document.defaultView) {
    elem = $(elem);
      
    /* Try to figure out whether we can use getPropertyCSSValue */
    var cs = document.defaultView.getComputedStyle(elem, null);
    var getpropertycssvalue_works = false;
    
    if(cs.getPropertyCSSValue) {
      try {
        getElementInnerDimensions_getPropertyCSSValue(elem);
        getpropertycssvalue_works = true;
      } catch(DOMException) {
        ;
      }
    }
    
    if(getpropertycssvalue_works) {
      getElementInnerDimensions = getElementInnerDimensions_getPropertyCSSValue;
    } else {
      getElementInnerDimensions = getElementInnerDimensions_parseFloat;
    }
    
  } else if(elem.clientWidth) {
    getElementInnerDimensions = getElementInnerDimensions_ie;
  } else {
    throw new OperationNotSupported("Bah!");
  }
  
  return getElementInnerDimensions(elem);
}

function getElementInnerDimensions_ie(elem) {
  elem = $(elem);
  return new Dimensions(elem.clientWidth,
                        elem.clientHeight);
};

getElementInnerDimensions = getElementInnerDimensions_ie;

function getElementInnerDimensions_getPropertyCSSValue(elem) {
  elem = $(elem);
  var cs = document.defaultView.getComputedStyle(elem, null);
  
  return new Dimensions(
    cs.getPropertyCSSValue('width').getFloatValue(5),   /* 5 = get dimension */
    cs.getPropertyCSSValue('height').getFloatValue(5)); /* in pixels */
};

function getElementInnerDimensions_parseFloat(elem) {
  elem = $(elem);
  var cs = document.defaultView.getComputedStyle(elem, null);
  return new Dimensions(parseFloat(cs.width), parseFloat(cs.height));
};

/**
 * Returns a Rect structure indicating the element's position and
 * dimensions, including borders and padding
 *
 * @param elem Element or ID
 * @return Rect
 *
 */
function getElementOuterGeometry(elem) {
  /* XXX: Implement this function using the W3C specs */
  if(elem.clientWidth) {
    getElementOuterGeometry = getElementOuterGeometry_ie;
  } else {
    throw new OperationNotSupported("Bah!");
  }
  
  return new getElementOuterGeometry(elem);
}

function getElementOuterGeometry_ie(elem) {
  elem = $(elem);
  return new Rect(elem.offsetLeft, elem.offsetTop,
                  elem.offsetWidth, elem.offsetHeight);
}

function setElementPxStyle(elem, name, px) {
  $(elem).style[name] = parseInt(px) + 'px';
}

function setElementPercentStyle(elem, name, percent) {
  $(elem).style[name] = parseFloat(percent) + '%';
}

/**********************************************************
 * Various utility functions                              *
 **********************************************************/
function submit_form(name) {
  document.forms[name].submit();
}

function on_payflowtest_click(ev) {
  var form = document.forms['payment'];
  form.ccnum.value = '4222222222222';
  form.cctype.value = 2;
  return false;
}

function make_XMLHttpRequest() {
  if(browser.ie && browser.ie5) {
    return new ActiveXObject('Msxml2.XMLHTTP');
  } else if(browser.ie) {
    return new ActiveXObject('Microsoft.XMLHTTP');
  } else {
    return new XMLHttpRequest();
  }
}

function titlecase(val) {
  ret = '';
  val = val.split(' ');
  for(var c=0; c < val.length; c++) {
    ret += val[c].substring(0,1).toUpperCase() +
      val[c].substring(1,val[c].length) + ' ';
  }
  return ret;
}

function get_event(e) {
  if(!e) var e = window.event;
  return e;
}

function get_event_target(e) {
  var target;
  var e = get_event(e);
  
  if(e.target) {
    target = e.target;
  } else {
    target = e.srcElement;
  }

  if(target.nodeType == 3) target = target.parentNode;
  return target;
}

function add_elem_to_head(elem) {
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(elem);
}

/* Add the given stylesheet, given as a URL, to the current page */
function add_stylesheet(ss) {
  var le = document.createElement('link');
  le.rel = 'stylesheet';
  le.type = 'text/css';
  le.href = ss;
  add_elem_to_head(le);
}

/* Returns a function which, when executed by onloadhook, will
   add the given stylesheet to the page */
function add_stylesheet_closure(ss) {
  return function() {
    add_stylesheet(ss);
  }
    }

//This code is in the public domain. Feel free to link back to http://jan.moesen.nu/
function sprintf()
{
  if (!arguments || arguments.length < 1 || !RegExp)
  {
    return;
  }
  var str = arguments[0];
  var re = /([^%]*)%(\'.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/;
  var b;
  var a = b = [], numSubstitutions = 0, numMatches = 0;
  while (a = re.exec(str))
  {
    var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4];
    var pPrecision = a[5], pType = a[6], rightPart = a[7];
		
    numMatches++;
    if (pType == '%')
    {
      subst = '%';
    }
    else
    {
      numSubstitutions++;
      if (numSubstitutions >= arguments.length)
      {
          throw Error('dsfa');
      }
//        alert('Error! Not enough function arguments (' + (arguments.length - 1) + ', excluding the string)\nfor the number of substitution parameters in string (' + numSubstitutions + ' so far).');
//      }
      var param = arguments[numSubstitutions];
      var pad = '';
      if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1);
      else if (pPad) pad = pPad;
      var justifyRight = true;
      if (pJustify && pJustify === "-") justifyRight = false;
      var minLength = -1;
      if (pMinLength) minLength = parseInt(pMinLength);
      var precision = -1;
      if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1));
      var subst = param;
      if (pType == 'b') subst = parseInt(param).toString(2);
      else if (pType == 'c') subst = String.fromCharCode(parseInt(param));
      else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0;
      else if (pType == 'u') subst = Math.abs(param);
      else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
      else if (pType == 'o') subst = parseInt(param).toString(8);
      else if (pType == 's') subst = param;
      else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase();
      else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase();
    }
    str = leftpart + subst + rightPart;
  }
  return str;
}

/* Return true of the form field has no value */
function form_field_empty_p(field) {
  if (field.type == 'text' ||
      field.type == 'password' ||
      field.type == 'textarea')
  {
    return field.value == undefined || field.value == '';
  }
  
  if (field.type == 'select-one') {
    return field.value == 0;
  }
}

/* Focus the first field in a form that doesn't have anything in it.
 * form is the form element. */
function select_first_empty_field(form)
{
  for(var i = 0; i < form.elements.length; ++i) {
    var elem = form.elements[i];
    if (form_field_empty_p(elem)) {
      elem.focus();
      break;
    }
  }
}

/**********************************************************
 * Widgets                                               *
 **********************************************************/

/**
 * Base class for a widget that "possesses" an HTML DIV element,
 * giving it behaviors and child elements. Generally,
 * these widgets inherit styles from the main widget,
 * such as width and class, but provide their
 * own styles for interior elements.
 */
var HTMLWidget = Class.extend({});

HTMLWidget.addStatic('possess', function(elem) {
  /* IE DOM elements don't have a prototype, so the best we can do
     is copy all the function objects to the element, sort of
     faking its identity */
  var e = update($(elem), this.prototype);
  this.prototype.construct.apply(
    e, Array.prototype.slice.call(arguments, 1));
  return e;
});

/**
 * A horizontal slider widget
 */
var HorizSlider = HTMLWidget.extend({
    /**
     * Construct a new slider widget
     *
     * @param min Minimum range of slider
     * @param max Maximum range of slider
     */
    construct: function(min, max) {
      arguments.callee.$.construct.call(this);
      // The minimum and maximum values to report to the user of
      // the widget
      this._min = 0;
      this._max = 100;
      
      // The current value of the slider
      this._value = 0;
      
      this._bar = DIV({"class": "sliderBar sliderBarHoriz"});
      this._handle = DIV({"class": "sliderHandle sliderHandleHoriz"});

      addElementClass(this, "sliderWidget sliderWidgetHoriz");
      
      this.appendChild(this._bar);
      this.appendChild(this._handle);
      
      // Bookkeeping for dragging
      this._movesignal = null;
      this._upsignal = null;
      this._dragStartX = 0;
      this._origX = 0;
      this._origValue = null;
      
      connect(this._handle, 'onmousedown', this, this._onHandleDown);
      this._updateSizesAndSlider();
    },
    
    set: function(value, min, max) {
      if(min == undefined) {
        min = this._min;
      }
      
      if(max == undefined) {
        max = this._max;
      }
      
      this._min = min;
      this._max = max;
      
      if(this._value != value) {
        signal(this, 'onchange', value, this._value);
      }
      
      this._value = value;
      this._updateSizesAndSlider();
    },
    
    get: function() {
      return this._value;
    },
    
    _updateSizesAndSlider: function() {
      this._handleSize = getElementOuterGeometry(this._handle).w;
      this._barSize = getElementInnerDimensions(this).w - this._handleSize;
      
      setElementPxStyle(this._handle, 'left', this._valueToCoord(this._value));
    },
    
    _clearDrag: function() {
      if(this._movesignal) {
        disconnect(this._movesignal);
        disconnect(this._upsignal);
      }
      
      this._movesignal = null;
      this._upsignal = null;
      removeElementClass(this._handle, 'sliderHandleDragging');
    },
    
    _valueToCoord: function(value) {
      return (value - this._min + 0.0) * this._barSize
        / (this._max - this._min);
    },
    
    _coordToValue: function(coord) {
      return minimax(this._min, this._max,
                     this._min + (this._max - this._min + 0.0) * (coord + 0.0) / this._barSize);
    },
    
    _onHandleDown: function(e) {
      this._clearDrag();
      this._updateSizesAndSlider();
      
      addElementClass(this._handle, 'sliderHandleDragging');
      capture_dragging(this, this._onDrag);
      
      this._dragStartX = e.mouse().client.x;
      this._origX = this._valueToCoord(this._value);
      this._origValue = this._value;
      
      e.stop();
    },
    
    _onDrag: function(reason, x, y) {
      var deltaX = x - this._dragStartX + this._origX;
      this._value = this._coordToValue(deltaX);
      setElementPxStyle(this._handle, 'left', this._valueToCoord(this._value));
      signal(this, 'onchange', this._value, this._origValue);
      
      if(reason == DRAG_HANDLER_STOP) {
        removeElementClass(this._handle, 'sliderHandleDragging');
      }
    }
  });

