-
Notifications
You must be signed in to change notification settings - Fork 35
Onboarding Project #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
9de206d
feddf76
20ab5f0
161dba8
4db8da4
4379d03
1574a99
d7cc5a7
68d6fdc
5590608
92c2304
0449b6f
18305ee
07b2e55
46fa399
120a175
0250464
e79a514
8df21a6
b2c0a70
b16c29c
75c52e0
5c1408b
ee7b2ca
07261b4
5a64f0b
b92adc3
e06829b
b49d5a8
28f1ff8
2a2a633
f70d831
1a2ee0d
784d208
7b77515
d7367c0
d15a355
0b1bb06
6318826
6b095c5
3af4520
9722222
5a6339b
c81a308
60d758a
79b2f6a
2d11963
3f81c01
7a1f771
20a4790
105295d
42bca52
57f6a66
f0e6652
e31e167
432722c
24af382
9311f71
73c6b0b
a0b243f
2d5786b
c73e797
6b5b5b4
9214692
7c5ee28
603fc3d
04cb27a
a43ea08
7bf7ba5
c5174fc
4eb4f82
5528fd6
7c7ea1a
1df95c0
9518331
8093c8a
dc20c08
6fd8954
5ffd990
8e9d50a
f2b7bf3
138f01c
e94ac2e
cd397ac
e2d638d
27e027a
126a1f0
03274eb
194d261
4d61536
350c90b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,9 @@ | ||
| /node_modules | ||
| app.js | ||
| app.js.map | ||
| ApiData.js | ||
| ApiData.js.map | ||
| GridTemplate.js | ||
| GridTemplate.js.map | ||
| Interfaces.js | ||
| Interfaces.js.map |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| /** Debounce utility function to limit function execution frequency */ | ||
| function debounce<F extends (...args: any) => any>(func: F, waitFor: number) { | ||
| let timeout: number; | ||
|
|
||
| return (...args: Parameters<F>): Promise<ReturnType<F>> => { | ||
| clearTimeout(timeout); | ||
|
|
||
| return new Promise((resolve) => { | ||
| timeout = setTimeout(() => { | ||
| resolve(func(...args)); | ||
| }, waitFor); | ||
| }); | ||
| }; | ||
| } | ||
|
|
||
| /** Constants for grid calculation | ||
| * GRID_RATIO represents the ratio of the grid's height to the window's height. | ||
| */ | ||
| const GRID_RATIO = 9 / 20; | ||
| const ROW_HEIGHT = 16; | ||
|
|
||
| /** manage data and settings on the grid */ | ||
| class ApiData { | ||
|
|
||
| pageSize: number; | ||
| currentPage: number = 1; | ||
| data: GridData[] = []; | ||
| totalItems: number = 0; | ||
| columnNames: ColumnName[] = []; | ||
| maxGridHeight: number = 0; | ||
| firstVal: number = 0; | ||
| lastVal: number = -1; | ||
| maxRange: number; | ||
|
|
||
| constructor(pageSize: number) { | ||
| this.pageSize = pageSize; | ||
| this.maxRange = 0; | ||
| } | ||
|
|
||
| /** Initialize method to set up the grid */ | ||
| initialize(): Promise<void> { | ||
| this.adjustGridHeight(); | ||
| return this.recordCount() | ||
| .then(() => this.fetchColumns()) | ||
| .then(() => this.fetchAndDisplayRecords()) | ||
| .then(() => this.setupControls()); | ||
| } | ||
|
|
||
| /** Fetch total record count from the server,fetches data from an API and populates class properties */ | ||
| recordCount(): Promise<void> { | ||
| return this.fetchNumData('http://localhost:2050/recordCount') | ||
| .then((response: number) => { | ||
| this.totalItems = response; | ||
| this.maxRange = this.totalItems - 1; | ||
| }) | ||
| .catch(error => { | ||
| console.error('Failed to fetch record count:', error); | ||
| throw error; | ||
| }); | ||
| } | ||
|
|
||
| /** Use the fetchData() func to make an HTTP request to the API endpoint and process the data */ | ||
| fetchColumns(): Promise<void> { | ||
| return this.fetchStrData('http://localhost:2050/columns') | ||
| .then((response: string) => { | ||
| const res = JSON.parse(response); | ||
| this.columnNames = res.map((columnName: string) => ({ name: columnName })); | ||
| // Initialize the 'data' property as an empty array of GridData objects | ||
| this.data = new Array<GridData>(this.columnNames.length); | ||
| }) | ||
| .catch(error => { | ||
| console.error('Failed to fetch columns:' + error); | ||
| throw ('Failed to fetch columns:' + error); | ||
| }); | ||
| } | ||
|
|
||
| /** Get records from API for fetch and search functionality */ | ||
| fetchAndProcessRecords(from: number, to: number): Promise<GridData[]> { | ||
| $('#spinner').show(); | ||
| $('#grid').hide(); | ||
|
|
||
| return this.fetchStrData(`http://localhost:2050/records?from=${from}&to=${to}`) | ||
| .then((response: string) => { | ||
| const res = JSON.parse(response); | ||
| const processedData = res.map((record: string[]) => { | ||
| const obj: GridData = {}; | ||
| for (let j = 0; j < this.columnNames.length && j < record.length; j++) { | ||
| obj[this.columnNames[j].name] = record[j]; | ||
| } | ||
| return obj; | ||
| }); | ||
| $('#spinner').hide(); | ||
| $('#grid').show(); | ||
| return processedData; | ||
| }) | ||
| .catch(error => { | ||
| console.error('Failed to fetch records: ', error); | ||
| throw new Error('Failed to fetch records: ' + error); | ||
| }); | ||
| } | ||
|
|
||
| /** Fetches records using fetchAndProcessRecords(), processes them, displays them, and updates page information. */ | ||
| fetchAndDisplayRecords(): Promise<void> { | ||
| let from = this.firstVal; | ||
| let to = Math.min(from + this.pageSize - 1, this.maxRange); | ||
|
|
||
| if (to >= this.maxRange) { | ||
| const lastPage = Math.ceil(this.firstVal / this.pageSize) + 1; | ||
| this.currentPage = lastPage; | ||
| from = this.maxRange - this.pageSize + 1; | ||
| to = this.maxRange; | ||
| } | ||
|
|
||
| return this.fetchAndProcessRecords(from, to) | ||
| .then(processedData => { | ||
| this.data = processedData; | ||
| this.displayRecords(); | ||
| }) | ||
| .catch(error => { | ||
| console.error('Failed to fetch records:', error); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically speaking, what do you think your |
||
| alert('Error occured while fetching records!'); | ||
| }); | ||
| } | ||
|
|
||
| /** search through records using fromID */ | ||
| searchRecords(searchValue: number): Promise<void> { | ||
| if (searchValue >= 0 && searchValue <= this.maxRange) { | ||
| this.firstVal = searchValue; | ||
| if (searchValue + this.pageSize > this.maxRange) { | ||
| this.firstVal = Math.max(0, this.maxRange - this.pageSize + 1); | ||
| } | ||
| this.currentPage = Math.ceil(this.firstVal / this.pageSize) + 1; | ||
| // empty search input after searching | ||
| $('#fromInput').val(''); | ||
| return this.fetchAndDisplayRecords(); | ||
| } else { | ||
| alert(`Error while searching, please enter values in the range (0-${this.maxRange})`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need to make an changes, just a comment. I find it interesting that you would handle the error here instead of throwing it and letting the calling function deal with it, but it kinda makes sens, I can't imagine doing anything after the function that needs it to succeed. |
||
| return Promise.resolve(); | ||
| } | ||
| } | ||
|
|
||
| /** use Ajax for data fetching */ | ||
| private async fetchStrData(url: string): Promise<string> { | ||
| $('#overlay').show(); | ||
| const response = await $.ajax({ | ||
| url, | ||
| method: 'GET', | ||
| }); | ||
| $('#overlay').hide(); | ||
| return response; | ||
| } | ||
|
|
||
| private async fetchNumData(url: string): Promise<number> { | ||
| const response = await $.ajax({ | ||
| url, | ||
| method: 'GET', | ||
| }); | ||
| return response; | ||
| } | ||
|
Comment on lines
+153
to
+159
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really want to comment on how you don't know if |
||
|
|
||
| /** Change grid height according to screen size */ | ||
| private adjustGridHeight(): void { | ||
| const gridElement = document.getElementById('grid'); | ||
| const pageCntrl = $('.grid-controls').innerHeight(); | ||
| const screenHeight = $(window).innerHeight(); | ||
| if (gridElement && pageCntrl !== undefined && screenHeight !== undefined) { | ||
| this.maxGridHeight = screenHeight - pageCntrl; | ||
| gridElement.style.height = `${this.maxGridHeight}px`; | ||
| } | ||
| } | ||
|
|
||
| /** Update the page information and records display based on the current state of the grid. */ | ||
| private updatePageInfo(): void { | ||
| const totalPages = Math.ceil(this.totalItems / this.pageSize); | ||
| const pageInfo = `Page ${this.currentPage} of ${totalPages}`; | ||
| const from = this.firstVal; | ||
| let to = Math.min(from + this.pageSize - 1, this.maxRange); | ||
| $('#pageInfo').text(`${pageInfo}`); | ||
| $('.records').text(`Showing records ${from} to ${to}`); | ||
| } | ||
|
|
||
| private setupControls(): void { | ||
| $('#prevBtn').on('click', () => this.handlePageChange(-1)); | ||
| $('#nextBtn').on('click', () => this.handlePageChange(1)); | ||
| $(window).on('resize', debounce(() => { this.handleResize(); }, 100)); | ||
| } | ||
|
|
||
| /** Handles page navigation by updating the firstVal, lastVal, current page, and enabling/disabling previous and next buttons as needed. */ | ||
| private handlePageChange(delta: number): void { | ||
CelesteNaude marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let prevBtn = $('#prevBtn'); | ||
| let nextBtn = $('#nextBtn'); | ||
|
|
||
| // Check if delta(change in page number) is positive and disable the next page if firstval + pageSize exceeds the MaxRange. | ||
| if (delta > 0 && this.firstVal + delta * this.pageSize > this.maxRange) { | ||
| this.firstVal = this.maxRange - this.pageSize + 1; | ||
| prevBtn.attr("disabled", null); | ||
| nextBtn.attr("disabled", "disabled"); | ||
| } else if (delta < 0 && this.firstVal + delta * this.pageSize < 0) { | ||
| // If delta is negative then reset firstVal to 0 and disable prev button | ||
| this.firstVal = 0; | ||
| prevBtn.attr("disabled", "disabled"); | ||
| nextBtn.attr("disabled", null); | ||
| } else { | ||
| this.firstVal = Math.max(0, Math.min(this.firstVal + delta * this.pageSize, this.maxRange)); | ||
| prevBtn.attr("disabled", null); | ||
| nextBtn.attr("disabled", null); | ||
| } | ||
|
|
||
| this.currentPage = Math.ceil(this.firstVal / this.pageSize) + 1; | ||
|
|
||
| this.fetchAndDisplayRecords() | ||
| .catch(error => { | ||
| console.error("Error fetching records while changing page :", error); | ||
| alert('Error occured while changing page!'); | ||
| }); | ||
| } | ||
|
|
||
| private handleResize(): void { | ||
| const newGridSize = Math.ceil((Math.ceil(<number>($(window).innerHeight())) * GRID_RATIO) / ROW_HEIGHT) - 1; | ||
|
|
||
| // Check if the new grid size is non-negative | ||
| if (newGridSize >= 0) { | ||
| // Adjust firstVal for the last page | ||
| if (this.firstVal + newGridSize > this.maxRange) { | ||
| this.firstVal = this.maxRange - newGridSize + 1; | ||
| } | ||
|
|
||
| this.pageSize = newGridSize; | ||
| this.lastVal = this.firstVal + newGridSize - 1; | ||
|
|
||
| this.adjustGridHeight(); | ||
FritzOnFire marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| this.fetchAndDisplayRecords() | ||
| .then(() => { | ||
| this.updatePageInfo(); | ||
| }) | ||
| .catch(error => { | ||
| console.error("Error fetching records while resizing:", error); | ||
| alert('Error occured while resizing!'); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private displayRecords(): void { | ||
| const gridTemplate = new GridTemplate(this.columnNames, this.data); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we are creating the GridTemplate class everytime we displayRecords()? I would have loved that we could just set the dataRecords every time we update the display because |
||
| gridTemplate.displayRecords(); | ||
| this.updatePageInfo(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /** manage the grid template and display records */ | ||
| class GridTemplate { | ||
| private columnNames: ColumnName[]; | ||
| private dataRecords: GridData[]; | ||
|
|
||
| /** Initializes the column names and data records that will be used to display records in the grid. */ | ||
| constructor(columnNames: ColumnName[], dataRecords: GridData[]) { | ||
| this.columnNames = columnNames; | ||
| this.dataRecords = dataRecords; | ||
| } | ||
|
|
||
| /** Display records in a grid in table format */ | ||
| displayRecords(): void { | ||
|
|
||
| const gridElement = document.getElementById('grid'); | ||
| if (gridElement) { | ||
| gridElement.innerHTML = ''; | ||
| const table = document.createElement('table'); | ||
| const thead = document.createElement('thead'); | ||
| const headerRow = document.createElement('tr'); | ||
| for (const column of this.columnNames) { | ||
| const th = document.createElement('th'); | ||
| th.textContent = column.name; | ||
| headerRow.appendChild(th); | ||
| } | ||
| thead.appendChild(headerRow); | ||
| table.appendChild(thead); | ||
| // Create table body | ||
| const tbody = document.createElement('tbody'); | ||
| for (const row of this.dataRecords) { | ||
| const tr = document.createElement('tr'); | ||
| for (const column of this.columnNames) { | ||
| const td = document.createElement('td'); | ||
| td.textContent = row[column.name]; | ||
| tr.appendChild(td); | ||
| } | ||
| tbody.appendChild(tr); | ||
| } | ||
| table.appendChild(tbody); | ||
| // Append the table to the grid element | ||
| gridElement.appendChild(table); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /** Interface to define column names */ | ||
| interface ColumnName { | ||
| name: string; | ||
| } | ||
|
|
||
| /** Interface to define the structure of grid data */ | ||
| interface GridData { | ||
| [key: string]: any; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.