<!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>
angularjs 封装的表格组件
于 2022-02-11 14:06:03 首次发布