Categories
CSS HTML JavaScript

Flex Table Sort Solution

In order to use the power of Flex for this exercise, it is very important to remember one thing:

because Flexbox is all about the relationship between parent and child elements, we need the tbody to be included  in this table. The “sort” will involve shuffling the order of the tr elements inside the tbody. Without the tbody, if we use table as the flex parent, our header row will move when we click buttons.

Building the Table

I am omitting here a bunch of rows from the tbody to save space, but here is our initial table layout. Hopefully, you used Sublime Text or Atom selection efficiencies to build it.

<table>
    
    <thead>
        <tr>
            <th>Term</th>
            <th>Prime Minister</th>
            <th>Party</th>
        </tr>
    </thead>

    <tbody>
        <tr data-party="conservative">
            <td>1867–1873</td>
            <td>Sir John A. Macdonald</td>
            <td>Conservative</td>
        </tr>
        <tr data-party="liberal">
            <td>1873–1878</td>
            <td>Alexander Mackenzie</td>
            <td>Liberal</td>
        </tr>
       
        <tr data-party="liberal">
            <td>2015–</td>
            <td>Justin Trudeau</td>
            <td>Liberal</td>
        </tr>

       <!-- more rows here -->

    </tbody>

</table>

You will likely note the use of the data- attribute here. This html5 attribute allows you to add attribute names of your choice, as long as they start with data-. Here I have used party as the second part of that attribute. However, for this exercise, you could just as easily use a class attribute to do this.

And here is how we can code the buttons in each of the two table headers that require them:

<th>Term
<button id="termsort">
    <img src="images/maple-leaf.svg" alt="Sort">
</button>
</th>

<th>Prime Minister</th>

<th>Party
<button id="partysort">
    <img src="images/maple-leaf.svg" alt="Sort">
</button>
</th>

Styling the Table

To save space and time, I will discuss only some of the styling considerations here. However, the completed file contains all the CSS required to produce the desired effects.

To stripe the table, we need to give the TR element a background-color and then use the nth-of-type selector to make even or odd rows lighter or darker than the tr style:

tr {
  background-color: #ddd;	
}
tr:nth-of-type(even) {
	background-color: #eee;
}

To style the button, we could do something like this:

button img {
   width: 16px;
   height: 16px;
}

button {
   margin-top: 10px;
   height: 20px;
   width: 40px;
   background-color: transparent;
   border: none;
   float: right;
}

button:hover {
   cursor: pointer;
}

button:focus {
   outline: white;
}

Making the Buttons Work

If we leverage Flex for this task, the JavaScript required becomes quite minimal. Since both aspects of the task really just involve changing the order of child rows of the tbody, all we need to do is toggle classes on the tbody in response to click events:

var btnTerm = document.getElementById('btn-term');
var btnParty = document.getElementById('btn-party');
var tBody = document.getElementsByTagName('tbody')[0];

btnTerm.addEventListener('click', function(){
	tBody.classList.toggle('term-sort');
});

btnParty.addEventListener('click', function(){
		tBody.classList.toggle('party-sort');
});

Test your page, click each button, then use the Inspector to make sure that your classes are indeed being added:

Now you can add the CSS that does the rearranging of the rows. First of all, we make the tbody a flex parent, so we can then arrange the rows how we want them to go when the buttons are clicked:

.term-sort {
	flex-flow: column-reverse nowrap;
}


.party-sort tr[data-party="liberal"]{
	order: 1;
	background-color: #D71920;
	color: white;
}

.party-sort tr[data-party="conservative"]{
	order: 2;
	background-color: #1A4782;
	color: white;
}

.party-sort tr[data-party="unionist"]{
	order: 3;
	background-color: #99f;
	color: white;
}

Test the page. Click one button, then the other. We soon discover that our page is not behaving quite the way we want it to. Specifically, when the Prime Ministers are grouped by party, clicking the TERM button does not remove the party grouping (and it is therefore making nonsense of the sort-by-term option).

To fix that, add two conditions to your event handlers:

btnTerm.addEventListener('click', function(){

	if (tBody.classList.contains('party-sort')){
		tBody.classList.remove('party-sort');
	}

	tBody.classList.toggle('term-sort');
});

btnParty.addEventListener('click', function(){

	if (tBody.classList.contains('term-sort')){
		tBody.classList.remove('term-sort');
	}
	
	tBody.classList.toggle('party-sort');
});

Responsive Strategy

And here is how I would approach the responsive strategy. Here I am not doing a Mobile First layout because the assignment made the phone layout a bonus question rather than part of the main exercise.

@media screen and (max-width: 600px) {
	
	th {
		display: none;
	}

	tr {
		display: flex;
		flex-flow: column nowrap;
		margin-bottom: .75rem;
	}
	/* Create FLEX relationship in the TD so we can align each TD's
	generated and original content nicely. */
	td {
		display: flex;
		width: 100%;
	}

	/* Generated content to compensate for hidden table headers */
	tr td:first-of-type:before {
		content: "TERM: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

	tr td:nth-of-type(2):before {
		content: "PM: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

	tr td:nth-of-type(3):before {
		content: "PARTY: ";
		font-weight: bold;
		flex: 0 0 70px;
	}

}

There are still one or two things to clean up here, but this much should definitely give you an idea of ways to approach the task.

Download a completed version of the file.