document.disableRequired = false; //Set this to true to disable the "required field"-check. (For debugging)
document.fields = new Object();   //Global Map<fieldId,Field>
document.options = new Object();  //Global Map<fullOptionId,Option> (fullOptionId = fieldId +"_"+ optionId)
document.fieldList = new Array(); //Global Array[Field]; field list in creation order

//external function (defined in global_functions.js): registerChanges()

//STATIC FUNCTIONS:

//Check all fields and save in case of success.
function save(){
  var checkResult = null;
  //var retorno = true;
  //alert("save");
  checkResult = checkAllFields();
  //alert("ckecked");
  if (checkResult=="") {
    for(id in document.fields)getFieldById(id).preSave();
    clearChanges();
    document.form1.submit();
    }
  else{
    alert(translate("data_not_saved")+"\n"+translate("please_correct")+"\n"+
    checkResult+"\n\n("+translate("eRegistry_name")+")");
	//retorno = false;
    }
	//return retorno;
  }
  
function saveAction(cadAction){
  var checkResult = null;
  //var retorno = true;
  //alert("save");
  checkResult = checkAllFields();
  //alert("ckecked");
  if (checkResult=="") {
    for(id in document.fields)getFieldById(id).preSave();
    clearChanges();
	document.form1.action = document.form1.action + cadAction;
	//alert(document.form1.action);
    document.form1.submit();
    }
  else{
    alert(translate("data_not_saved")+"\n"+translate("please_correct")+"\n"+
    checkResult+"\n\n("+translate("eRegistry_name")+")");
	//retorno = false;
    }
	//return retorno;
  }

//For debugging.
function pingFc(){
  errorAlert("ping: field_control.js");
  }

//Check all fields until or until the first field that returns a
//message. That message is returned (as String) and the field is highlighted.
//The empty string is return if the check of all fields succeeded.
function checkAllFields(){
  var result = "";
  var i=0;
  //alert(document.fieldList.length);
  while (i<document.fieldList.length && result=="") {
    var field = document.fieldList[i];
    //alert("checking "+i+field.getTitle());
    check = field.check();
    if(check!=""){
      //result= field.id+" "+check;
      result= check;
      field.highlight();
      }
    i++;
    }
  return (result);
  }

//For debugging:
function listAllFields(){
  var result = "";
  for(id in document.fields){
    result = result+"["+id+":"+getFieldById(id).name+"] ";
    }
  return (result);
  }

function getFieldById(id){
  var result = document.fields[id];
  if(!result){
    errorAlert("Field \""+id+"\" not registered.\nExisting fields: "+listAllFields());
    }
  return (result);
  }

function errorAlert(msg){
  alert(msg);
  }



// PROTOTYPE FIELD (abstract) ========================================================

// ATTRIBUTES:

Field.prototype.id = null;
Field.prototype.name = null;
//element: The corresponding DOM/HTML input element. If there is no element, redefine
//readFromDocument(), writeToDocument(), preSave() and initialize().
Field.prototype.element = null;
//Null is not permitted. Must contain always a String. Normally "" means initial.
//Only exception: It is not used at all for multiple fields.
Field.prototype.value = "";
Field.prototype.previousValue = null;//guarda el valor del campo cuando no esta visible
//outerElement: The HTML element that contains the whole field, including the
//header etc.. Used for controlling the visibility. May be null only for hidden fields.
Field.prototype.outerElement = null; //The HTML element that contains the whole field,
Field.prototype.hidden = false; //A hidden field (never visible).
Field.prototype.readonly = false;
Field.prototype.required = false;
Field.prototype.regex = null; //Not checked if null.
Field.prototype.format = null; //Description of the format for user message
Field.prototype.normStripSpaces = false; //quit leading and trailing whitespace
Field.prototype.normQuitSpaces = false; //quit all whitespace
Field.prototype.normUpperCase = false; //convert to uppercase
Field.prototype.normLowerCase = false; //convert to uppercase
Field.prototype.normCutLength = false; //cut at right end to limit length
// min/max-Attributes: ignored if null
// xxFromField: Another field sets the limit. Not checked if the other field is not valid or
// is initial.
Field.prototype.minValue = null;
Field.prototype.minValueFromField = false;
Field.prototype.maxValue = null;
Field.prototype.maxValueFromField = false;
Field.prototype.minLength = null;
Field.prototype.maxLength = null;
Field.prototype.computed = false;
Field.prototype.highlighted = false; //Highlight state
Field.prototype.dependsOnFields = null; //Map<fieldId,DepInfo>
Field.prototype.dependantFields = null; //Map<fieldId,field>
Field.prototype.displayed = true; //estado de visibilidad actual
Field.prototype.sourceFields = null; //Map<fieldId,field>; for computed fields
Field.prototype.depends_on_condition = "And"

//METHODS:

//addSourceField(String fieldId) (public void)
//For computed fields:
Field.prototype.addSourceField = function(fieldId){
  this.sourceFields[fieldId]=getFieldById(fieldId);
  getFieldById(fieldId).addDependantField(this);
  }

//preSave() (protected void)
//Writes internal value to HTML, without the usual formatting done by
//formatForDisplay. Deregisters event handler before. Done imediately
//before saving.
Field.prototype.preSave = function(){
  this.element.onchange = function(){};
  this.writeToDocument(this.value);
  }

//readFromDocument() (protected V)
//Reads the HTML value.
Field.prototype.readFromDocument = function(){
  if(!this.element) errorAlert("Element not initialized ("+this.name+").");
  return (this.element.value);}

//writeToDocument(V value) (protected void)
//Writes a value to HTML
Field.prototype.writeToDocument = function(value){
  if(!this.element) errorAlert("Element not initialized ("+this.name+").");
  this.element.value = value;}

//formatForDisplay(V value) (protected V)
//Format conversion necessary if the internal format and displayed format are different.
//E.g.: Date: internal format: 2006-04-01, display format: 01/04/2006
Field.prototype.formatForDisplay = function(value){return (value);}

//setValue(V value) (public void)
//For programmatic change of field value:
Field.prototype.setValue = function(value){
  //alert("Setting value of field "+this.id+" to:"+value+"."+(value==""));
  normValue = this.normalize(value);
  if(normValue !== this.value){
    this.value = normValue;
    this.writeToDocument(this.formatForDisplay(normValue));
    this.updateDependantFields();
    }
  }

//setDependsOn(String fieldId, V value) (public void)
//The field will only be visible, if the the field identified by fieldId (String) is
//also visible and has the value specified by value.
Field.prototype.setDependsOn = function(fieldId, value){
  //alert(this.name+" depends on field "+fieldId+", value "+value);
  this.getDependsOnFieldInfo(fieldId).values.push(value);
  }

Field.prototype.setDependsOnFilled = function(fieldId){
  this.getDependsOnFieldInfo(fieldId).filled = true;
  }

Field.prototype.getDependsOnFieldInfo = function(fieldId){
  //alert(this.name+" depends on field "+fieldId+", value "+value);
  if(this.dependsOnFields==null) this.dependsOnFields = new Object();
  var df = this.dependsOnFields[fieldId];
  if(df==null){
    this.dependsOnFields[fieldId] = new Object();
    df = this.dependsOnFields[fieldId];
    df.fieldId = fieldId;
    df.field = getFieldById(fieldId);
    df.values = new Array();
    df.filled = false;
    df.field.addDependantField(this);
    }
  return (df);
  }

//addDependantField(Field field) (private void)
//A dependant field must be registered so that it can be updated if any change in
//visibility or value of this field occurs.
Field.prototype.addDependantField = function(field){
  if(!this.dependantFields)errorAlert("Internal Error 181.");
  this.dependantFields[field.id]=field;
  }

//compute() (protected V)
//For computed fields:
Field.prototype.compute = function(){errorAlert("Error: compute function not set.");}

//normalize(Value v) (protected V)
//Normalization means to transform different valid formats of some data in a unified
//standard format. E.g. the normalized format of the number 2 3 4. 6 1 2,05 would be
//234612.05. If v doesn´t correspond to any valid format, the function returns v without change
//or applying only partial normalization.
Field.prototype.normalize = function(v){
  var v = new String(v);
  if(this.normStripSpaces){v = v.replace(/^\s+/,"");v = v.replace(/\s+$/,"");}
  if(this.normQuitSpaces){v = v.replace(/\s/g, "");}
  if(this.normUpperCase){v = v.toUpperCase();}
  if(this.normLowerCase){v = v.toLowerCase();}
  if(this.normCutLength)
    if(this.maxLength!=null) if (v.length > this.maxLength) v = v.substring(0, this.maxLength);
  return (this.normalizeSpecific(v));
  }

//normalizeSpecific(Value v) (protected V)
//Hook for subclasses. Must return a String.
Field.prototype.normalizeSpecific = function(v){return (v);}

//hasValue(Value v) (public boolean)
//Is the value of this field equal to value? If the type of the field is a collection
//(e.g.: MultipleField): Does the collection contain value? (boolean)
Field.prototype.hasValue = function(value){
  return (this.value==value);
  }

//toBeDisplayed() (public boolean)
//Should the field be displayed? (boolean) This can depend recursively on other fields and their values.
Field.prototype.toBeDisplayed = function(){
  //calcula si el campo se debe mostrar o no
  var almostOne = false;
  var toBeDisplayed = true;
  if(this.dependsOnFields != null){
    for(fieldId in this.dependsOnFields){
      if(toBeDisplayed){
        var fieldInfo = this.dependsOnFields[fieldId];
        if(!fieldInfo.field.toBeDisplayed()) {
          toBeDisplayed = false;
          if(this.depends_on_condition == "Or")toBeDisplayed = true;
          }
        else{
          if(fieldInfo.filled){
            if(fieldInfo.field.initial()){
              toBeDisplayed = false;
              if(this.depends_on_condition == "Or")toBeDisplayed = true;
              }
            }
          else{
            var oneOfTheValues = false;
            for(i in fieldInfo.values){
              if(!oneOfTheValues){
                if(fieldInfo.field.hasValue(fieldInfo.values[i])) {
                  oneOfTheValues = true;
                  almostOne = true;
                    //alert("Field "+fieldInfo.field.id+" has value "+fieldInfo.values[i]);
                  }
                         //else alert("Field "+fieldInfo.field.id+" has NOT value "+fieldInfo.values[i]);
                }
              }
              if(!oneOfTheValues){
                toBeDisplayed = false;
                if(this.depends_on_condition == "Or")toBeDisplayed = true;
              }
            }
          }
        }
      }
    }
  if(this.depends_on_condition == "Or")toBeDisplayed = almostOne;
  return (toBeDisplayed);
  }

//update() (protected void)
//Recalculate value for computed fields; recalculate visibility for all fields;
//call update of dependant fiels in case of visibility change
Field.prototype.update = function(){
  //alert("updating "+this.id);
  if(this.computed){
    var cValue = this.compute();
    this.setValue(cValue);
    }
  var d = this.toBeDisplayed();
  //alert(this.id+" toBeDisplayed: "+d);
  if(d!=this.displayed){ //true: hay que cambiar la visibilidad
    this.displayed = d;
    if (d){ //hacer el campo visible
      if(!this.computed)if(this.previousValue!=null)this.setValue(this.previousValue);
      this.previousValue = null;
      if(!this.hidden)this.show();
      }
    else{//hacer el campo invisible
      this.hide();
      this.previousValue = this.value;
      this.setValue("");
      }
    this.updateDependantFields();
    }
  };

//hide() (protected void)
//Makes the field invisible on HTML level. Hook for subclasses.
Field.prototype.hide = function(){
  this.highlighted = false;
  this.outerElement.className="invisible";
  };

//show() (protected void)
//Makes the field visible on HTML level. Hook for subclasses.
Field.prototype.show = function(){
  this.highlighted = false;
  this.outerElement.className="visible";
  };

//highlight() (protected void)
//Highlights the field on HTML level. Hook for subclasses.
Field.prototype.highlight = function(){
  this.highlighted = true;
  this.outerElement.className="highlight";
  this.element.focus();
  }

//initialize() (public void)
//Connects the field with the corresponding HTML elements. Writes initial value to HTML
//and ensures that the field is visible or unvisible.
Field.prototype.initialize = function(){
  this.element = document.getElementById(this.id);
  if(!this.element)errorAlert("Element "+this.id+" not found.");
  if(!this.hidden){
    this.outerElement = document.getElementById("tr_"+this.id);
    if(!this.outerElement)errorAlert("Outer element "+this.id+" not found.");
    }
  this.registerEventHandler();
  if(this.computed){
    this.value = this.normalize(this.compute());
    }
  this.writeToDocument(this.formatForDisplay(this.value));
  this.displayed = this.toBeDisplayed();
  if(!this.hidden){
    if(!this.displayed) this.hide();
    else this.show();
    }
  //alert(this.id+":"+this.computed);
  }

//registerEventHandler() (protected void)
//Subclass hook to be able to register HTML event handlers differently.
//(Event handlers are registered to promote value changes from HTML (user input) to the
// JS-field controller.)
Field.prototype.registerEventHandler = function()
{
  this.element.onblur = function(){getFieldById(this.id).changed();return (true);}
  //this.element.onchange = function(){getFieldById(this.id).changed();return (true);}
  }

//checkRequired() (private String)
//Check if this field is required and if so, if there is a value filled in.
//Returns the empty string, if o.k. and an error message if not so.
Field.prototype.checkRequired = function(){
  var result = "";
  if(this.required && this.initial() && !document.disableRequired){
    result = translate("field_required1")+" \""+
    this.getTitle()+"\" "+translate("field_required2");
    }
  //alert(this.id+" v:"+this.value+" r:"+this.required+" i:"+this.initial()+"<");
  return (result);
  }

//check() (public String)
//Checks if the value of this field is o.k.. Returns the empty string if o.k. or an error message (String) if not so.
//For invisible fields the empty string is always returned.
Field.prototype.check = function(){
  //alert("checking "+this.id+" value:"+this.value+". Initial:"+this.initial()+" From Doc:"+this.readFromDocument());
  var result = "";
  if(this.toBeDisplayed()){
    result = this.checkRequired();
    if(result=="") if(!this.initial()){
      result = this.checkRegex();
      if(result=="") result = this.checkLength();
      if(result=="") result = this.checkSpecific();
      if(result=="") result = this.checkMinValue();
      if(result=="") result = this.checkMaxValue();
      }
    }
  return (result);
  }

//checkLength() (private String)
//Checks the the length of the value of this field.
//Returns the empty string, if o.k. and an error message if not so.
Field.prototype.checkLength = function(value){
  var result = "";
  if(this.minLength!=null) if(this.value.length < this.minLength) result = this.tooShortMessage();
  if (result=="") if(this.maxLength!=null) if(this.value.length > this.maxLength) result = this.tooLongMessage();
  return (result);
  }

//tooShortMessage() (protected String)
//Hook to change the error message returned by checkLength().
Field.prototype.tooShortMessage = function(value){
  return (
    translate("value_at_least_chars1")+" \""+this.getTitle()+"\" "+
    translate("value_at_least_chars2")+" "+this.minLength+" "+translate("value_at_least_chars3"));
  }

//tooLongMessage() (protected String)
//Hook to change the error message returned by checkLength().
Field.prototype.tooLongMessage = function(value){
  return (
    translate("value_at_most_chars1")+" \""+this.getTitle()+"\" "+
    translate("value_at_most_chars2")+" "+this.maxLength+" "+translate("value_at_most_chars3"));
  }

//checkRegex() (private String)
//Check the value of this field against the regular expression, if one is defined.
//Returns the empty string, if o.k. and an error message if not so.
Field.prototype.checkRegex = function(){
  var result = "";
  if(this.regex!=null) if(!this.regex.test(this.value)) result = this.wrongFormatMessage();
  return (result);
  }

//wrongFormatMessage() (protected String)
//Hook to change the error message returned by checkRegex().
Field.prototype.wrongFormatMessage = function(){
  return (
    translate("field_format1")+" \""+this.getTitle()+"\" "+
    translate("field_format2")+this.format+translate("field_format3"));
  }

//checkSpecific() (protected String)
//Hook to add specific checks.
//Returns the empty string, if o.k. and an error message if not so.
Field.prototype.checkSpecific = function(){
  return ("");
  }

//checkMinValue() (private String)
//Check of minimum value. The check is only be done if minValue is not null.
//Returns the empty string, if o.k. and an error message if not so.
//If the minValue is to be obtained from another field(dynamic), which is the case if
//minValueFromField is true, the check is only be done if the other fields value
//is ok (calling check() on other).
//Returns the empty string, if o.k. and an error message if not so.
Field.prototype.checkMinValue = function(){
  var result = "";
  var minValue = this.minValue;
  if(minValue != null){
    if(this.minValueFromField){
      var field = getFieldById(minValue);
      if(field.check()==""){
        minValue = field.value;
        if(this.firstLessThanSecond(this.value,minValue)) {result = this.minValueFieldMessage(field);}
      } }
    else if(this.firstLessThanSecond(this.value,minValue)) result = this.minValueMessage();
    }
  return (result);
  }

//firstLessThanSecond(protected boolean(String,String))
//must be overwritten for numeric comparison
Field.prototype.firstLessThanSecond = function(first,second){
  return (first < second);
  }


//minValueMessage() (protected String)
//Hook to change the error message returned by checkMinValue() (only for static checks).
Field.prototype.minValueMessage = function(){
  return (
    translate("not_less1")+" \""+this.getTitle()+"\" "+
    translate("not_less2")+" "+this.formatForDisplay(this.minValue)+" "+translate("not_less3"));
  }

//minValueFieldMessage() (protected String)
//Hook to change the error message returned by checkMinValue() (only for dynamic checks).
Field.prototype.minValueFieldMessage = function(field){
  return (
    translate("not_less_field1")+" \""+this.getTitle()+"\" "+
    translate("not_less_field2")+" \""+field.getTitle()+"\" "+translate("not_less_field3") );
  }

//checkMaxValue() (private String)
//see checkMinValue()
Field.prototype.checkMaxValue = function(){
  var result = "";
  var maxValue = this.maxValue;
  if(maxValue != null){
    if(this.maxValueFromField){
      var field = getFieldById(maxValue);
      if(field.check()==""){
        maxValue = field.value;
        if(this.firstLessThanSecond(maxValue,this.value)) result = this.maxValueFieldMessage(field);
      } }
    else if(this.firstLessThanSecond(maxValue,this.value)) result = this.maxValueMessage();
    }
  return (result);
  }

//maxValueMessage() (protected String)
//see minValueMessage()
Field.prototype.maxValueMessage = function(){
  return (
    translate("not_greater1")+" \""+this.getTitle()+"\" "+
    translate("not_greater2")+" "+this.formatForDisplay(this.maxValue)+" "+translate("not_greater3"));
  }

//maxValueFieldMessage() (protected String)
//see minValueFieldMessage()
Field.prototype.maxValueFieldMessage = function(field){
  return (
    translate("not_greater_field1")+" \""+this.getTitle()+"\" "+
    translate("not_greater_field2")+" \""+field.getTitle()+"\" "+translate("not_greater_field3"));
  }

//initial() (public boolean)
//Is the actual internal value of this field the initial value? (boolean)
//Attention: This doesn´t work if value is not a String.
Field.prototype.initial = function(){
  return (this.value === "");
  }

//changed() (public void)
//HTML event handler function. Is registered by registerEventHandler() as the function
//to be called on a change of the HTML form data. May be called any times.
//Normalized the new HTML value, changes the internal value to the normalized one and
//writes the normalized value back to HTML after formatting it for display.
//Updates dependant fields and clears the highlight.
//Endless loops are prevented by checking for real change before writing anything back
//to HTML or to the internal value.
//Calls registerChanges() to mark the form "dirty" (Activating the warning that changes
//may be lost if leaving without saving).
//For read-only fields it just ignores anything coming from HTML and writes the internal
//value to HTML. That way readonly fields may stay writable at the HTML level.
Field.prototype.changed = function(){
  //lee datos del HTML y los guarda en Javascript
  //actualiza los campos dependientes si el valor ha cambiado
  var docValue = this.readFromDocument();
  var correctValue;
  if(this.readonly) correctValue = this.value;
  else 
  	if(this.computed) correctValue = this.compute();
  		else correctValue = docValue;
  correctValue = this.normalize(correctValue);
  var displValue = this.formatForDisplay(correctValue);
  if(displValue!==docValue)this.writeToDocument(displValue);
  if (correctValue!==this.value){
    registerChanges();
    this.value = correctValue;
    this.updateDependantFields();
    this.show(); //deshacer el highlight.
    //alert("Field "+this.id+" changed to"+this.value);
    }
  }

//updateDependantFields() (private void)
//Calls update() on all the dependant fields.
Field.prototype.updateDependantFields = function(){
  for(field in this.dependantFields){
    //alert(this.id+" changed, updating "+field);
    this.dependantFields[field].update();
    }
  }

//getTitle() (public String)
//Returns the translated title of this field, reading it directly from HTML.
Field.prototype.getTitle = function(){
  var title = document.getElementById("field_title_"+this.id).firstChild.nodeValue;
  if(title==null||/^\s*$/.test(title))alert("Title not found (id:field_title_"+this.id+")");
  return (title);
  }

//Constructor (protected abstract)
function Field(){
  }


// PROTOTYPE INTEGER ==================================================================


IntegerField.prototype = new Field();
IntegerField.prototype.noSpaces = false;
IntegerField.prototype.regexBigPt = /^\d{1,3}(\.\d{3})+$/; //format con separation points
IntegerField.prototype.regex = /^([0-9]\d*)$/;
//IntegerField.prototype.regex = /^(([1-9]\d*)|0)$/;
IntegerField.prototype.normQuitSpaces = true; //quit all whitespace
IntegerField.prototype.normalizeSpecific = function(v){
  if(this.regexBigPt.test(v)) v = v.replace(/\./g,""); //quit points
  v = v.replace(/^(0+)([1-9]\d*)$/,"$2"); //quit leading zeros
  if(this.regex.test(v)) v = parseInt(v);
  return (v);
  }
IntegerField.prototype.formatForDisplay = function(value){
  var result;
  if(this.regex.test(value)){
    value = parseInt(value);
    if(!this.noSpaces && /^\d{4,}$/.test(value)){
      result = "";
      var n = value;
      while(n.length>3){
        result = " "+n.substring(n.length-3)+result;
        n = n.substring(0,n.length-3);
        }
      result = n+result;
      }
    else result = value;
    }
  else result = value;
  return (result);
  }
IntegerField.prototype.wrongFormatMessage = function(){
  return (translate("must_be_a_number1")+" \""+this.getTitle()+"\" "+translate("must_be_a_number2"));
  }
IntegerField.prototype.initial = function(){
  return (this.value === "");
  }

//OVERRIDE: Field.firstLessThanSecond()
IntegerField.prototype.firstLessThanSecond = function(first,second){
  return (Number(first) < Number(second));
  }

function IntegerField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "IntegerFieldController "+this.id;
    }
  }


// PROTOTYPE DECIMAL ==================================================================

//The decimal notation is limited to 21 digits in Javascript. Longer numbers are
//converted to scientific notation, which is not supported by this field type.

DecimalField.prototype = new Field();
DecimalField.prototype.decimals = 2;
DecimalField.prototype.regexEu = /^((\d{1,3}(\.\d{3})*)||(\d+))(\,\d+)?$/;
DecimalField.prototype.regexUs = /^((\d{1,3}(\,\d{3})*)||(\d+))(\.\d+)?$/;
DecimalField.prototype.regex   = /^\d+(\.\d+)?$/;
DecimalField.prototype.separator   = ",";
DecimalField.prototype.normQuitSpaces = true; //quit all whitespace

DecimalField.prototype.normalizeSpecific = function(v){
  if(this.regexEu.test(v)) v = v.replace(/\./g,"").replace(',','.');
  else if(this.regexUs.test(v)) v = v.replace(/\,/g,"");
  if(this.regex.test(v)){
    v = parseFloat(v).toFixed(this.decimals);
    }
  return (v);
  }
//replace point with comma and add spaces every 3 digits:
DecimalField.prototype.formatForDisplay = function(value){
  if(this.regex.test(value)){
    var result = "";
    var a = new String(value).split(".");
    if(a.length>1)result=this.separator + a[1];
    var n = a[0];
    while(n.length>3){
      result = " "+n.substring(n.length-3)+result;
      n = n.substring(0,n.length-3);
      }
    result = n+result;
    }
  else result = value;
  return (result);
  }
DecimalField.prototype.wrongFormatMessage = function(){
  return (translate("must_be_decimal1")+" \""+this.getTitle()+"\" "+translate("must_be_decimal2"));
  }

//OVERRIDE: Field.firstLessThanSecond()
DecimalField.prototype.firstLessThanSecond = function(first,second){
  return (Number(first) < Number(second));
  }

function DecimalField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "DecimalFieldController "+this.id;
    }
  }

// PROTOTYPE TEXT ==================================================================


TextField.prototype = new Field();
TextField.prototype.normStripSpaces = true;
function TextField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "TextFieldController "+this.id;
    }
  }
  
 

//NUMERIC TEXT ==================================================================
NumericTextField.prototype = new TextField();
NumericTextField.prototype.regex = /^([0-9]\d*)$/;
NumericTextField.prototype.wrongFormatMessage = function(){
  return (translate("must_be_a_number1")+" \""+this.getTitle()+"\" "+translate("must_be_a_number2"));
  }


function NumericTextField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "NumericTextFieldController "+this.id;
    }
  }


// PROTOTYPE TEXTAREA ==================================================================


TextAreaField.prototype = new TextField();
TextAreaField.prototype.normStripSpaces = false;
TextAreaField.prototype.normQuitSpaces = false;
TextAreaField.prototype.normCutLength = true; //cut at right end to limit length
TextAreaField.prototype.registerEventHandler = function(){
  this.element.onchange = function(){getFieldById(this.id).changed();return (true);}
  if(this.maxLength!=null && this.normCutLength){
    this.element.onkeyup   = function(){getFieldById(this.id).cutLength();return (true);}
    this.element.onkeydown = function(){getFieldById(this.id).cutLength();return (true);}
    }
  }
TextAreaField.prototype.cutLength = function(){
  var v = this.readFromDocument();
  getFieldById(this.id).changed();
  if (v.length > this.maxLength) this.writeToDocument(v.substring(0, this.maxLength));
  }

TextAreaField.prototype.initial = function(){
  return (this.value == "");
  }

function TextAreaField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "TextAreaFieldController "+this.id;
    }
  }


// PROTOTYPE EMAIL =======================================================================

eMailTextField.prototype = new Field();
eMailTextField.prototype.normStripSpaces = true;
eMailTextField.prototype.regex   = /^\S+@\S+\.\S+$/;
eMailTextField.prototype.wrongFormatMessage = function(){
  return (translate("must_be_a_valid_mail1")+" \""+this.getTitle()+"\" "+translate("must_be_a_valid_mail2"));
  }

function eMailTextField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "eMailTextFieldController "+this.id;
    }
  }


// PROTOTYPE SELECTION ==================================================================

SelectionField.prototype = new Field();
SelectionField.prototype.initial = function(){
  return this.value=="" || this.value=="-1";
  }
  
SelectionField.prototype.getTitle_control_value = function(){
	//alert(document.getElementById(this.id).options[document.getElementById(this.id).selectedIndex].text);
	return document.getElementById(this.id).options[document.getElementById(this.id).selectedIndex].text;
	}

SelectionField.prototype.registerEventHandler = function()
{
  //this.element.onblur = function(){getFieldById(this.id).changed();return (true);}
  this.element.onchange = function(){getFieldById(this.id).changed();return (true);}
  }

function SelectionField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "SelectionFieldController "+this.id;
    }
  }



// PROTOTYPE MULTIPLE (boolean) ========================================================


MultipleFieldBl.prototype = new Field();
MultipleFieldBl.prototype.options = null;
MultipleFieldBl.prototype.elements = null;
MultipleFieldBl.prototype.normalize = function(value){return (value);}
MultipleFieldBl.prototype.initialize = function(){
  this.elements = new Object();
  for (var no in this.options){
    var option = this.options[no]
    var opId = this.id+"_"+option;
    document.options[opId] = this;
    var el = document.getElementById(opId);
    if(!el)errorAlert("Element "+opId+" not found.");
    this.elements[option] = el;
    }
  this.outerElement = document.getElementById("tr_"+this.id);
  if(!this.outerElement)errorAlert("Outer element "+this.id+" not found.");
  for (element in this.elements){
    this.elements[element].onclick = function(){document.options[this.id].changed();return (true);}
    }
  this.writeToDocument(this.value);
  this.displayed = this.toBeDisplayed();
  if(!this.displayed) this.hide();
  else this.show();
  //if(this.readonly) this.element.disabled="disabled";
  }
MultipleFieldBl.prototype.readFromDocument = function(){
  var result = new Object();
  for(var no in this.options){
    var option = this.options[no]
    result[option]=this.elements[option].checked;
    }
  return (result);
  }
MultipleFieldBl.prototype.writeToDocument = function(value){
  for(var no in this.options){
    var option = this.options[no]
    this.elements[option].checked = value[option];
    }
  }
MultipleFieldBl.prototype.highlight = function(){
  this.highlighted = true;
  this.outerElement.className="highlight";
  this.elements[this.options[0]].focus();
  }
MultipleFieldBl.prototype.changed = function(){
  var docValue = this.readFromDocument();
  var changed = false;
  for(var no in this.options){
    var option = this.options[no]
    if(docValue[option]!=this.value[option])changed=true;
    }
  if (changed){
    this.value = docValue;
    registerChanges();
    for(field in this.dependantFields){
      //alert(this.id+" changed, updating "+field);
      this.dependantFields[field].update();
      }
    }
  }
MultipleFieldBl.prototype.hasValue = function(value){
  return (this.value[value]==true);
  }
MultipleFieldBl.prototype.initial = function(){
  var initial = true
  for(var no in this.options){
    var option = this.options[no];
    //alert(option+":"+this.value[option]);
    if(this.value[option]) initial=false;
    }
  //alert("Field "+this.id+" initial:"+initial);
  return (initial);
  }

//getNoneTitle() (public String)
//Returns the translated title of the none option of this field, reading it directly from HTML.
MultipleFieldBl.prototype.getNoneTitle = function(){
  var input = document.getElementById(this.id+"_none");
  if(input==null)alert("Element \""+this.id+"_none\" not found. (Field "+this.id+")");
  var span = input.nextSibling.nextSibling;
  var noneTitle = span.firstChild.nodeValue;
  if(noneTitle==null||/^\s*$/.test(noneTitle))alert("None title not found (id:field_title_"+this.id+")");
  return (noneTitle);
  }

MultipleFieldBl.prototype.getNot_DoneTitle = function(){
  var input = document.getElementById(this.id+"_not_done");
  if(input==null)alert("Element \""+this.id+"_not_done\" not found. (Field "+this.id+")");
  var span = input.nextSibling.nextSibling;
  var not_doneTitle = span.firstChild.nodeValue;
  if(not_doneTitle==null||/^\s*$/.test(not_doneTitle))alert("Not Done title not found (id:field_title_"+this.id+")");
  return (not_doneTitle);
  }
  
MultipleFieldBl.prototype.checkSpecific = function(){
  var result = "";
  var noneChecked = (this.value.none == true);
  var not_doneChecked = (this.value.not_done == true);
  if(noneChecked){
    for(var no in this.options){
      if(result=="") if(this.options[no]!="none") if(this.value[this.options[no]]){
        result=translate("none_and_other1")+" \""+this.getTitle()+"\" "+translate("none_and_other2")+this.getNoneTitle()+translate("none_and_other3");
        }
      }
    }
  if(result==""){
    if(not_doneChecked){
      for(var no in this.options){
        if(result=="") if(this.options[no]!="not_done") if(this.value[this.options[no]]){
          result=translate("none_and_other1")+" \""+this.getTitle()+"\" "+translate("none_and_other2")+this.getNot_DoneTitle()+translate("none_and_other3");
          }
        }
      }
    }
  if(result == "") this.show();
  return (result);
  }
MultipleFieldBl.prototype.preSave = function(){}

function MultipleFieldBl(id, options){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.options = options;
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "MultipleFieldController "+this.id;
    }
  }



// PROTOTYPE MULTIPLE (textbased) ========================================================


MultipleFieldTx.prototype = new Field();
MultipleFieldTx.prototype.options = null;//Array of option names
MultipleFieldTx.prototype.elements = null;
MultipleFieldTx.prototype.initialize = function(){
  this.elements = new Object();
  for (var i=0;i<this.options.length;i++){
    var option = this.options[i]
    var opId = this.id+"_"+option;//combined id: field and option
    document.options[opId] = this;
    var el = document.getElementById(opId); //the html field
    if(!el)errorAlert("Element "+opId+" not found.");
    this.elements[option] = el;
    }
  this.outerElement = document.getElementById("tr_"+this.id);//the enclosing html table row
  if(!this.outerElement)errorAlert("Outer element "+this.id+" not found.");
  for (element in this.elements){
    this.elements[element].onclick = function(){document.options[this.id].changed();return (true);}
    }
  this.writeToDocument(this.value);
  this.displayed = this.toBeDisplayed();
  if(!this.displayed) this.hide();
  else this.show();
  //if(this.readonly) this.element.disabled="disabled";
  }
MultipleFieldTx.prototype.readFromDocument = function(){
  var result = "";
  for(var i=0;i<this.options.length;i++){
    var option = this.options[i];
    var checked=this.elements[option].checked;
    if(this.elements[option].checked) result = result + "X";
    else result = result + ".";
    }
  return (result);}
MultipleFieldTx.prototype.writeToDocument = function(value){
  if(value.length!=this.options.length)errorAlert("Internal error (1)");
  for(var i=0;i<this.options.length;i++){
    var option = this.options[i];
    var char_ = value.charAt(i);
    if(char_!="." && char_!="X")errorAlert("Internal error (2)");
    this.elements[option].checked = (char_=="X");
    }
  }
MultipleFieldTx.prototype.hasValue = function(value){
  var result = false;
  for(var i=0;i<this.options.length;i++){
    var option = this.options[i];
    if(option==value){
      if(this.value.charAt(i)=="X") result=true;
      }
    }
  return (result);
  }
MultipleFieldTx.prototype.initial = function(){
  return (this.value.indexOf("X")==-1);
  }
MultipleFieldTx.prototype.normalize = function(value){
  var n = new String(value);
  var result = "";
  for(var i=0;i<this.options.length;i++){
    if(n.charAt(i)=="X") result=result+"X";
    else result = result + ".";
    }
  return (result);
  }
MultipleFieldTx.prototype.checkSpecific = function(){
  var result = "";
  var noneChecked = false;
  var otherChecked = false;
  var n = new String(this.value);
  for (var i=0;i<this.options.length;i++){
    var option = this.options[i];
    if(n.charAt(i)=="X"){
      if(option=="none")noneChecked=true;
      else otherChecked = true;
      }
    }
  if(noneChecked && otherChecked)
    result=translate("none_and_other1")+" \""+this.getTitle()+"\" "+translate("none_and_other2");
  return (result);
  }
MultipleFieldTx.prototype.highlight = function(){
  this.highlighted = true;
  this.outerElement.className="highlight";
  this.outerElement.focus();
  }
MultipleFieldTx.prototype.preSave = function(){}
function MultipleFieldTx(id, options){
  if(id){
    this.id = id;
    this.name = "MultipleFieldTxController "+this.id;
    this.dependsOnValue = new Object();
    this.options = options;
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    //alert("Field is:"+getFieldById(id).name);
    listAllFields();
    document.fieldList.push(this);
    this.value = this.normalize("");
    }
  }



// PROTOTYPE DATE =====================================================================================

//31/01/2004, 01/31/2004, 31.01.2004, 01.31.2004, 2004-01-31, depending on "slash", "dayFirst" and "iso"
//internamente (normalizado) siempre ISO (2004-01-31),
//el cambio se hace al normalizar (ISO) y al escribir al documento.

DateField.prototype = new Field();
DateField.prototype.regex = /^\d{4}(\-\d{2}){2}$/;
DateField.prototype.slash = true;
DateField.prototype.dayFirst = true;
DateField.prototype.iso = false;
DateField.prototype.normQuitSpaces = true; //quit all whitespace
DateField.prototype.normUpperCase = true; //convert to uppercase
DateField.prototype.normalizeSpecific = function(n){
  if(!this.regex.test(n)){
    var year, month, day;
    var dte = null;
    if(/^(\d{1,2}\/){2}(\d\d){1,2}$/.test(n)) dte = n.split("/");
    else if(/^(\d{1,2}\.){2}(\d\d){1,2}$/.test(n)) dte = n.split(".");
    else if(/^(\d{6}|\d{8})$/.test(n)) {
      dte = new Array(3);
      dte[0]=n.substr(0,2);dte[1]=n.substr(2,2);dte[2]=n.substr(4);
      }
    else if(/^IOSEPH(\d{2}|\d{4})$/.test(n)) {
      dte = new Array(3);
      dte[0]=19;dte[1]=3;dte[2]=n.substr(6);
      }
    else if(/^MARIA(\d{2}|\d{4})$/.test(n)) {
      dte = new Array(3);
      dte[0]=1;dte[1]=1;dte[2]=n.substr(5);
      }
    else if(/^ISIDORUS(\d{2}|\d{4})$/.test(n)) {
      dte = new Array(3);
      dte[0]=4;dte[1]=4;dte[2]=n.substr(8);
      }
    if(dte!=null){
      if(this.dayFirst){
        day = parseInt(dte[0],10);
        month = parseInt(dte[1],10);
        }
      else{
        month = parseInt(dte[0],10);
        day = parseInt(dte[1],10);
        }
      year = parseInt(dte[2],10);
      if(year<100){
        if(year<20)year=year+2000;
        else year = year+1900;
        }
      if(day>=1&&day<=31&&month>=1&&month<=12){
        day = "0"+day; day = day.substring(day.length-2);
        month = "0"+month; month = month.substring(month.length-2);
        n = year+"-"+month+"-"+day;
        }
      }
    }
  return (n);
  }
DateField.prototype.formatForDisplay = function(value){
  var result = value;
  if(!this.iso && this.regex.test(value)){
    var dte = value.split("-");
    var year, month, day, sep;
    year=dte[0]; month=dte[1]; day=dte[2];
    if(this.slash) sep="/"; else sep = ".";
    if(this.dayFirst) result = day+sep+month+sep+year;
    else result = month+sep+day+sep+year;
    }
  return (result);
  }

//OVERRIDE: Field.wrongFormatMessage()
DateField.prototype.wrongFormatMessage = function(){
  return (translate("wrong_date_format1")+" \""+this.getTitle()+"\" "+translate("wrong_date_format2"));
  }

//OVERRIDE: Field.minValueMessage()
DateField.prototype.minValueMessage = function(){
  return (translate("not_earlier1")+" \""+this.getTitle()+"\" "+
    translate("not_earlier2")+" "+this.formatForDisplay(this.minValue)+" "+translate("not_earlier3"));
  }

//OVERRIDE: Field.minValueFieldMessage()
DateField.prototype.minValueFieldMessage = function(field){
  return (
    translate("not_earlier_field1")+" \""+this.getTitle()+"\" "+
    translate("not_earlier_field2")+" "+field.getTitle()+" "+translate("not_earlier_field3"));
  }

//OVERRIDE: Field.maxValueMessage()
DateField.prototype.maxValueMessage = function(){
  return (
    translate("not_later1")+" \""+this.getTitle()+"\" "+
    translate("not_later2")+" "+this.formatForDisplay(this.maxValue)+" "+translate("not_later3"));
  }

//OVERRIDE: Field.maxValueFieldMessage()
DateField.prototype.maxValueFieldMessage = function(field){
  return (
    translate("not_later_field1")+" \""+this.getTitle()+"\" "+
    translate("not_later_field2")+" "+field.getTitle()+" "+translate("not_later_field3"));
  }

DateField.prototype.checkSpecific = function(){
  var result = "";
  var dte = this.value.split("-");
  var year, month, day, sep;
  year=dte[0]; month=dte[1]; day=dte[2];
  jsDte = new Date(year,month-1,day);
  if(jsDte.getDate()!=day || jsDte.getMonth()+1!=month || jsDte.getFullYear()!=year){
    result = translate("date_not_existent1")+" \""+this.getTitle()+"\" "+translate("date_not_existent2");
    //alert(jsDte.getFullYear()+"-"+(jsDte.getMonth()+1)+"-"+jsDte.getDate());
    }
  return (result);
  }
function DateField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "DateFieldController "+this.id;
    }
  }


// PROTOTYPE MONTH-YEAR ========================================================================

MonthYearField.prototype = new DateField();
MonthYearField.prototype.regex = /^\d{4}(\-\d{2}){1}$/;
MonthYearField.prototype.iso = false;
MonthYearField.prototype.normalizeSpecific = function(n){
  if(!this.regex.test(n)){
    var year, month;
    var dte = null;
    if(/^(\d{1,2}\/){1}(\d\d){1,2}$/.test(n)) dte = n.split("/");
    else if(/^(\d{1,2}\.){1}(\d\d){1,2}$/.test(n)) dte = n.split(".");
    if(dte!=null){
      month = parseInt(dte[0],10);
      year = parseInt(dte[1],10);
      if(year<100){
        if(year<20)year=year+2000;
        else year = year+1900;
        }
      if(month>=1&&month<=12){
        month = "0"+month; month = month.substring(month.length-2);
        n = year+"-"+month;
        }
      }
    }
  return (n);
  }
MonthYearField.prototype.formatForDisplay = function(value){
  var result = new String(value);
  if(!this.iso && this.regex.test(value)){
    var dte = value.split("-");
    var year, month, sep;
    year=dte[0]; month=dte[1];
    if(this.slash) sep="/"; else sep = ".";
    result = month+sep+year;
    }
  return (result);
  }

//OVERRIDE: Field.checkSpecific()
MonthYearField.prototype.checkSpecific = function(){
  var result = "";
  var month = this.value.split("-")[1];
  if(month<1 || month>12){
    result = translate("date_not_existent1")+" \""+this.getTitle()+"\" "+translate("date_not_existent2");
    }
  return (result);
  }


//OVERRIDE: Field.wrongFormatMessage()
MonthYearField.prototype.wrongFormatMessage = function(){
  return (translate("wrong_month_year_format1")+" \""+this.getTitle()+"\" "+translate("wrong_month_year_format2"));
  }

function MonthYearField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "MonthYearFieldController "+this.id;
    }
  }

// PROTOTYPE HEADER ========================================================================


HeaderField.prototype = new Field();

HeaderField.prototype.readFromDocument = function(){return (value);}
HeaderField.prototype.writeToDocument = function(value){}
HeaderField.prototype.initialize = function(){
  if(!this.hidden){
    this.outerElement = document.getElementById("tr_"+this.id);
    if(!this.outerElement)errorAlert("Outer element "+this.id+" not found.");
    }
  this.displayed = this.toBeDisplayed();
  if(!this.hidden){
    if(!this.displayed) this.hide();
    else this.show();
    }
  }
HeaderField.prototype.checkRequired = function(){
  return ("");
  }
HeaderField.prototype.preSave = function(){}
function HeaderField(id){
  if(id){
    this.id = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "HeaderFieldController "+this.id;
    }
  }

// PROTOTYPE JS-ONLY ===============================================================


JsOnlyField.prototype = new Field();
JsOnlyField.prototype.title = null;
JsOnlyField.prototype.readFromDocument = function(){
  return (this.value);};
JsOnlyField.prototype.writeToDocument = function(){};
JsOnlyField.prototype.hide = function(){
  this.highlighted = false;
  };
JsOnlyField.prototype.show = function(){
  this.highlighted = false;
  };
JsOnlyField.prototype.highlight = function(){
  this.highlighted = true;
  }
JsOnlyField.prototype.registerEventHandler = function(){}
JsOnlyField.prototype.getTitle = function(){return (this.title);}
JsOnlyField.prototype.initialize = function(){
  if(this.computed){
    this.value = this.normalize(this.compute());
    }
  this.displayed = this.toBeDisplayed();
  };
function JsOnlyField(id){
  if(id){
    this.id = id;
    this.title = id;
    this.dependsOnValue = new Object();
    this.dependantFields = new Object();
    this.sourceFields = new Object();
    document.fields[id]=this;
    document.fieldList.push(this);
    this.name = "JsOnlyFieldController "+this.id;
    }
  }



//alert("field_control.js loaded.");
