Jump to content

Tehnologii Web/2022-2023/Laborator 6

From Wikiversity

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 | edit source]
  • 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.
Lab assignment - data table

Laboratory Steps

[edit | edit source]

Step 1

[edit | edit source]

Add 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

[edit | edit source]

Add 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

[edit | edit source]

Add 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

[edit | edit source]

Our 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

[edit | edit source]

Make 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

[edit | edit source]

Your 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

[edit | edit source]

Your 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

[edit | edit source]

Your Task

  • Update the tableData getter to implement search filtering and sorting.

Step 9

[edit | edit source]

Bonus 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.