/**
/* form_methods.js
/*
 * @copyright: 2009 by Thomas M. Stambaugh & Zeetix, LLC (http://www.zeetix.com)
 * All rights reserved.
 *
 * Requires:
 *  styes:
 *    validating_contact_form.js: Highlights invalid fields, confirmation formats,
 *                                etc.
 *  javascript:
 *     form_definitions.js: Javascript class definitions. When assemblies exist,
 *                          move all this into an assembly.
 *       form_functions.js: Global functions needed by this.
 *
 * The contents of this file may not be copied, duplicated, or used without the
 * written consent of Zeetix, LLC.
 *
 * Contains the methods (as opposed functions) of classes in forms.
 *
 * Loaded after the class definitions (so that they are available to here) and
 * functions (so that the functions are available to those methods).
 *
**/

/**
 * CSSClass class methods
 */
CSSClass.isElement_inCSSClass_ = function(anElement, aCSSClass) {
  if (typeof anElement == "string") anElement = document.getElementById(anElement);
  if (!anElement) return false;

  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 (!anElement) return;

  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);
  if (!anElement) return;
  anElement.className = anElement.className.replace(new RegExp("\\b" + aCSSClass+"\\b\\s*", "g"), "");
};

/**
 * AbstractFormData instance methods
 */
AbstractFormData.prototype.doFields = function() {
  return new Array('_timeStamp');
};

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;
};

/**
 * Collects the values from aDOMForm
 */
AbstractFormData.prototype.refreshFrom_ = function(aDOMForm) {
  this._timeStamp = Date();
  var aFieldsObject = this.fields();
  for (var aFieldName in aFieldsObject) {
    var aFormElement = aDOMForm[aFieldName];
    if (aFormElement) {
      //If there is a formElement, capture its value
      this[aFieldName] = aFormElement.value;
      }
    }
};

AbstractFormData.prototype.initialize = function() {
  this.doInitialize();
};

/**
 * 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");
    }
};

/**
 * ContactFormData class methods
 */

/*
 * Required fields have a value of true, optional fields false.
 */
ContactFormData._fields = {
  'emailAddress': true,
  'name': true,
  'phone': true,
  'subject': false,
  'message': false
  };
ContactFormData._formName = 'contactForm';

ContactFormData.create = function(){
  var answer = new ContactFormData();
  answer.initialize();
  return answer;
};

/**
 * ContactFormData instance methods
 */
ContactFormData.prototype.doFields = function () {
  var answer = ContactFormData._fields;
  return answer;
};

ContactFormData.prototype.doFormName = function() {
  return ContactFormData._formName;
};

/**
 * FormAdapter class methods
 */

/**
 * Currently hard-coded for the ContactForm.
 * Silently do nothing if aDOMForm is empty.
**/
FormAdaptor.on_ = function(aDOMForm) {
  var answer = new FormAdaptor();
  answer._implementation = aDOMForm;
  answer._formData = ContactFormData.create();
  if (aDOMForm) {
    aDOMForm.onsubmit = function(){return answer.onSubmitHandler()};
    };
  answer.initialize();
  return answer;
};

/**
 * FormAdapter instance methods
 */

/**
 * Turn the labels of the invalid fields red.
 */
FormAdaptor.prototype.highlightInvalidFieldsIn_ = function (anInvalidFieldNameArray) {
  for (var i=0; i<anInvalidFieldNameArray.length; i++) {
    var anInvalidFieldName = anInvalidFieldNameArray[i];
    var anElementId = anInvalidFieldName + 'Label';
    CSSClass.toElement_addCSSClass_(anElementId, "invalid");
    }
};

FormAdaptor.prototype.bogusFieldNames = function () {
  var answer = new Array(0);
  var aFieldsObject = this._formData.fields();
  var aFieldName;
  var aFormElement;
  for (aFieldName in aFieldsObject) {
    aFormElement = this._implementation[aFieldName];
    //If the field is required, then insist that it be present and non-null.
    if (aFieldsObject[aFieldName] && (!aFormElement || aFormElement.value == '')) {
      answer.push(aFieldName);
      }
    };
  return answer;
};

FormAdaptor.prototype.validateForm = function() {
  var aBogusNameArray = this.bogusFieldNames();
  if (aBogusNameArray.length != 0) {
    //Complain and block submission
    this.highlightInvalidFieldsIn_(aBogusNameArray);
    this.complain_('Please complete the fields marked in red and resubmit.');
    answer = false;
    }
  else {
    answer = true;
    }
  return answer;
};

/**
 * Answers a formElement encoded as a URI component.
 */
FormAdaptor.prototype.uriComponentAt_ = function(anElementIndex) {
  var aFormElement = this._implementation.elements[anElementIndex];
  var answer = aFormElement.name + '=' + encodeURIComponent(aFormElement.value);
  return answer;
};

/**
 * Answers the receiver as a queryString
 *
 * For now, just snarf the DOMForm. Soon, refresh and collect
 * data from the formData of the receiver. This is so that only
 * known fields are collected and sent.
 */
FormAdaptor.prototype.asQueryString = function(){
  var answer="";
  var anElementCount =  this._implementation.elements.length;
  if (anElementCount > 0) {
    answer += this.uriComponentAt_(0);
    };
  for (var i = 1; i < anElementCount; i++){
    answer += "&" + this.uriComponentAt_(i);
    };
  return answer;
};

FormAdaptor.prototype.onSubmitHandler = function() {
  this.resetForm();
  var isValid = this.validateForm();
  var aPostData = null;
  if (isValid) {
    //Go ahead and submit the form
    aPostData = this.asQueryString();
    this._request.submit_(aPostData);
    }
  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");
};

/**
 * Handles the response captured in anHttpRequest.
 * Collects and parse anJSONString (returned by the HttpPostRequest).
 *
 * This is run inside a closure in HttpRequest. That means that
 * "this" is bound to something wierd.
 *
 * Why isn't anException bound to anything when the try block barfs?
 */
FormAdaptor.prototype.handleResponseFrom_ = function(anHttpRequest) {
  try {
    var aJSONString = anHttpRequest._responseText;
    var aThing = JSON.parse(aJSONString);
    if (!aThing || !aThing._wasSuccessful) {
      this.complain_(aThing._complaint);
      }
    else {
      this.confirm_(aThing._response);
      }
    }
  catch (anException) {
    anHttpRequest._adaptor.complain_(anException.toString())
    }
};

/**
 * Answers a closure that, when evaluated, invokes a method of
 * the receiver with the responding HttpRequest as its argument
**/
FormAdaptor.prototype.responseHandler = function (){
  var aReceiver = this;
  var answer = function(anHttpRequest) {aReceiver.handleResponseFrom_(anHttpRequest)};
  return answer;
}

FormAdaptor.prototype.doInitialize = function() {
  this.resetForm();
  this._request = HttpPostRequest.createUrl_isAsynch_responseHandler_postData_(
    "/cgi-bin/formHandler.cgi", true, this.responseHandler(), null);
  this._request._adaptor = this;
};

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();
};

/**
 * FormApplication class methods
 */

FormApplication.create = function() {
  var answer = new FormApplication();
  answer.initialize();
  return answer;
};

/**
 * FormApplication instance methods
**/

FormApplication.prototype.initialize = function() {
  var aFormElement = document.getElementById('contactForm');
  this._form = FormAdaptor.on_(aFormElement);
};

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);
};

