angularjs 封装的表格组件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>an-table</title>
  <script src="./angular.js"></script>
  <link href="./css/font-awesome.css" rel="stylesheet">
</head>

<style>
  * {
    padding: 0;
    margin: 0;
    font-size: 14px;
  }

  .main {
    width: 80%;
    margin: 50px auto;
  }


  .agTables {
    overflow: auto;
    position: relative;
  }

  .agTables table {
    border-collapse: collapse;
    width: 100%;
  }

  .agTables .line {
    display: none;
    width: 1px;
    height: 100%;
    background: #dedede;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 2;
  }

  .agTables table td,
  .agTables table th {
    border-collapse: collapse;
    border: 1px solid #dadee5;
    box-sizing: border-box;
  }

  .agTables table .cell {
    text-overflow: -o-ellipsis-lastline;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    padding: 6px;
    box-sizing: border-box;
  }

  .agTables .t-header table {
    width: auto;
  }

  .agTables .t-header table th {
    background-color: #f5f7fa;
    position: relative;
  }

  .agTables .t-header table .sort {
    position: absolute;
    right: 10px;
    top: 10%;
    width: 18px;
    height: 80%;
    display: block;
    color: #bbb;
  }

  .agTables .t-header table .sort i {
    display: block;
    cursor: pointer;
    line-height: 12px;
  }

  .agTables .t-header table .sort i:hover {
    color: #666;
  }

  .agTables .t-header input[type=checkbox] {
    float: left;
  }

  .agTables .t-body {
    overflow-x: auto;
    overflow-y: auto;
    height: 100%;
    box-sizing: border-box;
    margin-top: -1px;
    border-bottom: 1px solid #dadee5;
  }

  .agTables .hide {
    display: none;
  }
</style>

<body>


  <!-- angularjs -->
  <div ng-app="app" ng-controller="appCtrl" class="main">

    <ag-tables data="tableData" height='250' row-click="click_Fn(row)" row-dblclick="dbClick_Fn(row)"
      selection-change="checkFn(data)">

      <ag-tables-column width="30" type="selection"></ag-tables-column>

      <ag-tables-column prop="name" label="名字" width="180" sortable>
        <b ng-click="testFn(row)">名字_槽 : {{row.name}}</b>
      </ag-tables-column>

      <ag-tables-column prop="date" label="日期" sortable width="120"></ag-tables-column>

      <ag-tables-column prop="val" label="选值">
        <select ng-change="change(row,event)" ng-model="row.val">
          <option value="1">1</option>
          <option value="2">2</option>
        </select>
      </ag-tables-column>

      <ag-tables-column prop="address" label="地址" sortable></ag-tables-column>

      <ag-tables-column prop="address" label="地址" width="100">
        <button ng-click="del(row)">删除</button>
      </ag-tables-column>

    </ag-tables>

  </div>

  <script>
    /*
      需求: 
      01: 基本数据传递, 指令间数据嵌套
      02: 自定义指令内嵌入模板
      03: 自定义指令内,如何获取当前渲染的数据
      04: 全选select
      06: 表格单击+双击
    */
    const app = angular.module('app', []);
    app.controller('appCtrl', function ($scope, $element) {

      $scope.tableData = [{
        date: '2016-05-02',
        name: '1王小虎',
        address: '上海市普陀区金沙江路 1518 弄',
        val: '1'
      },
      {
        date: '2016-05-04',
        name: '3李连杰',
        address: '新加坡普陀区金沙江路 1517 弄',
        val: '1'
      }, {
        date: '2016-05-03',
        name: '2李连杰',
        address: '新加坡普陀区金沙江路 1517 弄',
        val: '2'
      }, {
        date: '2016-05-02',
        name: '1王小虎',
        address: '上海市普陀区金沙江路 1518 弄',
        val: '1'
      },
      {
        date: '2016-05-04',
        name: '3李连杰',
        address: '新加坡普陀区金沙江路 1517 弄',
        val: '1'
      }, {
        date: '2016-05-03',
        name: '2李连杰',
        address: '新加坡普陀区金沙江路 1517 弄',
        val: '2'
      }];

      $scope.del = function (row) {
        $scope.tableData = $scope.tableData.filter(val => val.name != row.name);
      };

      // 插槽: 绑定事件
      $scope.change = function (row) {
        console.log(row, 'change');
      };

      $scope.testFn = function (row) {
        console.log(row, 'testFn');
      };

      // 表格: 点击 + 双击
      $scope.click_Fn = function (row) {
        console.log('___click_Fn_');
      };

      $scope.dbClick_Fn = function (row) {
        console.log('__dbClick_Fn');
      };


      // 表格: 选中
      $scope.checkFn = function (data) {
        console.log(data, 'checkFn');
      };

    });


    // 和控制器,数据共享
    // agTables
    app.directive('agTables', function () {
      return {
        scope: {
          data: '=?',
          height: '=?',
          rowClick: '&',
          rowDblclick: '&',
          selectionChange: '&'
        },
        restrict: 'AE',
        replace: true,
        transclude: true,
        template: `
        <div class='agTables'>
          <div class="line"></div>
          <ag-tables-head></ag-tables-head>
          <ag-tables-body height="{{height}}" >
            <div ng-transclude></div>
          </ag-tables-body>
        </div>
        `,
        controller: function ($scope) {

          /***************************************
            筛选出事件($scope.$parent)
            传递anTableCell绑定私有自身
          *****************************************/
          $scope._$eventList = {};
          for (let [key, val] of Object.entries($scope.$parent)) {
            if (!key.includes('$') && typeof val == "function") {
              $scope._$eventList[key] = val;
            }
          };


          /***************************************
             全选(因Thead中repeat, 故采用对象方式)
          *****************************************/
          $scope.checkAll = {
            state: false
          };
          $scope.$watch('checkAll.state', (n, o) => {
            $scope.data = $scope.data.map(item => {
              item.check = n;
              return item;
            });
          });


          /***************************************
          检测数据变化,设置选中状态(全选|半选|取消)
          只检测数组中,每一项check的变换
          selectionChange事件返回值代理
          *****************************************/
          $scope.$watch(function ($scope) {
            return $scope.data.map(item => item.check);
          }, function (newVal) {

            const checkData = $scope.data.filter(val => val.check);
            $scope.selectionChange({ data: checkData });

            if ($scope._$inputAll) {
              $scope._$inputAll[0].indeterminate = false;
            }

            // 全选状态
            if (!newVal.includes(false)) {
              $scope.checkAll.state = true;
            };

            // 全不选状态
            if (!newVal.includes(true)) {
              $scope.checkAll.state = false;
            };

            // 半选状态
            if (newVal.includes(true) && newVal.includes(false)) {
              $scope._$inputAll && ($scope._$inputAll[0].indeterminate = true);
            }
          }, true);

        },

        link: function (scope, elem, attrs, ctrl) {

          /***************************************
             提示线,位置
          *****************************************/
          const line = elem[0].querySelector('.line');
          elem[0].onmousemove = function (event) {
            const pageX = event.x;
            const ofsetX = this.offsetLeft;
            line.style.left = (pageX - ofsetX) + 'px';
          };
        }
      };
    });

    // anTableHead
    app.directive('agTablesHead', function ($timeout) {
      return {
        restrict: 'AEC',
        replace: true,
        transclude: true,
        template: `
        <div class='t-header'>
          <table>
              <thead>
                <tr>
                  <th ng-repeat="val in _$headColumnArr">
                    <span class='sort' ng-if="val.sortable">
                      <i class="fa fa-caret-up" ng-click="_$sort({'type':'up',code:val.prop})"></i>
                      <i class="fa fa-caret-down" ng-click="_$sort({'type':'down',code:val.prop})"></i>
                    </span>
                    <div class='cell'>
                      <span ng-if="val.type!=='selection'">{{val.label}}</span>
                      <input ng-if="val.type==='selection'" type="checkBox" ng-model="checkAll.state">
                    </div>
                  </th>
                </tr>
              </thead>
          </table>
        </div>`,
        controller: function ($scope) {

          /*********************************************
          排序
          ***********************************************/
          $scope._$sort = function ({ type, code }) {
            type === 'up' ? sortUp(code) : sortDown(code)
          };

          function sortUp(code) {
            $scope.data.sort(function (a, b) {
              return a[code].localeCompare(b[code], 'zh')
            })
          };

          function sortDown(code) {
            $scope.data.sort(function (a, b) {
              return b[code].localeCompare(a[code], 'zh')
            })
          };

        },

        link: function link(scope, elem, attrs, ctrl) {
          const tHeader = elem.find('table');
          let bodyTdsData = null;
          let tbodyDom = null;

          $timeout(() => {
            scope._$inputAll = elem.find('input');
          });

          /*********************************************
          收集Tbody第一行td数据
          ***********************************************/
          scope.$on('tbodyDomEmit', function (event, dom) {
            tbodyDom = dom;
            setThead(moveTheadFn);
          });

          /*********************************************
          收集head信息
          ***********************************************/
          scope._$headColumnArr = [];
          scope.$on('collectHeadData', function (event, arg) {
            scope._$headColumnArr.push(arg);
          });

          /*********************************************
          设置Thead的宽度
          ***********************************************/
          function setThead(fn) {
            $timeout(() => {
              tHeader[0].style.width = 'auto';
              if (!tbodyDom[0].rows[0]) { // 数据被删除完了
                tHeader[0].style.width = '100%';
                return
              };
              const ths = Array.from(tHeader[0].rows[0].cells);
              bodyTdsData = Array.from(tbodyDom[0].rows[0].cells);
              bodyTdsData.forEach((cellDOM, index) => {
                var _w = cellDOM.offsetWidth - 1; // 边框1像素
                ths[index].querySelector('.cell').style.width = _w + 'px'
              });
              fn && fn(ths);
            });
          };


          window.onresize = function () {
            setThead();
          };

          // 元素被删除,重新绘制表格宽度
          scope.$watchCollection('data', function (n, o) {
            if (n.length !== o.length) {
              setThead();
            };
          });

          /*********************************************
          鼠标拖动
          ***********************************************/
          function moveTheadFn(theadThs) {
            // 被拖动th
            let crruentTD;
            let crruentTDindex = 0;
            let moveDistance = 0;
            const line = elem[0].previousElementSibling;
            const tableContainerMarginLeft = elem.parent().offsetLeft;


            theadThs.forEach((cellDOM, index) => {

              cellDOM.onmousedown = function (event) {
                //记录单元格(选中最右边5像素内触发)
                // 1: 记录按下时的宽度
                // 2: 记录按下时的坐标(相对页面)
                // 3: line显示,设置位置
                var crruentTDWidth = this.offsetWidth;
                var offsetX = event.offsetX;

                if (crruentTDWidth - offsetX < 8) {

                  crruentTD = this;
                  crruentTDindex = index;

                  crruentTD.mouseDown = true;
                  crruentTD.oldX = event.x;
                  crruentTD.oldWidth = crruentTDWidth;

                  line.style.display = "block";
                  line.style.left = event.x - tableContainerMarginLeft + 1 + 'px';
                }
              };

              //移动
              cellDOM.onmousemove = function (event) {

                //更改鼠标样式
                if (event.offsetX > this.offsetWidth - 5) {
                  this.style.cursor = 'col-resize';
                  this.style.userSelect = "none"  // 禁止选取
                } else {
                  this.style.cursor = 'default';
                };

                if (!crruentTD || !crruentTD.mouseDown) {
                  return;
                };

                //调整宽度
                // 01: 移动的距离
                var moveX = event.x - crruentTD.oldX;
                crruentTD.style.cursor = 'col-resize';

                // 02:移动后的宽度
                moveDistance = crruentTD.oldWidth + moveX;
              };

            });

            // 抬起
            document.onmouseup = function () {

              line.style.display = "none";

              if (!crruentTD || !crruentTD.mouseDown) {
                return;
              };
              crruentTD.mouseDown = false;
              crruentTD.style.cursor = 'default';
              crruentTD.style.userSelect = "auto";

              // 01: 赋值宽度
              bodyTdsData[crruentTDindex].width = moveDistance;
              setThead();
            };

          }
        }

      };
    })


    // anTableBody
    app.directive('agTablesBody', function ($timeout) {
      return {
        restrict: 'AE',
        replace: true,
        transclude: true,
        template: `  
        <div class='t-body'>
          <div class="hide" ng-transclude></div>
          <table>
            <tbody>
              <tr ng-repeat="(index,row) in data" ng-click="_$proxyRowClick(row)" ng-dblclick="rowDblclick({row})">
                <td ng-repeat="code in _$headColumnArr" width="{{ index === 0 ? code.width: null}}">
                  <ag-tables-cell row="row" code="code" event-list="_$eventList">
                    {{ code.template ? null : row[code.prop] }}
                    <input ng-if="code.type==='selection'" type="checkBox" ng-model="row.check">
                  </ag-tables-cell>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
        `,
        controller: function ($scope) { },
        link: function (scope, elem, attrs, ctrl) {

          $timeout(function () {

            // 过滤 :点击表单元素触发事件,过滤点击的元素(绑定事件的)
            scope._$proxyRowClick = function (row) {
              const attributes = Array.from(event.target.attributes).map(e => e.name);
              const flag = attributes.includes('ng-change') || attributes.includes('ng-click') || attributes.includes('ng-dblclick') || attributes.includes('ng-model');
              !flag && scope.rowClick({ row });
            };


            /*********************************************
            删除 .hide
            ***********************************************/
            let hideDOM = elem.find('div')[0];
            hideDOM.remove();

            /*********************************************
            设置高度
            ***********************************************/
            elem[0].style.maxHeight = attrs.height + 'px';

            /*********************************************
            收集 : Tbody中第一行中tr的td宽为基准,赋值给Thead中th 
            ***********************************************/
            const tBody = elem.find('table');
            // const tds = Array.from(tBody[0].rows[0].cells);
            scope.$emit('tbodyDomEmit', tBody);

          }, 100);

        }
      };
    });


    // an-table-columns 继承控制器数据
    app.directive('agTablesColumn', function () {
      return {
        restrict: 'AE',
        replace: true,
        transclude: true,
        template: `<div class='cell' ng-transclude></div>`,
        controller: function ($scope) { },
        link: function (scope, elem, attrs, ctrl) {

          /*********************************************
          收集表头信息
          ***********************************************/
          scope.$emit('collectHeadData', {
            label: attrs.label,
            prop: attrs.prop,
            type: attrs.type,
            width: attrs.width,
            sortable: attrs.sortable !== undefined,
            template: elem.html().trim()
          });
        }
      };
    });

    // an-table-cell
    app.directive('agTablesCell', function ($compile) {
      return {
        scope: {
          row: '=?',
          code: '=?',
          width: "=?",
          eventList: '=?'
        },
        restrict: 'AE',
        replace: true,
        transclude: true,
        template: `<div class='cell' ng-transclude></div>`,
        controller: function ($scope) {

          // 事件: eventList代理到自身
          for (let [key, val] of Object.entries($scope.eventList)) {
            $scope[key] = function () {
              val($scope.row)
            };
          };

        },
        link: function (scope, elem, attrs, ctrl) {

          /*********************************************
          插槽编译
          ***********************************************/
          if (scope.code.template) {
            let dom = $compile(scope.code.template)(scope);
            elem.empty().append(dom);
          };
        }
      };
    });


  </script>
</body>

</html>

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值