Skip to content
Open
Changes from all commits
Commits
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
244 changes: 243 additions & 1 deletion src/scripts/main.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,245 @@
'use strict';

// write code here
const table = document.querySelector('table');
const tBody = table.tBodies[0];
const body = document.querySelector('body');

table.addEventListener('click', (e) => {
const target = e.target.closest('th');
const tbody = table.tBodies[0] || table;
let rows = [];

if (!target) {
return;
}

const columnIndex = target.cellIndex;

const currentDir = target.dataset.order;
const sortDirection = currentDir === 'asc' ? 'desc' : 'asc';

table
.querySelectorAll('th')
.forEach((th) => th.removeAttribute('data-order'));

target.dataset.order = sortDirection;

rows = Array.from(tbody.rows);

rows.sort((a, b) => {
if (/\d/.test(a.cells[columnIndex].textContent)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The numeric-column detection uses a simple /\d/.test(...) on a cell's textContent. That can misclassify columns if a textual cell contains digits. Consider a more robust approach: either mark numeric columns via a header/data attribute or attempt to parse both cell values (after stripping non-numeric chars) and fall back to text comparison only when parsing yields NaN.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using /\d/ to detect numeric columns (checking a single cell's text for any digit) can misclassify some columns that contain digits inside text. Consider trying to parse both cell values to numbers (e.g. strip non-numeric chars and use parseFloat) and check whether both parsed values are valid numbers (!Number.isNaN(parsed)) before doing numeric comparisons; otherwise fall back to localeCompare. This will make numeric detection more robust for edge cases.

const aNum = parseFloat(
a.cells[columnIndex].textContent.replace(/[^\d.-]/g, ''),
);
const bNum = parseFloat(
b.cells[columnIndex].textContent.replace(/[^\d.-]/g, ''),
);
Comment on lines +31 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Parsing salary for sorting removes only the '$' sign but does not remove thousands separators (commas). parseFloat("1,000") returns 1, so salary column sorting will be incorrect. Remove commas (or non-numeric characters) before parsing, e.g. replace(/[^\\d.-]/g, '') or replace(/,/g, '') as well.


if (sortDirection === 'asc') {
return aNum - bNum;
} else {
return bNum - aNum;
}
}

const aText = a.cells[columnIndex].textContent.trim();
const bText = b.cells[columnIndex].textContent.trim();

if (sortDirection === 'asc') {
return aText.localeCompare(bText);
} else {
return bText.localeCompare(aText);
}
});

rows.forEach((row) => tbody.appendChild(row));
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The optional cell-editing feature (double-click to create an input with class cell-input, allow only one editor at a time, save on blur/Enter, restore original value if input empty) is not implemented. If you decide to add it, attach a dblclick handler on the table body (e.g. near the row-selection logic) and follow the rules in checklist item #31 (only one editor, save on blur/Enter, restore previous value if empty).


let activeRow = null;

table.tBodies[0].addEventListener('click', (e) => {
const target = e.target.closest('tr');

if (target) {
if (activeRow) {
activeRow.classList.remove('active');
}
target.classList.add('active');
activeRow = target;
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The optional double-click cell editing feature (append an input with class cell-input, allow only one edited cell, save on blur/Enter and restore initial value if input is empty) is not implemented. If you need the optional requirement, add a dblclick handler on the table body to create the input element and handle blur/keydown lifecycle.

const form = document.createElement('form');

form.classList.add('new-employee-form');
form.action = '#';
form.method = 'POST';

const fields = [
{ label: 'Name', name: 'name', type: 'text' },
{ label: 'Position', name: 'position', type: 'text' },
{
label: 'Office',
name: 'office',
type: 'select',
options: [
`Tokyo`,
`Singapore`,
`London`,
`New York`,
`Edinburgh`,
`San Francisco`,
],
},
{ label: 'Age', name: 'age', type: 'number' },
{ label: 'Salary', name: 'salary', type: 'number' },
];

fields.forEach((field) => {
const label = document.createElement('label');

label.textContent = `${field.label}: `;

let element;

if (field.type === 'select') {
element = document.createElement('select');
element.name = field.name;
element.required = true;

field.options.forEach((optText) => {
const option = document.createElement('option');

option.value = optText;
option.textContent = optText;
element.appendChild(option);
});
} else {
element = document.createElement('input');
element.name = field.name;
element.type = field.type;
element.required = true;
}

element.setAttribute('data-qa', field.name);
label.appendChild(element);
form.appendChild(label);
});

const submitBtn = document.createElement('button');

submitBtn.type = 'submit';
submitBtn.textContent = 'Save to table';
form.appendChild(submitBtn);

body.appendChild(form);

form.addEventListener('submit', (e) => {
e.preventDefault();

const formData = new FormData(form);
const employee = Object.fromEntries(formData.entries());

if (checkValidation(employee)) {
addEmployeeToTable(employee);

pushNotification(
20,
20,
'Success',
'Employee added successfully',
'success',
);
form.reset();
}
});

function addEmployeeToTable(employee) {
const newRow = document.createElement('tr');
const formattedSalary = Number(employee.salary).toLocaleString('en-US');

newRow.innerHTML = `
<td>${employee.name}</td>
<td>${employee.position}</td>
<td>${employee.office}</td>
<td>${employee.age}</td>
<td>$${formattedSalary}</td>
`;
tBody.appendChild(newRow);
}

function checkValidation(employee) {
if (employee.name.length < 4) {
pushNotification(
20,
20,
'Validation Error',
'Name must be at least 4 characters long',
'error',
);

return false;
}

const age = Number(employee.age);

if (age < 18 || age > 90) {
pushNotification(
20,
20,
'Validation Error',
'Age must be between 18 and 90',
'error',
);

return false;
Comment on lines +185 to +195
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Age is read from FormData as a string. Comparing employee.age < 18 relies on implicit coercion and can be error-prone. Convert the value explicitly (for example const age = Number(employee.age)) before checking < 18 or > 90 and use age in the comparisons and error messages.

}

return true;
}

const pushNotification = (posTop, posRight, title, description, type) => {
const notification = document.createElement('div');

notification.classList.add('notification', type);
notification.setAttribute('data-qa', 'notification');
notification.style.top = posTop + 'px';
notification.style.right = posRight + 'px';

const notificationTitle = document.createElement('h2');

notificationTitle.classList.add('title');
notificationTitle.textContent = title;
notification.appendChild(notificationTitle);

const notificationDescription = document.createElement('p');

notificationDescription.textContent = description;
notification.appendChild(notificationDescription);

document.body.appendChild(notification);

setTimeout(() => {
notification.remove();
}, 2000);
Comment on lines +222 to +224
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notifications are hidden by setting notification.style.display = 'none' after timeout but the element remains in the DOM. To avoid DOM buildup and to keep behavior predictable, consider removing the element (notification.remove()) after hiding (or use a CSS transition and remove on transitionend).

};

form.addEventListener(
'invalid',
(e) => {
e.preventDefault();

const input = e.target;
const fieldName = input.name;
const errorMessage = input.validationMessage;

pushNotification(
20,
20,
'Validation Error',
fieldName + ': ' + errorMessage,
'error',
);
},
true,
);
Loading