JSFiddle - React, Tailwind, and code Playground

by ajai jothi

HTML

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<div ng-app="app" ng-controller="TestCtrl">
  <grid data="data" config="config"></grid>

  <script type="text/ng-template" id="grid.html">
    <div class="grid">
      <div class="grid-filter">
        <div class="pull-left" ng-transclude></div>
        <input type="text" ng-model="search" placeholder="search">
      </div>
      <table class="table">
        <thead>
          <tr>
            <th ng-repeat="col in columnDefn">
              <span ng-click="config.col=col.field;config.sortOrder=!config.sortOrder"><span>{{col.headerName}}</span>
              <i class="fa fa-sort" ng-class="{'fa-sort-asc':!config.sortOrder && config.col===col.field, 'fa-sort-desc':config.sortOrder && config.col===col.field}"></i>
              </span>
              <div class="filter" ng-click="$event.stopPropagation();" ng-if="config.showFilter && col.showFilter!==false">
                <i class="fa fa-filter" ng-click="toggleFilter(col)" ng-class="{active:getSelectedFilterValues(col).length}"></i>
                <ul class="filter-dropdown" ng-if="col._filterOpen">
                  <li ng-repeat="(k,v) in col.filterValues">
                    <label title="{{k}}">
                      <input type="checkbox" ng-model="col[k]" ng-change="updateFilter(col, k)"> {{k}}
                    </label>
                  </li>
                </ul>
              </div>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="row in _data | filter:search | orderBy:config.col:config.sortOrder">
            <td ng-repeat="col in columnDefn">
              <div ng-if="col.rowTemplate" bind-html-compile="col.rowTemplate"></div>
              <div ng-if="col.rowTemplateProvider" bind-html-compile="col.rowTemplateProvider(row, col)"></div>
              <div ng-if="!col.rowTemplateProvider && !col.rowTemplate">
                {{row[col.field]}}
...

SCSS

.grid {
  table {
    width: 100%;
    border: 1px solid #f1f1f1;
    background: white;
    padding: 0;
    margin: 0;
    border-spacing: 0px;
    border-collapse: collapse;
    color: #333;
    font-family: arial;
    font-size: 12px;
    tbody tr {
      td {
        border: 1px solid #f1f1f1;
        border-bottom: 1px solid #eee;
        margin: 0;
        padding: 5px;
      }
      &:nth-child(2n) {
        background: #f9f9f9;
      }
    }
  }
  .grid-filter {
    padding: 5px 0;
    text-align: right;
    &:after {
      content: "";
      display: block;
      clear: both;
    }
  }
  thead th {
    background: #eee;
    border: 1px solid #f1f1f1;
    text-align: left;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    white-space: nowrap;
    cursor: pointer;
    position: relative;
    padding: 5px 40px 5px 10px;
    .filter {
      position: absolute;
      cursor: pointer;
      right: 5px;
      top: 25%;
      .active {
        color: #2196F3;
      }
    }
    .filter-dropdown {
      position: absolute;
      max-height: 250px;
      min-width: 80px;
      max-width: 200px;
      overflow-x: hidden;
      ;
      overflow-y: auto;
      background: #f9f9f9;
      border: 1px solid #eee;
      top: 20px;
      right: -10px;
      margin: 0;
      padding: 0;
      list-style: none;
      box-shadow: #eee 0 10px 20px;
    }
    .filter-dropdown li {
      border-bottom: 1px solid #eee;
      font-weight: lighter;
    }
    .filter-dropdown li label {
      display: block;
      padding: 3px;
      width: 100%;
      -ms-text-overflow: ellipsis;
      text-overflow: ellipsis;
      overflow: hidden;
    }
  }
}

JavaScript

(function() {
  'use strict';

  function GridDirective() {
    return {
      restrict: 'EA',
      replace: true,
      transclude: true,
      templateUrl: 'grid.html',
      scope: {
        data: '=',
        config: '=?'
      },
      link: function(scope) {
        scope._data = scope.data;
        scope.config = scope.config || {};
        scope.appScope = scope.appScope || scope.$parent;
        scope.columnDefn = scope.config.columnDefn || getDefaultColumnDefn(scope.data[0] || {});
        console.log(scope.columnDefn);
        scope.$watch('data', function(nv, ov) {
          if (nv !== ov) {
            scope.columnDefn = scope.config.columnDefn || getDefaultColumnDefn(scope.data[0]);
            scope.updatFilterValues();
          }
        });

        scope.updatFilterValues = function() {
          scope.columnDefn.forEach(function(c) {
            c.filterValues = getFilterValues(scope.data, c.field);
          });
        };
        scope.updatFilterValues();
        
        scope.updateFilter = function(col, key) {
          col.filterValues[key] = !col.filterValues[key];
          scope.populateGridData();
        };

        scope.getSelectedFilterValues = getSelectedFilterValues;

        scope.populateGridData = function() {
          scope._data = scope.data.filter(function(r) {
            return scope.columnDefn.every(function(c) {
              var selectedFilterValue = (getSelectedFilterValues(c));
              return selectedFilterValue.length === 0 || selectedFilterValue.indexOf(r[c.field]) >= 0;
            });
          });
        };

        scope.hideFilters = function(col) {
          scope.columnDefn.forEach(function(_col) {
            if (col !== _col) {
              _col._filterOpen = false;
            }
          });
        };

        scope.toggleFilter = function(col) {
          col._filterOpen = !col._filterOpen;
          scope.hideFilters(col);
        };

        angular.element(document).on('click',...