<commit_before><link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-flex-layout/classes/iron-flex-layout.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-icons/iron-icons.html">
<link rel="import" href="../iron-dropdown/iron-dropdown.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-material/paper-material.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="paper-datagrid-column.html">

<!--
A Material Design inspired data grid element for Polymer.js apps.

This element uses, as an initial foundation, the Google Material Design specification for data tables:
[Data Tables](https://www.google.com/design/spec/components/data-tables.html)

However, the goal of the element is to support much richer _data grid_ functionality, so there will be necessary
deviations or extrapolations, as features that are not included in the specification are added to this element.

### Simple Example:

    <paper-datagrid items="[[_myItems]]">
      <template>
        <paper-datagrid-column id="foo" header="Foo"><span>[[row.foo]]</span></paper-datagrid-column>
        <paper-datagrid-column id="bar" header="Bar"><span>[[row.bar]]</span></paper-datagrid-column>
        <paper-datagrid-column id="baz" header="Baz"><span>[[row.baz]]</span></paper-datagrid-column>
      </template>
    </paper-datagrid>

A row will be rendered for each item of the collection, and the _row_ property will be available for the template of each
column.

@element paper-datagrid
@demo demo/index.html Basic configurations
@demo demo/index2.html Paging footer
@demo demo/index3.html Item selection
@demo demo/index4.html Fixed header
@demo demo/index5.html Infinite scroll
@demo demo/index6.html Infinite scroll 2
-->
<dom-module id="paper-datagrid">
  <style is="custom-style">
    :host {
      -moz-user-select     : none;
      -khtml-user-select   : none;
      -webkit-user-select  : none;
      -o-user-select       : none;
      user-select          : none;
    }
    #dgwrapper {
      overflow-x: auto;
      height: 100%;
    }
    #dgheader {
      height: 56px;
      white-space: nowrap;
    }
    #dgheader ::content > paper-datagrid-column{
      line-height: 56px;
      max-height: 56px;
    }
    :host([selection-enabled]) ::content .row:hover {
      background-color: #EEEEEE;
    }
    :host([selection-enabled]) ::content .row.header:hover {
      background-color: initial;
    }
    :host ::content .row paper-datagrid-column:first-of-type .splitter {
      display: none;
    }
    :host ::content .row paper-datagrid-column:first-of-type #dgcolumn {
      margin-left: 24px;
      width: calc(100% - 36px);
    }
    :host ::content .row paper-datagrid-column:last-of-type #dgcolumn {
      margin-right: 24px;
      width: calc(100% - 36px);
    }
    :host ::content .hztl-lines paper-datagrid-column {
      border-bottom-style: solid;
      border-bottom-width: 1px;
      border-bottom-color: var(--grid-line-color,#DDDDDD);
    }
    :host ::content .vert-lines paper-datagrid-column {
      border-right-style: solid;
      border-right-width: 1px;
      border-right-color: var(--grid-line-color,#DDDDDD);
    }
    :host ::content .vert-lines paper-datagrid-column:last-of-type {
      border-right: none;
    }
    #dgfooter {
      height: 56px;
      padding: 0px 6px;
      font-size: 12px;
      font-weight: 400;
      color: rgba(0, 0, 0, 0.54);
      border-top: solid 1px #DDDDDD;
    }
    #dgfooter > paper-icon-button {
      margin: 0px 2px;
    }
    #pages {
      margin-left: 32px;
      margin-right: 32px;
    }
    #perPageArrow {
      padding: 0px;
      width: 24px;
      height: 24px;
    }
    #perPage {
      text-align: right;
      width: 40px;
    }
    #optionsDropdown paper-item {
      color: #666666;
      cursor: pointer;
      background-color: #FFFFFF;
    }
    #optionsDropdown paper-item:hover {
      color: #000000;
      background-color: #EEEEEE;
    }
    #optionsDropdown paper-item.iron-selected {
      font-weight: initial;
    }
    #infScrollIcon {
      height: 56px;
    }
    #infScrollIcon iron-icon {
      color: rgba(0, 0, 0, 0.4);
      -webkit-animation: icon-spin 1s infinite linear;
      animation: icon-spin 1s infinite linear;
    }
    @-webkit-keyframes icon-spin {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
      }
    }
    @keyframes icon-spin {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
      }
    }
  </style>
  <template>
    <array-selector id="selector" items="{{items}}"
      selected="{{selectedItems}}" selected-item="{{selectedItem}}">
    </array-selector>
    <div id="dgwrapper">
      <div id="dgheader" class="header row center horizontal layout"></div>
      <div id="dggrid"></div>
      <template is="dom-if" if="[[enablePaging]]">
        <div id="dgfooter" class="center horizontal-reverse layout">
          <paper-icon-button icon="chevron-right" on-click="_nextPage"></paper-icon-button>
          <paper-icon-button icon="chevron-left" on-click="_prevPage"></paper-icon-button>
          <span id="pages">[[_calcPaging(currentPage,itemsPerPage,totalItems,pageCountLabel)]]</span>
          <div>
            <paper-icon-button id="perPageArrow" icon="arrow-drop-down" noink on-click="_showPageOpts"></paper-icon-button>
            <iron-dropdown id="optionsDropdown" horizontal-align="right" vertical-align="bottom">
              <paper-material class="dropdown-content">
                <div class="options">
                  <template is="dom-repeat" items="[[perPageOptions]]">
                    <paper-item on-click="_optHandler">[[item]]</paper-item>
                  </template>
                </div>
              </paper-material>
            </iron-dropdown>
          </div>
          <span id="perPage">[[itemsPerPage]]</span>
          <span>[[perPageLabel]]</span>
        </div>
      </template>
      <div id="infScrollIcon" class="layout horizontal center-center" hidden$="[[!_infiniteScrolling]]"><iron-icon icon="refresh"></iron-icon></div>
    </div>
    <content id="template" select="template"></content>
  </template>
</dom-module>
<script>
  (function() {
    Polymer({
      is: 'paper-datagrid',

      /*
       * A column is being sorted
       *
       * @event sort
       */

      /*
       * A row was double-clicked
       *
       * @event row-dblclick
       */

      /*
       * A new page size was selected
       *
       * @event per-page-change
       */

      /*
       * The user desires to navigate to the next page
       *
       * @event next-page
       */

      /*
       * The user desires to navigate to the previous page
       *
       * @event prev-page
       */

      /*
       * When infinite scrolling is enabled, this indicates the need to fetch more data
       *
       * @event scroll-more
       */

      properties: {
        /**
         * An array containing the objects that will correspond to each row of the grid
         */
        items: {
          type: Array,
          value: function(){return [];}
        },
        /**
         * Which row/column border lines to draw - _horizontal_, _vertical_, or _both_
         */
        gridLines: {
          type: String,
          value: 'horizontal'
        },
        /**
         * Whether or not activate and display the paging footer controls
         */
        enablePaging: {
          type: Boolean,
          value: false
        },
        /**
         * Current page displayed, when paging
         */
        currentPage: {
          type: Number,
          value: 1
        },
        /**
         * Number of items shown per page, when paging
         */
        itemsPerPage: {
          type: Number,
          value: 10
        },
        /**
         * Total number of items available, when paging
         */
        totalItems: {
          type: Number,
          value: 0
        },
        /**
         * Page sizes avilable for the user to choose from, when paging
         */
        perPageOptions: {
          type: Array,
          value: function() { return []; }
        },
        /**
         * Label to use for indicating the page information, when paging.  Allows for language translation, providing
         * value substitution - `{1}-{2} of {3}`
         */
        pageCountLabel: {
          type: String,
          value: '{1}-{2} of {3}'
        },
        /**
         * Label to use for the page options dropdown, when paging.  Allows for language translation
         */
        perPageLabel: {
          type: String,
          value: 'Rows per page:'
        },
        /**
         * Whether or not row selection is enabled
         */
        selectionEnabled: {
          type: Boolean,
          value: false,
          reflectToAttribute: true
        },
        /**
         * When `multiSelection` is false, this is the currently selected item, or `null`
         * if no item is selected.
         */
        selectedItem: {
          type: Object,
          notify: true
        },
        /**
         * When `multiSelection` is true, this is an array that contains the selected items.
         */
        selectedItems: {
          type: Object,
          notify: true
        },
        /**
         * Whether or not row multi selection is enabled
         */
        multiSelection: {
          type: Boolean,
          value: false
        },
        /**
         * Whether or not _desktop like_ row multi selection is enabled.  By default, multi-selection will behave in a
         * manner that is useful for both mobile and desktop scenarios, i.e. each click will select/deselect a given
         * row, and keyboard combinations like _ctrl+click_ and _shift+click_ have no effect.  With
         * `desktop-multii-selection`, these keyboard combinations are supported.
         */
        desktopMultiSelection: {
          type: Boolean,
          value: false
        },
        /**
         * Whether or not to keep the header and footer (if paging is enabled) from scrolling with the grid rows.  This
         * requires that the datagrid has an explicit height declared.
         */
        fixedHeaderFooter: {
          type: Boolean,
          value: false
        }
      },
      behaviors: [Polymer.Templatizer],
      observers: [
        '_itemsChanged(items.*)',
        '_selectionEnabledChanged(selectionEnabled)',
        '_multiSelectionChanged(multiSelection)',
        '_fixedHeaderFooterChanged(fixedHeaderFooter)',
        '_gridLinesChanged(gridLines)'
      ],
      _scroller: null,
      _infiniteScrolling: false,
      _itemsChanged: function(change) {
        if (change.path === 'items' || change.path === 'items.splices') {
          var template = Polymer.dom(this.$.template).getDistributedNodes()[0];
          if (!this.ctor) {
            this.templatize(template);
          }
          var headerDom = Polymer.dom(this.$.dgheader);
          if (headerDom.childNodes.length === 0) {
            headerDom.appendChild(this.stamp({isHeader:true}).root);
          }
          //TODO: brute force right now, need to improve
          var gridDom = Polymer.dom(this.$.dggrid);
          for (var c = gridDom.childNodes.length - 1; c >= 0; c--) {
            var child = gridDom.childNodes[c];
            if (child.className.indexOf('header') < 0) {
              gridDom.removeChild(child);
            }
          }
          for (var i = 0; i < this.items.length; i++) {
            var itemNode = this.stamp({});
            itemNode.row = this.items[i];
            var row = document.createElement('div');
            row.className += 'row center horizontal layout';
            row.appendChild(itemNode.root);
            row.addEventListener('dblclick', this._rowDblClick.bind(this));
            gridDom.appendChild(row);
          }
          this._hideInfiniteScrollIcon();
        } else if (change.path.indexOf('items.#') === 0) {
          var path = change.path.substr(7);
          var subpathIdx = path.indexOf('.');
          var itemIdx = parseInt(path.substring(0, subpathIdx));
          var path = path.substr(subpathIdx + 1);
          var gridRow = this.$.dggrid.childNodes[itemIdx];
          for (var c = gridRow.childNodes.length - 1; c >= 0; c--) {
            var child = gridRow.childNodes[c];
            if (child.nodeType === 1) {
              child.dataHost.set('row.' + path, change.value);
              child.set('_model.' + path, change.value);
            }
          }
        }
      },
      _selectionEnabledChanged: function(selectionEnabled) {
        if (selectionEnabled) {
          this.listen(this, 'tap', '_selectionHandler');
          this.listen(this, 'keypress', '_selectionHandler');
        } else {
          this.unlisten(this, 'tap', '_selectionHandler');
          this.unlisten(this, 'keypress', '_selectionHandler');
        }
        this.clearSelection();
      },
      _selectionHandler: function(e) {
        if (e.type !== 'keypress' || e.keyCode === 13) {
          var model = this.modelForElement(e.target);
          if (model) {
            if (!this.desktopMultiSelection) {
              this.toggleSelectionForItem(model.row);
            } else {
              this._desktopMultiSelect(e.detail.sourceEvent, model.row);
            }
          }
        }
      },
      _desktopMultiSelect: function(evt, item) {
        if (this.multiSelection) {
          if (evt.ctrlKey === false && evt.metaKey === false && evt.shiftKey === false) {
            this.clearSelection();
          }
          var idx = this.items.indexOf(item);
          if (evt.shiftKey === true) {
            if (!isNaN(this._lastSelectedIndex)) {
              var smaller = (idx < this._lastSelectedIndex) ? idx : this._lastSelectedIndex;
              var bigger = (idx < this._lastSelectedIndex) ? this._lastSelectedIndex : idx;
              for (var i = smaller; i <= bigger; i++) {
                this.selectItem(this.items[i]);
              }
            }
          } else {
            this.toggleSelectionForItem(item);
          }
          this._lastSelectedIndex = idx;
        } else {
          this.toggleSelectionForItem(item);
        }
      },
      _multiSelectionChanged: function(multiSelection) {
        this.clearSelection();
        this.$.selector.multi = multiSelection;
      },
      _fixedHeaderFooterChanged: function() {
        if (this.fixedHeaderFooter === true) {
          //TODO get these values from css
          var hfHeight = '56px';
          if (this.enablePaging === true) {
            hfHeight = '112px';
          }
          this.$.dggrid.style.height = 'calc(100% - ' + hfHeight + ')';
          this.$.dggrid.style['overflow-y'] = 'overlay';
          this.$.dgwrapper.style['overflow-y'] = 'hidden';
        } else {
          this.$.dggrid.style.height = '';
          this.$.dggrid.style['overflow-y'] = 'initial';
          this.$.dgwrapper.style['overflow-y'] = 'initial';
        }
      },
      _gridLinesChanged: function() {
        this._updateGridLines(this.$.dgheader);
        this._updateGridLines(this.$.dggrid);
      },
      _updateGridLines: function(div) {
        this.toggleClass('hztl-lines', false, div);
        this.toggleClass('vert-lines', false, div);
        if (this.gridLines.toLowerCase() === 'horizontal' || this.gridLines.toLowerCase() === 'both') {
          this.toggleClass('hztl-lines', true, div);
        }
        if (this.gridLines.toLowerCase() === 'vertical' || this.gridLines.toLowerCase() === 'both') {
          this.toggleClass('vert-lines', true, div);
        }
      },
      _rowDblClick: function(e) {
        var col;
        var el = e.target;
        while (col === undefined) {
          if (el.nodeName.toLowerCase() === 'paper-datagrid-column') {
            col = el
          } else {
            el = el.parentNode;
          }
        }
        this.fire('row-dblclick', this.modelForElement(col).row);
      },
      _resizeCell: function(cell, width) {
        cell.style['min-width'] = width;
        cell.style['max-width'] = width;
        cell.style.flex = 'initial';
      },
      _resizeColumns: function(row, idx, delta) {
        var minColWidth = 48;
        var col = row.children[idx];
        var colWidth = document.defaultView.getComputedStyle(col, '').getPropertyValue('width');
        var newColWidth = (parseInt(colWidth, 10) - delta);
        var prev = row.children[idx - 1];
        var prevWidth = document.defaultView.getComputedStyle(prev, '').getPropertyValue('width');
        var newPrevWidth = (parseInt(prevWidth, 10) + delta);
        if (newColWidth > minColWidth && newPrevWidth > minColWidth) {
          this._resizeCell(col, newColWidth + 'px');
          this._resizeCell(prev, newPrevWidth + 'px');
        }
      },
      _columnResize: function(e) {
        var idx = e.detail.idx;
        var delta = e.detail.delta;
        this._resizeColumns(this.$.dgheader, idx, delta);
        var rows = Polymer.dom(this.$.dggrid).children;
        for (var i = 0; i < rows.length; i++) {
          this._resizeColumns(rows[i], idx, delta);
        }
      },
      _forwardParentProp: function(prop, value) {
        if (prop !== 'row') {
          var columns = this.querySelectorAll('paper-datagrid-column');
          for (var i = 0; i < columns.length; i++) {
            columns[i]._templateInstance[prop] = value;
          }
        }
      },
      _calcPaging: function(cp, pp, ttl, pcl) {
        var sIdx = ((cp - 1) * pp) + 1;
        var eIdx = (sIdx + pp) - 1;
        eIdx = (eIdx < ttl) ? eIdx : ttl;
        var args = [null,sIdx,eIdx,ttl];
        return pcl.replace(/{(\d+)}/g, function(match, number) {
          return typeof args[number] != 'undefined' ? args[number] : match;
        });
      },
      _optHandler: function(e) {
        this.$$('#optionsDropdown').close();
        this.fire('per-page-change', e.model.__data__.item)
      },
      _nextPage: function(e) {
        this.fire('next-page');
      },
      _prevPage: function(e) {
        this.fire('prev-page');
      },
      _showPageOpts: function(e) {
        var dd = this.$$('#optionsDropdown');
        if (dd.opened) { return; }
        dd.open();
      },
      _scrollHandler: function() {
        var h1 = (this.fixedHeaderFooter)
                ? this._scroller.scrollHeight
                : parseInt(document.defaultView.getComputedStyle(this.$.dgwrapper, '').getPropertyValue('height'), 10);
        var h2 = (this.fixedHeaderFooter)
                ? parseInt(document.defaultView.getComputedStyle(this.$.dgwrapper, '').getPropertyValue('height'), 10)
                : parseInt(document.defaultView.getComputedStyle(this._scroller, '').getPropertyValue('height'), 10);
        if (this._infiniteScrolling === false && this._scroller.scrollTop >= (h1 - h2)) {
          this._showInfiniteScrollIcon();
          this.fire('scroll-more');
        }
      },
      _showInfiniteScrollIcon: function() {
        if (this.fixedHeaderFooter) {
          this.$.dggrid.style.height = 'calc(100% - 112px)';
        }
        this._infiniteScrolling = true;
      },
      _hideInfiniteScrollIcon: function() {
        if (this.fixedHeaderFooter) {
          this.$.dggrid.style.height = 'calc(100% - 56px)';
        }
        this._infiniteScrolling = false;
      },
      /**
       * Clear any currently selected rows
       *
       * @method clearSelection
       */
      clearSelection: function() {
        if (Array.isArray(this.selectedItems)) {
          for (var i = this.selectedItems.length - 1; i >= 0; i--) {
            this.deselectItem(this.selectedItems[i]);
          }
        } else if (this.selectedItem) {
          this.deselectItem(this.selectedItem);
        }
      },
      /**
       * Select the row that corresponds to the provided item
       *
       * @method selectItem
       * @param {Object} Item to select
       */
      selectItem: function(item) {
        if (!this.multiSelection && this.selectedItem) {
          this.deselectItem(this.selectedItem);
        }
        var idx = this.items.indexOf(item);
        this.set('items.' + idx + '.selected', true);
        this.$.selector.select(item);
      },
      /**
       * Deselect the row that corresponds to the provided item
       *
       * @method deselectItem
       * @param {Object} Item to deselect
       */
      deselectItem: function(item) {
        var idx = this.items.indexOf(item);
        this.set('items.' + idx + '.selected', false);
        this.$.selector.deselect(item);
      },
      /**
       * Select or deselect the row that corresponds to the provided item, depending on its current selected state
       *
       * @method toggleSelectionForItem
       * @param {Object} Item to select/deselect
       */
      toggleSelectionForItem: function(item) {
        if (this.$.selector.isSelected(item)) {
          this.deselectItem(item);
        } else {
          this.selectItem(item);
        }
      },
      /**
       * Enable infinite scroll.  The DOM element that is handling scrolling for the grid should be provided as the
       * parameter. If the grid has a fixed size (such as with a fixed header), the scroller parameter is omitted.
       * This feature cannot be used in combination with the paging feature.
       *
       * @method enableInfiniteScroll
       * @param {?Element} The element that controls the scroll
       */
      enableInfiniteScroll: function(scroller) {
        if (scroller !== undefined) {
          this._scroller = scroller;
        } else {
          this._scroller = this.$.dggrid;
        }
        this._scrollListener = this._scrollHandler.bind(this);
        this._scroller.addEventListener('scroll', this._scrollListener);
      },
      /**
       * When using the infinite scroll feature, this method can be called during the on-scroll-more handler to indicate
       * there is no additional data available.
       *
       * @method infiniteScrollDone
       */
      infiniteScrollDone: function() {
        this._hideInfiniteScrollIcon();
      },
      detached: function() {
        if (this._scroller) {
          this._scroller.removeEventListener('scroll', this._scrollListener);
        }
      },
      ready: function() {
        this.$.dgwrapper.addEventListener('column-resize', this._columnResize.bind(this));
      }
    });
  })();
</script>
<commit_msg>special css rule when only one grid column<commit_after><link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../iron-flex-layout/classes/iron-flex-layout.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-icons/iron-icons.html">
<link rel="import" href="../iron-dropdown/iron-dropdown.html">
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-material/paper-material.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="paper-datagrid-column.html">

<!--
A Material Design inspired data grid element for Polymer.js apps.

This element uses, as an initial foundation, the Google Material Design specification for data tables:
[Data Tables](https://www.google.com/design/spec/components/data-tables.html)

However, the goal of the element is to support much richer _data grid_ functionality, so there will be necessary
deviations or extrapolations, as features that are not included in the specification are added to this element.

### Simple Example:

    <paper-datagrid items="[[_myItems]]">
      <template>
        <paper-datagrid-column id="foo" header="Foo"><span>[[row.foo]]</span></paper-datagrid-column>
        <paper-datagrid-column id="bar" header="Bar"><span>[[row.bar]]</span></paper-datagrid-column>
        <paper-datagrid-column id="baz" header="Baz"><span>[[row.baz]]</span></paper-datagrid-column>
      </template>
    </paper-datagrid>

A row will be rendered for each item of the collection, and the _row_ property will be available for the template of each
column.

@element paper-datagrid
@demo demo/index.html Basic configurations
@demo demo/index2.html Paging footer
@demo demo/index3.html Item selection
@demo demo/index4.html Fixed header
@demo demo/index5.html Infinite scroll
@demo demo/index6.html Infinite scroll 2
-->
<dom-module id="paper-datagrid">
  <style is="custom-style">
    :host {
      -moz-user-select     : none;
      -khtml-user-select   : none;
      -webkit-user-select  : none;
      -o-user-select       : none;
      user-select          : none;
    }
    #dgwrapper {
      overflow-x: auto;
      height: 100%;
    }
    #dgheader {
      height: 56px;
      white-space: nowrap;
    }
    #dgheader ::content > paper-datagrid-column{
      line-height: 56px;
      max-height: 56px;
    }
    :host([selection-enabled]) ::content .row:hover {
      background-color: #EEEEEE;
    }
    :host([selection-enabled]) ::content .row.header:hover {
      background-color: initial;
    }
    :host ::content .row paper-datagrid-column:first-of-type .splitter {
      display: none;
    }
    :host ::content .row paper-datagrid-column:first-of-type #dgcolumn {
      margin-left: 24px;
      width: calc(100% - 36px);
    }
    :host ::content .row paper-datagrid-column:last-of-type #dgcolumn {
      margin-right: 24px;
      width: calc(100% - 36px);
    }
    :host ::content .row paper-datagrid-column:only-child #dgcolumn {
      width: calc(100% - 48px);
    }
    :host ::content .hztl-lines paper-datagrid-column {
      border-bottom-style: solid;
      border-bottom-width: 1px;
      border-bottom-color: var(--grid-line-color,#DDDDDD);
    }
    :host ::content .vert-lines paper-datagrid-column {
      border-right-style: solid;
      border-right-width: 1px;
      border-right-color: var(--grid-line-color,#DDDDDD);
    }
    :host ::content .vert-lines paper-datagrid-column:last-of-type {
      border-right: none;
    }
    #dgfooter {
      height: 56px;
      padding: 0px 6px;
      font-size: 12px;
      font-weight: 400;
      color: rgba(0, 0, 0, 0.54);
      border-top: solid 1px #DDDDDD;
    }
    #dgfooter > paper-icon-button {
      margin: 0px 2px;
    }
    #pages {
      margin-left: 32px;
      margin-right: 32px;
    }
    #perPageArrow {
      padding: 0px;
      width: 24px;
      height: 24px;
    }
    #perPage {
      text-align: right;
      width: 40px;
    }
    #optionsDropdown paper-item {
      color: #666666;
      cursor: pointer;
      background-color: #FFFFFF;
    }
    #optionsDropdown paper-item:hover {
      color: #000000;
      background-color: #EEEEEE;
    }
    #optionsDropdown paper-item.iron-selected {
      font-weight: initial;
    }
    #infScrollIcon {
      height: 56px;
    }
    #infScrollIcon iron-icon {
      color: rgba(0, 0, 0, 0.4);
      -webkit-animation: icon-spin 1s infinite linear;
      animation: icon-spin 1s infinite linear;
    }
    @-webkit-keyframes icon-spin {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
      }
    }
    @keyframes icon-spin {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(359deg);
        transform: rotate(359deg);
      }
    }
  </style>
  <template>
    <array-selector id="selector" items="{{items}}"
      selected="{{selectedItems}}" selected-item="{{selectedItem}}">
    </array-selector>
    <div id="dgwrapper">
      <div id="dgheader" class="header row center horizontal layout"></div>
      <div id="dggrid"></div>
      <template is="dom-if" if="[[enablePaging]]">
        <div id="dgfooter" class="center horizontal-reverse layout">
          <paper-icon-button icon="chevron-right" on-click="_nextPage"></paper-icon-button>
          <paper-icon-button icon="chevron-left" on-click="_prevPage"></paper-icon-button>
          <span id="pages">[[_calcPaging(currentPage,itemsPerPage,totalItems,pageCountLabel)]]</span>
          <div>
            <paper-icon-button id="perPageArrow" icon="arrow-drop-down" noink on-click="_showPageOpts"></paper-icon-button>
            <iron-dropdown id="optionsDropdown" horizontal-align="right" vertical-align="bottom">
              <paper-material class="dropdown-content">
                <div class="options">
                  <template is="dom-repeat" items="[[perPageOptions]]">
                    <paper-item on-click="_optHandler">[[item]]</paper-item>
                  </template>
                </div>
              </paper-material>
            </iron-dropdown>
          </div>
          <span id="perPage">[[itemsPerPage]]</span>
          <span>[[perPageLabel]]</span>
        </div>
      </template>
      <div id="infScrollIcon" class="layout horizontal center-center" hidden$="[[!_infiniteScrolling]]"><iron-icon icon="refresh"></iron-icon></div>
    </div>
    <content id="template" select="template"></content>
  </template>
</dom-module>
<script>
  (function() {
    Polymer({
      is: 'paper-datagrid',

      /*
       * A column is being sorted
       *
       * @event sort
       */

      /*
       * A row was double-clicked
       *
       * @event row-dblclick
       */

      /*
       * A new page size was selected
       *
       * @event per-page-change
       */

      /*
       * The user desires to navigate to the next page
       *
       * @event next-page
       */

      /*
       * The user desires to navigate to the previous page
       *
       * @event prev-page
       */

      /*
       * When infinite scrolling is enabled, this indicates the need to fetch more data
       *
       * @event scroll-more
       */

      properties: {
        /**
         * An array containing the objects that will correspond to each row of the grid
         */
        items: {
          type: Array,
          value: function(){return [];}
        },
        /**
         * Which row/column border lines to draw - _horizontal_, _vertical_, or _both_
         */
        gridLines: {
          type: String,
          value: 'horizontal'
        },
        /**
         * Whether or not activate and display the paging footer controls
         */
        enablePaging: {
          type: Boolean,
          value: false
        },
        /**
         * Current page displayed, when paging
         */
        currentPage: {
          type: Number,
          value: 1
        },
        /**
         * Number of items shown per page, when paging
         */
        itemsPerPage: {
          type: Number,
          value: 10
        },
        /**
         * Total number of items available, when paging
         */
        totalItems: {
          type: Number,
          value: 0
        },
        /**
         * Page sizes avilable for the user to choose from, when paging
         */
        perPageOptions: {
          type: Array,
          value: function() { return []; }
        },
        /**
         * Label to use for indicating the page information, when paging.  Allows for language translation, providing
         * value substitution - `{1}-{2} of {3}`
         */
        pageCountLabel: {
          type: String,
          value: '{1}-{2} of {3}'
        },
        /**
         * Label to use for the page options dropdown, when paging.  Allows for language translation
         */
        perPageLabel: {
          type: String,
          value: 'Rows per page:'
        },
        /**
         * Whether or not row selection is enabled
         */
        selectionEnabled: {
          type: Boolean,
          value: false,
          reflectToAttribute: true
        },
        /**
         * When `multiSelection` is false, this is the currently selected item, or `null`
         * if no item is selected.
         */
        selectedItem: {
          type: Object,
          notify: true
        },
        /**
         * When `multiSelection` is true, this is an array that contains the selected items.
         */
        selectedItems: {
          type: Object,
          notify: true
        },
        /**
         * Whether or not row multi selection is enabled
         */
        multiSelection: {
          type: Boolean,
          value: false
        },
        /**
         * Whether or not _desktop like_ row multi selection is enabled.  By default, multi-selection will behave in a
         * manner that is useful for both mobile and desktop scenarios, i.e. each click will select/deselect a given
         * row, and keyboard combinations like _ctrl+click_ and _shift+click_ have no effect.  With
         * `desktop-multii-selection`, these keyboard combinations are supported.
         */
        desktopMultiSelection: {
          type: Boolean,
          value: false
        },
        /**
         * Whether or not to keep the header and footer (if paging is enabled) from scrolling with the grid rows.  This
         * requires that the datagrid has an explicit height declared.
         */
        fixedHeaderFooter: {
          type: Boolean,
          value: false
        }
      },
      behaviors: [Polymer.Templatizer],
      observers: [
        '_itemsChanged(items.*)',
        '_selectionEnabledChanged(selectionEnabled)',
        '_multiSelectionChanged(multiSelection)',
        '_fixedHeaderFooterChanged(fixedHeaderFooter)',
        '_gridLinesChanged(gridLines)'
      ],
      _scroller: null,
      _infiniteScrolling: false,
      _itemsChanged: function(change) {
        if (change.path === 'items' || change.path === 'items.splices') {
          var template = Polymer.dom(this.$.template).getDistributedNodes()[0];
          if (!this.ctor) {
            this.templatize(template);
          }
          var headerDom = Polymer.dom(this.$.dgheader);
          if (headerDom.childNodes.length === 0) {
            headerDom.appendChild(this.stamp({isHeader:true}).root);
          }
          //TODO: brute force right now, need to improve
          var gridDom = Polymer.dom(this.$.dggrid);
          for (var c = gridDom.childNodes.length - 1; c >= 0; c--) {
            var child = gridDom.childNodes[c];
            if (child.className.indexOf('header') < 0) {
              gridDom.removeChild(child);
            }
          }
          for (var i = 0; i < this.items.length; i++) {
            var itemNode = this.stamp({});
            itemNode.row = this.items[i];
            var row = document.createElement('div');
            row.className += 'row center horizontal layout';
            row.appendChild(itemNode.root);
            row.addEventListener('dblclick', this._rowDblClick.bind(this));
            gridDom.appendChild(row);
          }
          this._hideInfiniteScrollIcon();
        } else if (change.path.indexOf('items.#') === 0) {
          var path = change.path.substr(7);
          var subpathIdx = path.indexOf('.');
          var itemIdx = parseInt(path.substring(0, subpathIdx));
          var path = path.substr(subpathIdx + 1);
          var gridRow = this.$.dggrid.childNodes[itemIdx];
          for (var c = gridRow.childNodes.length - 1; c >= 0; c--) {
            var child = gridRow.childNodes[c];
            if (child.nodeType === 1) {
              child.dataHost.set('row.' + path, change.value);
              child.set('_model.' + path, change.value);
            }
          }
        }
      },
      _selectionEnabledChanged: function(selectionEnabled) {
        if (selectionEnabled) {
          this.listen(this, 'tap', '_selectionHandler');
          this.listen(this, 'keypress', '_selectionHandler');
        } else {
          this.unlisten(this, 'tap', '_selectionHandler');
          this.unlisten(this, 'keypress', '_selectionHandler');
        }
        this.clearSelection();
      },
      _selectionHandler: function(e) {
        if (e.type !== 'keypress' || e.keyCode === 13) {
          var model = this.modelForElement(e.target);
          if (model) {
            if (!this.desktopMultiSelection) {
              this.toggleSelectionForItem(model.row);
            } else {
              this._desktopMultiSelect(e.detail.sourceEvent, model.row);
            }
          }
        }
      },
      _desktopMultiSelect: function(evt, item) {
        if (this.multiSelection) {
          if (evt.ctrlKey === false && evt.metaKey === false && evt.shiftKey === false) {
            this.clearSelection();
          }
          var idx = this.items.indexOf(item);
          if (evt.shiftKey === true) {
            if (!isNaN(this._lastSelectedIndex)) {
              var smaller = (idx < this._lastSelectedIndex) ? idx : this._lastSelectedIndex;
              var bigger = (idx < this._lastSelectedIndex) ? this._lastSelectedIndex : idx;
              for (var i = smaller; i <= bigger; i++) {
                this.selectItem(this.items[i]);
              }
            }
          } else {
            this.toggleSelectionForItem(item);
          }
          this._lastSelectedIndex = idx;
        } else {
          this.toggleSelectionForItem(item);
        }
      },
      _multiSelectionChanged: function(multiSelection) {
        this.clearSelection();
        this.$.selector.multi = multiSelection;
      },
      _fixedHeaderFooterChanged: function() {
        if (this.fixedHeaderFooter === true) {
          //TODO get these values from css
          var hfHeight = '56px';
          if (this.enablePaging === true) {
            hfHeight = '112px';
          }
          this.$.dggrid.style.height = 'calc(100% - ' + hfHeight + ')';
          this.$.dggrid.style['overflow-y'] = 'overlay';
          this.$.dgwrapper.style['overflow-y'] = 'hidden';
        } else {
          this.$.dggrid.style.height = '';
          this.$.dggrid.style['overflow-y'] = 'initial';
          this.$.dgwrapper.style['overflow-y'] = 'initial';
        }
      },
      _gridLinesChanged: function() {
        this._updateGridLines(this.$.dgheader);
        this._updateGridLines(this.$.dggrid);
      },
      _updateGridLines: function(div) {
        this.toggleClass('hztl-lines', false, div);
        this.toggleClass('vert-lines', false, div);
        if (this.gridLines.toLowerCase() === 'horizontal' || this.gridLines.toLowerCase() === 'both') {
          this.toggleClass('hztl-lines', true, div);
        }
        if (this.gridLines.toLowerCase() === 'vertical' || this.gridLines.toLowerCase() === 'both') {
          this.toggleClass('vert-lines', true, div);
        }
      },
      _rowDblClick: function(e) {
        var col;
        var el = e.target;
        while (col === undefined) {
          if (el.nodeName.toLowerCase() === 'paper-datagrid-column') {
            col = el
          } else {
            el = el.parentNode;
          }
        }
        this.fire('row-dblclick', this.modelForElement(col).row);
      },
      _resizeCell: function(cell, width) {
        cell.style['min-width'] = width;
        cell.style['max-width'] = width;
        cell.style.flex = 'initial';
      },
      _resizeColumns: function(row, idx, delta) {
        var minColWidth = 48;
        var col = row.children[idx];
        var colWidth = document.defaultView.getComputedStyle(col, '').getPropertyValue('width');
        var newColWidth = (parseInt(colWidth, 10) - delta);
        var prev = row.children[idx - 1];
        var prevWidth = document.defaultView.getComputedStyle(prev, '').getPropertyValue('width');
        var newPrevWidth = (parseInt(prevWidth, 10) + delta);
        if (newColWidth > minColWidth && newPrevWidth > minColWidth) {
          this._resizeCell(col, newColWidth + 'px');
          this._resizeCell(prev, newPrevWidth + 'px');
        }
      },
      _columnResize: function(e) {
        var idx = e.detail.idx;
        var delta = e.detail.delta;
        this._resizeColumns(this.$.dgheader, idx, delta);
        var rows = Polymer.dom(this.$.dggrid).children;
        for (var i = 0; i < rows.length; i++) {
          this._resizeColumns(rows[i], idx, delta);
        }
      },
      _forwardParentProp: function(prop, value) {
        if (prop !== 'row') {
          var columns = this.querySelectorAll('paper-datagrid-column');
          for (var i = 0; i < columns.length; i++) {
            columns[i]._templateInstance[prop] = value;
          }
        }
      },
      _calcPaging: function(cp, pp, ttl, pcl) {
        var sIdx = ((cp - 1) * pp) + 1;
        var eIdx = (sIdx + pp) - 1;
        eIdx = (eIdx < ttl) ? eIdx : ttl;
        var args = [null,sIdx,eIdx,ttl];
        return pcl.replace(/{(\d+)}/g, function(match, number) {
          return typeof args[number] != 'undefined' ? args[number] : match;
        });
      },
      _optHandler: function(e) {
        this.$$('#optionsDropdown').close();
        this.fire('per-page-change', e.model.__data__.item)
      },
      _nextPage: function(e) {
        this.fire('next-page');
      },
      _prevPage: function(e) {
        this.fire('prev-page');
      },
      _showPageOpts: function(e) {
        var dd = this.$$('#optionsDropdown');
        if (dd.opened) { return; }
        dd.open();
      },
      _scrollHandler: function() {
        var h1 = (this.fixedHeaderFooter)
                ? this._scroller.scrollHeight
                : parseInt(document.defaultView.getComputedStyle(this.$.dgwrapper, '').getPropertyValue('height'), 10);
        var h2 = (this.fixedHeaderFooter)
                ? parseInt(document.defaultView.getComputedStyle(this.$.dgwrapper, '').getPropertyValue('height'), 10)
                : parseInt(document.defaultView.getComputedStyle(this._scroller, '').getPropertyValue('height'), 10);
        if (this._infiniteScrolling === false && this._scroller.scrollTop >= (h1 - h2)) {
          this._showInfiniteScrollIcon();
          this.fire('scroll-more');
        }
      },
      _showInfiniteScrollIcon: function() {
        if (this.fixedHeaderFooter) {
          this.$.dggrid.style.height = 'calc(100% - 112px)';
        }
        this._infiniteScrolling = true;
      },
      _hideInfiniteScrollIcon: function() {
        if (this.fixedHeaderFooter) {
          this.$.dggrid.style.height = 'calc(100% - 56px)';
        }
        this._infiniteScrolling = false;
      },
      /**
       * Clear any currently selected rows
       *
       * @method clearSelection
       */
      clearSelection: function() {
        if (Array.isArray(this.selectedItems)) {
          for (var i = this.selectedItems.length - 1; i >= 0; i--) {
            this.deselectItem(this.selectedItems[i]);
          }
        } else if (this.selectedItem) {
          this.deselectItem(this.selectedItem);
        }
      },
      /**
       * Select the row that corresponds to the provided item
       *
       * @method selectItem
       * @param {Object} Item to select
       */
      selectItem: function(item) {
        if (!this.multiSelection && this.selectedItem) {
          this.deselectItem(this.selectedItem);
        }
        var idx = this.items.indexOf(item);
        this.set('items.' + idx + '.selected', true);
        this.$.selector.select(item);
      },
      /**
       * Deselect the row that corresponds to the provided item
       *
       * @method deselectItem
       * @param {Object} Item to deselect
       */
      deselectItem: function(item) {
        var idx = this.items.indexOf(item);
        this.set('items.' + idx + '.selected', false);
        this.$.selector.deselect(item);
      },
      /**
       * Select or deselect the row that corresponds to the provided item, depending on its current selected state
       *
       * @method toggleSelectionForItem
       * @param {Object} Item to select/deselect
       */
      toggleSelectionForItem: function(item) {
        if (this.$.selector.isSelected(item)) {
          this.deselectItem(item);
        } else {
          this.selectItem(item);
        }
      },
      /**
       * Enable infinite scroll.  The DOM element that is handling scrolling for the grid should be provided as the
       * parameter. If the grid has a fixed size (such as with a fixed header), the scroller parameter is omitted.
       * This feature cannot be used in combination with the paging feature.
       *
       * @method enableInfiniteScroll
       * @param {?Element} The element that controls the scroll
       */
      enableInfiniteScroll: function(scroller) {
        if (scroller !== undefined) {
          this._scroller = scroller;
        } else {
          this._scroller = this.$.dggrid;
        }
        this._scrollListener = this._scrollHandler.bind(this);
        this._scroller.addEventListener('scroll', this._scrollListener);
      },
      /**
       * When using the infinite scroll feature, this method can be called during the on-scroll-more handler to indicate
       * there is no additional data available.
       *
       * @method infiniteScrollDone
       */
      infiniteScrollDone: function() {
        this._hideInfiniteScrollIcon();
      },
      detached: function() {
        if (this._scroller) {
          this._scroller.removeEventListener('scroll', this._scrollListener);
        }
      },
      ready: function() {
        this.$.dgwrapper.addEventListener('column-resize', this._columnResize.bind(this));
      }
    });
  })();
</script>

{'fraction_non_alphanumeric': 0.13318648725274246, 'fraction_numerical': 0.007654897171395547, 'mean_word_length': 1.9907383250717454, 'pattern_counts': {'":': 0, '<': 171, '<?xml version=': 0, '>': 171, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2534630', 'n_tokens_mistral': 13503, 'n_tokens_neox': 13113, 'n_words': 3035}