Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
9de206d
structure and some functionalality
Ntobe99 Aug 1, 2023
feddf76
grid and controls added
Ntobe99 Aug 2, 2023
20ab5f0
search using id working
Ntobe99 Aug 11, 2023
161dba8
spinner added
Ntobe99 Aug 11, 2023
4db8da4
some changes
Ntobe99 Aug 11, 2023
4379d03
changes to code format
Ntobe99 Aug 14, 2023
1574a99
scrollbar removed and empty array debug
Ntobe99 Aug 14, 2023
d7cc5a7
dyanmic grid and some search functionality
Ntobe99 Aug 17, 2023
68d6fdc
grid has range (0-999999) and some search functionality
Ntobe99 Aug 21, 2023
5590608
stationary firstVal and improved search functionality
Ntobe99 Aug 24, 2023
92c2304
firstpage functionality improved
Ntobe99 Aug 24, 2023
0449b6f
firstpage functionality improved
Ntobe99 Aug 24, 2023
18305ee
mmm
Ntobe99 Aug 25, 2023
07b2e55
mmm
Ntobe99 Aug 25, 2023
46fa399
final
Ntobe99 Aug 25, 2023
120a175
final
Ntobe99 Aug 25, 2023
0250464
formatting added
Ntobe99 Aug 28, 2023
e79a514
formatting added
Ntobe99 Aug 28, 2023
8df21a6
Update app.ts
Ntobe99 Aug 28, 2023
b2c0a70
script tag moved to head tag
Ntobe99 Aug 28, 2023
b16c29c
script tag moved to head tag
Ntobe99 Aug 28, 2023
75c52e0
some changes
Ntobe99 Aug 29, 2023
5c1408b
update
Ntobe99 Aug 29, 2023
ee7b2ca
update
Ntobe99 Aug 29, 2023
07261b4
update
Ntobe99 Aug 29, 2023
5a64f0b
update
Ntobe99 Aug 29, 2023
b92adc3
update
Ntobe99 Aug 29, 2023
e06829b
update
Ntobe99 Aug 29, 2023
b49d5a8
no more any types
Ntobe99 Aug 29, 2023
28f1ff8
no more any types
Ntobe99 Aug 29, 2023
2a2a633
some changes
Ntobe99 Aug 29, 2023
f70d831
files separated
Ntobe99 Aug 30, 2023
1a2ee0d
checking
Ntobe99 Aug 30, 2023
784d208
file separated into smaller parts
Ntobe99 Aug 30, 2023
7b77515
file separated into smaller parts
Ntobe99 Aug 30, 2023
d7367c0
file separated into smaller parts
Ntobe99 Aug 30, 2023
d15a355
first and last page more dynamic
Ntobe99 Aug 31, 2023
0b1bb06
first and last page more dynamic
Ntobe99 Aug 31, 2023
6318826
unnecessary semicolons removed
Ntobe99 Sep 4, 2023
6b095c5
updates
Ntobe99 Sep 4, 2023
3af4520
some of the requsted changes
Ntobe99 Sep 5, 2023
9722222
some of the requsted changes
Ntobe99 Sep 5, 2023
5a6339b
some of the requested changes
Ntobe99 Sep 5, 2023
c81a308
requested changes
Ntobe99 Sep 5, 2023
60d758a
requested changes
Ntobe99 Sep 6, 2023
79b2f6a
requested changes
Ntobe99 Sep 6, 2023
2d11963
updates
Ntobe99 Sep 6, 2023
3f81c01
updates
Ntobe99 Sep 6, 2023
7a1f771
new updates
Ntobe99 Sep 7, 2023
20a4790
new updates
Ntobe99 Sep 7, 2023
105295d
new updates
Ntobe99 Sep 7, 2023
42bca52
some updates
Ntobe99 Sep 7, 2023
57f6a66
some updates
Ntobe99 Sep 8, 2023
f0e6652
some of the requested updates
Ntobe99 Sep 8, 2023
e31e167
some updates to logic
Ntobe99 Sep 11, 2023
432722c
some updates to logic
Ntobe99 Sep 11, 2023
24af382
some updates to logic
Ntobe99 Sep 11, 2023
9311f71
most updates requested done
Ntobe99 Sep 12, 2023
73c6b0b
most updates requested done
Ntobe99 Sep 12, 2023
a0b243f
most updates requested done
Ntobe99 Sep 12, 2023
2d5786b
most updates requested done
Ntobe99 Sep 12, 2023
c73e797
most updates requested done
Ntobe99 Sep 13, 2023
6b5b5b4
new updates with changes requested
Ntobe99 Sep 13, 2023
9214692
most changes done, to-do add more comments and functionality
Ntobe99 Sep 13, 2023
7c5ee28
most changes done, to-do add more comments and functionality.
Ntobe99 Sep 13, 2023
603fc3d
most changes done, to-do add more comments and functionality.
Ntobe99 Sep 14, 2023
04cb27a
most requested changes done
Ntobe99 Sep 14, 2023
a43ea08
most requested changes done
Ntobe99 Sep 14, 2023
7bf7ba5
most requested changes done
Ntobe99 Sep 14, 2023
c5174fc
most requested changes done
Ntobe99 Sep 14, 2023
4eb4f82
most requested changes done
Ntobe99 Sep 14, 2023
5528fd6
most requested changes done
Ntobe99 Sep 14, 2023
7c7ea1a
requested changes done
Ntobe99 Sep 15, 2023
1df95c0
some changes done
Ntobe99 Sep 18, 2023
9518331
some changes done
Ntobe99 Sep 19, 2023
8093c8a
some changes done
Ntobe99 Sep 19, 2023
dc20c08
changes to search and a few
Ntobe99 Sep 21, 2023
6fd8954
changes to search and a few changes
Ntobe99 Sep 21, 2023
5ffd990
removed unnecessary maxrange var
Ntobe99 Sep 21, 2023
8e9d50a
some changes to logic
Ntobe99 Sep 22, 2023
f2b7bf3
some changes to logic
Ntobe99 Sep 22, 2023
138f01c
accounted for zero based indexing
Ntobe99 Sep 26, 2023
e94ac2e
27/09
Ntobe99 Sep 27, 2023
cd397ac
logic updated
Ntobe99 Sep 29, 2023
e2d638d
logic updated
Ntobe99 Sep 29, 2023
27e027a
style updated for width resize
Ntobe99 Sep 29, 2023
126a1f0
resize logic updated
Ntobe99 Sep 29, 2023
03274eb
changes requested done
Ntobe99 Oct 2, 2023
194d261
changes requested done
Ntobe99 Oct 2, 2023
4d61536
changes requested done
Ntobe99 Oct 2, 2023
350c90b
changes requested done for real this time
Ntobe99 Oct 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
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
249 changes: 249 additions & 0 deletions ApiData.ts
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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically speaking, what do you think your console.error will look like since you have nested promises?
Think about it 🤔

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})`);
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really want to comment on how you don't know if response is actually a number... but I am finding it hard to phrase it... but I guess for this project it is fine.


/** 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 {
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();

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);

Choose a reason for hiding this comment

The 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 columnNames never changes throughout this life. It just sounds like a lot of objects being created and created and created...

gridTemplate.displayRecords();
this.updatePageInfo();
}
}
44 changes: 44 additions & 0 deletions GridTemplate.ts
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);
}
}
}
9 changes: 9 additions & 0 deletions Interfaces.ts
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;
}
Loading