/**
/* forms.js
/*
 * @copyright: 2009 by Thomas M. Stambaugh & Zeetix, LLC (http://www.zeetix.com)
 * All rights reserved.
 *
 * The contents of this file may not be copied, duplicated, or used without the
 * written consent of Zeetix, LLC.
 *
 * Javascript routines used by forms on various Zeetix family sites.
 *
 * This has *NOT* yet been folded into the Zeetix framework. Specifically,
 * FormApplication should descend from It (or even Application).
 *
 * AbstractFormData and its descendants describe the data of a form. This is a
 * model object exchanged with the server.
 *
 * FormAdapter wraps the Form class provided by the DOM.
 *
 * FormApplication provides the behavior expected by my client.
 *
**/

/* functions and utilities */
/**
 * This section handles the loading and json stuff.
 */
var _hasBodyOnLoadBeenCalled = false;

/**
 * Do this so that when the asynchronous query runs, it has an empty page to
 * put stuff in.
**/
window.document.vpage = '<?xml version="1.0"?><page></page>';

function isLoaded() {
  //Answer true if the onload handler has been called, else false.
  return _hasBodyOnLoadBeenCalled;
}

function _handleOnloadEventBasic(){
  // Record the firing of onload from the body
  _hasBodyOnLoadBeenCalled = true;
}

/* dynamic style manipulation */
var CSSClass = {};

CSSClass.isElement_inCSSClass_ = function(anElement, aCSSClass) {
  if (typeof anElement == "string") anElement = document.getElementById(anElement);
  var classes = anElement.className;
  if (!classes) return false;
  if (classes == aCSSClass) return true;
  return anElement.className.search("\\b" + aCSSClass + "\\b") != -1;
  };

CSSClass.toElement_addCSSClass_ = function(anElement, aCSSClass) {
  if (typeof anElement == "string") anElement = document.getElementById(anElement);
  if (CSSClass.isElement_inCSSClass_(anElement, aCSSClass)) return;
  if (anElement.className) aCSSClass = " " + aCSSClass;
  anElement.className += aCSSClass;
  };

CSSClass.fromElement_removeCSSClass_ = function(anElement, aCSSClass) {
  if (typeof anElement == "string") anElement = document.getElementById(anElement);
  anElement.className = anElement.className.replace(new RegExp("\\b" + aCSSClass+"\\b\\s*", "g"), "");
  };

/**
 * Handle any pending load
 */
function _checkLoad() {
  if (window._pending) {
    var pending = window._pending;
    window._pending = null;
    window._formApplication.loadVPage.apply(window._formApplication, pending);}
}

/**
 * The doc is loaded, see if any deferred loads exist
 */
function onloadHandler() {
  // An onload event has been received from the body.
  _handleOnloadEventBasic();

  if (window._pending) {
      _checkLoad();
      }
}

/**
 * The Abstract ancestor of the FormData family
 */
function AbstractFormData(){
}

AbstractFormData.prototype.handleInvalidField_ = function (aFieldName){
  this._invalidFieldNames.push(aFieldName);
}

AbstractFormData.prototype.doFields = function() {
  return this.subclassResponsibility();
}

AbstractFormData.prototype.fields = function() {
  return this.doFields();
}

AbstractFormData.prototype.doFormName = function() {
  return this.subclassResponsibility();
}

AbstractFormData.prototype.formName = function() {
  return this.doFormName();
}

AbstractFormData.prototype.doInitialize = function() {
  this._timeStamp = null;
  this._invalidFieldNames = new Array(0);
  this._timeStamp = Date();
  var aFieldsObject = this.fields();
  for (var aFieldName in aFieldsObject) {
      var aFormElement = document.forms[this.formName()][aFieldName];
      //If the field is required, then insist that it present and non-null.
      if (aFieldsObject[aFieldName] && (!aFormElement || aFormElement.value == '')) {
          this.handleInvalidField_(aFieldName);
          }
      else {
          this[aFieldName] = aFormElement.value;
          }
      };
}

AbstractFormData.prototype.initialize = function() {
  this.doInitialize();
}

AbstractFormData.prototype.doFinalize = function() {
  for (var aFieldName in this) {
    this[aFieldName] = null;
    }
}

/**
 * Fix an apparent core leak, the _invalidFieldNames array is
 * apparently not getting garbage collected.
 */
AbstractFormData.prototype.finalize = function() {
  this.doFinalize();
}

AbstractFormData.prototype.doFields = function () {
  answer = this.subclassResponsibility();
  return answer;
}

AbstractFormData.prototype.doFormName = function() {
  answer = this.subclassResponsibility();
  return answer;
}

/**
 * Turn the labels of the invalid fields red.
 */
AbstractFormData.prototype.highlightInvalidFields = function () {
  for (var i=0; i<this._invalidFieldNames.length; i++) {
    var anInvalidFieldName = this._invalidFieldNames[i];
    var anElementId = anInvalidFieldName + 'Label';
    CSSClass.toElement_addCSSClass_(anElementId, "invalid");
    }
}

AbstractFormData.prototype.resetFields = function () {
  var aFieldName;
  var anElementId;
  for (aFieldName in this.fields()) {
    anElementId = aFieldName + 'Label';
    CSSClass.fromElement_removeCSSClass_(anElementId, "invalid");
    }
}

/**
 * The ContactFormData
 */
function ContactFormData(){}

/*
 * Required fields have a value of true, optional fields false.
 */
ContactFormData._fields = {
  'emailAddress': true,
  'subject': false,
  'message': false
  }
ContactFormData._formName = 'contactForm';
ContactFormData.prototype = new AbstractFormData();
ContactFormData.prototype.constructor = ContactFormData;

ContactFormData.create = function(){
  var answer = new ContactFormData();
  answer.initialize();
  return answer;
}

ContactFormData.prototype.doFields = function () {
  var answer = ContactFormData._fields;
  return answer;
}

ContactFormData.prototype.doFormName = function() {
  return ContactFormData._formName;
}

/**
 * The FormAdaptor class
 *
 * Use an instance of this to wrap and interact with
 * the DOM form.
 */
FormAdaptor = function() {}

/**
 * Not allowed set the target of a form in xhtml, so do it here instead.
 */
FormAdaptor.on_ = function(aDOMForm) {
  var answer = new FormAdaptor();
  answer._implementation = aDOMForm;
  aDOMForm.onsubmit = function(){return answer.onSubmitHandler()};
  aDOMForm.target = "vp"
  answer.initialize();
  return answer;
}

FormAdaptor.prototype.onSubmitHandler = function() {
  this._formData = ContactFormData.create();
  var answer = false;
  this.resetForm();
  if (this._formData._invalidFieldNames.length != 0) {
    //Complain and block submission
    this._formData.highlightInvalidFields();
    this.complain_('Please complete the fields marked in red and resubmit.');
    answer = false;
    }
  else {
    answer = true;
    }
  this._formData.finalize();
  this._formData = null;
  return answer;
}

/**
 * Displays the current contents of the messageDiv in the complaint style.
 */
FormAdaptor.prototype.showComplaint = function() {
  var aMessageElement = document.getElementById('messageDiv');
  CSSClass.fromElement_removeCSSClass_(aMessageElement, "confirmation");
  CSSClass.fromElement_removeCSSClass_(aMessageElement, "invisible");
  CSSClass.toElement_addCSSClass_(aMessageElement, "complaint");
  CSSClass.toElement_addCSSClass_(aMessageElement, "visible");
}

/**
 * Hides the current contents of the messageDiv
 */
FormAdaptor.prototype.hideMessage = function() {
  var aMessageElement = document.getElementById('messageDiv');
  CSSClass.fromElement_removeCSSClass_(aMessageElement, "visible");
  CSSClass.toElement_addCSSClass_(aMessageElement, "invisible");
}

/**
 * Fills the messageDiv and displays it as a complaint.
 */
FormAdaptor.prototype.complain_ = function (aComplaint) {
  var aMessageElement = document.getElementById('messageDiv');
  aMessageElement.innerHTML=aComplaint;
  this.showComplaint();
}

/**
 * Fills the messageDiv and displays it as a confirmation.
 */
FormAdaptor.prototype.confirm_ = function (aConfirmationMessage) {
  var aMessageElement = document.getElementById('messageDiv');
  aMessageElement.innerHTML=aConfirmationMessage;
  this.showConfirmation();
}

/**
 * Displays the current contents of the messageDiv in the confirmation style.
 */
FormAdaptor.prototype.showConfirmation = function() {
  var aMessageElement = document.getElementById('messageDiv');
  CSSClass.fromElement_removeCSSClass_(aMessageElement, "complaint");
  CSSClass.fromElement_removeCSSClass_(aMessageElement, "invisible");
  CSSClass.toElement_addCSSClass_(aMessageElement, "confirmation");
  CSSClass.toElement_addCSSClass_(aMessageElement, "visible");
}

FormAdaptor.prototype.doInitialize = function() {
  this.resetForm();
}

FormAdaptor.prototype.initialize = function() {
  this.doInitialize();
}

/**
 * Hides the messageDiv and turns off any invalid notifications.
 */
FormAdaptor.prototype.resetForm = function () {
  this.hideMessage();
  this.resetFields();
  }

FormAdaptor.prototype.resetFields = function () {
  if (this._formData) this._formData.resetFields();
  }

/**
 * The FormApplication class
 */
FormApplication = function() {}

FormApplication.create = function() {
  var answer = new FormApplication;
  answer.initialize();
  return answer;
  }

FormApplication.prototype.initialize = function() {
  var aFormElement = document.getElementById('contactForm');
  this._form = FormAdaptor.on_(aFormElement);
  }

FormApplication.prototype.loadVPage = function(aJSONString, aVPageDocument, aLocation, aBoolean) {
  if (!isLoaded()) {
    window._pending = [];
    for (var i = 0; i < arguments.length; ++i) {
      window._pending.push(arguments[i])}}
  else {
    this.loadJSON(aJSONString, aVPageDocument, aLocation, aBoolean);
  }
}

/**
 * Collect and parse anJSONString (provided by the vp iframe).
 */
FormApplication.prototype.loadJSON = function(aJSONString, aDocument, aLocation, aBoolean) {
  this.vpageDoc=aDocument;
  this.vpageUrl=aLocation;
  try {
    var aThing = JSON.parse(aJSONString);
    if (!aThing || !aThing.wasSuccessful) {
      this.complain_(aThing.message);
      }
    else {
      this.confirm_(aThing.message);
      }
    }
  catch (anException) {
    this.complain_(anException)
    }
}

FormApplication.prototype.confirm_ = function(aConfirmationMessage) {
  this._form.confirm_(aConfirmationMessage);
  //var aTrackingString = aFormDiv.form_sku.value.split(" ").join("_");
  //urchinTracker('/form/confirm/' + aTrackingString);
}

/**
 * Turn on the complaint
 */
FormApplication.prototype.complain_ = function (aComplaint) {
  this._form.complain_(aComplaint);
  }
