text: /**
   * Plugin (ptype = 'rowwidget') that adds the ability to second row body in a grid which expands/contracts.
   *
   * The expand/contract behavior is configurable to react on clicking of the column, double click of the row, and/or hitting enter while a row is selected.
   *
   * The expansion row may contain a {@link #cfg-widget} which is primed with the record of the corresponding grid row.
   * The widget's {@link Ext.Component#cfg-defaultBindProperty defaultBindProperty} property is set to the record.
   */
  Ext.define('Ext.grid.plugin.RowWidget', {
      extend: 'Ext.grid.plugin.RowExpander',
      mixins: [
          'Ext.mixin.Identifiable',
          'Ext.mixin.StyleCacher'
      ],
  
      lockableScope: 'top',
  
      alias: 'plugin.rowwidget',
  
      config: {
          /**
           * @cfg {Object} defaultWidgetUI
           * A map of xtype to {@link Ext.Component#ui} names to use when using Components in the expansion row.
           */
          defaultWidgetUI: {}
      },
  
      /**
       * @cfg {Object} widget
       * A config object containing an {@link Ext.Component#cfg-xtype xtype}.
       *
       * This is used to create the widgets or components which are rendered into the expansion row.
       *
       * The associated grid row's record is used to update the widget/component's {@link Ext.Component#defaultBindProperty defaultBindProperty}.
       *
       * Note that if this plugin is applied to a lockable grid, the widget applies to the normal (unlocked) side.
       * See {@link #lockedWidget}
       *
       */
      widget: null,
  
      /**
       * @cfg {Object} [lockedWidget]
       * A config object containing an {@link Ext.Component#cfg-xtype xtype}.
       *
       * This is used to create the widgets or components which are rendered into the expansion row *on the locked side of a lockable grid*.
       */
      lockedWidget: null,
  
      addCollapsedCls: {
          fn: function(out, values, parent) {
              var me = this.rowExpander;
              if (!me.recordsExpanded[values.record.internalId]) {
                  values.itemClasses.push(me.rowCollapsedCls);
              }
              this.nextTpl.applyOut(values, out, parent);
          },
  
          // We need a high priority to get in ahead of the outerRowTpl
          // so we can setup row data
          priority: 20000
      },
  
      setCmp: function(grid) {
          var me = this,
              features,
              widget;
  
          // Generate a unique class name so we can identify our row element.
          me.rowIdCls = Ext.id(null, Ext.baseCSSPrefix + 'rowwidget-');
  
          // Keep track of which record internalIds are expanded.
          me.recordsExpanded = {};
  
          Ext.plugin.Abstract.prototype.setCmp.apply(me, arguments);
  
          widget = me.widget;
          //<debug>
          if (!widget || widget.isComponent) {
              Ext.raise('RowWidget requires a widget configuration.');
          }
          //</debug>
          me.widget = widget = Ext.apply({}, widget);
  
          // Apply the default UI for the xtype which is going to feature in the normal side's expansion row.
          if (!widget.ui) {
              widget.ui = me.getDefaultWidgetUI()[widget.xtype] || 'default';
          }
  
          // If the grid is a lockable assembly, we have to track locked widgets.
          if (grid.enableLocking && me.lockedWidget) {
              me.lockedWidget = widget = Ext.apply({}, me.lockedWidget);
  
              // Apply the default UI for the xtype which is going to feature in the locked side's expansion row.
              if (!widget.ui) {
                  widget.ui = me.getDefaultWidgetUI()[widget.xtype] || 'default';
              }
          }
  
          features = me.getFeatureConfig(grid);
  
          if (grid.features) {
              grid.features = Ext.Array.push(features, grid.features);
          } else {
              grid.features = features;
          }
          // NOTE: features have to be added before init (before Table.initComponent)
      },
  
      /**
       * @protected
       * @return {Array} And array of Features or Feature config objects.
       * Returns the array of Feature configurations needed to make the RowWidget work.
       * May be overridden in a subclass to modify the returned array.
       */
      getFeatureConfig: function(grid) {
          var me = this,
              features = [],
              featuresCfg = {
                  ftype: 'rowbody',
                  rowExpander: me,
                  doSync: false,
                  rowIdCls: me.rowIdCls,
                  bodyBefore: me.bodyBefore,
                  recordsExpanded: me.recordsExpanded,
                  rowBodyHiddenCls: me.rowBodyHiddenCls,
                  rowCollapsedCls: me.rowCollapsedCls,
                  setupRowData: me.setupRowData,
                  setup: me.setup,
  
                  // Do not relay click events into the client grid's row
                  onClick: Ext.emptyFn
              };
  
          features.push(Ext.apply({
              lockableScope: 'normal'
          }, featuresCfg));
  
          // Locked side will need a copy to keep the two DOM structures symmetrical.
          // A lockedWidget config is available to create content in locked side.
          // The enableLocking flag is set early in Ext.panel.Table#initComponent if any columns are locked.
          if (grid.enableLocking) {
              features.push(Ext.apply({
                  lockableScope: 'locked'
              }, featuresCfg));
          }
  
          return features;
      },
      
      setupRowData: function(record, rowIndex, rowValues) {
          var me = this.rowExpander;
          
          me.rowBodyFeature = this;
          rowValues.rowBodyCls = me.recordsExpanded[record.internalId] ? '' : me.rowBodyHiddenCls;
      },
  
      bindView: function(view) {
          var me = this;
  
          me.viewListeners = view.on({
              refresh: me.onViewRefresh,
              itemadd: me.onItemAdd,
              scope: me,
              destroyable: true
          });
          Ext.override(view, me.viewOverrides);
      },
  
      destroy: function() {
          var me = this,
              id = me.getId();
  
          me.viewListeners.destroy();
          
          if (me.grid.lockable) {
              me.grid.destroyManagedWidgets(id + '-' + me.lockedView.getId());
              me.grid.destroyManagedWidgets(id + '-' + me.normalView.getId());
          } else {
              me.grid.destroyManagedWidgets(id + '-' + me.view.getId());
          }
          
          me.callParent();
      },
  
      privates: {
          viewOverrides: {
              handleEvent: function(e) {
                  // An override applied to the client view so that it ignores events from within the expander row
                  // Ignore all events from within our rowwidget
                  if (e.getTarget('.' + this.rowExpander.rowIdCls, this.body)) {
                      return;
                  }
  
                  this.callParent([e]);
              },
  
              onFocusEnter: function(e) {
                  // An override applied to the client view so that it ignores focus moving into the expander row
                  if (e.event.getTarget('.' + this.rowExpander.rowIdCls, this.body)) {
                      return;
                  }
  
                  this.callParent([e]);
              },
  
              toggleChildrenTabbability: function(enableTabbing) {
                  // An override applied to the client view so that it does not interfere
                  // with tabbability of elements within the expander rows.
                  var focusEl = this.getTargetEl(),
                      rows = this.all,
                      restoreOptions = { skipSelf: true },
                      saveOptions = { skipSelf: true, includeSaved: false },
                      i;
  
                  for (i = rows.startIndex; i <= rows.endIndex; i++) {
                      // Extract the data row from each row.
                      // We do not interfere with tabbing in the the expander row.
                      focusEl = Ext.fly(this.getRow(rows.item(i)));
  
                      if (!focusEl) {
                          continue;
                      }
                      
                      if (enableTabbing) {
                          focusEl.restoreTabbableState(restoreOptions);
                      }
                      else {
                          // Do NOT includeSaved
                          // Once an item has had tabbability saved, do not increment its save level
                          focusEl.saveTabbableState(saveOptions);
                      }
                  }
              }
          },
  
          destroyLiveWidget: function(recId, widget) {
              widget.destroy();
          },
  
          destroyFreeWidget: function(widget) {
              widget.destroy();
          },
  
          onItemAdd: function(newRecords, startIndex, newItems, view) {
              var me = this,
                  len = newItems.length, i,
                  record,
                  ownerLockable = me.grid.lockable;
  
              // May be multiple widgets being layed out here
              Ext.suspendLayouts();
  
              for (i = 0; i < len; i++) {
                  record = newRecords[i];
                  if (!record.isNonData && me.recordsExpanded[record.internalId]) {
                      // If any added items are expanded, we will need a syncRowHeights call on next layout
                      if (ownerLockable) {
                          me.grid.syncRowHeightOnNextLayout = true;
                      }
                      me.addWidget(view, record);
                  }
              }
              Ext.resumeLayouts(true);
          },
  
          onViewRefresh: function(view, records) {
              var me = this,
                  rows = view.all,
                  itemIndex, recordIndex;
  
              Ext.suspendLayouts();
              for (itemIndex = rows.startIndex, recordIndex = 0; itemIndex <= rows.endIndex; itemIndex++, recordIndex++) {
                  if (me.recordsExpanded[records[recordIndex].internalId]) {
                      me.addWidget(view, records[recordIndex]);
                  }
              }
              Ext.resumeLayouts(true);
          },
  
          returnFalse: function() {
              return false;
          },
  
          /**
           * Returns if possible the widget currently associated with the passed record within the passed view.
           *
           * Note that if the record is not currently in the rendered block, *or*, it has never been expanded
           * then there will not be a widget associated with that `record/view` context.
           * @param {type} view The view for which to return the widget
           * @param {type} record The record for which to return the widget
           * @return {me.lockedLiveWidgets/me.liveWidgets}
           */
          getWidget: function(view, record) {
              var me = this,
                  result,
                  widget;
  
              if (record) {
                  widget = me.grid.lockable && view === me.lockedView ? me.lockedWidget : me.widget;
                  if (widget) {
                      result = me.grid.createManagedWidget(view, me.getId() + '-' + view.getId(), widget, record);
                      result.measurer = me;
                      result.ownerLayout = view.componentLayout;
                  }
              }
  
              return result;
          },
  
          addWidget: function(view, record) {
              var me = this,
                  target,
                  width,
                  widget,
                  hasAttach = !!me.onWidgetAttach,
                  isFixedSize = me.isFixedSize,
                  el;
  
              // If the record is non data (placeholder), or not expanded, return
              if (record.isNonData || !me.recordsExpanded[record.internalId]) {
                  return;
              }
  
              target = Ext.fly(view.getNode(record).querySelector(me.rowBodyFeature.innerSelector));
              width = target.getWidth(true) - target.getPadding('lr');
              widget = me.getWidget(view, record);
  
              // Might be no widget if we are handling a lockable grid
              // and only one side has a widget definition.
              if (widget) {
                  if (hasAttach) {
                      Ext.callback(me.onWidgetAttach, me.scope, [me, widget, record], 0, me);
                  }
  
                  el = widget.el || widget.element;
                  if (el) {
                      target.dom.appendChild(el.dom);
                      if (!isFixedSize && widget.width !== width) {
                          widget.setWidth(width);
                      } else {
                          widget.updateLayout();
                      }
                      widget.reattachToBody();
                  } else {
                      if (!isFixedSize) {
                          widget.width = width;
                      }
                      widget.render(target);
                  }
                  widget.updateLayout();
              }
  
              return widget;
          },
  
          toggleRow: function(rowIdx, record) {
              var me = this,
                  // If we are handling a lockable assembly,
                  // handle the normal view first
                  view = me.normalView || me.view,
                  rowNode = view.getNode(rowIdx),
                  normalRow = Ext.fly(rowNode),
                  lockedRow,
                  nextBd = normalRow.down(me.rowBodyTrSelector, true),
                  wasCollapsed = normalRow.hasCls(me.rowCollapsedCls),
                  addOrRemoveCls = wasCollapsed ? 'removeCls' : 'addCls',
                  ownerLockable = me.grid.lockable && me.grid,
                  widget, vm;
  
              normalRow[addOrRemoveCls](me.rowCollapsedCls);
              Ext.fly(nextBd)[addOrRemoveCls](me.rowBodyHiddenCls);
  
              // All layouts must be coalesced.
              // Particularly important for locking assemblies which need
              // to sync row height on the next layout.
              Ext.suspendLayouts();
  
              // We're expanding
              if (wasCollapsed) {
                  me.recordsExpanded[record.internalId] = true;
                  widget = me.addWidget(view, record);
                  vm = widget.lookupViewModel();
              } else {
                  delete me.recordsExpanded[record.internalId];
                  widget = me.getWidget(view, record);
              }
  
              // Sync the collapsed/hidden classes on the locked side
              if (ownerLockable) {
  
                  // Only attempt to toggle lockable side if it is visible.
                  if (ownerLockable.lockedGrid.isVisible()) {
  
                      view = me.lockedView;
  
                      // Process the locked side.
                      lockedRow = Ext.fly(view.getNode(rowIdx));
                      // Just because the grid is locked, doesn't mean we'll necessarily have a locked row.
                      if (lockedRow) {
                          lockedRow[addOrRemoveCls](me.rowCollapsedCls);
  
                          // If there is a template for expander content in the locked side, toggle that side too
                          nextBd = lockedRow.down(me.rowBodyTrSelector, true);
                          Ext.fly(nextBd)[addOrRemoveCls](me.rowBodyHiddenCls);
  
                          // Pass an array if we're in a lockable assembly.
                          if (wasCollapsed && me.lockedWidget) {
                              widget = [widget, me.addWidget(view, record)];
                          } else {
                              widget = [widget, me.getWidget(view, record)];
                          }
  
                      }
  
                      // We're going to need a layout run to synchronize row heights
                      ownerLockable.syncRowHeightOnNextLayout = true;
                  }
              }
  
              me.view.fireEvent(wasCollapsed ? 'expandbody' : 'collapsebody', rowNode, record, nextBd, widget);
              view.updateLayout();
  
              // Before layouts are resumed, if we have *expanded* the widget row, then ensure bound data
              // is flushed into the widget so that it assumes its final size.
              if (vm) {
                  vm.notify();
              }
              Ext.resumeLayouts(true);
  
              if (me.scrollIntoViewOnExpand && wasCollapsed) {
                  me.grid.ensureVisible(rowIdx);
              }
          }
      }
  });
  