Tehnologii Web/2022-2023/Laborator 6
In this laboratory, we will review the presented front-end techniques in a data table page.
- You will start from a partial implementation - or you can start your own.
- Steps 1-5 are provided to you, the rest are lab assignments.
Requirements
edit- Fetch an existing API endpoint (e.g. GET https://dummyjson.com/users) using Typescript and fetch
- Add the table rows dynamically.
- Add pagination support.
- Sort the entries by column: ascending or descending.
- Filter rows by search.
- Bonus: let the user download the data using .CSV export.
Laboratory Steps
editStep 1
editAdd the HTML markup for your page. It should look like the one below.
The table headers will be statically defined, the rest of the content will be loaded dynamically.
<div id="app">
<table id="table">
<thead>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Phone Number</td>
<td>Email</td>
<td>Picture</td>
</tr>
</thead>
<tbody id="table-body">
<!-- Table rows will load here. -->
</tbody>
</table>
<div id="table-pagination">
<!-- Pagination buttons will load here. -->
</div>
</div>
Step 2
editAdd an interface for your data. In our example (GET https://dummyjson.com/users ) we will expose the following fields.
interface Person {
firstName: string;
lastName: string;
email: string;
phone: string;
image: string;
}
Step 3
editAdd a Typescript class controller. We will define a load method in the next step.
class DataTableController {
sourceURL: string;
itemsPerPage: number;
constructor(sourceURL: string, itemsPerPage: number) {
this.sourceURL = sourceURL;
this.itemsPerPage = itemsPerPage;
}
}
const controller = new DataTableController('https://dummyjson.com/users', 10);
controller.load();
Step 4
editOur load method will use the fetch call to load the JSON data from an external source.
Please note, we will use a private field _data to store our data, but our table view will be defined in the getter function tableData which is public.
The spread ... operator will make a copy since sort is a mutable call.
load = () => {
fetch(this.sourceURL)
.then(data => data.json())
.then(json => {
this._data = json.users;
// display the first page on load
this.goToPage(0);
}
);
}
get tableData(): Person[] {
let data = [...this._data]
.filter(x => true);
// TODO: add your own filter
// TODO: add your own sorting function
data.sort();
return data;
}
Step 5
editMake sure your class and helper methods are defined as follows.
interface Person {
firstName: string;
lastName: string;
email: string;
phone: string;
image: string;
}
class DataTableController {
// TODO: Use these private fields.
private _search: string = '';
private _sortField: 'firstName';
private _ascending: true;
private _currentPageIdx: number = 0;
public sourceURL: string;
private _data: Person[];
constructor(sourceURL: string, itemsPerPage: number) {
this.sourceURL = sourceURL;
this.itemsPerPage = itemsPerPage;
}
load = () => {
fetch(this.sourceURL)
.then(data => data.json())
.then(json => {
this._data = json.users;
this.goToPage(0);
}
);
}
get tableData(): Person[] {
let data = [...this._data]
.filter(x => true); // TODO: add your own filter
// TODO: add your own sorting function
data.sort();
return data;
}
goToPage = (pageIndex: number) => {
this.clear();
for (let i = 0; i < this.itemsPerPage; i++) {
this.addRow(this.tableData[pageIndex * this.itemsPerPage + i]);
}
this.addPaginationButtons();
}
clear = () => {
document.getElementById('table-pagination')!.innerHTML = '';
document.getElementById('table-body')!.innerHTML = '';
}
numberOfPages = (length: number) => (
length / this.itemsPerPage +
(length % this.itemsPerPage > 0 ? 1 : 0);
)
addPaginationButtons = () => {
for (let i = 1; i <= this.numberOfPages(this.tableData.length); i++) {
const element = document.createElement('span');
element.innerHTML = `${i}`;
element.onclick = () => {
this.goToPage(i - 1);
}
document.getElementById('table-pagination')!.appendChild(element);
}
}
addRow = (person: Person) => {
const element = document.createElement("tr");
element.innerHTML = `
<td>${person.firstName}</td>
<td>${person.lastName}</td>
<td>${person.phone}</td>
<td>${person.email}</td>
<td>${person.image}</td>`;
document.getElementById('table-body')!.appendChild(element);
}
}
const controller = new DataTableController('https://dummyjson.com/users', 10);
controller.load();
Step 6
editYour Task
- Set the class name for the active pagination button accordingly.
- You can do that in the method addPaginationButtons by setting element.className = isActivePage ? 'A' : 'B'
- You also need to set the private this._currentPageIdx in your goToPage method
- Add a CSS transition, you can use also the one below which is animating the width.
.pagination-btn:after {
width: 0px;
transition: width ease-in-out 0.5s;
content: "";
}
.pagination-btn.active:after {
content: "";
width: 80px;
height: 4px;
background: #5FA4F9;
position: absolute;
left: 0;
right: 0;
bottom: -40px;
margin: 0 auto;
}
Step 7
editYour Task
- Set in the constructor a query selector for each column header.
- Add a click event listener, whenever you click a column, the private field _sortField would be set accordingly.
- When you click the second time, the _ascending field would toggle from true to false or from false to true.
Step 8
editYour Task
- Update the tableData getter to implement search filtering and sorting.
Step 9
editBonus Task - add an Export button that will prompt the user to download the data in .csv format.
You can do it from scratch using blobs or using external Javascript libraries.