Core JavaScript library

The core JavaScript library in Halfmoon comes with a bunch of reusable functions, methods, and attributes for handling components that require JavaScript. This page talks about them in details.

Before we begin

For any of this to work, the halfmoon.js file must be included in a <script> tag, ideally at the end of the <body> tag. After this is done, a global namespace called halfmoon will be available, which contains all the functions.

Toggling dark mode #

The dark mode can be toggled using the halfmoon.toggleDarkMode() function. This adds/removes the .dark-mode class to/from the DOM's <body>, and saves the preference in a cookie called halfmoon_preferredMode. The value of this cookie can be "light-mode" or "dark-mode". Both of these values are strings, and their meanings should be self-explanatory.

Please note, prior to v1.1.0, the cookie for storing the preference was named darkModeOn, with a possible value of "yes" or "no" (again, both of these values are strings).

You can click on the following button to toggle dark mode.

<!-- Toggling dark mode -->
<button class="btn btn-primary" type="button" onclick="halfmoon.toggleDarkMode()">Click me!</button>

<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

Get the preferred mode #

The user's saved preference can be read using the halfmoon.getPreferredMode() method, which returns one of the following strings: "light-mode", "dark-mode", or "not-set" (if the cookie does not exist).

Method for getting the preferred mode
<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

<!-- Script for getting the preferred mode -->
<script type="text/javascript">
  if (halfmoon.getPreferredMode() == "light-mode") {
    // Light mode is preferred
  }
  else if (halfmoon.getPreferredMode() == "dark-mode") {
    // Dark mode is preferred
  }
  else if (halfmoon.getPreferredMode() == "not-set") {
    // Cookie does not exist
  }
</script>

Halfmoon also has a built-in function to read cookies called halfmoon.readCookie(cookieName). Therefore, this can be used to directly read the preference cookie, instead of using the halfmoon.getPreferredMode() method. This is shown in the code below:

Reading the preference cookie
<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

<!-- Script for reading the preference cookie -->
<script type="text/javascript">
  // Make sure that the cookie exists
  if (halfmoon.readCookie("halfmoon_preferredMode")) {
    if (halfmoon.readCookie("halfmoon_preferredMode") == "light-mode") {
      // Light mode is preferred
    }
    else if (halfmoon.readCookie("halfmoon_preferredMode") == "dark-mode") {
      // Dark mode is preferred
    }
  }
</script>

Set the preferred mode on load #

The saved preference can be automatically set on page load (ie, if the user prefers dark mode, then the dark mode will be set automatically) by adding the data-set-preferred-mode-onload="..." attribute to the DOM's body.

Please note, prior to v1.1.0, this attribute was actually called data-set-preferred-theme-onload. This alias still works for the sake of backward compatibility. However, it is strongly recommended that you use the new attribute name, and not the old one.

Anyway, the following code shows how the preferred mode can be set on page load using JavaScript:

Attribute for setting the preferred mode on load
<!-- Set preferred mode on load -->
<body data-set-preferred-mode-onload="true">
  ...
  <!-- Requires halfmoon.js -->
  <script src="path/to/halfmoon.js"></script>
</body>

How it works

If the preference cookie exists, this is given the first priority, because it means that the user has interacted with the page and set their own preferred mode (light or dark). If the cookie does not exist, then the priority is given to the user's system's color preference (prefers-color-scheme: dark).

Slight delay before setting the preferred mode

One possible issue you may have noticed using this method is the slight delay that occurs before the preferred mode is set. This is because the DOM has to be fully loaded before the JavaScript can actually attach a class to the DOM's body and set the preferred mode. This waiting causes the tiny delay. One way to fix this is by using a JS framework that can change routes without needing to reload the full page, ie, single page applications (SPAs).

Another way to mitigate this issue is with server-side rendering. In this case, simply avoid using the data-set-preferred-mode-onload="..." attribute, read the cookie server-side, and use it to set the correct mode when generating the template on your server. This is the method that is being used on this documentation website, which runs on Django (on the server). The same concept can be applied for other frameworks as well, such as Rails, Laravel, Flask, etc.

Changes from v1.0.x #

As mentioned above, prior to v1.1.0, a few things were different in the core JavaScript libarary (specifically relating to the dark mode functionalities). These changes are listed below:

  • The cookie for storing the user's preferred mode was named darkModeOn (instead of halfmoon_preferredMode), with a possible value of "yes" or "no". Again, both of these values are strings. This has been updated mainly to avoid possible conflicts with cookies set by other systems.
  • The attribute for setting the preferred mode on load was named data-set-preferred-theme-onload (instead of data-set-preferred-mode-onload). This alias still works for the sake of backward compatibility.

While these changes are minor, and non-breaking, they should at least still be kept in mind.

Toggling sidebar #

If you are using a sidebar component (opens in new tab) on your page, you can toggle it using the halfmoon.toggleSidebar() function. This adds/removes the data-sidebar-hidden="hidden" attribute to the page wrapper (.page-wrapper).

<!-- Toggle sidebar -->
<button class="btn" type="button" onclick="halfmoon.toggleSidebar()">Toggle sidebar</button>

<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

Shortcuts #

There are two built-in shortcuts which can be enabled by adding the following attributes to the DOM's <body>:

  • data-dm-shortcut-enabled="..." enables the shortcut for toggling dark mode by pressing shift + D.
  • data-sidebar-shortcut-enabled="..." enables the shortcut for toggling the sidebar by pressing shift + S.

Please note that these shortcuts will not interfere with input elements, as they fire only if no input element is focused. Moreover, they will also not interfere with browser shortcuts, as those are given priority. The following code shows a page with both the shortcuts enabled:

Attributes for shortcuts
<!-- Page with both the shortcuts enabled -->
<body data-dm-shortcut-enabled="true" data-sidebar-shortcut-enabled="true">
  ...
  <!-- Requires halfmoon.js -->
  <script src="path/to/halfmoon.js"></script>
</body>

Keydown event listener #

There is a method called halfmoon.keydownHandler(event) which fires every time a key is pressed. By default, this does nothing. However, you can override this method to handle keydown events without adding another event listener to the DOM. The following example shows how this can be done.

Focus with /
<div class="input-group w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <div class="input-group-prepend">
    <span class="input-group-text">Focus with <kbd class="ml-5">/</kbd></span> <!-- ml-5 = margin-left: 0.5rem (5px) -->
  </div>
  <input type="text" class="form-control" placeholder="Search repo" id="search-repo-input">
</div>

<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

<!-- Script for overriding keydown event handler function -->
<script type="text/javascript">
  /* Things to do once the DOM is loaded */
  document.addEventListener("DOMContentLoaded", function () {
    // Getting the repository search input
    var searchRepoInputElem = document.getElementById("search-repo-input");

    // Handle keydown events (overridden)
    halfmoon.keydownHandler = function(event) {
      event = event || window.event;
      // Shortcuts are triggered only if no input, textarea, or select has focus
      if (!(document.querySelector("input:focus") || document.querySelector("textarea:focus") || document.querySelector("select:focus"))) {
        // Focus the repository search input when [/] is pressed
        if (event.which == 191) {
          searchRepoInputElem.focus();
          event.preventDefault();
        }
      }
      // You can handle other keydown events here using if or else-if statements
    }
  });
</script>

Click event listener #

There is another method called halfmoon.clickHandler(event) which fires every time there is a click on the DOM. By default, this does nothing. However, you can override this method to handle click events without adding another event listener to the DOM. The following example shows how this can be done.

Number of clicks: 0

<button class="btn" type="button" id="click-me-button">Click me</button>
<p>
  Number of clicks: <span id="number-of-clicks">0</span>
</p>

<!-- Requires halfmoon.js -->
<script src="path/to/halfmoon.js"></script>

<!-- Script for overriding click event handler function -->
<script type="text/javascript">
  /* Things to do once the DOM is loaded */
  document.addEventListener("DOMContentLoaded", function() {
    // Setting the initial number of clicks
    var numberOfClicks = 0;
  
    // Getting the number of clicks element
    var numberOfClicksElem = document.getElementById("number-of-clicks");
  
    // Handle click events (overridden)
    halfmoon.clickHandler = function(event) {
      var target = event.target;
      if (target.matches("#click-me-button")) {
        numberOfClicks = numberOfClicks + 1;
        numberOfClicksElem.innerHTML = numberOfClicks;
      }
      // You can handle other click events here using if or else-if statements
    }
  });
</script>

Components that use the JavaScript library #

The following components make use of the core JavaScript library: