text: /**
   * Provides input field management, validation, submission, and form loading services for the collection
   * of {@link Ext.form.field.Field Field} instances within a {@link Ext.container.Container}. It is recommended
   * that you use a {@link Ext.form.Panel} as the form container, as that has logic to automatically
   * hook up an instance of {@link Ext.form.Basic} (plus other conveniences related to field configuration.)
   *
   * ## Form Actions
   *
   * The Basic class delegates the handling of form loads and submits to instances of {@link Ext.form.action.Action}.
   * See the various Action implementations for specific details of each one's functionality, as well as the
   * documentation for {@link #doAction} which details the configuration options that can be specified in
   * each action call.
   *
   * The default submit Action is {@link Ext.form.action.Submit}, which uses an Ajax request to submit the
   * form's values to a configured URL. To enable normal browser submission of an Ext form, use the
   * {@link #standardSubmit} config option.
   *
   * ## File uploads
   *
   * File uploads are not performed using normal 'Ajax' techniques; see the description for
   * {@link #hasUpload} for details. If you're using file uploads you should read the method description.
   *
   * ## Example usage:
   *
   *     @example
   *     Ext.create('Ext.form.Panel', {
   *         title: 'Basic Form',
   *         renderTo: Ext.getBody(),
   *         bodyPadding: 5,
   *         width: 350,
   *
   *         // Any configuration items here will be automatically passed along to
   *         // the Ext.form.Basic instance when it gets created.
   *
   *         // The form will submit an AJAX request to this URL when submitted
   *         url: 'save-form.php',
   *
   *         items: [{
   *             xtype: 'textfield',
   *             fieldLabel: 'Field',
   *             name: 'theField'
   *         }],
   *
   *         buttons: [{
   *             text: 'Submit',
   *             handler: function() {
   *                 // The getForm() method returns the Ext.form.Basic instance:
   *                 var form = this.up('form').getForm();
   *                 if (form.isValid()) {
   *                     // Submit the Ajax request and handle the response
   *                     form.submit({
   *                         success: function(form, action) {
   *                            Ext.Msg.alert('Success', action.result.message);
   *                         },
   *                         failure: function(form, action) {
   *                             Ext.Msg.alert('Failed', action.result ? action.result.message : 'No response');
   *                         }
   *                     });
   *                 }
   *             }
   *         }]
   *     });
   */
  Ext.define('Ext.form.Basic', {
      extend: 'Ext.util.Observable',
      alternateClassName: 'Ext.form.BasicForm',
  
      requires: [
          'Ext.util.MixedCollection',
          'Ext.form.action.Load',
          'Ext.form.action.Submit',
          'Ext.window.MessageBox',
          'Ext.data.ErrorCollection',
          'Ext.util.DelayedTask'
      ],
  
      // Not a public API config, this is useful when we're unit testing so we can
      // turn off the delayed tasks so they fire immediately.
      taskDelay: 10,
  
      /**
       * @event beforeaction
       * Fires before any action is performed. Return false to cancel the action.
       * @param {Ext.form.Basic} this
       * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} to be performed
       */
  
      /**
       * @event actionfailed
       * Fires when an action fails.
       * @param {Ext.form.Basic} this
       * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that failed
       */
  
      /**
       * @event actioncomplete
       * Fires when an action is completed.
       * @param {Ext.form.Basic} this
       * @param {Ext.form.action.Action} action The {@link Ext.form.action.Action} that completed
       */
  
      /**
       * @event validitychange
       * Fires when the validity of the entire form changes.
       * @param {Ext.form.Basic} this
       * @param {Boolean} valid `true` if the form is now valid, `false` if it is now invalid.
       */
  
      /**
       * @event dirtychange
       * Fires when the dirty state of the entire form changes.
       * @param {Ext.form.Basic} this
       * @param {Boolean} dirty `true` if the form is now dirty, `false` if it is no longer dirty.
       */
      
      /**
       * @event errorchange
       * Fires when the error of one (or more) of the fields in the form changes.
       * @param {Ext.form.Basic} this
       *
       * @private
       */
  
      /**
       * Creates new form.
       * @param {Ext.container.Container} owner The component that is the container for the form, usually a {@link Ext.form.Panel}
       * @param {Object} config Configuration options. These are normally specified in the config to the
       * {@link Ext.form.Panel} constructor, which passes them along to the BasicForm automatically.
       */
      constructor: function(owner, config) {
          var me = this,
              reader;
  
          /**
           * @property {Ext.container.Container} owner
           * The container component to which this BasicForm is attached.
           */
          me.owner = owner;
          
          me.fieldMonitors = {
              validitychange: me.checkValidityDelay,
              enable: me.checkValidityDelay,
              disable: me.checkValidityDelay,
              dirtychange: me.checkDirtyDelay,
              errorchange: me.checkErrorDelay,
              scope: me
          };
  
          me.checkValidityTask = new Ext.util.DelayedTask(me.checkValidity, me);
          me.checkDirtyTask = new Ext.util.DelayedTask(me.checkDirty, me);
          me.checkErrorTask = new Ext.util.DelayedTask(me.checkError, me);
          
          // We use the monitor here as opposed to event bubbling. The problem with bubbling is it doesn't
          // let us react to items being added/remove at different places in the hierarchy which may have an
          // impact on the dirty/valid state.
          me.monitor = new Ext.container.Monitor({
              selector: '[isFormField]:not([excludeForm])',
              scope: me,
              addHandler: me.onFieldAdd,
              removeHandler: me.onFieldRemove,
              invalidateHandler: me.onMonitorInvalidate
          });
          me.monitor.bind(owner);
  
          Ext.apply(me, config);
  
          // Normalize the paramOrder to an Array
          if (Ext.isString(me.paramOrder)) {
              me.paramOrder = me.paramOrder.split(/[\s,|]/);
          }
          
          reader = me.reader;
          if (reader && !reader.isReader) {
              if (typeof reader === 'string') {
                  reader = {
                      type: reader
                  };
              }
              me.reader = Ext.createByAlias('reader.' + reader.type, reader);
          }
          
          reader = me.errorReader;
          if (reader && !reader.isReader) {
              if (typeof reader === 'string') {
                  reader = {
                      type: reader
                  };
              }
              me.errorReader = Ext.createByAlias('reader.' + reader.type, reader);
          }
  
          me.callParent();
      },
  
      /**
       * Do any post layout initialization
       * @private
       */
      initialize : function() {
          this.initialized = true;
          this.onValidityChange(!this.hasInvalidField());
      },
  
  
      /**
       * @cfg {String} method
       * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
       */
  
      /**
       * @cfg {Object/Ext.data.reader.Reader} reader
       * An Ext.data.reader.Reader (e.g. {@link Ext.data.reader.Xml}) instance or
       * configuration to be used to read data when executing 'load' actions. This 
       * is optional as there is built-in support for processing JSON responses.
       */
  
      /**
       * @cfg {Object/Ext.data.reader.Reader} errorReader
       * An Ext.data.reader.Reader (e.g. {@link Ext.data.reader.Xml}) instance or
       * configuration to be used to read field error messages returned from 'submit' actions. 
       * This is optional as there is built-in support for processing JSON responses.
       *
       * The Records which provide messages for the invalid Fields must use the
       * Field name (or id) as the Record ID, and must contain a field called 'msg'
       * which contains the error message.
       *
       * The errorReader does not have to be a full-blown implementation of a
       * Reader. It simply needs to implement a `read(xhr)` function
       * which returns an Array of Records in an object with the following
       * structure:
       *
       *     {
       *         records: recordArray
       *     }
       */
  
      /**
       * @cfg {String} url
       * The URL to use for form actions if one isn't supplied in the
       * {@link Ext.form.Basic#doAction doAction} options.
       */
  
      /**
       * @cfg {Object} baseParams
       * Parameters to pass with all requests. e.g. baseParams: `{id: '123', foo: 'bar'}`.
       *
       * Parameters are encoded as standard HTTP parameters using {@link Ext.Object#toQueryString}.
       */
  
      /**
       * @cfg {Number} timeout
       * Timeout for form actions in seconds.
       */
      timeout: 30,
  
      /**
       * @cfg {Object} api
       * If specified, load and submit actions will be handled with {@link Ext.form.action.DirectLoad DirectLoad}
       * and {@link Ext.form.action.DirectSubmit DirectSubmit}.  Methods which have been imported by
       * {@link Ext.direct.Manager} can be specified here to load and submit forms. API methods may also be
       * specified as strings. See {@link Ext.data.proxy.Direct#directFn}.  Such as the following:
       *
       *     api: {
       *         load: App.ss.MyProfile.load,
       *         submit: App.ss.MyProfile.submit
       *     }
       *
       * Load actions can use {@link #paramOrder} or {@link #paramsAsHash} to customize how the load method
       * is invoked.  Submit actions will always use a standard form submit. The `formHandler` configuration
       * (see Ext.direct.RemotingProvider#action) must be set on the associated server-side method which has
       * been imported by {@link Ext.direct.Manager}.
       */
  
      /**
       * @cfg {String/String[]} paramOrder
       * A list of params to be executed server side. Only used for the {@link #api} `load`
       * configuration.
       *
       * Specify the params in the order in which they must be executed on the
       * server-side as either (1) an Array of String values, or (2) a String of params
       * delimited by either whitespace, comma, or pipe. For example,
       * any of the following would be acceptable:
       *
       *     paramOrder: ['param1','param2','param3']
       *     paramOrder: 'param1 param2 param3'
       *     paramOrder: 'param1,param2,param3'
       *     paramOrder: 'param1|param2|param'
       */
  
      /**
       * @cfg {Boolean} paramsAsHash
       * Only used for the {@link #api} `load` configuration. If true, parameters will be sent as a
       * single hash collection of named arguments. Providing a {@link #paramOrder} nullifies this
       * configuration.
       */
      paramsAsHash: false,
      
      /**
       * @cfg {Object/Array} [metadata]
       * Optional metadata to pass with the actions when Ext.Direct {@link #api} is used.
       * See {@link Ext.direct.Manager} for more information.
       */
  
      //<locale>
      /**
       * @cfg {String} waitTitle
       * The default title to show for the waiting message box
       */
      waitTitle: 'Please Wait...',
      //</locale>
  
      /**
       * @cfg {Boolean} trackResetOnLoad
       * If set to true, {@link #method-reset}() resets to the last loaded or
       * {@link Ext.form.Basic#setValues}() data instead of when the form was first
       * created.
       */
      trackResetOnLoad: false,
  
      /**
       * @cfg {Boolean} standardSubmit
       * If set to true, a standard HTML form submit is used instead of a XHR (Ajax) style form submission.
       * All of the field values, plus any additional params configured via {@link #baseParams}
       * and/or the `options` to {@link #submit}, will be included in the values submitted in the form.
       */
  
      /**
       * @cfg {Boolean} jsonSubmit
       * If set to true, the field values are sent as JSON in the request body.
       * All of the field values, plus any additional params configured via {@link #baseParams}
       * and/or the `options` to {@link #submit}, will be included in the values POSTed in the body of the request.
       */
  
      /**
       * @cfg {String/HTMLElement/Ext.dom.Element} waitMsgTarget
       * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
       * element by passing it or its id or mask the form itself by passing in true.
       */
  
  
      // Private
      wasDirty: false,
  
  
      /**
       * Destroys this object.
       */
      destroy: function() {
          var me = this,
              mon = me.monitor;
          
          if (mon) {
              mon.unbind();
              me.monitor = null;
          }
          me.clearListeners();
          me.checkValidityTask.cancel();
          me.checkDirtyTask.cancel();
          me.checkErrorTask.cancel();
  
          me.checkValidityTask = me.checkDirtyTask = me.checkErrorTask = null;
          me.isDestroyed = true;
      },
      
      onFieldAdd: function(field){
          field.on(this.fieldMonitors);
          this.onMonitorInvalidate();
      },
      
      onFieldRemove: function(field){
          field.un(this.fieldMonitors);
          this.onMonitorInvalidate();
      },
      
      onMonitorInvalidate: function() {
          if (this.initialized) {
              this.checkValidityDelay();
          }
      },
      
      /**
       * Return all the {@link Ext.form.field.Field} components in the owner container.
       * @return {Ext.util.MixedCollection} Collection of the Field objects
       */
      getFields: function() {
          return this.monitor.getItems();
      },
  
      /**
       * @private
       * Finds and returns the set of all items bound to fields inside this form
       * @return {Ext.util.MixedCollection} The set of all bound form field items
       */
      getBoundItems: function() {
          var boundItems = this._boundItems;
          
          if (!boundItems || boundItems.getCount() === 0) {
              boundItems = this._boundItems = new Ext.util.MixedCollection();
              boundItems.addAll(this.owner.query('[formBind]'));
          }
          
          return boundItems;
      },
  
      /**
       * Returns true if the form contains any invalid fields. No fields will be marked as invalid
       * as a result of calling this; to trigger marking of fields use {@link #isValid} instead.
       */
      hasInvalidField: function() {
          return !!this.getFields().findBy(function(field) {
              var preventMark = field.preventMark,
                  isValid;
              field.preventMark = true;
              isValid = field.isValid();
              field.preventMark = preventMark;
              return !isValid;
          });
      },
  
      /**
       * Returns true if client-side validation on the form is successful. Any invalid fields will be
       * marked as invalid. If you only want to determine overall form validity without marking anything,
       * use {@link #hasInvalidField} instead.
       * @return {Boolean}
       */
      isValid: function() {
          var me = this,
              invalid;
          Ext.suspendLayouts();
          invalid = me.getFields().filterBy(function(field) {
              return !field.validate();
          });
          Ext.resumeLayouts(true);
          return invalid.length < 1;
      },
  
      /**
       * Check whether the validity of the entire form has changed since it was last checked, and
       * if so fire the {@link #validitychange validitychange} event. This is automatically invoked
       * when an individual field's validity changes.
       */
      checkValidity: function() {
          var me = this,
              valid;
          
          if (me.isDestroyed) {
              return;
          }
              
          valid = !me.hasInvalidField();
          if (valid !== me.wasValid) {
              me.onValidityChange(valid);
              me.fireEvent('validitychange', me, valid);
              me.wasValid = valid;
          }
      },
      
      checkValidityDelay: function(){
          var timer = this.taskDelay;
          if (timer) {
              this.checkValidityTask.delay(timer);
          } else {
              this.checkValidity();
          }
      },
  
      checkError: function() {
          // Currently this event is private, we don't really care
          // about the summation of the change, rather that something has
          // changed so we may need to recalculate. In the future if this
          // is made public, we would need to track the error on a per-field basis.
          this.fireEvent('errorchange', this);
      },
  
      checkErrorDelay: function() {
          var timer = this.taskDelay;
          if (timer) {
              this.checkErrorTask.delay(timer);
          } else {
              this.checkError();
          }
      },
  
      /**
       * @private
       * Handle changes in the form's validity. If there are any sub components with
       * `formBind=true` then they are enabled/disabled based on the new validity.
       * @param {Boolean} valid
       */
      onValidityChange: function(valid) {
          var boundItems = this.getBoundItems(),
              items, i, iLen, cmp;
  
          if (boundItems) {
              items = boundItems.items;
              iLen  = items.length;
  
              for (i = 0; i < iLen; i++) {
                  cmp = items[i];
  
                  if (cmp.disabled === valid) {
                      cmp.setDisabled(!valid);
                  }
              }
          }
      },
  
      /**
       * Returns `true` if any fields in this form have changed from their original values.
       *
       * Note that if this BasicForm was configured with {@link Ext.form.Basic#trackResetOnLoad
       * trackResetOnLoad} then the Fields' *original values* are updated when the values are
       * loaded by {@link Ext.form.Basic#setValues setValues} or {@link #loadRecord}. This means
       * that:
       * 
       * - {@link #trackResetOnLoad}: `false` -> Will return `true` after calling this method.
       * - {@link #trackResetOnLoad}: `true` -> Will return `false` after calling this method.
       *
       * @return {Boolean}
       */
      isDirty: function() {
          return !!this.getFields().findBy(function(f) {
              return f.isDirty();
          });
      },
      
      checkDirtyDelay: function(){
          var timer = this.taskDelay;
          if (timer) {
              this.checkDirtyTask.delay(timer);
          } else {
              this.checkDirty();
          }
      },
  
      /**
       * Check whether the dirty state of the entire form has changed since it was last checked, and
       * if so fire the {@link #dirtychange dirtychange} event. This is automatically invoked
       * when an individual field's `dirty` state changes.
       */
      checkDirty: function() {
          var me = this,
              dirty;
              
          if (me.isDestroyed) {
              return;
          }
              
          dirty = this.isDirty();
          if (dirty !== this.wasDirty) {
              this.fireEvent('dirtychange', this, dirty);
              this.wasDirty = dirty;
          }
      },
  
      /**
       * Returns `true` if the form contains a file upload field. This is used to determine the method for submitting the
       * form: File uploads are not performed using normal 'Ajax' techniques, that is they are **not** performed using
       * XMLHttpRequests. Instead a hidden `<form>` element containing all the fields is created temporarily and submitted
       * with its [target][1] set to refer to a dynamically generated, hidden `<iframe>` which is inserted into the document
       * but removed after the return data has been gathered.
       *
       * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON
       * to send the return object, then the [Content-Type][2] header should be set to "text/plain" in order to tell the
       * browser to insert the text unchanged into a '&lt;pre>' element in the document body from which it can be retrieved.
       *
       * If the [Content-Type][2] header is sent as the default, "text/html", then characters which are significant to an HTML
       * parser must be sent as HTML entities, so encode `"<"` as `"&lt;"`, `"&"` as `"&amp;"` etc.
       *
       * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
       * responseText property in order to conform to the requirements of event handlers and callbacks.
       *
       * Be aware that file upload packets are sent with the content type [multipart/form][3] and some server technologies
       * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from
       * the packet content.
       *
       * [1]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
       * [2]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
       * [3]: http://www.faqs.org/rfcs/rfc2388.html
       *
       * @return {Boolean}
       */
      hasUpload: function() {
          return !!this.getFields().findBy(function(f) {
              return f.isFileUpload();
          });
      },
  
      /**
       * Performs a predefined action (an implementation of {@link Ext.form.action.Action}) to perform application-
       * specific processing.
       *
       * @param {String/Ext.form.action.Action} action The name of the predefined action type, or instance of {@link
       * Ext.form.action.Action} to perform.
       *
       * @param {Object} [options] The options to pass to the {@link Ext.form.action.Action} that will get created,
       * if the action argument is a String.
       *
       * All of the config options listed below are supported by both the {@link Ext.form.action.Submit submit} and
       * {@link Ext.form.action.Load load} actions unless otherwise noted (custom actions could also accept other
       * config options):
       *
       * @param {String} options.url
       * The url for the action (defaults to the form's {@link #url}.)
       *
       * @param {String} options.method
       * The form method to use (defaults to the form's method, or POST if not defined)
       *
       * @param {String/Object} options.params
       * The params to pass (defaults to the form's baseParams, or none if not defined)
       *
       * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode Ext.Object.toQueryString}.
       *
       * @param {Object} options.headers
       * Request headers to set for the action.
       *
       * @param {Function} options.success
       * The callback that will be invoked after a successful response (see top of {@link Ext.form.action.Submit submit}
       * and {@link Ext.form.action.Load load} for a description of what constitutes a successful response).
       * @param {Ext.form.Basic} options.success.form The form that requested the action.
       * @param {Ext.form.action.Action} options.success.action The Action object which performed the operation.
       * The action object contains these properties of interest:
       *
       *  - {@link Ext.form.action.Action#response response}
       *  - {@link Ext.form.action.Action#result result} - interrogate for custom post-processing
       *  - {@link Ext.form.action.Action#type type}
       *
       * @param {Function} options.failure
       * The callback that will be invoked after a failed transaction attempt.
       * @param {Ext.form.Basic} options.failure.form The form that requested the action.
       * @param {Ext.form.action.Action} options.failure.action The Action object which performed the operation.
       * The action object contains these properties of interest:
       *
       * - {@link Ext.form.action.Action#failureType failureType}
       * - {@link Ext.form.action.Action#response response}
       * - {@link Ext.form.action.Action#result result} - interrogate for custom post-processing
       * - {@link Ext.form.action.Action#type type}
       *
       * @param {Object} options.scope
       * The scope in which to call the callback functions (The this reference for the callback functions).
       *
       * @param {Boolean} options.clientValidation
       * Submit Action only. Determines whether a Form's fields are validated in a final call to {@link
       * Ext.form.Basic#isValid isValid} prior to submission. Set to false to prevent this. If undefined, pre-submission
       * field validation is performed.
       *
       * @return {Ext.form.Basic} this
       */
      doAction: function(action, options) {
          if (Ext.isString(action)) {
              action = Ext.ClassManager.instantiateByAlias('formaction.' + action, Ext.apply({}, options, {form: this}));
          }
          if (this.fireEvent('beforeaction', this, action) !== false) {
              this.beforeAction(action);
              Ext.defer(action.run, 100, action);
          }
          return this;
      },
  
      /**
       * Shortcut to {@link #doAction do} a {@link Ext.form.action.Submit submit action}. This will use the
       * {@link Ext.form.action.Submit AJAX submit action} by default. If the {@link #standardSubmit} config
       * is enabled it will use a standard form element to submit, or if the {@link #api} config is present
       * it will use the {@link Ext.form.action.DirectLoad Ext.direct.Direct submit action}.
       *
       * The following code:
       *
       *     myFormPanel.getForm().submit({
       *         clientValidation: true,
       *         url: 'updateConsignment.php',
       *         params: {
       *             newStatus: 'delivered'
       *         },
       *         success: function(form, action) {
       *            Ext.Msg.alert('Success', action.result.msg);
       *         },
       *         failure: function(form, action) {
       *             switch (action.failureType) {
       *                 case Ext.form.action.Action.CLIENT_INVALID:
       *                     Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
       *                     break;
       *                 case Ext.form.action.Action.CONNECT_FAILURE:
       *                     Ext.Msg.alert('Failure', 'Ajax communication failed');
       *                     break;
       *                 case Ext.form.action.Action.SERVER_INVALID:
       *                    Ext.Msg.alert('Failure', action.result.msg);
       *            }
       *         }
       *     });
       *
       * would process the following server response for a successful submission:
       *
       *     {
       *         "success":true, // note this is Boolean, not string
       *         "msg":"Consignment updated"
       *     }
       *
       * and the following server response for a failed submission:
       *
       *     {
       *         "success":false, // note this is Boolean, not string
       *         "msg":"You do not have permission to perform this operation"
       *     }
       *
       * @param {Object} options The options to pass to the action (see {@link #doAction} for details).
       * @return {Ext.form.Basic} this
       */
      submit: function(options) {
          options = options || {};
          var me = this,
              action;
              
          if (options.standardSubmit || me.standardSubmit) {
              action = 'standardsubmit';
          } else {
              action = me.api ? 'directsubmit' : 'submit';
          }
              
          return me.doAction(action, options);
      },
  
      /**
       * Shortcut to {@link #doAction do} a {@link Ext.form.action.Load load action}.
       * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
       * @return {Ext.form.Basic} this
       */
      load: function(options) {
          return this.doAction(this.api ? 'directload' : 'load', options);
      },
  
      /**
       * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
       * If the record is not specified, it will attempt to update (if it exists) the record provided to loadRecord.
       * @param {Ext.data.Model} [record] The record to edit
       * @return {Ext.form.Basic} this
       */
      updateRecord: function(record) {
          record = record || this._record;
          if (!record) {
              //<debug>
              Ext.Error.raise("A record is required.");
              //</debug>
              return this;
          }
          
          var fields = record.self.fields,
              values = this.getFieldValues(),
              obj = {},
              i = 0,
              len = fields.length,
              name;
  
          for (; i < len; ++i) {
              name  = fields[i].name;
  
              if (values.hasOwnProperty(name)) {
                  obj[name] = values[name];
              }
          }
  
          record.beginEdit();
          record.set(obj);
          record.endEdit();
  
          return this;
      },
  
      /**
       * Loads an {@link Ext.data.Model} into this form by calling {@link #setValues} with the
       * {@link Ext.data.Model#getData record data}. The fields in the model are mapped to 
       * fields in the form by matching either the {@link Ext.form.field.Base#name} or {@link Ext.Component#itemId}.  
       * See also {@link #trackResetOnLoad}. 
       * @param {Ext.data.Model} record The record to load
       * @return {Ext.form.Basic} this
       */
      loadRecord: function(record) {
          this._record = record;
          return this.setValues(record.getData());
      },
  
      /**
       * Returns the last Ext.data.Model instance that was loaded via {@link #loadRecord}
       * @return {Ext.data.Model} The record
       */
      getRecord: function() {
          return this._record;
      },
  
      /**
       * @private
       * Called before an action is performed via {@link #doAction}.
       * @param {Ext.form.action.Action} action The Action instance that was invoked
       */
      beforeAction: function(action) {
          var me = this,
              waitMsg = action.waitMsg,
              maskCls = Ext.baseCSSPrefix + 'mask-loading',
              fields  = me.getFields().items,
              f,
              fLen    = fields.length,
              field, waitMsgTarget;
  
          // Call HtmlEditor's syncValue before actions
          for (f = 0; f < fLen; f++) {
              field = fields[f];
  
              if (field.isFormField && field.syncValue) {
                  field.syncValue();
              }
          }
  
          if (waitMsg) {
              waitMsgTarget = me.waitMsgTarget;
              if (waitMsgTarget === true) {
                  me.owner.el.mask(waitMsg, maskCls);
              } else if (waitMsgTarget) {
                  waitMsgTarget = me.waitMsgTarget = Ext.get(waitMsgTarget);
                  waitMsgTarget.mask(waitMsg, maskCls);
              } else {
                  me.floatingAncestor = me.owner.up('[floating]');
  
                  // https://sencha.jira.com/browse/EXTJSIV-6397
                  // When the "wait" MessageBox is hidden, the ZIndexManager activates the previous
                  // topmost floating item which would be any Window housing this form.
                  // That kicks off a delayed focus call on that Window.
                  // So if any form post submit processing displayed a MessageBox, that gets
                  // stomped on.
                  // The solution is to not move focus at all during this process.
                  if (me.floatingAncestor) {
                      me.savePreventFocusOnActivate = me.floatingAncestor.preventFocusOnActivate;
                      me.floatingAncestor.preventFocusOnActivate = true;
                  }
                  Ext.MessageBox.wait(waitMsg, action.waitTitle || me.waitTitle);
              }
          }
      },
  
      /**
       * @private
       * Called after an action is performed via {@link #doAction}.
       * @param {Ext.form.action.Action} action The Action instance that was invoked
       * @param {Boolean} success True if the action completed successfully, false, otherwise.
       */
      afterAction: function(action, success) {
          var me = this;
          if (action.waitMsg) {
              var messageBox = Ext.MessageBox,
                  waitMsgTarget = me.waitMsgTarget;
              if (waitMsgTarget === true) {
                  me.owner.el.unmask();
              } else if (waitMsgTarget) {
                  waitMsgTarget.unmask();
              } else {
                  messageBox.hide();
              }
          }
          // Restore setting of any floating ancestor which was manipulated in beforeAction
          if (me.floatingAncestor) {
              me.floatingAncestor.preventFocusOnActivate = me.savePreventFocusOnActivate;
          }
          if (success) {
              if (action.reset) {
                  me.reset();
              }
              Ext.callback(action.success, action.scope || action, [me, action]);
              me.fireEvent('actioncomplete', me, action);
          } else {
              Ext.callback(action.failure, action.scope || action, [me, action]);
              me.fireEvent('actionfailed', me, action);
          }
      },
  
  
      /**
       * Find a specific {@link Ext.form.field.Field} in this form by id or name.
       * @param {String} id The value to search for (specify either a {@link Ext.Component#id id} or
       * {@link Ext.form.field.Field#getName name or hiddenName}).
       * @return {Ext.form.field.Field} The first matching field, or `null` if none was found.
       */
      findField: function (id) {
          return this.getFields().findBy(function (f) {
              return f.id === id || f.name === id || f.dataIndex === id;
          });
      },
  
  
      /**
       * This method allows you to mark one or more fields in a form as invalid along with 
       * one or more invalid messages per field.
       * 
       *     var formPanel = Ext.create('Ext.form.Panel', {
       *         title: 'Contact Info',
       *         width: 300,
       *         bodyPadding: 10,
       *         renderTo: Ext.getBody(),
       *         items: [{
       *             xtype: 'textfield',
       *             name: 'name',
       *             id: 'nameId',
       *             fieldLabel: 'Name'
       *         }, {
       *             xtype: 'textfield',
       *             name: 'email',
       *             id: 'emailId',
       *             fieldLabel: 'Email Address'
       *         }],
       *         bbar: [{
       *             text: 'Mark both fields invalid',
       *             handler: function() {
       *                 formPanel.getForm().markInvalid([{
       *                     field: 'name',
       *                     message: 'Name invalid message'
       *                 }, {
       *                     field: 'email',
       *                     message: ['First invalid message', 'Second message']
       *                 }]);
       *             }
       *         }]
       *     });
       * 
       * **Note**: this method does not cause the Field's {@link #validate} or 
       * {@link #isValid} methods to return `false` if the value does _pass_ validation. 
       * So simply marking a Field as invalid will not prevent submission of forms
       * submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
       * 
       * For additional information on how the fields are marked invalid see field's 
       * {@link Ext.form.field.Base#markInvalid markInvalid} method.
       * 
       * @param {Object/Object[]} errors
       * The errors param may be in one of two forms: Object[] or Object
       * 
       * - **Array:** An array of Objects with the following keys:
       *     - _field_ ({@link String}): The {@link Ext.form.field.Base#name name} or 
       * {@link Ext.form.field.Base#id id} of the form field to receive the error message
       *     - _message_ ({@link String}/{@link String}[]): The error message or an array 
       * of messages
       * 
       * Example Array syntax:
       * 
       *     form.markInvalid([{
       *         field: 'email', // the field name
       *         message: 'Error message'
       *     }]);
       * 
       * - **Object:** An Object hash with key/value pairs where the key is the field name 
       * or field ID and the value is the message or array of messages to display.
       * 
       * Example Object syntax:
       * 
       *     form.markInvalid({
       *         name: 'Err. message',
       *         emailId: ['Error1', 'Error 2']
       *     });
       * 
       * @return {Ext.form.Basic} basicForm The Ext.form.Basic instance
       */
      markInvalid: function(errors) {
          var me = this,
              e, eLen, error, value,
              key;
  
          function mark(fieldId, msg) {
              var field = me.findField(fieldId);
              if (field) {
                  field.markInvalid(msg);
              }
          }
  
          if (Ext.isArray(errors)) {
              eLen = errors.length;
  
              for (e = 0; e < eLen; e++) {
                  error = errors[e];
                  mark(error.id || error.field, error.msg || error.message);
              }
          } else if (errors instanceof Ext.data.ErrorCollection) {
              eLen  = errors.items.length;
              for (e = 0; e < eLen; e++) {
                  error = errors.items[e];
  
                  mark(error.field, error.message);
              }
          } else {
              for (key in errors) {
                  if (errors.hasOwnProperty(key)) {
                      value = errors[key];
                      mark(key, value, errors);
                  }
              }
          }
          return this;
      },
  
      /**
       * Set values for fields in this form in bulk.
       *
       * @param {Object/Object[]} values Either an array in the form:
       *
       *     [{id:'clientName', value:'Fred. Olsen Lines'},
       *      {id:'portOfLoading', value:'FXT'},
       *      {id:'portOfDischarge', value:'OSL'} ]
       *
       * or an object hash of the form:
       *
       *     {
       *         clientName: 'Fred. Olsen Lines',
       *         portOfLoading: 'FXT',
       *         portOfDischarge: 'OSL'
       *     }
       *
       * @return {Ext.form.Basic} this
       */
      setValues: function(values) {
          var me = this,
              v, vLen, val;
  
          function setVal(fieldId, val) {
              var field = me.findField(fieldId);
              if (field) {
                  field.setValue(val);
                  if (me.trackResetOnLoad) {
                      field.resetOriginalValue();
                  }
              }
          }
  
          // Suspend here because setting the value on a field could trigger
          // a layout, for example if an error gets set, or it's a display field
          Ext.suspendLayouts();
          if (Ext.isArray(values)) {
              // array of objects
              vLen = values.length;
  
              for (v = 0; v < vLen; v++) {
                  val = values[v];
  
                  setVal(val.id, val.value);
              }
          } else {
              // object hash
              Ext.iterate(values, setVal);
          }
          Ext.resumeLayouts(true);
          return this;
      },
  
      /**
       * Retrieves the fields in the form as a set of key/value pairs, using their
       * {@link Ext.form.field.Field#getSubmitData getSubmitData()} method to collect the values.
       * If multiple fields return values under the same name those values will be combined into an Array.
       * This is similar to {@link Ext.form.Basic#getFieldValues getFieldValues} except that this method
       * collects only String values for submission, while getFieldValues collects type-specific data
       * values (e.g. Date objects for date fields.)
       *
       * @param {Boolean} [asString=false] If true, will return the key/value collection as a single
       * URL-encoded param string.
       * @param {Boolean} [dirtyOnly=false] If true, only fields that are dirty will be included in the result.
       * @param {Boolean} [includeEmptyText=false] If true, the configured emptyText of empty fields will be used.
       * @param {Boolean} [useDataValues=false] If true, the {@link Ext.form.field.Field#getModelData getModelData}
       * method is used to retrieve values from fields, otherwise the {@link Ext.form.field.Field#getSubmitData getSubmitData}
       * method is used.
       * @return {String/Object}
       */
      getValues: function(asString, dirtyOnly, includeEmptyText, useDataValues, isSubmitting) {
          var values  = {},
              fields  = this.getFields().items,
              fLen    = fields.length,
              isArray = Ext.isArray,
              field, data, val, bucket, name, f;
  
          for (f = 0; f < fLen; f++) {
              field = fields[f];
              if (!dirtyOnly || field.isDirty()) {
                  data = field[useDataValues ? 'getModelData' : 'getSubmitData'](includeEmptyText, isSubmitting);
  
                  if (Ext.isObject(data)) {
                      for (name in data) {
                          if (data.hasOwnProperty(name)) {
                              val = data[name];
  
                              if (includeEmptyText && val === '') {
                                  val = field.emptyText || '';
                              }
  
                              if (!field.isRadio) {
                                  if (values.hasOwnProperty(name)) {
                                      bucket = values[name];
  
                                      if (!isArray(bucket)) {
                                          bucket = values[name] = [bucket];
                                      }
  
                                      if (isArray(val)) {
                                          values[name] = bucket.concat(val);
                                      } else {
                                          bucket.push(val);
                                      }
                                  } else {
                                      values[name] = val;
                                  }
                              } else {
                                  values[name] = values[name] || val;
                              }
                          }
                      }
                  }
              }
          }
  
          if (asString) {
              values = Ext.Object.toQueryString(values);
          }
          return values;
      },
  
      /**
       * Retrieves the fields in the form as a set of key/value pairs, using their
       * {@link Ext.form.field.Field#getModelData getModelData()} method to collect the values.
       * If multiple fields return values under the same name those values will be combined into an Array.
       * This is similar to {@link #getValues} except that this method collects type-specific data values
       * (e.g. Date objects for date fields) while getValues returns only String values for submission.
       *
       * @param {Boolean} [dirtyOnly=false] If true, only fields that are dirty will be included in the result.
       * @return {Object}
       */
      getFieldValues: function(dirtyOnly) {
          return this.getValues(false, dirtyOnly, false, true);
      },
  
      /**
       * Clears all invalid field messages in this form.
       * @return {Ext.form.Basic} this
       */
      clearInvalid: function() {
          Ext.suspendLayouts();
  
          var me     = this,
              fields = me.getFields().items,
              f,
              fLen   = fields.length;
  
          for (f = 0; f < fLen; f++) {
              fields[f].clearInvalid();
          }
  
          Ext.resumeLayouts(true);
          return me;
      },
  
      /**
       * Resets all fields in this form. By default, any record bound by {@link #loadRecord}
       * will be retained.
       * @param {Boolean} [resetRecord=false] True to unbind any record set
       * by {@link #loadRecord}
       * @return {Ext.form.Basic} this
       */
      reset: function(resetRecord) {
          Ext.suspendLayouts();
  
          var me     = this,
              fields = me.getFields().items,
              f,
              fLen   = fields.length;
  
          for (f = 0; f < fLen; f++) {
              fields[f].reset();
          }
  
          Ext.resumeLayouts(true);
          
          if (resetRecord === true) {
              delete me._record;
          }
          return me;
      },
  
      /**
       * Calls {@link Ext#apply Ext.apply} for all fields in this form with the passed object.
       * @param {Object} obj The object to be applied
       * @return {Ext.form.Basic} this
       */
      applyToFields: function(obj) {
          var fields = this.getFields().items,
              f,
              fLen   = fields.length;
  
          for (f = 0; f < fLen; f++) {
              Ext.apply(fields[f], obj);
          }
  
          return this;
      },
  
      /**
       * Calls {@link Ext#applyIf Ext.applyIf} for all field in this form with the passed object.
       * @param {Object} obj The object to be applied
       * @return {Ext.form.Basic} this
       */
      applyIfToFields: function(obj) {
          var fields = this.getFields().items,
              f,
              fLen   = fields.length;
  
          for (f = 0; f < fLen; f++) {
              Ext.applyIf(fields[f], obj);
          }
  
          return this;
      }
  });
  