text: /**
   * The Form Builder Component
   *
   * @module aui-form-builder
   * @submodule aui-form-builder-field
   */
  
  var L = A.Lang,
      isArray = L.isArray,
      isObject = L.isObject,
  
      AArray = A.Array,
      AEscape = A.Escape,
  
      getCN = A.getClassName,
  
      CSS_CLEARFIX = getCN('clearfix'),
      CSS_COMPONENT = getCN('component'),
      CSS_FB_DROP_ZONE = getCN('form', 'builder', 'drop', 'zone'),
      CSS_FB_FIELD = getCN('form', 'builder', 'field'),
      CSS_FB_FIELD_NODE = getCN('form', 'builder', 'field', 'node'),
      CSS_FB_FIELD_SELECTED = getCN('form', 'builder', 'field', 'selected'),
      CSS_FB_UNIQUE = getCN('form', 'builder', 'unique'),
      CSS_ICON = getCN('glyphicon'),
      CSS_ICON_ASTERISK = getCN('glyphicon', 'asterisk'),
      CSS_ICON_PLUS = getCN('glyphicon', 'plus'),
      CSS_ICON_QUESTION_SIGN = getCN('glyphicon', 'question', 'sign'),
      CSS_ICON_TRASH = getCN('glyphicon', 'trash'),
      CSS_ICON_WRENCH = getCN('glyphicon', 'wrench'),
      CSS_WIDGET = getCN('widget'),
  
      TPL_BOUNDING_BOX = '<div class="' + [CSS_WIDGET, CSS_COMPONENT, CSS_FB_FIELD].join(' ') + '"></div>',
      TPL_DROP_ZONE = '<div class="' + CSS_FB_DROP_ZONE + '"></div>',
      TPL_FLAG_REQUIRED = '<span class="' + [CSS_ICON, CSS_ICON_ASTERISK].join(' ') + '"></span>',
      TPL_FLAG_TIP = '<span class="' + [CSS_ICON, CSS_ICON_QUESTION_SIGN].join(' ') + '"></span>',
      TPL_LABEL = '<label for="{id}">{label}</label>',
  
      INVALID_CLONE_ATTRS = ['id', 'name'];
  
  /**
   * A base class for `A.FormBuilderField`.
   *
   * @class A.FormBuilderField
   * @param {Object} config Object literal specifying widget configuration
   *     properties.
   * @constructor
   */
  var FormBuilderField = A.Component.create({
  
      /**
       * Static property provides a string to identify the class.
       *
       * @property NAME
       * @type String
       * @static
       */
      NAME: 'form-builder-field',
  
      /**
       * Static property used to define the default attribute
       * configuration for the `A.FormBuilderField`.
       *
       * @property ATTRS
       * @type Object
       * @static
       */
      ATTRS: {
  
          /**
           * If `true` children are accepted.
           *
           * @attribute acceptChildren
           * @default true
           * @type Boolean
           */
          acceptChildren: {
              value: true
          },
  
          /**
           * The `A.FormBuilder` instance.
           *
           * @attribute builder
           * @default null
           */
          builder: {
              value: null
          },
  
          /**
           * Collection of toolbar controls.
           *
           * @attribute controlsToolbar
           * @type Object
           */
          controlsToolbar: {
              validator: isObject,
              valueFn: function() {
                  var instance = this;
  
                  return {
                      children: instance._getToolbarItems(instance.get('required'), instance.get('unique')),
                      visible: A.UA.touchEnabled && A.UA.mobile
                  };
              }
          },
  
          /**
           * Indicates which is the type of data for the input field.
           *
           * @attribute dataType
           * @default 'string'
           * @type String
           */
          dataType: {
              value: 'string'
          },
  
          /**
           * Checks if the input field is disabled or not.
           *
           * @attribute disabled
           * @default false
           * @type Boolean
           */
          disabled: {
              value: false
          },
  
          /**
           * Checks if the input field is selected or not.
           *
           * @attribute selected
           * @default false
           * @type Boolean
           */
          selected: {
              value: false
          },
  
          /**
           * List of hidden attributes.
           *
           * @attribute hiddenAttributes
           * @default []
           * @type Array
           */
          hiddenAttributes: {
              validator: isArray,
              value: []
          },
  
          /**
           * The id of the input field.
           *
           * @attribute id
           */
          id: {
              setter: '_setId'
          },
  
          /**
           * The label of the input field.
           *
           * @attribute label
           * @default ''
           * @type String
           */
          label: {
              value: ''
          },
  
          /**
           * Collection for content localization.
           *
           * @attribute localizationMap
           * @default {}
           * @type Object
           */
          localizationMap: {
              value: {}
          },
  
          /**
           * The name of the input field.
           *
           * @attribute name
           */
          name: {
              valueFn: function() {
                  var instance = this,
                      type = instance.get('type');
  
                  return A.FormBuilderField.buildFieldName(type);
              }
          },
  
          /**
           * Container for the field parent.
           *
           * @attribute parent
           * @default null
           */
          parent: {
              value: null
          },
  
          /**
           * Specifies a predefined value for the input field.
           *
           * @attribute predefinedValue
           * @default ''
           * @type String
           */
          predefinedValue: {
              value: ''
          },
  
          /**
           * Checks if an input field is read-only.
           * In other words, it cannot be modified.
           *
           * @attribute readOnly
           * @default false
           * @type Boolean
           */
          readOnly: {
              setter: A.DataType.Boolean.parse,
              value: false
          },
  
          /**
           * List of read-only input fields.
           *
           * @attribute readOnlyAttributes
           * @default []
           * @type Array
           */
          readOnlyAttributes: {
              validator: isArray,
              value: []
          },
  
          /**
           * Checks if an input field is required.
           * In other words, it needs content to be valid.
           *
           * @attribute required
           * @default false
           * @type Boolean
           */
          required: {
              setter: A.DataType.Boolean.parse,
              value: false
          },
  
          /**
           * If `true` the label is showed.
           *
           * @attribute showLabel
           * @default true
           * @type Boolean
           */
          showLabel: {
              setter: A.DataType.Boolean.parse,
              value: true
          },
  
          /**
           * Collection of strings used to label elements of the UI.
           *
           * @attribute strings
           * @type Object
           */
          strings: {
              value: {
                  button: 'Button',
                  buttonType: 'Button Type',
                  deleteFieldsMessage: 'Are you sure you want to delete the selected field(s)?',
                  duplicateMessage: 'Duplicate',
                  editMessage: 'Edit',
                  label: 'Label',
                  large: 'Large',
                  medium: 'Medium',
                  multiple: 'Multiple',
                  name: 'Name',
                  no: 'No',
                  options: 'Options',
                  predefinedValue: 'Predefined Value',
                  readOnly: 'Read Only',
                  required: 'Required',
                  reset: 'Reset',
                  showLabel: 'Show Label',
                  small: 'Small',
                  submit: 'Submit',
                  tip: 'Tip',
                  type: 'Type',
                  width: 'Width',
                  yes: 'Yes'
              }
          },
  
          /**
           * Specify the tab order.
           *
           * @attribute tabIndex
           * @default 0
           * @type Number
           */
          tabIndex: {
              value: 0
          },
  
          /**
           * Reusable block of markup used to generate the field.
           *
           * @attribute template
           * @default ''
           * @type String
           */
          template: {
              value: ''
          },
  
          /**
           * Hint to help the user to fill the input field.
           *
           * @attribute tip
           * @default ''
           * @type String
           */
          tip: {
              value: ''
          },
  
          /**
           * Defines the type of field.
           *
           * @attribute type
           * @default ''
           * @type String
           */
          type: {
              value: ''
          },
  
          /**
           * Checks if the input field is unique or not.
           *
           * @attribute unique
           * @default false
           * @type Boolean
           */
          unique: {
              setter: A.DataType.Boolean.parse,
              value: false
          },
  
          /**
           * Stack order of the field. An element with greater stack order is
           * always in front of an element with a lower stack order.
           *
           * @attribute zIndex
           * @default 100
           * @type Number
           */
          zIndex: {
              value: 100
          },
  
          /**
           * Node used to generate the drop zone.
           *
           * @attribute dropZoneNode
           */
          dropZoneNode: {
              valueFn: function() {
                  return A.Node.create(TPL_DROP_ZONE);
              }
          },
  
          /**
           * Node used to generate a label.
           *
           * @attribute labelNode
           */
          labelNode: {
              valueFn: function() {
                  var instance = this;
  
                  return A.Node.create(
                      L.sub(
                          TPL_LABEL, {
                              id: AEscape.html(instance.get('id')),
                              label: AEscape.html(instance.get('label'))
                          }
                      )
                  );
              }
          },
  
          /**
           * Node used to generate the required flag.
           *
           * @attribute requiredFlagNode
           */
          requiredFlagNode: {
              valueFn: function() {
                  return A.Node.create(TPL_FLAG_REQUIRED);
              }
          },
  
          /**
           * Node used to generate a template.
           *
           * @attribute templateNode
           */
          templateNode: {
              valueFn: 'getNode'
          },
  
          /**
           * Node used to generate a tip.
           *
           * @attribute tipFlagNode
           */
          tipFlagNode: {
              valueFn: function() {
                  return A.Node.create(TPL_FLAG_TIP);
              }
          }
  
      },
  
      /**
       * Static property used to define the UI attributes.
       *
       * @property UI_ATTRS
       * @type Array
       * @static
       */
      UI_ATTRS: [
          'acceptChildren', 'disabled', 'fields', 'label', 'name',
          'predefinedValue', 'required', 'selected', 'showLabel', 'tip', 'unique'
      ],
  
      /**
       * Static property used to define the augmented classes.
       *
       * @property AUGMENTS
       * @type Array
       * @static
       */
      AUGMENTS: [A.PropertyBuilderFieldSupport],
  
      /**
       * Creates the field id.
       *
       * @method buildFieldId
       * @param id
       * @private
       * @return {String}
       */
      buildFieldId: function(id) {
          return 'fields' + '_' + 'field' + '_' + id;
      },
  
      /**
       * Creates the field name.
       *
       * @method buildFieldName
       * @param type
       * @private
       * @return {String}
       */
      buildFieldName: function(type) {
          return type + (++A.Env._uidx);
      },
  
      /**
       * Object hash, defining how attribute values have to be parsed from markup.
       *
       * @property HTML_PARSER
       * @type Object
       * @static
       */
      HTML_PARSER: {
          dropZoneNode: '.' + CSS_FB_DROP_ZONE,
          labelNode: 'label',
          requiredFlagNode: '.' + CSS_ICON + ' .' + CSS_ICON_ASTERISK,
          tipFlagNode: '.' + CSS_ICON + ' .' + CSS_ICON_QUESTION_SIGN
      },
  
      prototype: {
          BOUNDING_TEMPLATE: TPL_BOUNDING_BOX,
  
          /**
           * Construction logic executed during `A.FormBuilderField` instantiation.
           * Lifecycle.
           *
           * @method initializer
           * @protected
           */
          initializer: function() {
              var instance = this;
  
              instance.controlsToolbar = new A.Toolbar(
                  instance.get('controlsToolbar')
              );
          },
  
          /**
           * Render the `A.FormBuilderField` component instance. Lifecycle.
           *
           * @method renderUI
           * @protected
           */
          renderUI: function() {
              var instance = this,
                  boundingBox = instance.get('boundingBox'),
                  contentBox = instance.get('contentBox'),
                  labelNode = instance.get('labelNode'),
                  requiredFlagNode = instance.get('requiredFlagNode'),
                  templateNode = instance.get('templateNode'),
                  tipFlagNode = instance.get('tipFlagNode');
  
              contentBox.addClass(CSS_CLEARFIX);
  
              contentBox.append(labelNode);
              contentBox.append(requiredFlagNode);
              contentBox.append(tipFlagNode);
              contentBox.append(templateNode);
  
              instance.controlsToolbar.render(boundingBox);
          },
  
          /**
           * Destructor lifecycle implementation for the `A.FormBuilderField`
           * class.
           *
           * @method destructor
           * @protected
           */
          destructor: function() {
              var instance = this,
                  builder = instance.get('builder'),
                  fieldId = builder._getFieldId(instance);
  
              instance.get('fields').each(function(field) {
                  field.destroy();
              });
  
              if (builder.editingField === instance) {
                  delete builder.editingField;
  
                  builder.closeEditProperties();
              }
  
              if (instance.controlsToolbar) {
                  instance.controlsToolbar.destroy();
              }
  
              // destroy manually because NestedList doesn`t
              // use delegate
              instance.get('boundingBox').dd.destroy();
  
              instance.tooltip.destroy();
  
              instance.get('parent').removeField(instance);
  
              builder.uniqueFieldsMap.remove(fieldId);
          },
  
          /**
           * Creates the field using the `createField` method from
           * `A.FormBuilder`.
           *
           * @method createField
           * @param val
           * @return {Object}
           */
          createField: function(val) {
              var instance = this,
                  builder = instance.get('builder');
  
              val = builder.createField(val);
  
              val.set('parent', instance);
  
              return val;
          },
  
          /**
           * Gets the field markup.
           *
           * To developer: Implement this
           *
           * @method getHTML
           * @return {String}
           */
          getHTML: function() {
              return '';
          },
  
          /**
           * Creates a `Node` from the HTML string.
           *
           * @method getNode
           * @return {Node}
           */
          getNode: function() {
              var instance = this;
  
              return A.Node.create(instance.getHTML());
          },
  
          /**
           * Gets all necessary attributes for cloning this field.
           *
           * @method getAttributesForCloning
           * @return {Object}
           */
          getAttributesForCloning: function() {
              // List of all non-property attributes that need to be cloned.
              var attributes = {
                  hiddenAttributes: this.get('hiddenAttributes'),
                  readOnlyAttributes: this.get('readOnlyAttributes'),
                  localizationMap: this.get('localizationMap')
              };
  
              // All field properties should be cloned as well.
              AArray.each(this.getProperties(), function(property) {
                  var name = property.attributeName;
  
                  if (AArray.indexOf(INVALID_CLONE_ATTRS, name) === -1) {
                      attributes[name] = property.value;
                  }
              });
  
              return attributes;
          },
  
          /**
           * Gets properties from the property model.
           *
           * @method getProperties
           * @return {Array}
           */
          getProperties: function(excludeHidden) {
              var instance = this,
                  propertyModel = instance.getPropertyModel(),
                  hiddenAttributes = instance.get('hiddenAttributes'),
                  readOnlyAttributes = instance.get('readOnlyAttributes'),
                  properties = [];
  
              AArray.each(propertyModel, function(property) {
                  var attribute = property.attributeName;
  
                  // TODO - Change checking to use hashes O(1) instead of indexOf
                  // arrays O(N)
                  if (excludeHidden && AArray.indexOf(hiddenAttributes, attribute) > -1) {
                      return;
                  }
  
                  var value = instance.get(attribute),
                      type = L.type(value);
  
                  if (type === 'boolean') {
                      value = String(value);
                  }
  
                  property.value = value;
  
                  // TODO - Change checking to use hashes O(1) instead of indexOf
                  // arrays O(N)
                  if (AArray.indexOf(readOnlyAttributes, attribute) > -1) {
                      property.readOnly = true;
                  }
  
                  properties.push(property);
              });
  
              return properties;
          },
  
          /**
           * Returns a list of property models. Each property model is made of a
           * name, attribute, editor, and formatter.
           *
           * @method getPropertyModel
           * @return {Array}
           */
          getPropertyModel: function() {
              var instance = this,
                  strings = instance.getStrings();
  
              return [{
                  attributeName: 'type',
                  editor: false,
                  name: strings.type
              }, {
                  attributeName: 'label',
                  editor: new A.TextCellEditor(),
                  name: strings.label
              }, {
                  attributeName: 'showLabel',
                  editor: new A.RadioCellEditor({
                      options: {
                          'true': strings.yes,
                          'false': strings.no
                      }
                  }),
                  formatter: A.bind(instance._booleanFormatter, instance),
                  name: strings.showLabel
              }, {
                  attributeName: 'readOnly',
                  editor: new A.RadioCellEditor({
                      options: {
                          'true': strings.yes,
                          'false': strings.no
                      }
                  }),
                  formatter: A.bind(instance._booleanFormatter, instance),
                  name: strings.readOnly
              }, {
                  attributeName: 'required',
                  editor: new A.RadioCellEditor({
                      options: {
                          'true': strings.yes,
                          'false': strings.no
                      }
                  }),
                  formatter: A.bind(instance._booleanFormatter, instance),
                  name: strings.required
              }, {
                  attributeName: 'name',
                  editor: new A.TextCellEditor({
                      validator: {
                          rules: {
                              value: {
                                  required: true
                              }
                          }
                      }
                  }),
                  name: strings.name
              }, {
                  attributeName: 'predefinedValue',
                  editor: new A.TextCellEditor(),
                  name: strings.predefinedValue
              }, {
                  attributeName: 'tip',
                  editor: new A.TextAreaCellEditor(),
                  name: strings.tip
              }];
          },
  
          /**
           * Transforms a `Boolean` value into "yes" or "no" string.
           *
           * @method _booleanFormatter
           * @param o
           * @protected
           */
          _booleanFormatter: function(o) {
              var instance = this,
                  strings = instance.getStrings();
  
              return A.DataType.Boolean.parse(o.data.value) ? strings.yes : strings.no;
          },
  
          /**
           * Returns the node for the current field.
           *
           * @method _getFieldNode
           * @protected
           */
          _getFieldNode: function() {
              var templateNode = this.get('templateNode'),
                  fieldNode = templateNode.one('.' + CSS_FB_FIELD_NODE);
  
              if (!fieldNode) {
                  fieldNode = templateNode;
              }
  
              return fieldNode;
          },
  
          /**
           * Gets a list of toolbar items.
           *
           * @method _getToolbarItems
           * @return {Array}
           */
          _getToolbarItems: function() {
              var instance = this,
                  builder = instance.get('builder'),
                  items = [
                      {
                          icon: [CSS_ICON, CSS_ICON_WRENCH].join(' '),
                          on: {
                              click: A.bind(instance._handleEditEvent, instance)
                          }
                      }
                  ];
  
              if (!instance.get('unique')) {
                  items.push({
                      icon: [CSS_ICON, CSS_ICON_PLUS].join(' '),
                      on: {
                          click: A.bind(instance._handleDuplicateEvent, instance)
                      }
                  });
              }
  
              if ((builder && builder.get('allowRemoveRequiredFields')) || !instance.get('required')) {
                  items.push({
                      icon: [CSS_ICON, CSS_ICON_TRASH].join(' '),
                      on: {
                          click: A.bind(instance._handleDeleteEvent, instance)
                      }
                  });
              }
  
              return [items];
          },
  
          /**
           * Checks if the field isn't unique. If not, duplicates the instance.
           *
           * @method _handleDuplicateEvent
           * @param event
           * @protected
           */
          _handleDuplicateEvent: function(event) {
              var instance = this;
  
              if (!instance.get('unique')) {
                  instance.get('builder').duplicateField(instance);
              }
  
              event.stopPropagation();
          },
  
          /**
           * Handles the edit event.
           *
           * @method _handleEditEvent
           * @param event
           * @protected
           */
          _handleEditEvent: function(event) {
              var instance = this;
  
              instance.get('builder').editField(instance);
  
              event.stopPropagation();
          },
  
          /**
           * Popups a dialog to confirm deletion. If "yes", destroys the instance.
           *
           * @method _handleDeleteEvent
           * @param event
           * @protected
           */
          _handleDeleteEvent: function(event) {
              var instance = this,
                  strings = instance.getStrings();
  
              if (window.confirm(strings.deleteFieldsMessage)) {
                  instance.destroy();
              }
  
              event.stopPropagation();
          },
  
          /**
           * Set the `id` attribute on the UI.
           *
           * @method _setId
           * @param val
           * @protected
           */
          _setId: function(val) {
              return A.FormBuilderField.buildFieldId(val);
          },
  
          /**
           * Set the `acceptChildren` attribute on the UI.
           *
           * @method _uiSetAcceptChildren
           * @param val
           * @protected
           */
          _uiSetAcceptChildren: function(val) {
              var instance = this,
                  boundingBox = instance.get('boundingBox'),
                  dropZone = instance.get('dropZoneNode'),
                  markupDropZone = boundingBox.one('.' + CSS_FB_DROP_ZONE);
  
              if (val && !markupDropZone) {
                  boundingBox.append(dropZone);
              }
              else if (!val && markupDropZone) {
                  markupDropZone.remove();
              }
              else if (val && markupDropZone) {
                  instance.set('dropZoneNode', markupDropZone);
              }
          },
  
          /**
           * Set the `selected` attribute on the UI.
           *
           * @method _uiSetSelected
           * @param val
           * @protected
           */
          _uiSetSelected: function(val) {
              var instance = this;
  
              instance.get('boundingBox').toggleClass(CSS_FB_FIELD_SELECTED, val);
          },
  
          /**
           * Set the `disabled` attribute on the UI.
           *
           * @method _uiSetDisabled
           * @param val
           * @protected
           */
          _uiSetDisabled: function(val) {
              var fieldNode = this._getFieldNode();
  
              if (val) {
                  fieldNode.setAttribute('disabled', val);
              }
              else {
                  fieldNode.removeAttribute('disabled');
              }
          },
  
          /**
           * Set the fields on the UI using the `plotFields` method from
           * `A.FormBuilder`.
           *
           * @method _uiSetFields
           * @param val
           * @protected
           */
          _uiSetFields: function(val) {
              var instance = this,
                  builder = instance.get('builder');
  
              builder.plotFields(val, instance.get('dropZoneNode'));
          },
  
          /**
           * Set the label content on the UI.
           *
           * @method _uiSetLabel
           * @param val
           * @protected
           */
          _uiSetLabel: function(val) {
              var instance = this,
                  labelNode = instance.get('labelNode');
  
              labelNode.setContent(AEscape.html(val));
          },
  
          /**
           * Set the `name` attribute on the UI.
           *
           * @method _uiSetName
           * @param val
           * @protected
           */
          _uiSetName: function(val) {
              var fieldNode = this._getFieldNode();
  
              fieldNode.set('name', val);
          },
  
          /**
           * Set the `predefinedValue` attribute on the UI.
           *
           * @method _uiSetPredefinedValue
           * @param val
           * @protected
           */
          _uiSetPredefinedValue: function(val) {
              var fieldNode = this._getFieldNode();
  
              fieldNode.val(val);
          },
  
          /**
           * Set the `required` attribute on the UI.
           *
           * @method _uiSetRequired
           * @param val
           * @protected
           */
          _uiSetRequired: function(val) {
              var instance = this,
                  controlsToolbar = instance.controlsToolbar,
                  requiredNode = instance.get('requiredFlagNode');
  
              requiredNode.toggle(val);
  
              controlsToolbar.set('children', instance._getToolbarItems());
          },
  
          /**
           * Set the `showLabel` attribute on the UI.
           *
           * @method _uiSetShowLabel
           * @param val
           * @protected
           */
          _uiSetShowLabel: function(val) {
              var instance = this,
                  labelNode = instance.get('labelNode');
  
              labelNode.toggle(val);
          },
  
          /**
           * Set the `tip` attribute on the UI.
           *
           * @method _uiSetTip
           * @param val
           * @protected
           */
          _uiSetTip: function(val) {
              var tipFlagNode = this.get('tipFlagNode');
  
              tipFlagNode.toggle(val.length !== 0);
  
              if (this.tooltip) {
                  this.tooltip.set('bodyContent', val);
                  return;
              }
  
              this.tooltip = new A.Tooltip({
                  bodyContent: val,
                  trigger: tipFlagNode,
                  position: 'right',
                  visible: false
              }).render();
          },
  
          /**
           * Set the `unique` attribute on the UI.
           *
           * @method _uiSetUnique
           * @param val
           * @protected
           */
          _uiSetUnique: function(val) {
              var instance = this,
                  boundingBox = instance.get('boundingBox'),
                  controlsToolbar = instance.controlsToolbar;
  
              boundingBox.toggleClass(CSS_FB_UNIQUE, val);
  
              controlsToolbar.set('children', instance._getToolbarItems());
          }
  
      }
  
  });
  
  A.FormBuilderField = FormBuilderField;
  A.namespace('FormBuilderField.types').field = FormBuilderField;
  