# User-focused timeline logging

I'm working on a small piece of my personal website. I'd like a timeline logging all of my relevant achievements in my career in Computer Science, in general. I'd like to implement a feature that would allow the user to focus on a specific section of the timeline.

I have implemented a fairly sparse version of what I would like. I populate an ordered list via JavaScript, with each list element containing a single child element containing a year. When the user clicks a year that is not the last year in the timeline, it will delete all but the selected year and the proceeding year, then position the selected year on the left end of the timeline and the proceeding year on the right end of the timeline.

I intend to populate the area in between with month that will exhibit the same focus function described above, in order to view each event between the months.

My concern is that I don't think this is the most efficient way of implementing what I want, hence my presence on SO. The implementation of this feature is, in my opinion, quite messy. However, due to the absence of great degrees of experience in the field of front-end development, my opinion may very well be wrong.

Naturally, I've added the code to this question. I really don't know whether or not this is the best implementation; any advice would be much appreciated.

let focussed = false;

generateTimeline();
});

function generateTimeline() {
let timeline = document.getElementsByClassName("timeline")[0];
timeline.appendChild(createYearElement("2019"));
timeline.appendChild(createYearElement("2018"));
timeline.appendChild(createYearElement("2017"));
timeline.appendChild(createYearElement("2016"));

for (let i = 0; i < timeline.children.length; i++) {
const child = timeline.children[i];
let text = child.children[0];

if (text.innerHTML === "2016") continue;

let year = text.innerHTML;
let lastYear = null;

if (focussed) return;

if (year === "2017") {
lastYear = "2016";
}

if (year === "2018") {
lastYear = "2017";
}

if (year === "2019") {
lastYear = "2018";
}

if (year !== "2019") removeYearElement("2019");
if (year !== "2018" && lastYear !== "2018") removeYearElement("2018");
if (year !== "2017" && lastYear !== "2017") removeYearElement("2017");
if (lastYear !== "2016") removeYearElement("2016");

let lastYearElement = document.getElementById(${lastYear}YearElement); lastYearElement.style.marginLeft = "400px"; focussed = !focussed; createBackButton(); }); } } function createYearElement(year) { let element = document.createElement("li"); element.setAttribute("class", "year"); element.setAttribute("id", ${year}YearElement);
let text = document.createElement("div");
text.innerHTML = year;
element.appendChild(text);
return element;
}

function createBackButton() {
let button = document.createElement("button");
button.innerHTML = "Back";
button.setAttribute("id", "backButton");

let timeline = document.getElementsByClassName("timeline")[0];
let childrenArray = Array.slice(timeline.children);

for (let i = 0; i < childrenArray.length; i++) {
const child = childrenArray[i];

child.remove();
}

generateTimeline();
focussed = !focussed;
button.remove();
});

document.getElementsByClassName("timeline_box")[0].appendChild(button);
}

function removeYearElement(year) {
let timeline = document.getElementsByClassName("timeline")[0];

for (let i = 0; i < timeline.children.length; i++) {
const child = timeline.children[i];

if (child.children[0].innerHTML === year) child.remove();
}
}
.timeline_box {
width: 80%;
height: 50%;
background: lightslategrey;
margin: auto;
margin-top: 10%;
display: grid;
justify-content: center;
align-content: center;
position: relative;

}

.timeline_wrapper {
width: 500px; }

.timeline_line {
z-index: 1;
width: inherit;
height: 20px;
margin-top: 5px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 45%, #fd2600 51%, rgba(255, 255, 255, 0) 57%, rgba(255, 255, 255, 0) 100%); }

.timeline {
z-index: 2;
list-style: none;
width: inherit; }
.timeline > li {
float: left;
margin-left: 100px;
background: greenyellow;
.timeline > li:nth-child(1) {
margin-left: 0; }

button {
width: 100px;
height: 30px;
color: black;
margin: auto; }

html,
body,
element,
* {
margin: 0;
border: 0; }
  <body>
<div class="timeline_box">
<div class="timeline_wrapper">
<ol class="timeline">
</ol>
<div class="timeline_line"></div>
</div>
</div>
</body>

I really don't know whether or not this is the best implementation; any advice would be much appreciated

In my opinion, the 'best' way is relative, subjective, arbitrary. There's a lot of moving parts to the DOM and many avenues that get you to the same destination. So there, for the majority of scenarios, is no 'righter' way. I do, circumstantially, think there are more efficient ways to do things, but I stress circumstantially. Your performance hits are few and far between in personal websites like you are building though so in my opinion I would say don't make that a focal point for concern.

For brevity, I think it's a valid concern and shows that you have a great mentality thinking about the 'grand scheme' of things, but you should never say things like

due to the absence of great degrees of experience in the field of front-end development, my opinion may very well be wrong

That pre-dispositions you for failure. Just because you don't have a degree, which is debatably relevant in today's scope anyways, does not automatically dismiss your creative freedom or mean you automatically know less. Anyone on a team can have a good idea and even PMs mess up :) we're all just people. Although I get what you are saying, try not to make it a habit, for many reasons.

Anyways, I'm going to offer a solution and I'm aware it may not be what you are looking for, but it's there if you fancy it, it's there if you don't. I'll certainly explain my thought process however, but right, wrong or indifferent, it's a way to do it, I'm not implicitly arguing it's the way to do it either.

Located at the bottom for review, just keep in mind I'm not a graphic designer :p The solution is also not responsive because I don't know what your plans are so I didn't want to force it, but of course it can/should be. The CSS isn't important anyways, it's just for the demo, you're going to change it to fit your needs.

However, my solution, as it stands now, is a pure CSS solution. My high-level reasons for that are

• Maintainability
• Accessibility
• Semantics

There really are no efficiency concerns with this ask. Overall though, in my opinion, keeping a timeline like this CSS-focused will make it easier to maintain as your requirements change or get more complex. As you can see, there isn't that much HTML involved, we let CSS handle it independently, like it should, so that way we can move the HTML around anywhere without causing conflicts, and we can even use the same stylesheet for other projects. Overall, it feels moduralized, your HTML is maintainable now, same for CSS but now you don't have scripts dependent on, or creating, elements or altering/creating selectors like ids other files are dependent on. I'm not saying what you are doing is wrong, I'm just saying don't make it hard on yourself maintainability wise by having attributes and elements set in different ways in different places, if it can be avoided.

I don't want to drag this on or bore you, so I think readability is understood.

I'm sure other people have suggestions about your javascript, as do I, but I just wanted to talk about semantics and accessibility.

### semantics

Semantics are very important for many reasons, like SEO and accessibility. There's lots of resources out there already for you to review so I won't double-tap; but for me, the biggest take away is that it's layered. Semantics isn't just about using the right tag for it's intended purpose, it's also about UIX and how are those tags semantically being used by the end user. Meaning, would you rather, as a user, want to manually click 'load more' at the end of a picture feed, or would you prefer the 'infinite scrolling' method. Both work, both apply, but one of them just feels right as a user. You know what you want, you're obviously at the end for a reason.

So I would just ask, re the UIX topic, how will your users know they need to click on a year for more details? How do they even know they can? Timelines are tough because usually they are static content and what you want to do is very cool and different, so you need to give them a reason to invest their time in your portfolio and use those cool things. You have to effectively sell yourself very quickly and I would just ask maybe reconsider the explicit interaction you are suggesting, or at least how I interpreted it. Think about how often you really digest a persons portfolio, I'm guessing not much but I think that's normal; just consider there potentially are a lot of people out there with "not much attitude" too.

My way may not be the best answer to that, but I think people at least can gather that with every year there are months next to it. Curiosity is left up to them after that in my case, but again, I'm not a designer I actually hire some for my personal stuff so I leave no room for doubt in my own mind.

Lastly on this topic, what you have in HTML isn't wrong, it's what the spec would call conformed, but it could use improvement. Think about the structure, a timeline is a list of events that happen in a specific order(ol) and can also happen in any order (ul). All checks out, your syntax meets that requirement.

But by your definition, "a timeline separated by years that contains a list of months" implicitly identifies a sectioning flow of content; a la an address book. Each section has a header, usually the first letter of last name, then lists those contacts that qualify. Again, this helps screen readers, but more importantly SEO. What you are trying to do is fit a header into an li, so how do you suppose google will index that? Probably as just another li.

What I like to do when I think about content model is simply go to MDN or the spec and review what the permitted content is to make sure I'm giving myself the best chance at SEO. Over time this will come to you like it's second nature because there are only so many pre-defined tags.

If you look at the permitted content of an li, it states "flow content", which actually includes h1,h2,h3 etc. So again, while it's valid HTML, it's not reflecting intention.

Now, if you read the spec at the very end of that section, it states:

While it is conforming to include heading elements (e.g. h1) inside li elements, it likely does not convey the semantics that the author intended. A heading starts a new section, so a heading in a list implicitly splits the list into spanning multiple sections.

How oddly specific to this situation :P

Now the question comes, how do you add a section title to a ul/ol, there's no tag or attribute for it? Well, turns out there are many ways, using a dl, an h3 before the list tag and more 'list-like' tags, but the example I used in the snippet below comes straight from the source and satisfies intention and helps SEO and screen readers and makes it easier to style! Win-win-win-wins!! If you go back to the spec, in the examples for li, the second example they provide demonstrates that edge case, what I consider an edge case any ways.

Really, when it comes to knowing what tag is used where and when and how just comes down to practice. I have spent many a nights in a endless loop of reading all the details for some scenarios but as mentioned, over time it is just a thought a way. I walked through all this just to show how I approach some of these same issues. Those resources are a great start but you'll find what works best for you.

### accessibility

Accessibility and semantics kind of go hand-in-hand. Whatever is semantically sound is almost accessibility-ready. Unfortunately, a lot of devs don't consider doing it because they aren't impacted by an inaccessible web or don't do it for their job. It's one of things that "you think will never happen to you" until something bad happens or you have a family member. I think a personal site is a great time to start learning if you haven't already because you have the luxury of taking your time to learn it right, understand how it works and be a resume builder. Mostly because you do not want to give anyone a reason to disqualify you from a job if their recruiter happens to be disabled and visits your site. Additionally, trust me, even if they aren't some will inspect the page to see whats under the hood, so if anything, it could very well set you apart from other candidates.

:root {
--color-tl-chevron-fg-hover: rgba(255,255,255,1);
--color-tl-chevron-fg: rgba(255,255,255,0.6);
--color-tl-chevron: cornflowerblue;
--color-tl-bg: snow;
--color-tl-fg: rgba(0,0,0,1);
}

html {
font-family: "Trebuchet MS", Geneva, sans-serif;
font-size: 16px;
background-color:#022b3a;
}

.timeline-container {
display: flex;
position: relative;
flex-flow: row nowrap;
width: 96%;

background-color:  var(--color-tl-bg);
box-shadow:  7px 0px 30px 0px rgba(0,0,0,0.3);
}
.timeline-container:after {
content: '';
position: absolute;
width: 0; height: 0;
right:0;
margin-right: -1em;

border: solid var(--color-tl-bg) 1.6em;
border-left-width: 1em;
border-right-width: 0;
border-top-color: transparent;
border-bottom-color: transparent;

overflow: hidden;
}

figure {
display: flex;

transition: flex 0.15s ease-in-out;
z-index: 1;
}
figure:hover:not(:last-child) ~ figure:last-child {
flex: unset;
}
figure:hover:not(:nth-last-child(-n+2)) ~ figure:last-child figcaption {
color: var(--color-tl-chevron-fg);
font-weight: normal;
}
figure:hover:not(:last-child) ~ figure:last-child ul {
display: none;
}

figure figcaption {
position: relative;

background-color: var(--color-tl-chevron);
color: var(--color-tl-chevron-fg);
}
figure figcaption:before,
figure figcaption:after {
content: '';
position: absolute;
width: 0; height: 0;
right:0;
top:0;

border: solid var(--color-tl-chevron) 1.59rem;
border-left-width: 1em;
border-right-width: 0;

overflow: hidden;
}
figure figcaption:after {
margin-right: -1em;

border-color: var(--color-tl-chevron);
border-top-color: transparent;
border-bottom-color: transparent;
}
figure figcaption:before {
margin-right: 3.5em;

border-right-width: 1em;
border-left-color: transparent;
}
figure:hover,
figure:not(:hover):last-child {
flex: 2;
cursor: default;
}

figure:hover figcaption,
figure:hover + figure figcaption,
figure:not(:hover):last-child figcaption {
color: var(--color-tl-chevron-fg-hover);
font-weight: bold;
}
figure:hover ul,
figure:not(:hover):last-child ul {
display: flex;
}

ul {
display: none;
flex: 1;
}

ul li {
display: flex;
flex: 1;
justify-content: center;
align-items:center;

font-size: 0.85em;
list-style: none;
}

a,
a:visited,
a:focus,
a:active,
a:hover {
color: var(--color-tl-fg);

text-decoration: none;
cursor: pointer;
}

a:hover {
font-weight: bold;
}
<div class="timeline-container">
<figure>
<figcaption>2015</figcaption>
<ul>
<li><a href="">Jan</a></li>
<li><a href="">Feb</a></li>
<li><a href="">March</a></li>
<li><a href="">April</a></li>
</ul>
</figure>
<figure>
<figcaption>2016</figcaption>
<ul>
<li><a href="">May</a></li>
<li><a href="">Jun</a></li>
<li><a href="">July</a></li>
<li><a href="">Aug</a></li>
</ul>
</figure>
<figure>
<figcaption>2017</figcaption>
<ul id="2017-Portfolio">
<li><a href="">Oct</a></li>
<li><a href="">Nov</a></li>
<li><a href="">Dec</a></li>
<li><a href="">Jan</a></li>
</ul>
</figure>
<figure>
<figcaption>2018</figcaption>
<ul>
<li><a href="">Jan</a></li>
<li><a href="">Feb</a></li>
<li><a href="">March</a></li>
<li><a href="">April</a></li>
</ul>
</figure>
<figure>
<figcaption>2019</figcaption>
<ul>
<li><a href="">Jan</a></li>
<li><a href="">Feb</a></li>
<li><a href="">March</a></li>
<li><a href="">April</a></li>
</ul>
</figure>
</div>

• You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. – Toby Speight Dec 2 '19 at 13:30

# Implementation Review

Ten years ago there were typically users that would commonly use IE and code like this that creates elements, adds event handlers to those elements and then removes those elements had the possibility to leak memory. While that may not be an issue anymore it is something to consider. A simpler approach would use event delegation to observe clicks on a parent element and distribute event handling to other functions.

Instead of removing and re-adding elements, a simpler approach would be to apply a class name to the selected item and hide adjacent siblings via CSS. This helps limit browser reflow. On a page with few elements that might not be an important consideration but it would on a large page/application.

# Bug

When the back button is clicked, this line is called

let childrenArray = Array.slice(timeline.children);


Error: Array.slice is not a function.

Perhaps the intention was to call the slice method from Array.prototype on that array/nodelist:

let childrenArray = Array.prototype.slice.call(timeline.children);


# Code Review

## Variable declarations

Good job using const in some places. There are however places where const could be used instead of let - e.g. for timeline, since it never gets re-assigned. It is advised to default to using const and then switch to let when re-assignment is deemed necessary. This helps avoid accidental re-assignment and other bugs.

## DRY code

The following lines are quite repetitive:

 timeline.appendChild(createYearElement("2019"));
timeline.appendChild(createYearElement("2018"));
timeline.appendChild(createYearElement("2017"));
timeline.appendChild(createYearElement("2016"));


They go against the Don’t Repeat Yourself principle. A loop could simplify this:

for (let year = 2019; year > 2015; year--) {
timeline.appendChild(createYearElement(year));
}


This also makes changes to the year limit simpler.

## Extra lambda/anonymous function/closure

There is an extra function here:

document.addEventListener("DOMContentLoaded", () => {
generateTimeline();
});


That could be simplified to just a reference to the function:

document.addEventListener("DOMContentLoaded", generateTimeline);


## Simplified code

Putting the suggestions above to work the code can be simplified dramatically.

document.addEventListener("DOMContentLoaded", generateTimeline);
const selectedItems = document.getElementsByClassName('selected');
const timelineBox = document.getElementsByClassName('timeline_box')[0];
function generateTimeline() {
const timeline = document.getElementsByClassName("timeline")[0];
for (let year = 2019; year > 2015; year--) {
timeline.appendChild(createYearElement(year));
}
createBackButton();
if (selectedItems.length) { //do this instead of tracking focussed
return;
}
if (event.target.tagName.toLowerCase() !== 'div') {
return;
}
if (event.target.innerText === '2016') {
return;
}
const selectedItem = event.target.parentNode;
});

}

function createYearElement(year) {
const element = document.createElement("li");
element.setAttribute("class", "year");
element.setAttribute("id", \${year}YearElement);
const text = document.createElement("div");
text.innerHTML = year;
element.appendChild(text);
return element;
}

function createBackButton() {
const button = document.createElement("button");
button.innerHTML = "Back";
button.setAttribute("id", "backButton");

timelineBox.classList.remove('focussed');
selectedItems[0].classList.remove('selected');
});

timelineBox.appendChild(button);
}
.timeline_box {
width: 80%;
height: 50%;
background: lightslategrey;
margin: auto;
margin-top: 10%;
display: grid;
justify-content: center;
align-content: center;
position: relative;
}

.timeline_wrapper {
width: 500px;
}

.timeline_line {
z-index: 1;
width: inherit;
height: 20px;
margin-top: 5px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 45%, #fd2600 51%, rgba(255, 255, 255, 0) 57%, rgba(255, 255, 255, 0) 100%);
}

.timeline {
z-index: 2;
list-style: none;
width: inherit;
}

.timeline>li {
float: left;
margin-left: 100px;
background: greenyellow;
}

.timeline>li:nth-child(1) {
margin-left: 0;
}
.timeline_box.focussed .timeline > li {
display: none;
}
.timeline_box.focussed .timeline > li.selected {
display: block;
margin-left: 0;
}
.timeline_box.focussed .timeline > li.selected + li {
display: block;
margin-left: 400px;
}
#backButton {
display: none;
}
.timeline_box.focussed > #backButton {
display: block;
}

button {
width: 100px;
height: 30px;
color: black;
margin: auto;
}

html,
body,
element,
* {
margin: 0;
}
<body>
</body>