﻿    //Record class, either pass just string ID with optional field init func:
    // var rec = new WAVE.RecordModel.Record("r1",function(rec){ new this.Field()... )
    //or complex init vector:
    // {ID: string, fields: [{def: fieldDef1,val: value1}...}]}
    published.Record = function(init, fieldFunc)
    {
        if (!init) throw "Record.ctor(need id+fieldFunc or initObject)";
        var record = this;
        var fRecordLoaded = false;

        WAVE.extend(record, WAVE.EventManager);

        this.eventInvoke(published.EVT_RECORD_LOAD, published.EVT_PHASE_BEFORE);//todo How to subscribe to this event?

        var id = null;
        var lang = null;
        var fFormMode = "unspecified";
        var fCSRFToken = "";
        var fRoundtrip = "";

        if (WAVE.isObject(init) &&  init.fields)
        {
          id = init.ID;
          lang = init.ISOLang;

          if (init.__FormMode)
           fFormMode = init.__FormMode;

          if (init.__CSRFToken)
           fCSRFToken = init.__CSRFToken;

          if (init.__Roundtrip)
           fRoundtrip = init.__Roundtrip;
        }
        else
        {
          id = init.toString();
          init = null;
        }

        var fID = WAVE.strEmpty(id)? WAVE.genRndKey(16) : id;
        var fISOLang = WAVE.strEmpty(lang) ? "eng" : lang;

        var fFields = [];
        var fRecValidated = false;
        var fRecValidationError = null;

         function resetRecValidation(){
              fRecValidated = false;
              fRecValidationError = null;
         }


        //Returns record instance ID
        this.ID = function(){ return fID;};

        //Returns record ISO language/culture
        this.ISOLang = function(){ return fISOLang;};

        //Returns form mode on the server if one was supplied (i.e. insert|edit). This property can not be set on client
        this.formMode = function(){ return fFormMode;};

        //Returns CSRF token supplied by server. This property can not be set on client
        this.csrfToken = function(){ return fCSRFToken;};

        //Returns Roundtrip bag content supplied by server. This property can not be set on client
        this.roundtrip = function(){ return fRoundtrip;};

        //Returns true when record has finished loading data and constructing fields
        //Field event consumers may reference this flag to exit out of unnecessary event processing
        this.loaded = function() {return fRecordLoaded;};

        //Returns copy of fields list
        this.fields = function() {
           return WAVE.arrayShallowCopy(fFields);
        };

        //Returns a map of {fieldName: fieldValue...}
        //modifiedOnly - will only get fields that have changed
        //includeNonStored - will include fields that are not Stored
        this.data = function(modifiedOnly, includeNonStored){
            var result = {};

            for(var i in fFields){
              var fld = fFields[i];
              if ((!modifiedOnly ||
                    fld.isModified() ||
                    fld.isGUIModified()) &&
                  (includeNonStored || fld.stored())
                 )  result[fld.name()] = fld.value();
            }

            if (!WAVE.strEmpty(fFormMode) && fFormMode!=="unspecified")
             result["__FormMode"] = fFormMode;

            if (!WAVE.strEmpty(fCSRFToken))
             result["__CSRFToken"] = fCSRFToken;

            if (!WAVE.strEmpty(fRoundtrip))
             result["__Roundtrip"] = fRoundtrip;

            return result;
        };

        //Returns a field by its case-insensitive name or null
        this.fieldByName = function(name){
            if (WAVE.strEmpty(name)) return null;
            for(var i in fFields){
              var fld = fFields[i];
              if (WAVE.strSame(fld.name(), name)) return fld;
            }
            return null;
        };


        //Returns array of all recrod and field-level validation errors
        this.allValidationErrors = function(){
            var errors = [];
            if (fRecValidationError!==null) errors.push(fRecValidationError);
            for(var i in fFields){
              var fld = fFields[i];
              var fe = fld.validationError();
              if (fe!==null) errors.push(fe);
            }
            return errors;
        };

        //Returns all record and field-level validation errors
        this.allValidationErrorStrings = function(){
            var errors = "";
            var all = this.allValidationErrors();
            for(var i in all)
              errors += all[i].toString() + "\n";
            return errors;
        };


        //Validates record and returns true is everything is valid
        this.validate = function() {
            resetRecValidation();
            fRecValidated = true;

            var result = true;
            for(var i in fFields){
              var f = fFields[i];
              if (!f.validate()) result = false;
            }

            if (result)
                try
                {
                    record.eventInvoke(published.EVT_VALIDATE);//this throws errors
                }
                catch(error)
                {
                    fRecValidationError = error;
                    result = false;
                }


            record.eventInvoke(published.EVT_VALIDATED);
            return result;
        };

        this.validated = function(){ return fRecValidated;};

        //Returns rec-level validation eror if any
        this.validationError = function(){ return fRecValidationError;};

        //Returns true if record and all its fields have been validated and valid
        this.valid = function() {
           return fRecValidated &&  fRecValidationError===null && this.fieldsValid();
        };


        //Sets external validation error (i,e, from the server side)
        this.setExternalValidationError = function(error, errorText){//for now error is ignored
          if (WAVE.strEmpty(errorText)) return;
          fRecValidationError = errorText;
          fRecValidated = false;
       };

        //Returns true if all field have been validated (but some may be invalid)
        this.fieldsValidated = function() {
           for(var i in fFields)
              if (!fFields[i].validated()) return false;
           return true;
        };

        //Returns true if all field have been validated and valid
        this.fieldsValid = function() {
            for(var i in fFields)
              if (!fFields[i].valid()) return false;
           return true;
        };


        //Returns true is some field have been modified
        this.isModified = function() {
           for(var i in fFields)
              if (fFields[i].isModified()) return true;
           return false;
        };

        //Returns true is some field have been modified through GUI-attached views
        this.isGUIModified = function() {
            for(var i in fFields)
              if (fFields[i].isGUIModified()) return true;
           return false;
        };

        //Resets all field modification flags
        this.resetModified = function() {
            for(var i in fFields){
              var f = fFields[i];
              f.resetModified();
              f.resetGUIModified();
            }
           return false;
        };

        //Resets all field schemas
        this.resetSchema = function() {
            for(var i in fFields) fFields[i].resetSchema();
        };

        //Applies default values to all fields
        this.applyDefaultValue = function(force) {
            for(var i in fFields) fFields[i].applyDefaultValue(force);
        };

        //Changes all eventInvocationSuspendCount on all fields and this record
        this.latchAllEvents = function(delta) {
            this.eventInvocationSuspendCount+=delta;
            for(var i in fFields) fFields[i].eventInvocationSuspendCount+=delta;
        };

        //Re-inits the record and all of its fields anew - resetting all flags.
        this.resetRecord = function(disableTransitiveEvents){
           if (disableTransitiveEvents) record.latchAllEvents(+1);
           try
           {
               resetRecValidation();
               this.resetSchema();
               this.applyDefaultValue(true);
               this.resetModified();
           }
           finally
           {
               if (disableTransitiveEvents) record.latchAllEvents(-1);
           }

           for(var i in fFields){
             var f = fFields[i];
             f.eventInvoke(published.EVT_FIELD_RESET, f);
           }
        };


        //Field class
        this.Field = function(fieldDef)
        {
            var fFieldLoaded = false;

            if (!WAVE.isObject(fieldDef)) throw "Field.ctor(fieldDef must be a map)";
            if (WAVE.strEmpty(fieldDef.Name)) throw "Field def must have a name";
            if (WAVE.strEmpty(fieldDef.Type)) throw "Field def must have a type";

            //Coerce strbools to bools
            fieldDef.Key      = WAVE.strAsBool(fieldDef.Key, published.FIELDDEF_DEFAULTS.Key);
            fieldDef.Stored   = WAVE.strAsBool(fieldDef.Stored, published.FIELDDEF_DEFAULTS.Stored);
            fieldDef.Required = WAVE.strAsBool(fieldDef.Required, published.FIELDDEF_DEFAULTS.Required);
            fieldDef.Applicable = WAVE.strAsBool(fieldDef.Applicable, published.FIELDDEF_DEFAULTS.Applicable);
            fieldDef.Enabled    = WAVE.strAsBool(fieldDef.Enabled, published.FIELDDEF_DEFAULTS.Enabled);
            fieldDef.ReadOnly   = WAVE.strAsBool(fieldDef.ReadOnly, published.FIELDDEF_DEFAULTS.ReadOnly);
            fieldDef.Visible    = WAVE.strAsBool(fieldDef.Visible, published.FIELDDEF_DEFAULTS.Visible);
            fieldDef.Password   = WAVE.strAsBool(fieldDef.Password, published.FIELDDEF_DEFAULTS.Password);
            fieldDef.Marked     = WAVE.strAsBool(fieldDef.Marked, published.FIELDDEF_DEFAULTS.Marked);
            fieldDef.DeferValidation = WAVE.strAsBool(fieldDef.DeferValidation, published.FIELDDEF_DEFAULTS.DeferValidation);


            var field = this;
            WAVE.extend(field, WAVE.EventManager);
            fFields.push( field );//register with parent
            var recPropertyName = "fld"+fieldDef.Name;
            record[recPropertyName] = field;//make rec.fldLastName shortcut

            record.eventInvoke(published.EVT_FIELD_LOAD, field, published.EVT_PHASE_BEFORE);

            //convert LookupDict to object if it is not an object
            WAVE.propStrAsObject(fieldDef, "LookupDict");

            //convert Lookup to object if it is not an object
            WAVE.propStrAsObject(fieldDef, "Lookup");

            var fSchemaDef = fieldDef;
            var fDef = WAVE.clone(fieldDef);//make copy
            WAVE.extend(fDef, published.FIELDDEF_DEFAULTS, true);

            var fValue = null;
            var fModified = false;
            var fGUIModified = false;
            var fValidated = false;
            var fValidationError = null;


            //Owner record
            this.record = function(){return record; };

            //Returns true when this field has finished loading
            this.loaded = function() {return fFieldLoaded;};

            //Deletes this field from the record
            this.drop = function() {
              if (!record[recPropertyName]) return false;
              this.eventInvoke(published.EVT_FIELD_DROP, published.EVT_PHASE_BEFORE);
              record.eventInvoke(published.EVT_FIELD_DROP, field, published.EVT_PHASE_BEFORE);
              WAVE.arrayDelete(fFields, this);
              delete record[recPropertyName];
              record.eventInvoke(published.EVT_FIELD_DROP, field, published.EVT_PHASE_AFTER);
              this.eventInvoke(published.EVT_FIELD_DROP, published.EVT_PHASE_AFTER);
              return true;
            };

            //Returns the the original schema field def.
            //DO NOT modify its values, use WAVE.clone() if copy is needed
            this.schemaDef  = function(){return fSchemaDef;};

            //Immutable field name
            this.name = function(){return fDef.Name;};

            //Immutable field data type
            this.type = function(){return fDef.Type;};

            //True if field contains boolean data
            this.isTypeLogical = function() { return WAVE.strOneOf(fDef.Type, ["bool", "boolean", "logical"]);};

            //True if field has lookup dictionary constraint
            this.isLookupDict = function(){
               var ld = fDef.LookupDict;
               return Object.keys(ld).length > 0;
            };

            //True if field is key
            this.key = function(){return fDef.Key;};

            //True if field must be stored back in the server (db)
            this.stored = function(){return fDef.Stored;};

            this.about = function(){
                var result = fDef.Description;
                if (WAVE.strEmpty(result)) result = fDef.Name;
                return result;
            };

            //Infers control type for the view from field definition disregarding ControlType set in schema
            this.inferControlType = function(){
               if (this.isTypeLogical()) return published.CTL_TP_CHECK;
               if (this.isLookupDict())
               {
                    var cnt = Object.keys(fDef.LookupDict).length;
                    if (cnt>4) return published.CTL_TP_COMBO;
                    return published.CTL_TP_RADIO;
               }

               if (fDef.Size>64)
                 return published.CTL_TP_TEXTAREA;
               else
                 return published.CTL_TP_TEXT;
            };

            //Returns control type for the view from schema, or if not avalable then infers it from field def
            this.getOrInferControlType = function(){
              var ct = fDef.ControlType;
              if (WAVE.strEmpty(ct) || WAVE.strOneOf(ct, [published.CTL_TP_AUTO, "automatic", "infer"])) ct = this.inferControlType();
              return ct;
            };



            function resetValidation(){
              resetRecValidation();
              fValidated = false;
              fValidationError = null;
            }

            function fireValidationDefChange(what, val){
              record.eventInvoke(published.EVT_VALIDATION_DEFINITION_CHANGE, field, what, val);
              field.eventInvoke(published.EVT_VALIDATION_DEFINITION_CHANGE, what, val);
            }

            function fireInteractionChange(what, val){
              record.eventInvoke(published.EVT_INTERACTION_CHANGE, field, what, val);
              field.eventInvoke(published.EVT_INTERACTION_CHANGE, what, val);
            }


            //Reverts the field instance to it's original schema def, firing events
            this.resetSchema = function(){
              fDef = WAVE.clone(fSchemaDef);//make copy
              WAVE.extend(fDef, published.FIELDDEF_DEFAULTS, true);

              resetValidation();

              fireValidationDefChange(null, null);
              fireInteractionChange(null, null);
            };


            //Validates field and returns true if it is valid
            this.validate = function(){

              function valError(txt, args){       return WAVE.strTemplate( WAVE.strLocalize(record.ISOLang(), "RMField", "error", txt), args);        }

              fValidated = true;
              fValidationError = null;
              try
              {
                  if (fDef.Required){
                   if (fValue===null || WAVE.strEmpty(fValue.toString())) throw valError("Field '@f@' must have a value", {f: this.about()});
                  }

                  if (fValue!==null &&
                     !WAVE.strEmpty(fValue.toString()))
                  {
                      var min = fDef.MinValue;
                      if (min!==null){
                        try { if (fValue<min) throw 1; }
                        catch(e) { throw valError("Field '@f@' value can not be less than '@b@'", {f: this.about(), b: min}); }
                      }

                      var max = fDef.MaxValue;
                      if (max!==null){
                        try { if (fValue>max) throw 1; }
                        catch(e) { throw valError("Field '@f@' value can not be greater than '@b@'", {f: this.about(), b: max}); }
                      }

                      var sz = fDef.Size;
                      if (sz!==null && sz>0){
                        if (fValue.toString().length>sz) throw valError("Field '@f@' value can not be longer than @b@ characters", {f: this.about(), b: sz});
                      }

                      sz = fDef.MinSize;
                      if (sz!==null && sz>0){
                        if (fValue.toString().length<sz) throw valError("Field '@f@' value can not be shorter than @b@ characters", {f: this.about(), b: sz});
                      }

                      var keys = Object.keys(fDef.LookupDict);
                      var isSimple = WAVE.isSimpleKeyStringMap(fDef.LookupDict);
                      if (keys.length > 0 && isSimple) {
                        var sval = WAVE.convertScalarType(false, fValue, "string", "<unconvertible>");
                        if (!WAVE.strOneOf(sval, keys)) throw valError("Field '@f@' value '@v@' is not allowed", {f: this.about(), v: sval});
                      }

                      if (fDef.Kind===published.KIND_EMAIL){
                        var evalid = WAVE.strIsEMail(fValue);
                        if (!evalid) throw valError("Field '@f@' must be a valid e-mail address", {f: this.about()});
                      }

                      if (fDef.Kind===published.KIND_SCREENNAME){
                        var svalid = WAVE.strIsScreenName(fValue);
                        if (!svalid) throw valError("Field '@f@' must start from letter and contain only letters or digits separated by single '.' or '-' or '_'", {f: this.about()});
                      }
                  }

                  field.eventInvoke(published.EVT_VALIDATE);//this trows errors
              }
              catch(error)
              {
                fValidationError = error;
              }
              field.eventInvoke(published.EVT_VALIDATED);
              return fValidationError===null;
            };

            //Error thrown during validation
            this.validationError = function() { return fValidationError;};

            //Sets external validation error (i,e, from the server side)
            this.setExternalValidationError = function(error, errorText){//for now error is ignored
                if (WAVE.strEmpty(errorText)) return;
                fValidationError = errorText;
                fValidated = false;
            };

            this.required = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Required) return fDef.Required;
                fDef.Required = val;
                resetValidation();
                fireValidationDefChange("required", val);
            };


            //Sets value and modified, validated flags if new value is different from an existing one.
            //Pass fromGUI to indicate that field is being altered from an attached control
            this.value = function(val, fromGUI){
                if (typeof(val)===tUNDEFINED) return fValue;
                var old = fValue;
                //convert value to field's data type
                var cval = WAVE.convertScalarType(true, val, fDef.Type, fDef.DefaultValue);

                var same = WAVE.isSame(old, cval);

                if (fromGUI){
                 var prior = cval;
                 cval = this.preProcessValueFromGUI(cval);
                 same = same && WAVE.isSame(prior, cval);
                }

                if (same) return fValue;//value did not change

                record.eventInvoke(published.EVT_DATA_CHANGE, published.EVT_PHASE_BEFORE, field, old, cval);
                this.eventInvoke(published.EVT_DATA_CHANGE, published.EVT_PHASE_BEFORE, old, cval);
                fValue = cval;
                fModified = true;
                if (fromGUI) fGUIModified = true;
                if (fDef.DeferValidation)
                    resetValidation();
                else
                    this.validate();
                this.eventInvoke(published.EVT_DATA_CHANGE, published.EVT_PHASE_AFTER, old, cval);
                record.eventInvoke(published.EVT_DATA_CHANGE, published.EVT_PHASE_AFTER, field, old, cval);

                return fValue;
            };

            //Pre-processes value when it is set from GUI, i.e. trim() string field or adjust phone numbers
            this.preProcessValueFromGUI = function(val){
                if (val===null) return null;

                if (typeof(val)==='string')
                {
                    val = WAVE.strTrim(val);

                    if (fDef.Kind===published.KIND_TEL)
                     val = WAVE.strNormalizeUSPhone(val);

                    if (fDef.Case===published.CASE_UPPER) val = val.toUpperCase();
                    else if (fDef.Case===published.CASE_LOWER) val = val.toLowerCase();
                    else if (fDef.Case===published.CASE_CAPS) val = WAVE.strCaps(val, false);
                    else if (fDef.Case===published.CASE_CAPSNORM) val = WAVE.strCaps(val, true);
                }

                return val;
            };

            this.isNull = function(){ return fValue===null;};

            //returns string value of field for display in attached controls/views
            this.displayValue = function(){
                if (fValue===null) return "";

                if (WAVE.isDateType(fDef.Type))
                {
                    //todo Add culture
                    if (fDef.Kind===published.KIND_DATE) return WAVE.toUSDateString(fValue);
                    return WAVE.toUSDateTimeString(fValue);
                }
                return fValue.toString();
            };

            //sets textual value from control into the field value performing necessary adjustements, i.e.
            //may adjust the format of the phone number etc.
            this.setGUIValue = function(val){
               //todo adjust phones, etc..
               this.value(val, true);
            };


            this.applyDefaultValue = function(force){
               if (force || this.isNull())
                this.value(fDef.DefaultValue);

               return fValue;
            };



            this.valid = function() { return fValidationError===null; };
            this.validated = function() { return fValidated;};
            this.validationError = function() { return fValidationError;};

            this.isModified = function() { return fModified; };
            this.resetModified = function() {
              var was = fModified;
              fModified = false;
              fireInteractionChange("modified", false);
              return was;
            };

            this.isGUIModified = function() { return fGUIModified; };
            this.resetGUIModified = function()
            {
              var was = fGUIModified;
              fGUIModified = false;
              fireInteractionChange("guimodified", false);
              return was;
            };

            //Returns true if field is enabled and applicable
            this.isEnabled = function(){
             return this.enabled() && this.applicable();
            };


            //Data kind for texboxes
            this.kind = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Kind) return fDef.Kind;
                fDef.Kind = val;
                fireInteractionChange("kind", val);
            };

            //String case  for texboxes
            this.case = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Case) return fDef.Case;
                fDef.Case = val;
                if (WAVE.isStringType(fDef.Type))
                    this.value(fValue, false);
            };

            this.applicable = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Applicable) return fDef.Applicable;
                fDef.Applicable = val;
                fireInteractionChange("applicable", val);
            };

            this.enabled = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Enabled) return fDef.Enabled;
                fDef.Enabled = val;
                fireInteractionChange("enabled", val);
            };

            this.readonly = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.ReadOnly) return fDef.ReadOnly;
                fDef.ReadOnly = val;
                fireInteractionChange("readonly", val);
            };

            this.visible = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Visible) return fDef.Visible;
                fDef.Visible = val;
                fireInteractionChange("visible", val);
            };

            this.password = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Password) return fDef.Password;
                fDef.Password = val;
                fireInteractionChange("password", val);
            };


            this.deferValidation = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.DeferValidation) return fDef.DeferValidation;
                fDef.DeferValidation = val;
                resetValidation();
                fireValidationDefChange("defervalidation", val);
            };

            this.minValue = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.MinValue) return fDef.MinValue;
                fDef.MinValue = val;
                resetValidation();
                fireValidationDefChange("minvalue", val);
            };

            this.maxValue = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.MaxValue) return fDef.MaxValue;
                fDef.MaxValue = val;
                resetValidation();
                fireValidationDefChange("maxvalue", val);
            };


            this.minSize = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.MinSize) return fDef.MinSize;
                fDef.MinSize = val;
                resetValidation();
                fireValidationDefChange("minsize", val);
            };

            this.size = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Size) return fDef.Size;
                fDef.Size = val;
                resetValidation();
                fireValidationDefChange("size", val);
            };

            this.controlType = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.ControlType) return fDef.ControlType;
                fDef.ControlType = val;
                fireInteractionChange("controltype", val);
            };

            this.scriptType = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.ScriptType) return fDef.ScriptType;
                fDef.ScriptType = val;
                fireInteractionChange("scripttype", val);
            };

            this.defaultValue = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.DefaultValue) return fDef.DefaultValue;
                fDef.DefaultValue = val;
                resetValidation();
                fireValidationDefChange("defaultvalue", val);
            };

            this.lookupDict = function(val){
                if (typeof(val)===tUNDEFINED) return fDef.LookupDict;
                if (val===null) val = {};
                if (WAVE.isSame(fDef.DefaultValue, val)) return fDef.LookupDict;
                fDef.LookupDict = val;
                resetValidation();
                fireValidationDefChange("lookupDict", val);
            };

            this.hint = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Hint) return fDef.Hint;
                fDef.Hint = val;
                fireInteractionChange("hint", val);
            };

            this.marked = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Marked) return fDef.Marked;
                fDef.Marked = val;
                fireInteractionChange("marked", val);
            };

            this.description = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Description) return fDef.Description;
                fDef.Description = val;
                fireInteractionChange("description", val);
            };

            this.placeholder = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Placeholder) return fDef.Placeholder;
                fDef.Placeholder = val;
                fireInteractionChange("placeholder", val);
            };

            this.config = function(val){
                if (typeof(val)===tUNDEFINED || val===fDef.Config) return fDef.Config;

                if(!WAVE.isObject(val))
                  throw "RecordView.config(val: must be object)";
                fDef.Config = val;

                resetValidation();
                fireValidationDefChange("config", null);
                fireInteractionChange("config", null);
            };

            this.toString = function(){
                return "["+this.type()+"]Field("+this.name()+" = '"+this.value()+"')";
            };


            fFieldLoaded = true;
            record.eventInvoke(published.EVT_FIELD_LOAD, field, published.EVT_PHASE_AFTER);
        };//Field

        //Complex object with schema was passed, init fields from it
        if (init){
          for(var i in init.fields){
            var finit = init.fields[i];
            var fld = new this.Field(finit.def);
            if (finit.hasOwnProperty('val')) fld.value(finit.val);
            if (finit.hasOwnProperty('errorText')) fld.setExternalValidationError(WAVE.strDefault(finit.error), finit.errorText);
            fld.resetModified();
          }

          //Record-level validation
          if (init.hasOwnProperty('errorText')) this.setExternalValidationError(WAVE.strDefault(init.error), init.errorText);

        }else if (WAVE.isFunction(fieldFunc)) fieldFunc.apply(record);

        fRecordLoaded = true;
        fRecords.push(this);
        this.eventInvoke(published.EVT_RECORD_LOAD, published.EVT_PHASE_AFTER);
    };//Record

    published.Record.prototype.toString = function(){ return "Record["+this.ID()+"]"; };