/****************************************************************************
 * Interwoven Form Builder Toolbox
 *
 * Author: Rex Staples
 * Date:   26-May-2006
 *
 * Copyright 2006 Interwoven, Inc. All Rights Reserved.
 *
 * Interwoven trademarks, service marks, logos, and taglines are
 * exclusively owned by Interwoven, Inc.
 *
 *
 * OVERVIEW
 *
 * The IwFormBuilderToolbox is an object with four public methods:
 *
 *   submit([form], [validate])
 *
 *   reset()
 *
 *   ajaxSubmit()
 *
 *   trySubmit()
 *
 * When called, these functions either clone the passed (optional) form
 * or clone the primary form builder toolbox form.
 *
 * They then scan the document for form fields matching the form builder
 * toolbox class name (see the moniker constant below) and append them
 * to the cloned form.
 *
 * If passed a submitButton, its label/value pair are appended to the form
 * clone, and the form is then either submitted or used in an ajax request.
 *
 ****************************************************************************/
var IwFormBuilderToolbox = new Object();


/**
 * Aggregates the form builder toolbox fields into a form and submits it.
 *
 * @param form (optional) the form to submit (instead of primary form)
 * @param validate (optional) calls validation logic before submitting form
 */
IwFormBuilderToolbox.submit = function(form, validate)
{
  if (null != form && typeof form != 'form')
  {
	form = document.getElementById(form);
  }

  if (true != validate || IwFormBuilderToolbox.validateFields())
  {
    if (form || IwFormBuilderToolbox.doSubmitFormOnCRLF)
    {
      IwFormBuilderToolbox.getSubmissionForm(form).submit();
    }
  }
  
  return false;
}


/**
 * Resets all the form builder toolbox forms.
 */
IwFormBuilderToolbox.reset = function(form)
{
  var forms = document.forms;

  for (var i=0; i < forms.length; i++)
  {
    if (forms[i].className.match(IwFormBuilderToolbox.classNameRegExp))
    {
      if(form && form.name)
      {
        if(form.name != forms[i].name)
          continue;
      }
      forms[i].reset();
    }
  }
  
  return false;
}


/**
 * Takes a form and performs an ajax submit on it.
 *
 * @param form (optional) the form to submit (instead of primary form)
 */
IwFormBuilderToolbox.ajaxSubmit = function(form)
{
  if (!form && !IwFormBuilderToolbox.doSubmitFormOnCRLF)
    return false;

  var form = IwFormBuilderToolbox.getSubmissionForm(form, submitButton);
  var callback = function(xml,txt){alert(txt);}; // TODO: do something!
  var req = new AjaxRequest(form.attributes.getNamedItem('action').value, callback)
  req.setParameters(form);
  req.submit();
}


/**
 * Adds a config object to the validator registry.  Any control that needs
 * to be validated upon submit should call this method.
 *
 * @param config (required) the hash object describing the validation parameters
 */
IwFormBuilderToolbox.addValidatorField = function(config)
{
  IwFormBuilderToolbox.validatorRegistry.push(config);
}


/**
 * Simple debug test to see if the Form is Valid for submitting
 */
IwFormBuilderToolbox.trySubmit = function()
{
  if (IwFormBuilderToolbox.validateFields())
  {
    alert('Form is okay for submission.');
  }
}


/****************************************************************************
 ***** HELPER FUNCTIONS *****************************************************
 ****************************************************************************/

/**
 * Returns a clone of the form builder toolbox submission primary submission
 * form. The children of the form are assembled from input, select, and
 * textarea tags whose class names match the iw-form-builder-toolbox class.
 *
 * @param formPrototype (optional) form to clone (overrides the primary form)
 * @return a form builder toolbox form ready for submission
 */
IwFormBuilderToolbox.getSubmissionForm = function(formPrototype)
{
  var forms = document.forms;
  var pageform = (formPrototype) ? formPrototype : forms[IwFormBuilderToolbox.moniker];
  var form = pageform.cloneNode(true);
  var fieldNames = new Array();
  var fieldCount = 0;
  form.style.display = 'none';

  for (var i=0; i < forms.length; i++)
  {
    if (forms[i].className.match(IwFormBuilderToolbox.classNameRegExp))
    {
      if(form.name)
      {
        if(form.name != forms[i].name)
          continue;
      }
			
      var fields = forms[i].elements;
      
      for (var j=0; j<fields.length; j++)
      {
        if (fields[j].disabled) continue;

        if (forms[i]!=pageform)
        {
          IwFormBuilderToolbox.appendFormField(form, fields[j]);
        }
        fieldNames[fieldCount] = fields[j].name;
        fieldCount++;
      }

    }
    
  }

  /*
   * Create a hidden field containing the name of all the fields
   * submitted in this form
   */
  IwFormBuilderToolbox.appendHiddenField(form, "form-builder-field-names-list", fieldNames);


  document.body.appendChild(form);
  return form;
}


/**
 * Appends to the form one or more hidden fields with name/value pairs from
 * the passed form field.
 *
 * @param form the form to which the form field is appended
 * @param formField the field whose value(s) are appeneded to the form
 */
IwFormBuilderToolbox.appendFormField = function(form, formField)
{
  if(typeof AjaxRequest != 'undefined')
  {
    switch(formField.type)
    {
      case AjaxRequest.FORM_FIELD_TEXT:
      case AjaxRequest.FORM_FIELD_HIDDEN:
      case AjaxRequest.FORM_FIELD_TEXTAREA:
        IwFormBuilderToolbox.appendHiddenField(form, formField.name, formField.value);
        break;
      
      case AjaxRequest.FORM_FIELD_RADIO:
      case AjaxRequest.FORM_FIELD_CHECKBOX:
        if (formField.checked)
        {
          IwFormBuilderToolbox.appendHiddenField(form, formField.name, formField.value);
        }
        break;
      
      case AjaxRequest.FORM_FIELD_SELECT_ONE:
      case AjaxRequest.FORM_FIELD_SELECT_MULTIPLE:
        var options = formField.options;
  
        for (var j=0; j < options.length; j++)
        {
          if (options[j].selected)
          {
            IwFormBuilderToolbox.appendHiddenField(form, formField.name, options[j].value);
          }
        }
        break;
    }
  }
}


/**
 * Appends to the form a hidden field with the passed name/value pair.
 *
 * @param form the form to which the hidden field is appended
 * @param name the name of the hidden form field
 * @param value the value of the hidden form field
 */
IwFormBuilderToolbox.appendHiddenField = function(form, name, value)
{
  var input;

  if (IwFormBuilderToolbox.isIE) // MSIE bug
  {
    input = document.createElement('<input name="'+name+'">');
  }
  else
  {
    input = document.createElement('input');
    input.name = name;
  }

  input.type = 'hidden';
  input.value = value;
  form.appendChild(input);
}

/**
 * Autofits the labels/fields in the named form using the widest form
 * field.
 *
 * @param formId the id of the form to autofit
 */
IwFormBuilderToolbox.autoFitFormFields = function(formId)
{
  var form = document.forms[formId];

  var divs = form.getElementsByTagName('div');
  var size = 0;
  var RPAD = 10; // right padding for the label

  for (var i=0; i<divs.length; i++)
  {
    if (divs[i].className.match(/\bform-label\b/))
    {
      size = Math.max(size, IwFormBuilderToolbox.getOffsetWidth(divs[i]));
    }
  }

  document.write(IwFormBuilderToolbox.autoFitCss
    .replace(/%ID%/g, formId).replace(/%WIDTH%/g, RPAD+size));

  if (IwFormBuilderToolbox.isIE)
  {
    // MSIE loses the scrollbars upon typing in a textarea
    // whose width is 100% (defined in the css stylesheet),
    // so set the width explicitly from the offset width
    // while focused and remove the width when blurred.
    var tags = form.getElementsByTagName('textarea');
    for (var i=0; i<tags.length; i++)
    {
      tags[i].onfocus = IwFormBuilderToolbox.setTextareaWidthMSIE;
      tags[i].onblur = IwFormBuilderToolbox.setTextareaWidthMSIE;
    }
    
    // MSIE will not auto-fit if inside an iframe
    if (top != self)
    {
      IwFormBuilderToolbox.repaintMSIE(formId);
    }
  }
}


/**
 * Gets the offset width of the passed element. MSIE reports an offset
 * width of zero if nested within a table (e.g. tabled layout). To get
 * around this the passed element's content is inserted into a node
 * outside of the nested table and calculated.
 *
 * @param obj the element to whose width is measured
 */
IwFormBuilderToolbox.getOffsetWidth = function(obj)
{
  if ('' == obj.innerHTML) return 0;
  
  var width = obj.offsetWidth;
  
  if (!width)
  {
    var offsetNode = IwFormBuilderToolbox.getOffsetCalculationNode();
    offsetNode.innerHTML = obj.innerHTML;
    width = offsetNode.offsetWidth;
  }
  
  return width;
}


/**
 * Gets an invisible node outside any table structure used to calculate
 * an offset width.
 */
IwFormBuilderToolbox.getOffsetCalculationNode = function()
{
  var id = 'iw-offset-calculation-node';
  var node = document.getElementById(id);
  
  if (!node)
  {
    node = document.createElement('div');
    node.style.visibility = 'hidden';
    node.style.position = 'absolute';
    node.id = id;

    if (document.body.firstChild)
    {
      document.body.insertBefore(node, document.body.firstChild);
    }
    else
    {
      document.body.appendChild(node);
    }
  }
  
  return node;
}


/**
 * MSIE will size a textarea such that the right border and scrollbars
 * disappear once a user starts typing if the textarea width is 100%.
 */
IwFormBuilderToolbox.setTextareaWidthMSIE = function()
{
  this.style.width = ('blur' == window.event.type) ? '' : this.offsetWidth;
}


/**
 * MSIE has a painting bug where auto-fit form fields will not adjust if
 * rendered within an iframe.  This function jiggles the form padding to
 * cause the iframe to repaint correctly.
 *
 * @param formId the id of the form to jiggle
 * @param doRestore whether to restore the form to its original state
 */
IwFormBuilderToolbox.repaintMSIE = function (formId, doRestore)
{
  var form = document.forms[formId];
  
  if (doRestore)
  {
    form.style.paddingRight = '';
  }
  else
  {
    form.style.paddingRight = '1px';
    setTimeout('IwFormBuilderToolbox.repaintMSIE("'+formId+'",true);',1);
  }
}


/**
 * Validates any fields added to the Registry.  Returns true if all fields
 * pass.  Otherwise, it alerts the user with the issue and returns a false.
 */
IwFormBuilderToolbox.validateFields = function()
{
  for (var i=0; i < IwFormBuilderToolbox.validatorRegistry.length; i++)
  {
    var config = IwFormBuilderToolbox.validatorRegistry[i];    
    var input = document.getElementById(config['id']);
     
    // TODO: not all input values are fetched this way, you will have
    //       to switch on the input.type, but we have lots of examples
    //       of this in both the AjaxRequest and IwFormBuilderToolbox     
    var trimmedValue = input.value.replace(/^\s*|\s*$/, '');
     
    // TEST REQUIRED     
    if (config['required'] && '' == trimmedValue)
    {
      input.focus();
      alert(config['label'] + ' is a required field.');
      return false;
    }

    // TEST MIN STRING LENGTH
    var mLen = config['minLength'];    
    if (!isNaN(mLen) && trimmedValue.length < mLen)
    {
      input.focus();
      alert(config['label'] + ' may not be shorter than ' + mLen + ' characters.');
      return false;
    } 

    // TEST MAX STRING LENGTH
    mLen = config['maxLength'];   
    if (!isNaN(mLen) && trimmedValue.length > mLen)
    {
      input.focus();
      alert(config['label'] + ' may not be longer than ' + mLen + ' characters.');
      return false;
    }

    // TEST MATCHING A MASK (Only for non-blank values) 
    if (config['inputMask'] && '' != trimmedValue && !trimmedValue.match(config['inputMask']))
    {
      input.focus();     
      var msg = config['label']
              + ' does not match the required pattern';
      msg += (config['maskHelp']) ? ' ' + config['maskHelp'] : '';
      msg += '.';
       
      alert(msg);
      return false;
    }
  }

  return true;
}


/****************************************************************************
 ***** CONSTANTS ************************************************************
 ****************************************************************************/

/**
 * unique moniker for the form builder toolbox
 */
IwFormBuilderToolbox.moniker = 'iw-form-builder-toolbox';

/**
 * regular expression that identifies a form builder toolbox field
 */
IwFormBuilderToolbox.classNameRegExp = new RegExp('\\b' + IwFormBuilderToolbox.moniker + '\\b');

/**
 * true if this is a MSIE browser
 */
IwFormBuilderToolbox.isIE = (navigator.userAgent.indexOf('MSIE') != -1);

/**
 * Auto-fit css template (replace form id and width tokens)
 */
IwFormBuilderToolbox.autoFitCss = '<style type="text/css">'
  + 'form#%ID% .form-field div.form-label {width:%WIDTH%;}'
  + 'form#%ID% .form-field div.form-input {margin-left:-%WIDTH%;}'
  + 'form#%ID% .form-field div.form-input div {margin-left:%WIDTH%;}'
  + '</style>';

/**
 * whether pressing CRLF in a text input should submit to the primary form
 */
IwFormBuilderToolbox.doSubmitFormOnCRLF = false;

/**
 * Validation Registry
 */
IwFormBuilderToolbox.validatorRegistry = [];

