Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A simple responsive single page that takes data from the Random User Generator API and then builds a fully accessible HTML table. The column can sort when the user clicks the column header button with a mouse or uses the enter/space key.

License

NotificationsYou must be signed in to change notification settings

abuna1985/sortable-table

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

51 Commits
 
 
 
 
 
 
 
 

Repository files navigation

made-with-javascriptGitHub commitsGitHub watchersGitHub license

Logo

Sortable Columns HTML Table

View Demo

Table Of Contents

About The Project

Desktop view of the sortable columns table

This project is a simple single-page responsive design which takes data from theRandom User Generator API and builds a table which can sorted by the column headers with a mouse and/or keyboard.

This project is a simple responsive web page which takes data from the Random User Generator API and builds an accessible table which can sorted by the column headers with aclick of a mouse and/orenter/space key.

Visual Examples

Click here to see what happens when thecolumns are clicked onsortable table click demo gif

Click here to see what happens when thetab key andshift+tab key is pressedsortable table tab/enter demo gif

Click here to see the Web Accessibility Evaluation Tool (WAVE) report summaryWave Report: 11 Features, 26 Structural Elements, 132 ARIA labels

Click here to see the mobile view of the page

Mobile View

mobile view of the sorted column table

Random User API

Read theAPI documentation to find out more about the response values and how to test the API. Notice the URL I am using for this project is requesting 10 users (results=10) from the United States (nat=us):https://randomuser.me/api/?nat=us&results=10

Click Here to see the random user properties available
  • gender: (string) gender (male/female),
  • name: (object) contains name data
    • title: (string) title (Mr., Ms, etc)
    • first: (string) first name
    • last: (string) last name
  • location: (object) contains location data
    • street: (string) street number and name
    • city: (string)city
    • state: (string) state
    • postcode: (string) zip/postal code
  • coordinates: (object) contains coordinates data
    • latitude: (string) latitude
    • longitude: (string) longitude
  • timezone: (object) contains time zone data
    • offset: (string) timezone offset
    • description: (string) time zone
  • email: (string) email address
  • login: (object) contains login data
    • uuid: (string) unique user id,
    • username: (string) username
    • password: (string) password
    • salt: (string) salt hash
    • md5: (string) md5 hash
    • sha1: (string) sha1 hash
    • sha256: (string) sha256 hash
  • dob: (object) contains age data
    • date: (timestamp) date of birth
    • age: (number) age of person
  • registered: (object) contains registration data
    • date: (timestamp) registration
    • age: (number) age of membership
  • phone: (string) phone number
  • cell: (string) cell phone number
  • id: (object) contains id data
    • name: (string) id name
    • value: (string) id value
  • picture: (object) contains picture data
    • large: (string) URL of large image
    • medium: (string) URL of medium image
    • thumbnail: (string) URL of thumbnail image
  • nat: (string) nationality

Back to Top

Requirements

  1. Use the result a from theRandom User Generator API
  2. Use HTML, CSS and Javascript to show the data in a readable table (including mobile view)
  3. All columns should have the ability to be sorted by mouse and/or keyboard

As a user, I should:

  • See aloading state when the page initially renders
  • See an HTMLtable when the data is successfully loaded
  • See all the HTMLtable data inmobile andtablet view
  • See anerror message within the table body if it is not working
  • Be able toclick on a column, see a visual cue that the column has been selected
  • Be able to use the following keyboard keys to control:
    • Direction Keys
      • tab,shift+tab
      • ,,,
      • w,s,a,d
      • home,end
    • Sorting
      • enter,space

Bonus Requirements

As a developer, I should

  • Implement 2 examples of caching in order to increase the overall performance of the page
  • Use the BEM (Block Element Modifier) naming convention for CSS class names
  • Use accessibility principles to ensure the page is accessible by the browser and any assistive technologies connected as well (i.e screen readers)

Back to Top

My Process

Since the requirements were fetching API data and rendering a table, I approached it the following way:

1. HTML

Create anindex.html file and fill it with the elements that were not going to be changing like the<header> and root<table> element. Add BEM (Block Element Modifier) naming convention for adding class names (i.ec-header andc-table).

2. JavaScript

Create ascript.js and write out the following functions:

  • fetch data from the Random User API
    • render the contents of the<table> element (<th>,<tr>,<td>, etc.) with the Random User API data
  • Create Event Listeners:
    • Click
      • <button> in Column header (<th>)
      • when clicked, it sorts the table ascending/descending order and rerenders the page with the results
    • Keydown
      • left arrow,up arrow,a,w
        • Move the focus to theprevious HTML element with atabindex attribute
      • right arrow,down arrow,d,s
        • Move the focus to thenext HTML element with atabindex attribute
      • home
        • Move the focus to thefirst HTML element with atabindex attribute
      • end
        • Move the focus to thelast HTML element with atabindex attribute

3. CSS

  1. AddNormalize.css to reset the CSS browser defaults
  2. Create astyle.css and add styles to:
  • Header -.c-header
    • Header Title -.c-header__title
    • Header Subtitle -.c-header__subtitle
  • Table -c-table
    • table header -c-table__head
      • header cell (th) -.c-table__th
        • button -.c-table__button
    • table body -c-table__body
      • table row (tr).c-table__tr
        • table data (td).c-table__td
  • Loading Screen -.l-loading-container.is-loading
  • Animations
    • move keyframe animation
    • grow keyframe animation
  • Mobile view styling@media screen and (max-width: 768px)

Back to Top

Built with

  • Semantic HTML5
  • CSS3
    • Normalize.css
    • CSS Animation (loading screen)
    • CSS custom properties
    • BEM naming convention
  • ES6 JavaScript
    • Async/Await
    • Fetch
    • Closures/Memoization

Back to Top

What I Learned

Web Accessability

Semantic HTML & ARIA attributes

After reviewing theDeque University Sortable Table Example, it looks like making a<table> with the appropriate nested table elements (<thead>,<tbody><th>,<tr>,<td>).

Here is a skeleton example with the recommended ARIA attributes:

<tablerole="grid"aria-readonly="true"><thead><trrole="row"><throle="columnheader"scope="col">col 1<th><throle="columnheader"scope="col">col 2<th><throle="columnheader"scope="col">col 3<th></tr></thead><tbody><trrole="row"><thscope="row"role="rowheader">data 1</th><tdrole="gridcell">data 2</td><tdrole="gridcell">data 3</td><tr></tbody></table>

For assistive technology, It is preferred that the selected<th> have the following attribute to let the reader know which order the column is sorted:

  • aria-sort="ascending"
  • aria-sort="descending"

NOTE: some ARIA attributes may be built into the semantic table elements. I found conflicting information and decided to add the ARIA attributes to ensure they are available to any assistive technologies.

Back to Top

Performance Optimizations

Caching API Data inSessionStorage

In most API fetching demos, the API call is made as the page is rendered. I decided to useSessionStorage to store the initial API call data. After the initial fetch, the table will pull the data directly fromSessionStorage.

Once the user closes out the window tab, the data from session storage is removed. Below is the snippet where I added session storage logic:

tableBody.innerHTML=displayLoadingContainer();
if(sessionStorage.getItem('userdata')){
// Use the data from session storage
results=JSON.parse(sessionStorage.getItem('userdata'));
// console.log('session storage used');
// console.log('--------------------');
}else{
// fetch the data from the random user API
try{
results=awaitfetchUserData(endpointURL);
// console.log({results});
sessionStorage.setItem('userdata',JSON.stringify(results));
// console.log('fetch call made');
// console.log('Session storage used');
// console.log('--------------------');
}catch(error){
console.log('Error:',error);
}
}

Caching Sorted Tables in the Event Listener (Memoization)

I had to demonstrate memoization for a few technical interviews recently. I wanted to implement memoization so that the table did not need to run a sort function every time the column button is clicked.

So I initially create a cache within the event listener function and return a function that will be used when the column button is clicked.

functionsaveToCache(){
letcache={};
/**
* Sorts the table in ascending/descending order and updates the view of the table
*
*@param {HTMLTableElement} table The desired table that needs to be sorted
*@param {Number} column The index of the column to sort
*@param {Boolean} asc Determines if the sorting will be in ascending/descending order
*@return {Boolean} Returns true when the function completes properly
*/
return(table,column,asc=true)=>{

Win the return function, we will try to access the cache to see ifcache[${order}${column}] (examplecache['ascending1'] for column 1 in ascending order). if it does not exist, we will perform the sort.

if(cache[`${order}${column}`]){
// console.log('cache has been used');
// Since it is available, we will use the sorted array stored in cache
sortedRows=cache[`${order}${column}`];
}else{
// Sort each row
sortedRows=rows.sort((a,b)=>{
constaColumn=a.querySelector(`.c-table__td:nth-child(${column+1})`);
constbColumn=b.querySelector(`.c-table__td:nth-child(${column+1})`);
letaColumnContent;
letbColumnContent;
switch(column){
case3:
// If it is 'IMAGES' column (4th), use the data-id attribute within the <img> element
aColumnContent=aColumn.getAttribute('data-id');
bColumnContent=bColumn.getAttribute('data-id');
break;
case5:
// In the 'Address' column (6th), only use the street number from the address to sort
aColumnContent=aColumn.textContent.split(' ')[0];
bColumnContent=bColumn.textContent.split(' ')[0];
// console.log({aColumnContent, bColumnContent})
// console.log('sorted by street number');
break;
case9:
// If both values can be converted into a Date value, convert it
// Just splitting the date (MM/DD/YYYY) by / and using the year (YYYY) for sorting
aColumnContent=newDate(aColumn.textContent.trim());
bColumnContent=newDate(bColumn.textContent.trim());
// console.log({ aColumnContent, bColumnContent });
// console.log('sorted by date');
break;
default:
// Default will be HTML Content as a String
aColumnContent=aColumn.textContent.trim();
bColumnContent=bColumn.textContent.trim();
}
// console.log({ aColumnContent, bColumnContent })
// If both values can be converted into a Number value, convert it to a number
if(!Number.isNaN(parseInt(aColumnContent))&&!Number.isNaN(parseInt(bColumnContent))){
aColumnContent=parseInt(aColumnContent);
bColumnContent=parseInt(bColumnContent);
// console.log('sorted by number');
}
returnaColumnContent>bColumnContent
?1*directionModifier
:bColumnContent>aColumnContent
?-1*directionModifier
:0;
});
// Store the asc/desc sorted rows in the cache for future reference
cache[`${order}${column}`]=sortedRows;
// console.log({cache})
// console.log({sortedRows});
}

After the sort is performed, we will store it in thecache object for future reference.

cache[`${order}${column}`]=sortedRows;

we will callsaveToCache and name the returning functionsortByTableColumn

letsortTableByColumn=saveToCache();

We then callsortByTableColumn in the click listener. Notice I create an event listener on the document and add a conditional for make sure the button with a classjs-column-button is the only element that the sorting function will work.

functionhandleClick(event){
// the function will only run when a <th> is clicked on
if(event.target?.closest('.js-column-button')){
// Get Column ID number
constcolumnIndex=parseInt(event.target.getAttribute('data-col'));
// Check if span.c-table__button--icon has the ascending icon class and return boolean (true/false)
constcurrentIsAscending=event.target.firstElementChild?.classList?.contains('c-table__button--asc');
sortTableByColumn(table,columnIndex,!currentIsAscending);
}
returnfalse;
}

// Click Event Listener
document.addEventListener('click',handleClick);

Back to Top

Using Modern CSS

CSS Custom Properties

ThisKevin Powell YouTube video on CSS custom properties really helped me better understand how to use these properties. Here is an example. I created 5 custom properties in mybody selector so I can use the custom properties within all nested elements

body {
/*Custom Properties*/
--header-background-color:#0074d9;
--main-background-color:#ffffff;
--main-background-accent-color:#dddddd;
--main-text-color:#111111;
--error-text-color:#ff4136;
color:var(--main-text-color);
font-family: sans-serif;
line-height:1.25;
}

Now I have a custom property called--main-text-color that stores the hex code of black (#111111). But since my button is going to be blue, I would like my text color to be white (#ffffff). Rather than create another custom property, I can overwrite (or locally scope) the property within a selector and use the same property name like so:

.c-table__button {
--main-text-color:#ffffff;
align-items: stretch;
background-color:var(--header-background-color);
border:1px transparent solid;
color:var(--main-text-color);

Now the text color within my button will be white (#ffffff)

BEM naming convention

After reading thenamespace section of this Smashing Magazine article on mistakes to avoid using BEM (Block, Element, Modifier), I decided to apply the same BEM prefix namespacing as in the Smashing Magazine article. Below is the table that shows the prefix description and examples from the article.

TypePrefixExamplesDescription
Componentc-c-card
c-checklist
Form the backbone of an application and contain all of the cosmetics for a standalone component.
Layout modulel-l-grid
l-container
These modules have no cosmetics and are purely used to positionc- components and structure an application’s layout.
Helpersh-h-show
h-hide
These utility classes have a single function, often using!important to boost their specificity. (Commonly used for positioning or visibility.)
Statesis-
has-
is-visible
has-loaded
Indicate different states that a c- component can have.
JavaScript hooksjs-js-tab-switcherThese indicate that JavaScript behavior is attached to a component. No styles should be associated with them; they are purely used to enable easier manipulation with script.
— David Berner

Source of namespacing:Harry Robert - More Transparent UI Code with Namespaces

As a result I used the following block class names:

  • Components -c-header,c-table
  • Elements -c-header__title,c-table__td
  • Layout -.l-table-container,.l-loading-container
  • States -is-loading,has-error

Back to Top

Additional Features

  1. Pagination for multiple pages of results
  2. In tablet/mobile view, add tab functionality to focus on each card full of data
  3. Add an input to search on the table and highlight

Back to Top

Resources

Accessibility Links

HTML Links

CSS Links

JavaScript Links

Back to Top

Author

Back to Top

Acknowledgments

  • @sw-yx @techieEliot @rayning0 and @amhayslip pushing the dev community (including myself) to learn and grow in public.
  • @kevin-powell for making me smarter about CSS
  • @cferdinandi for making me smarter about JavaScript.

Back to Top

About

A simple responsive single page that takes data from the Random User Generator API and then builds a fully accessible HTML table. The column can sort when the user clicks the column header button with a mouse or uses the enter/space key.

Topics

Resources

License

Stars

Watchers

Forks


[8]ページ先頭

©2009-2025 Movatter.jp