Nov 6, 2020
Once Angular is disabled, any Javascript that you need to actually run your website (not just build and render each page) will need to be added separately. I learned from Sam Vloeberghs source code that one good way to do this is to add it directly to your project's index.html
file. The script will then be copied in each static page that Scully generates for your site (every route in the scully-routes.json
file).
The only thing I need Javascript for right now is a dropdown menu. And first I tried to see how easy it was to do with just CSS, but what I found online was that you have to get pretty complicated to get the behavior I was looking for, which is you click the button to open the menu, and then click anywhere outside the menu (including the button) to close it. It is possible to do that with CSS, but you have to REALLY not want any javascript to add all that extra code in.
I'll show two ways of adding the dropdown menu with a little javascript. The first is if inline event handlers are allowed e.g. onclick=myFunction()
. My content security policy disallows this so I implemented a second method that adds a click listener to a CSS class that I only use for the button.
For this method, I am shamelessly copying the sample code from W3 schools.
<!-- dropdown menu - mine is in my toolbar.component.html file -->
<div fxFlex fxLayoutAlign="flex-end">
<a class="valign-center dropBtn" onclick="dropMenu()" aria-label="menu link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="28px" height="28px" class="dropBtn">
<path d="M0 0h24v24H0z" fill="none" class="dropBtn"/>
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" class="dropBtn"/>
</svg>
</a>
<div id="navDropdown" class="dropdown">
<a [routerLink]="['/about']">About</a>
<a [routerLink]="['/learn']">Learn in Public</a>
<a [routerLink]="['/projects']">Projects</a>
</div>
</div>
/* styles.css - note the CSS classes in the above html */
.dropBtn { text-decoration: none;} /* The only reason for this class is to group the dropdown button elements for the event listener in the index.html file script */
.dropdown {
display: none; /* menu is hidden to start */
position: absolute;
top: 45px; /* this drops the menu below the dropdown button */
z-index: 500; /* menu will be on top when visible */
}
.show { display: block } /* when this class is added to the dropdown menu with the javascript function (below), it will become visible */
<!-- index.html -->
<!-- NOTE: This code only works if inline event handlers are allowed
by the Content Security Policy. -->
<!doctype html>
<html lang="en">
<head>
<!-- headers taken out to save space -->
</head>
<body class="mat-typography">
<app-root></app-root>
<script>
function dropMenu() { //whenever the button is clicked
//toggle whether the dropdown menu is shown
document.getElementById("navDropdown").classList.toggle("show");
}
//whenever anywhere in the window except the button is clicked
window.onclick = function(event) {
if (!event.target.matches('.dropBtn')) {
document.getElementById('navDropdown').classList.remove("show");
}
}
</script>
</body>
</html>
I am using an SVG icon inside a <a>
block for my button, and I ran into the issue that if I set just my <a>
element with the CSS class, clicks inside the <a>
element on the SVG element or path would not register. Even with the CSS class on the <a>
and SVG elements (I think they were canceling themselves out?). I found the solution at Chris Ferdinandi's website Go Make Things. Instead of using event.target.matches
in the event listener, use event.target.closest
to return True whenever you click within an element with the class:
<!-- index.html -->
<script>
document.addEventListener('click', function (event) {
if (event.target.closest('.dropBtn')) {
document.getElementById('navDropdown').classList.toggle("show");
} else {
document.getElementById("navDropdown").classList.remove("show");
}
}, false);
</script>
We will use the same html for the button as before except just remove the inline event handler because we will listen for clicks on the dropBtn
class instead. Notice now too that the SVG and path do not need the dropBtn
class anymore because we use event.target.closest
in the script.
<!-- dropdown menu - mine is in my toolbar.component.html file -->
<div fxFlex fxLayoutAlign="flex-end">
<div class="valign-center dropBtn" aria-label="menu link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="28px" height="28px" class="dropBtn">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</svg>
</div>
<div id="navDropdown" class="dropdown">
<a [routerLink]="['/about']">About</a>
<a [routerLink]="['/learn']">Learn in Public</a>
<a [routerLink]="['/projects']">Projects</a>
</div>
</div>
.dropdown {
display: none;
position: absolute;
top: 45px;
overflow: hidden;
z-index: 500;
background-color: white;
box-shadow: 0px 0px 16px 0px rgba(0,0,0,0.2);
}
.show { display: block }
.dropBtn {
cursor: pointer;
color: white;
float: left;
}
I hope this helps you add any javascript you need to your Scully site with Angular disabled!