<!DOCTYPE html>
<html ng-app="materialTableApp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Material Table - AngularJS</title>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.2.1/angular-material.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<style>
body {
font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
background: #f5f5f5;
margin: 0;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.header h1 {
margin: 0 0 8px 0;
font-size: 2rem;
font-weight: 500;
}
.header p {
margin: 0;
opacity: 0.9;
font-size: 1rem;
}
.table-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.table-toolbar {
padding: 20px 24px;
background: #fafafa;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 15px;
}
.toolbar-title {
font-size: 1.25rem;
font-weight: 500;
color: #424242;
display: flex;
align-items: center;
gap: 10px;
}
.toolbar-actions {
display: flex;
gap: 10px;
align-items: center;
}
.search-box {
position: relative;
display: flex;
align-items: center;
}
.search-box input {
padding: 10px 15px 10px 40px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
width: 280px;
transition: all 0.3s;
}
.search-box input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-box .material-icons {
position: absolute;
left: 12px;
color: #999;
font-size: 20px;
}
.filter-select {
padding: 10px 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
background: white;
transition: all 0.3s;
}
.filter-select:focus {
outline: none;
border-color: #667eea;
}
md-table-container {
max-height: 600px;
}
table.md-table {
width: 100%;
border-collapse: collapse;
}
table.md-table thead tr {
background: #f5f5f5;
}
table.md-table th {
padding: 16px 24px;
text-align: left;
font-weight: 600;
color: #616161;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.5px;
border-bottom: 2px solid #e0e0e0;
cursor: pointer;
user-select: none;
white-space: nowrap;
position: relative;
}
table.md-table th:hover {
background: #eeeeee;
}
table.md-table th.sortable::after {
content: '↕';
margin-left: 8px;
color: #999;
font-size: 14px;
}
table.md-table th.sort-asc::after {
content: '↑';
color: #667eea;
}
table.md-table th.sort-desc::after {
content: '↓';
color: #667eea;
}
table.md-table td {
padding: 16px 24px;
border-bottom: 1px solid #e0e0e0;
font-size: 14px;
color: #424242;
}
table.md-table tbody tr {
transition: background 0.2s;
}
table.md-table tbody tr:hover {
background: #f5f5f5;
}
.status-badge {
display: inline-block;
padding: 6px 14px;
border-radius: 16px;
font-size: 12px;
font-weight: 600;
text-transform: capitalize;
}
.status-active {
background: #e8f5e9;
color: #2e7d32;
}
.status-inactive {
background: #ffebee;
color: #c62828;
}
.status-pending {
background: #fff3e0;
color: #ef6c00;
}
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: inline-flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 14px;
margin-right: 12px;
vertical-align: middle;
}
.action-btn {
background: none;
border: none;
cursor: pointer;
padding: 8px;
border-radius: 50%;
color: #666;
transition: all 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
}
.action-btn:hover {
background: #f5f5f5;
color: #667eea;
}
.action-btn .material-icons {
font-size: 20px;
}
.pagination {
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #e0e0e0;
background: #fafafa;
}
.pagination-info {
color: #666;
font-size: 14px;
}
.pagination-controls {
display: flex;
gap: 8px;
align-items: center;
}
.pagination-btn {
background: white;
border: 1px solid #e0e0e0;
padding: 8px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
justify-content: center;
}
.pagination-btn:hover:not(:disabled) {
background: #667eea;
color: white;
border-color: #667eea;
}
.pagination-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.pagination-btn .material-icons {
font-size: 20px;
}
.page-size-select {
padding: 6px 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
background: white;
margin: 0 8px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #999;
}
.empty-state .material-icons {
font-size: 64px;
margin-bottom: 16px;
opacity: 0.3;
}
.empty-state h3 {
margin: 0 0 8px 0;
color: #666;
}
.empty-state p {
margin: 0;
font-size: 14px;
}
@media (max-width: 768px) {
.table-toolbar {
flex-direction: column;
align-items: stretch;
}
.toolbar-actions {
flex-direction: column;
width: 100%;
}
.search-box input {
width: 100%;
}
table.md-table th,
table.md-table td {
padding: 12px 16px;
font-size: 13px;
}
.avatar {
width: 32px;
height: 32px;
font-size: 12px;
}
}
</style>
</head>
<body ng-controller="TableController">
<div class="container">
<div class="header">
<h1>📊 Material Data Table</h1>
<p>Advanced AngularJS table with sorting, filtering, and pagination</p>
</div>
<div class="table-card">
<!-- Toolbar -->
<div class="table-toolbar">
<div class="toolbar-title">
<span class="material-icons">people</span>
<span>Employee Directory</span>
</div>
<div class="toolbar-actions">
<select class="filter-select" ng-model="statusFilter" ng-change="filterData()">
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
<div class="search-box">
<span class="material-icons">search</span>
<input type="text"
placeholder="Search employees..."
ng-model="searchQuery"
ng-change="filterData()">
</div>
</div>
</div>
<!-- Table -->
<md-table-container>
<table class="md-table" ng-if="displayedData.length > 0">
<thead>
<tr>
<th class="sortable" ng-click="sortBy('name')"
ng-class="{'sort-asc': sortColumn === 'name' && sortDirection === 'asc',
'sort-desc': sortColumn === 'name' && sortDirection === 'desc'}">
Name
</th>
<th class="sortable" ng-click="sortBy('position')"
ng-class="{'sort-asc': sortColumn === 'position' && sortDirection === 'asc',
'sort-desc': sortColumn === 'position' && sortDirection === 'desc'}">
Position
</th>
<th class="sortable" ng-click="sortBy('department')"
ng-class="{'sort-asc': sortColumn === 'department' && sortDirection === 'asc',
'sort-desc': sortColumn === 'department' && sortDirection === 'desc'}">
Department
</th>
<th class="sortable" ng-click="sortBy('email')"
ng-class="{'sort-asc': sortColumn === 'email' && sortDirection === 'asc',
'sort-desc': sortColumn === 'email' && sortDirection === 'desc'}">
Email
</th>
<th class="sortable" ng-click="sortBy('salary')"
ng-class="{'sort-asc': sortColumn === 'salary' && sortDirection === 'asc',
'sort-desc': sortColumn === 'salary' && sortDirection === 'desc'}">
Salary
</th>
<th class="sortable" ng-click="sortBy('status')"
ng-class="{'sort-asc': sortColumn === 'status' && sortDirection === 'asc',
'sort-desc': sortColumn === 'status' && sortDirection === 'desc'}">
Status
</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="employee in displayedData">
<td>
<span class="avatar">{{employee.name.charAt(0)}}</span>
<strong>{{employee.name}}</strong>
</td>
<td>{{employee.position}}</td>
<td>{{employee.department}}</td>
<td>{{employee.email}}</td>
<td>${{employee.salary | number:0}}</td>
<td>
<span class="status-badge status-{{employee.status}}">
{{employee.status}}
</span>
</td>
<td>
<button class="action-btn" title="Edit">
<span class="material-icons">edit</span>
</button>
<button class="action-btn" title="Delete">
<span class="material-icons">delete</span>
</button>
<button class="action-btn" title="More">
<span class="material-icons">more_vert</span>
</button>
</td>
</tr>
</tbody>
</table>
<!-- Empty State -->
<div class="empty-state" ng-if="displayedData.length === 0">
<span class="material-icons">search_off</span>
<h3>No results found</h3>
<p>Try adjusting your search or filter to find what you're looking for</p>
</div>
</md-table-container>
<!-- Pagination -->
<div class="pagination" ng-if="filteredData.length > 0">
<div class="pagination-info">
Showing {{(currentPage - 1) * pageSize + 1}} to {{getEndIndex()}} of {{filteredData.length}} entries
</div>
<div class="pagination-controls">
<button class="pagination-btn"
ng-click="previousPage()"
ng-disabled="currentPage === 1">
<span class="material-icons">chevron_left</span>
</button>
<span>Page {{currentPage}} of {{getTotalPages()}}</span>
<select class="page-size-select"
ng-model="pageSize"
ng-change="pageSizeChanged()">
<option value="5">5 / page</option>
<option value="10">10 / page</option>
<option value="25">25 / page</option>
<option value="50">50 / page</option>
</select>
<button class="pagination-btn"
ng-click="nextPage()"
ng-disabled="currentPage === getTotalPages()">
<span class="material-icons">chevron_right</span>
</button>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular-aria.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.2.1/angular-material.min.js"></script>
<script>
angular.module('materialTableApp', ['ngMaterial'])
.controller('TableController', ['$scope', function($scope) {
// Sample data
$scope.employees = [
{ id: 1, name: 'John Smith', position: 'Software Engineer', department: 'Engineering', email: 'john.smith@company.com', salary: 85000, status: 'active' },
{ id: 2, name: 'Sarah Johnson', position: 'Product Manager', department: 'Product', email: 'sarah.j@company.com', salary: 95000, status: 'active' },
{ id: 3, name: 'Michael Chen', position: 'UX Designer', department: 'Design', email: 'michael.chen@company.com', salary: 78000, status: 'active' },
{ id: 4, name: 'Emily Davis', position: 'Data Analyst', department: 'Analytics', email: 'emily.davis@company.com', salary: 72000, status: 'pending' },
{ id: 5, name: 'Robert Wilson', position: 'DevOps Engineer', department: 'Engineering', email: 'robert.w@company.com', salary: 88000, status: 'active' },
{ id: 6, name: 'Jessica Brown', position: 'Marketing Manager', department: 'Marketing', email: 'jessica.brown@company.com', salary: 82000, status: 'inactive' },
{ id: 7, name: 'David Martinez', position: 'Senior Developer', department: 'Engineering', email: 'david.m@company.com', salary: 98000, status: 'active' },
{ id: 8, name: 'Lisa Anderson', position: 'HR Manager', department: 'Human Resources', email: 'lisa.anderson@company.com', salary: 75000, status: 'active' },
{ id: 9, name: 'James Taylor', position: 'Sales Executive', department: 'Sales', email: 'james.taylor@company.com', salary: 70000, status: 'active' },
{ id: 10, name: 'Maria Garcia', position: 'Content Writer', department: 'Marketing', email: 'maria.garcia@company.com', salary: 65000, status: 'pending' },
{ id: 11, name: 'Chris Lee', position: 'QA Engineer', department: 'Engineering', email: 'chris.lee@company.com', salary: 76000, status: 'active' },
{ id: 12, name: 'Amanda White', position: 'Business Analyst', department: 'Analytics', email: 'amanda.white@company.com', salary: 79000, status: 'inactive' },
{ id: 13, name: 'Daniel Kim', position: 'UI Designer', department: 'Design', email: 'daniel.kim@company.com', salary: 74000, status: 'active' },
{ id: 14, name: 'Rachel Green', position: 'Project Manager', department: 'Product', email: 'rachel.green@company.com', salary: 92000, status: 'active' },
{ id: 15, name: 'Tom Harris', position: 'Security Analyst', department: 'Engineering', email: 'tom.harris@company.com', salary: 86000, status: 'pending' }
];
// Pagination
$scope.currentPage = 1;
$scope.pageSize = 10;
// Sorting
$scope.sortColumn = 'name';
$scope.sortDirection = 'asc';
// Filtering
$scope.searchQuery = '';
$scope.statusFilter = '';
$scope.filteredData = angular.copy($scope.employees);
$scope.displayedData = [];
// Sort function
$scope.sortBy = function(column) {
if ($scope.sortColumn === column) {
$scope.sortDirection = $scope.sortDirection === 'asc' ? 'desc' : 'asc';
} else {
$scope.sortColumn = column;
$scope.sortDirection = 'asc';
}
$scope.applySort();
$scope.updateDisplay();
};
$scope.applySort = function() {
$scope.filteredData.sort(function(a, b) {
var valueA = a[$scope.sortColumn];
var valueB = b[$scope.sortColumn];
if (typeof valueA === 'string') {
valueA = valueA.toLowerCase();
valueB = valueB.toLowerCase();
}
if (valueA < valueB) return $scope.sortDirection === 'asc' ? -1 : 1;
if (valueA > valueB) return $scope.sortDirection === 'asc' ? 1 : -1;
return 0;
});
};
// Filter function
$scope.filterData = function() {
$scope.filteredData = $scope.employees.filter(function(employee) {
var matchesSearch = true;
var matchesStatus = true;
if ($scope.searchQuery) {
var query = $scope.searchQuery.toLowerCase();
matchesSearch = employee.name.toLowerCase().includes(query) ||
employee.position.toLowerCase().includes(query) ||
employee.department.toLowerCase().includes(query) ||
employee.email.toLowerCase().includes(query);
}
if ($scope.statusFilter) {
matchesStatus = employee.status === $scope.statusFilter;
}
return matchesSearch && matchesStatus;
});
$scope.currentPage = 1;
$scope.applySort();
$scope.updateDisplay();
};
// Pagination functions
$scope.updateDisplay = function() {
var start = ($scope.currentPage - 1) * $scope.pageSize;
var end = start + parseInt($scope.pageSize);
$scope.displayedData = $scope.filteredData.slice(start, end);
};
$scope.nextPage = function() {
if ($scope.currentPage < $scope.getTotalPages()) {
$scope.currentPage++;
$scope.updateDisplay();
}
};
$scope.previousPage = function() {
if ($scope.currentPage > 1) {
$scope.currentPage--;
$scope.updateDisplay();
}
};
$scope.getTotalPages = function() {
return Math.ceil($scope.filteredData.length / $scope.pageSize);
};
$scope.getEndIndex = function() {
var end = $scope.currentPage * $scope.pageSize;
return Math.min(end, $scope.filteredData.length);
};
$scope.pageSizeChanged = function() {
$scope.currentPage = 1;
$scope.updateDisplay();
};
// Initialize
$scope.applySort();
$scope.updateDisplay();
}]);
</script>
</body>
</html>
No comments yet. Be the first!