text: /**
   * Grids are an excellent way of showing large amounts of tabular data on the client side.
   * Essentially a supercharged `<table>`, Grid makes it easy to fetch, sort and filter large
   * amounts of data.
   *
   * Grids are composed of two main pieces - a {@link Ext.data.Store Store} full of data and
   * a set of columns to render.
   *
   * ## A Basic Grid
   *
   *     var store = Ext.create('Ext.data.Store', {
   *         fields: ['name', 'email', 'phone'],
   *         data: [
   *             { 'name': 'Lisa',  "email":"lisa@simpsons.com",  "phone":"555-111-1224"  },
   *             { 'name': 'Bart',  "email":"bart@simpsons.com",  "phone":"555-222-1234" },
   *             { 'name': 'Homer', "email":"home@simpsons.com",  "phone":"555-222-1244"  },
   *             { 'name': 'Marge', "email":"marge@simpsons.com", "phone":"555-222-1254"  }
   *         ]
   *     });
   *
   *     Ext.create('Ext.grid.Grid', {
   *         title: 'Simpsons',
   *
   *         store: store,
   *
   *         columns: [
   *             { text: 'Name',  dataIndex: 'name', width: 200 },
   *             { text: 'Email', dataIndex: 'email', width: 250 },
   *             { text: 'Phone', dataIndex: 'phone', width: 120 }
   *         ],
   *
   *         height: 200,
   *         layout: 'fit',
   *         fullscreen: true
   *     });
   *
   * The code above produces a simple grid with three columns. We specified a Store which will
   * load JSON data inline. In most apps we would be placing the grid inside another container
   * and wouldn't need to provide the {@link #height}, {@link #width} and 
   * {@link #cfg-fullscreen} options but they are included here to for demonstration.
   *
   * The grid we created above will contain a header bar with a title ('Simpsons'), a row of
   * column headers directly underneath and finally the grid rows under the headers.
   *
   * ## Columns
   *
   * By default, each {@link Ext.grid.column.Column column} is sortable and toggles between
   * ascending and descending sorting when you click on its header. There are several basic
   * configs that can be applied to columns to change these behaviors. For example:
   *
   *     columns: [
   *         {
   *             text: 'Name',
   *             dataIndex: 'name',
   *             sortable: false,  // column cannot be sorted
   *             width: 250
   *         },
   *         {
   *             text: 'Email',
   *             dataIndex: 'email',
   *             hidden: true  // column is initially hidden
   *         },
   *         {
   *             text: 'Phone',
   *             dataIndex: 'phone',
   *             width: 100
   *         }
   *     ]
   *
   * We turned off sorting on the 'Name' column so clicking its header now has no effect. We
   * also made the Email column hidden by default (it can be shown again by using the
   * {@link Ext.grid.plugin.ViewOptions ViewOptions} plugin). See the
   * {@link Ext.grid.column.Column column class} for more details.
   *
   * A top-level column definition may contain a `columns` configuration. This means that the 
   * resulting header will be a group header, and will contain the child columns.
   *
   * ## Rows and Cells
   *
   * Grid extends the `{@link Ext.dataview.List List}` component and connects records in the
   * store to `{@link Ext.grid.Row row components}` for the list's items. The Row component
   * utilizes the configs of the grid's {@link Ext.grid.column.Column columns} to create the
   * appropriate type of {@link Ext.grid.cell.Base cells}. Essentially, a Row is a container
   * for {@link Ext.Widget Cell widgets}.
   *
   * For the most part, configuring a grid is about configuring the columns and their cells.
   * There are several built-in column types to display specific types of data:
   *
   *  - {@link Ext.grid.column.Boolean} for true/false values.
   *  - {@link Ext.grid.column.Date} for date/time values.
   *  - {@link Ext.grid.column.Number} for numeric values.
   *
   * These columns specify (via their {@link Ext.grid.column.Column#cell cell config}) one
   * of these basic cell widget types:
   *
   *  - {@link Ext.grid.cell.Boolean}
   *  - {@link Ext.grid.cell.Date}
   *  - {@link Ext.grid.cell.Number}
   *
   * In addition to the above basic cell types, there are two other useful cell types to
   * know about:
   *
   *  - {@link Ext.grid.cell.Text} is the base class for the boolean, date and number cell
   *    classes. It is useful when a cell contains only text.
   *  - {@link Ext.grid.cell.Widget} is a cell class that manages a single child item (either
   *    a {@link Ext.Component component} or a {@link Ext.Widget widget}). The child item is
   *    configured using the `{@link Ext.grid.cell.Widget#widget widget config}`. The most
   *    important part of this config is the `xtype` of the child item to create.
   *
   * ## Cells and Binding
   *
   * One technique to controll cell content and styling is to use data binding to target
   * cell configs like {@link Ext.grid.cell.Base#cls} and {@link Ext.grid.cell.Base#innerCls}.
   * This is done by assigning a {@link Ext.app.ViewModel viewModel} to each Row like so:
   *
   *      itemConfig: {
   *          viewModel: true  // create default ViewModel for each item (i.e., Row)
   *      }
   *
   * Now that each Row has a ViewModel, cells can bind to the fields of the associated record
   * like so:
   *
   *      columns: [{
   *          ...
   *          cell: {
   *              bind: {
   *                  cls: '{record.someCls}'
   *              }
   *          }
   *      }]
   *
   * The "record" property in the ViewModel is managed by the Row. As Row instances are
   * recycled due to buffered rendering, the associated record instance simply changes over
   * time.
   *
   * ### Cell Widgets
   *
   * When using {@link Ext.grid.cell.Widget}, the contained widgets can also use binding to
   * configure themsleves using properties of the associated record.
   *
   *      columns: [{
   *          ...
   *          cell: {
   *              xtype: 'widgetcell',
   *              widget: {
   *                  xtype: 'button',
   *                  bind: {
   *                      text: 'Update {record.firstName}'
   *                  }
   *              }
   *          }
   *      }]
   *
   * ### Row ViewModels
   *
   * In some cases a custom ViewModel could be useful, for example to provide useful values
   * via {@link Ext.app.ViewModel#formulas formulas}.
   *
   *      itemConfig: {
   *          viewModel: {
   *              type: 'rowViewModel'
   *          }
   *      }
   *
   * ## Renderers and Templates
   *
   * Columns provide two other mechanisms to format their cell content:
   *
   *  - {@link Ext.grid.column.Column#renderer}
   *  - {@link Ext.grid.column.Column#tpl}
   *
   * These column configs are processed by the {@link Ext.grid.column.Cell default cell type}
   * for a column. These configs have some downsides compared to data binding but are provided
   * for compatibility with previous releases.
   *
   *  - Renderers and templates must update the cell content when _any_ field changes. They
   *    cannot assume that only changes to the dataIndex will affect the rendering. Using
   *    data binding, only the configs affected by the changed data will be updated.
   *  - Updates are processed synchronously in response to the record update notification.
   *    Contrast to ViewModels which provide a buffered update mechanism.
   *  - Constructing HTML blocks in code (even in a template) is a common cause of security
   *    problems such as XSS attacks.
   *
   * ## Sorting & Filtering
   *
   * Every grid is attached to a {@link Ext.data.Store Store}, which provides multi-sort and
   * filtering capabilities. It's easy to set up a grid to be sorted from the start:
   *
   *     var myGrid = Ext.create('Ext.grid.Panel', {
   *         store: {
   *             fields: ['name', 'email', 'phone'],
   *             sorters: ['name', 'phone']
   *         },
   *         columns: [
   *             { text: 'Name',  dataIndex: 'name' },
   *             { text: 'Email', dataIndex: 'email' }
   *         ]
   *     });
   *
   * Sorting at run time is easily accomplished by simply clicking each column header. If you
   * need to perform sorting on more than one field at run time it's easy to do so by adding
   * new sorters to the store:
   *
   *     myGrid.store.sort([
   *         { property: 'name',  direction: 'ASC' },
   *         { property: 'email', direction: 'DESC' }
   *     ]);
   *
   * See {@link Ext.data.Store} for examples of filtering.
   *
   * ## Plugins
   *
   * Grid supports addition of extra functionality through plugins:
   *
   * - {@link Ext.grid.plugin.ViewOptions ViewOptions} - adds the ability to show/hide
   *  columns and reorder them.
   *
   * - {@link Ext.grid.plugin.ColumnResizing ColumnResizing} - allows for the ability to
   *  resize columns.
   *
   * - {@link Ext.grid.plugin.Editable Editable} - editing grid contents one row at a time.
   *
   * - {@link Ext.grid.plugin.MultiSelection MultiSelection} - selecting and deleting several
   *   rows at a time.
   *
   * - {@link Ext.grid.plugin.PagingToolbar PagingToolbar} - adds a toolbar at the bottom of
   *   the grid that allows you to quickly navigate to another page of data.
   *
   * - {@link Ext.grid.plugin.SummaryRow SummaryRow} - adds and pins an additional row to the
   *   top of the grid that enables you to display summary data.
   */
  Ext.define('Ext.grid.Grid', {
      extend: 'Ext.dataview.List',
      xtype: 'grid',
  
      requires: [
          'Ext.grid.Row',
          'Ext.grid.column.Column',
          'Ext.grid.column.Date',
          'Ext.grid.column.Template',
          'Ext.grid.HeaderContainer',
          'Ext.grid.HeaderGroup',
          'Ext.TitleBar',
          'Ext.MessageBox'
      ],
  
      config: {
          defaultType: 'gridrow',
  
          /**
           * @cfg {Boolean} infinite
           * This List configuration should always be set to true on a Grid.
           * @hide
           */
          infinite: true,
  
          /**
           * @cfg {Ext.grid.column.Column[]} columns (required)
           * An array of column definition objects which define all columns that appear in this grid.
           * Each column definition provides the header text for the column, and a definition of where
           * the data for that column comes from.
           *
           * This can also be a configuration object for a {Ext.grid.header.Container HeaderContainer}
           * which may override certain default configurations if necessary. For example, the special
           * layout may be overridden to use a simpler layout, or one can set default values shared
           * by all columns:
           *
           *      columns: {
           *          items: [
           *              {
           *                  text: "Column A"
           *                  dataIndex: "field_A",
           *                  width: 200
           *              },{
           *                  text: "Column B",
           *                  dataIndex: "field_B",
           *                  width: 150
           *              },
           *              ...
           *          ]
           *      }
           *
           */
          columns: null,
  
          /**
           * @cfg baseCls
           * @inheritdoc
           */
          baseCls: Ext.baseCSSPrefix + 'grid',
  
          /**
           * @cfg {Boolean} variableHeights
           * This configuration is best left to false on a Grid for performance reasons.
           * @private
           */
          variableHeights: false,
  
          headerContainer: {
              xtype: 'headercontainer'
          },
  
          /**
           * @cfg {Boolean} hideHeaders
           * `true` to hide the grid column headers.
           *
           * @since 6.0.1
           */
          hideHeaders: false,
  
          /**
           * @cfg {Boolean} striped
           * @inherit
           */
          striped: true,
  
          itemCls: Ext.baseCSSPrefix + 'list-item',
          scrollToTopOnRefresh: false,
  
          titleBar: {
              xtype: 'titlebar',
              docked: 'top'
          },
  
          /**
           * @cfg {String} title
           * The title that will be displayed in the TitleBar at the top of this Grid.
           */
          title: ''
      },
  
      /**
       * @event columnadd
       * Fires whenever a column is added to the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The added column.
       * @param {Number} index The index of the added column.
       */
      
      /**
       * @event columnmove
       * Fires whenever a column is moved in the grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The moved column.
       * @param {Number} fromIndex The index the column was moved from.
       * @param {Number} toIndex The index the column was moved to.
       */
  
      /**
       * @event columnremove
       * Fires whenever a column is removed from the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The removed column.
       */
  
      /**
       * @event columnshow
       * Fires whenever a column is shown in the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The shown column.
       */
  
      /**
       * @event columnhide
       * Fires whenever a column is hidden in the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The shown column.
       */
  
      /**
       * @event columnresize
       * Fires whenever a column is resized in the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The resized column.
       * @param {Number} width The new column width.
       */
  
      /**
       * @event columnsort
       * Fires whenever a column is sorted in the Grid.
       * @param {Ext.grid.Grid} this The Grid instance.
       * @param {Ext.grid.column.Column} column The sorted column.
       * @param {String} direction The direction of the sort on this Column. Either 'asc' or 'desc'.
       */
  
      initialize: function() {
          var me = this,
              titleBar = me.getTitleBar(),
              headerContainer = me.getHeaderContainer(),
              scrollable = me.getScrollable(),
              container;
  
          me.callParent();
  
          if (scrollable) {
              headerContainer.getScrollable().addPartner(scrollable, 'x');
          }
          container = me.container;
          if (titleBar) {
              container.add(me.getTitleBar());
          }
          container.add(headerContainer);
  
          me.scrollElement.addCls(Ext.baseCSSPrefix + 'grid-scrollelement');
      },
  
      applyTitleBar: function(titleBar) {
          if (titleBar && !titleBar.isComponent) {
              titleBar = Ext.factory(titleBar, Ext.TitleBar);
          }
          return titleBar;
      },
  
      updateTitle: function(title) {
          var titleBar = this.getTitleBar();
          if (titleBar) {
              if (title) {
                  titleBar.setTitle(title);
              } else {
                  titleBar.hide();
              }
          }
      },
  
      applyHeaderContainer: function(headerContainer) {
          if (headerContainer && !headerContainer.isComponent) {
              headerContainer = Ext.factory(headerContainer, Ext.grid.HeaderContainer);
          }
          return headerContainer;
      },
  
      updateHeaderContainer: function(headerContainer, oldHeaderContainer) {
          var me = this;
  
          if (oldHeaderContainer) {
              oldHeaderContainer.un({
                  columnsort: 'onColumnSort',
                  columnresize: 'onColumnResize',
                  columnshow: 'onColumnShow',
                  columnhide: 'onColumnHide',
                  columnadd: 'onColumnAdd',
                  columnmove: 'onColumnMove',
                  columnremove: 'onColumnRemove',
                  scope: me
              });
          }
  
          if (headerContainer) {
              headerContainer.on({
                  columnsort: 'onColumnSort',
                  columnresize: 'onColumnResize',
                  columnshow: 'onColumnShow',
                  columnhide: 'onColumnHide',
                  columnadd: 'onColumnAdd',
                  columnmove: 'onColumnMove',
                  columnremove: 'onColumnRemove',
                  scope: me
              });
              headerContainer.setGrid(me);
          }
      },
  
      updateHideHeaders: function(hideHeaders) {
          var ct = this.getHeaderContainer(),
              oldCtHeight = this.oldCtHeight || null;
   
          // Don't touch the height if we don't need to
          if (!hideHeaders && ct.getHeight() !== 0) {
              return;
          }
  
          // We rely on the headers to provide sizing, so we can't just hide
          // the headerCt. Try and capture the old height if we had one.
          if (hideHeaders) {
              this.oldCtHeight = ct.getHeight();
          }
          ct.setHeight(hideHeaders ? 0 : oldCtHeight);
      },
  
      addColumn: function(column) {
          return this.getHeaderContainer().add(column);
      },
  
      removeColumn: function(column) {
          return this.getHeaderContainer().remove(column);
      },
  
      insertColumn: function(index, column) {
          return this.getHeaderContainer().insert(index, column);
      },
  
      onColumnAdd: function(container, column) {
          var me = this,
              items, ln, columnIndex, i, row;
  
          if (me.initialized && !me.destroying) {
              items = this.listItems;
              ln = items.length;
              columnIndex = container.getColumns().indexOf(column);
  
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.insertColumn(columnIndex, column);
              }
  
              me.updateTotalColumnWidth();
  
              me.fireEvent('columnadd', me, column, columnIndex);
          }
      },
  
      onColumnMove: function(container, column, group, fromIdx, toIdx) {
          var me = this,
              items, ln, i, row;
  
          if (me.initialized && !me.destroying) {
              items = me.listItems;
              ln = items.length;
  
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.moveColumn(column, fromIdx, toIdx);
              }
  
              me.fireEvent('columnmove', me, column, fromIdx, toIdx);
          }
      },
  
      onColumnRemove: function(container, column) {
          var me = this,
              items, ln, i, row;
  
          if (me.initialized && !me.destroying) {
              items = me.listItems;
              ln = items.length;
  
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.removeColumn(column);
              }
  
              me.updateTotalColumnWidth();
  
              me.fireEvent('columnremove', me, column);
          }
      },
  
      updateColumns: function(columns) {
          if (columns && columns.length) {
              var ln = columns.length,
                  i;
  
              for (i = 0; i < ln; i++) {
                  this.addColumn(columns[i]);
              }
  
              this.updateTotalColumnWidth();
          }
      },
  
      getColumns: function() {
          return this.getHeaderContainer().getColumns();
      },
  
      onColumnResize: function(container, column, width, oldWidth) {
          var me = this,
              items = me.listItems,
              ln = items.length,
              i, row;
  
          if (!me.destroying) {
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.setColumnWidth(column, width);
              }
              if (me.initialized) {
                  me.updateTotalColumnWidth();
                  // Will be null on the first time
                  if (oldWidth !== null && !column.getHidden()) {
                      me.fireEvent('columnresize', me, column, width);
                  }
              }
          }
      },
  
      onColumnShow: function(container, column) {
          var me = this,
              items, ln, i, row, w;
  
          if (me.initialized && !me.destroying) {
              items = me.listItems;
              ln = items.length;
  
              me.updateTotalColumnWidth();
              if (!column.getFlex()) {
                  w = column.getWidth();
              }
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.showColumn(column);
                  // If we have a fixed width column, we won't get a resize event
                  // from the resize listener, so force the cell width
                  if (w !== undefined) {
                      row.setColumnWidth(column, w);
                  }
              }
  
              me.fireEvent('columnshow', me, column);
          }
      },
  
      onColumnHide: function(container, column) {
          var me = this,
              items, ln, i, row;
  
          if (me.initialized && !me.destroying) {
              items = me.listItems;
              ln = items.length;
  
              me.updateTotalColumnWidth();
              for (i = 0; i < ln; i++) {
                  row = items[i];
                  row.hideColumn(column);
              }
  
              me.fireEvent('columnhide', me, column);
          }
      },
  
      onColumnSort: function(container, column, direction) {
          var me = this,
              sorted = me.sortedColumn;
  
          if (sorted && sorted !== column) {
              sorted.setSortDirection(null);
          }
          me.sortedColumn = column;
  
          me.getStore().sort(column.getDataIndex(), direction);
  
          me.fireEvent('columnsort', me, column, direction);
      },
  
      refreshScroller: function(skipOnRefresh) {
          this.callParent([skipOnRefresh]);
          this.getHeaderContainer().updateSpacer();
      },
  
      getTotalColumnWidth: function() {
          var me = this,
              columns = me.getColumns(),
              ln = columns.length,
              totalWidth = 0,
              i, column, parent;
  
  
          for (i = 0; i < ln; i++) {
              column = columns[i];
              parent = column.getParent();
  
              if (!column.isHidden() && (!parent.isHeaderGroup || !parent.isHidden())) {
                  totalWidth += column.getComputedWidth();
              }
          }
  
          return totalWidth;
      },
  
      updateTotalColumnWidth: function() {
          var me = this,
              scroller = me.getScrollable(),
              totalWidth = this.getTotalColumnWidth(),
              header = me.getHeaderContainer();
  
          me.scrollElement.setWidth(totalWidth);
          header.setTotalWidth(totalWidth);
          header.updateSpacer();
  
          scroller.setSize({
              x: totalWidth,
              y: scroller.getSize().y
          });
      },
  
      createItem: function(config) {
          config.grid = this;
  
          return this.callParent([config]);
      },
  
      destroy: function() {
          this.destroying = true;
          this.callParent();
          this.destroying = false;
      }
  });
  