-
Notifications
You must be signed in to change notification settings - Fork 1.2k
add task solution #1090
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?
add task solution #1090
Changes from all commits
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,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)) { | ||
|
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. Using |
||
| 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
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. 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)); | ||
| }); | ||
|
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. The optional cell-editing feature (double-click to create an input with class |
||
|
|
||
| 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; | ||
| } | ||
| }); | ||
|
|
||
|
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. The optional double-click cell editing feature (append an input with class |
||
| 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
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. Age is read from FormData as a string. Comparing |
||
| } | ||
|
|
||
| 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
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. Notifications are hidden by setting |
||
| }; | ||
|
|
||
| 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, | ||
| ); | ||
There was a problem hiding this comment.
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.