Problem
I’ve been annoyed with the new side bar layout, so I chose to hide it. But then, the useful links were gone, so I decided to make this little user script in Tampermonkey to bring them back to [roughly] how it looked before. Here is a screenshot:
It’s nothing very complicated, and it’s all plain regular JavaScript, but I’d like any suggestions to improve it before I put it on Stack Apps for everyone to see!
Note that design suggestions are also welcome, but I’d prefer if those were made in comments or in chat.
// ==UserScript==
// @name Stack Overflow custom top navbar
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Make Stack Overflow Design Great Again #MSODGA :)
// @author https://codereview.stackexchange.com/users/42632/phrancis
// @include http*://*.stackoverflow.com*
// @include http*://stackoverflow.com*
// @grant none
// ==/UserScript==
(function() {
"use strict";
const stackOverflowOrange = "#F48024";
// User of script can set these preferences to modify the script's behavior
const userPreferences = {
// Change to true to hide the "Jobs" Stack Overflow Careers link
hideJobsLink : false,
// Set your own colors for the linkss here
// Make sure to use valid CSS colors like "#f48024" or "black"
linkColor : "black",
linkHoverColor : stackOverflowOrange,
linkClickColor : stackOverflowOrange
};
// It is recommended to not change anything below this comment
// unless you know JavaScript, as it can break the functionality.
const links = [ { name: "Home", url: "https://stackoverflow.com/" }, { name: "Questions", url: "https://stackoverflow.com/questions" }, { name: "Tags", url: "https://stackoverflow.com/tags" }, { name: "Users", url: "https://stackoverflow.com/users" } ];
if (!userPreferences.hideJobsLink) {
links.push({ name: "Jobs", url: "https://stackoverflow.com/jobs?med=site-ui&ref=jobs-tab" });
};
const navbarContainer = document.createElement("div");
navbarContainer.id = "navbar-container";
document.getElementById("content").prepend(navbarContainer);
const navbarList = document.createElement("ul");
navbarList.id = "navbar-list";
navbarContainer.append(navbarList);
for (let item of links) {
const listItem = document.createElement("li");
listItem.className = "navbar-list-item";
const link = document.createElement("a");
link.className = "navbar-link";
link.href = item.url;
link.innerHTML = item.name;
listItem.append(link);
navbarList.append(listItem);
}
/**
* Add CSS properties to an HTMLCollection queried by document.querySelectorAll
* @param {string} selectorAllQuery
* a valid query to be used by document.querySelectorAll
* @param {object} cssObject
* an object containing pairs of CSS property names (in JS format, like listStyleTyle rather
* than 'list-style-type' and a value to assign to it
* @return {undefined}
*/
const applyCssToHTMLCollection = function(selectorAllQuery, cssObject) {
let elements = null;
try {
elements = document.querySelectorAll(selectorAllQuery);
} catch(exception) {
throw `Invalid document.querySelectorAll query expression: ${selectorAllQuery}`;
}
for (let element of elements) {
Object.assign(element.style, cssObject);
}
};
// Style inspired by example at the following link:
// https://www.w3schools.com/css/tryit.asp?filename=trycss_float5
applyCssToHTMLCollection("#navbar-list", {
listStyleType: "none",
margin: 0,
padding: 0,
overflow: "hidden",
textAlign: "center"
});
applyCssToHTMLCollection("li.navbar-list-item", {
float: "left"
});
applyCssToHTMLCollection("li.navbar-list-item > a", {
display: "inline-block",
color: userPreferences.linkColor,
textAlign: "center",
padding: "14px 16px",
textDecoration: "none",
fontSize: "1.4em",
fontWeight: "bold"
});
const navbarLinks = document.querySelectorAll(".navbar-link");
for (let element of navbarLinks) {
element.addEventListener("mouseenter", function() {
element.style.color = userPreferences.linkHoverColor;
});
element.addEventListener("mouseleave", function() {
element.style.color = userPreferences.linkColor;
});
element.addEventListener("click", function() {
element.style.color = userPreferences.linkClickColor;
});
}
})();
Solution
That’s generally a really neat code you’ve got there. Here are my remarks, though:
Applying styles and position of it in code
Instead of applyCssToHTMLCollection()
and especially 3 event listeners, you could create <style>
element, set its .textContent
once and append it to body.
Also, to prevent flash of unstyled content styles should be already in place before the elements are added to the body. For the same reason I would move document.getElementById("content").prepend(navbarContainer)
beyond the following for … of
.
throw {…} catch(…) {
in applyCssToHTMLCollection()
In applyCssToHTMLCollection()
you throw if querySelectorAll()
throws, which will happen only if it’s passed invalid CSS selector. Only selectors that can get to it are:
#navbar-list
,li.navbar-list-item
andli.navbar-list-item > a
Since they are all valid, you will never reach that catch()
and it is therefore needless to use it.
Also, it would be good to add @throws
to this function’s JSDoc.
let
vs cons
in for … of
In three for … of
s you use let
instead of const
. Despite intuitively it seems that, after all, that variable is going to change with the next iteration, we can actually use const
as long as we are not going to assign anything to it inside that loop.
Format
Compare readability of this:
for (let item of links) {
const listItem = document.createElement("li");
listItem.className = "navbar-list-item";
const link = document.createElement("a");
link.className = "navbar-link";
link.href = item.url;
link.innerHTML = item.name;
listItem.append(link);
navbarList.append(listItem);
}
to this:
for (let item of links) {
const listItem = document.createElement("li");
listItem.className = "navbar-list-item";
const link = document.createElement("a");
link.className = "navbar-link";
link.href = item.url;
link.innerHTML = item.name;
listItem.append(link);
navbarList.append(listItem);
}
Style
JavaScript Standard Style specifies use of two spaces for indentation and use of single quotes for strings. It’s also more popular option too. While style is matter of personal taste and habit of course, I think it’s still worth considering.
Rewrite
// ==UserScript==
// @name Stack Overflow custom top navbar
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Make Stack Overflow Design Great Again #MSODGA :)
// @author https://codereview.stackexchange.com/users/42632/phrancis
// @include http*://*.stackoverflow.com*
// @include http*://stackoverflow.com*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const stackOverflowOrange = '#F48024';
// User of script can set these preferences to modify the script's behavior
const userPreferences = {
// Change to true to hide the "Jobs" Stack Overflow Careers link
hideJobsLink: false,
// Set your own colors for the links here.
// Make sure to use valid CSS colors like "#f48024" or "black"
linkColor: 'black',
linkHoverColor: stackOverflowOrange,
linkClickColor: stackOverflowOrange
};
// It is recommended to not change anything below this comment
// unless you know JavaScript, as it can break the functionality
const links = [
{ name: 'Home', url: 'https://stackoverflow.com/' },
{ name: 'Questions', url: 'https://stackoverflow.com/questions' },
{ name: 'Tags', url: 'https://stackoverflow.com/tags' },
{ name: 'Users', url: 'https://stackoverflow.com/users' }
];
if (!userPreferences.hideJobsLink) {
links.push({ name: 'Jobs', url: 'https://stackoverflow.com/jobs?med=site-ui&ref=jobs-tab' });
}
// Set styles
const style = document.createElement('style');
style.textContent =
`#navbar-list {
list-style-type: none;
margin: 0;
overflow: hidden;
padding: 0;
text-align: center;
}
li.navbar-list-item {
float: left;
}
li.navbar-list-item > a {
color: ${userPreferences.linkColor};
display: inline-block;
font-size: 1.4em;
font-weight: bold;
padding: 14px 16px;
text-align: center;
text-decoration: none;
}
.navbar-link {
color: ${userPreferences.linkColor};
}
.navbar-link:hover {
color: ${userPreferences.linkHoverColor};
}
.navbar-link:focus {
color: ${userPreferences.linkClickColor};
}`;
document.body.appendChild(style);
// Create links
const navbarContainer = document.createElement('div');
navbarContainer.id = 'navbar-container';
const navbarList = document.createElement('ul');
navbarList.id = 'navbar-list';
navbarContainer.append(navbarList);
for (const item of links) {
const listItem = document.createElement('li');
listItem.className = 'navbar-list-item';
const link = document.createElement('a');
[link.className, link.href, link.textContent] = ['navbar-link', item.url, item.name];
listItem.append(link);
navbarList.append(listItem);
}
document.getElementById('content').prepend(navbarContainer);
})();