The first thing we need to do inside the JavaScript file is to wrap the entire code we write in the file into an immediately invoked function expression (IIFE). In doing this, we protect the scope of what we write from the global one and even use global variables with more commonly associated variable names inside our own scope. This is how this looks:
(function (Drupal, $) {
"use strict";
// Our code here.
}) (Drupal, jQuery);
The most important thing here is that inside this function we can now use the dollar sign ($) as a reference to the global jQuery object without interfering with other libraries that might use the same variable name. Also, we added the use strict declaration to ensure we write semantically correct code (and it's also part of the JavaScript coding standards for Drupal 8).
Let's now add the meat of our functionality and explain how it works:
Drupal.behaviors.helloWorldClock = {
attach: function (context, settings) {
function ticker() {
var date = new Date();
$(context).find('.clock').html(date.toLocaleTimeString());
}
var clock = '<div>The time is <span class="clock"></span></div>';
$(context).find('.salutation').append(clock);
setInterval(function() {
ticker();
}, 1000);
}
};
First of all, we are defining a new behavior, which is an object on the Drupal.behaviours object and needs to have a unique name. You can look at a single behavior as one piece of functionality. We only need one function on this object called attach, which receives two parameters--context (the page or part of the page that is being loaded) and settings (the variable containing data passed from PHP).
This function gets invoked by Drupal whenever behaviors need to be attached--Drupal.attachBehaviors(). This happens when the page gets loaded for the first time (in which case context is the entire DOM) or after an Ajax request (in which case context contains only the newly loaded parts of the page). Therefore, using the context instead of the entire document for looking up elements is best practice because it's more performant (especially after an Ajax request) and prevents other side effects.
Inside the attach function, we have our logic for creating a clock. First, we define a simple function that looks for the element with the class .clockand puts the current time into it. You'll notice that we used context to look for the element. Next, we create this element ourselves and append it to our salutation message element. Lastly, we set an interval every second to keep calling our ticker() function, essentially updating the time every second, giving the illusion of a clock. This is all pretty standard.
Clearing the cache and navigating to our /hello page, we can already see the new clock appearing (if we don't have the salutation message overridden). So we're done, right? Well, not really.
If we open up the browser's developer tools, namely the console, and try to attach the behaviors again:
Drupal.attachBehaviors();
We notice that our clock element gets appended again (it has been duplicated). Well, that's not right because if we have an Ajax request we run the risk of having this happen. This is where jQuery.once comes in.
The jQuery.once library is a plugin for jQuery, which allows us to track and make sure we are performing something only once. It's actually very simple to use. All we have to do is replace this line:
$(context).find('.salutation').append(clock);
With this:
$(context).find('.salutation').once('helloWorldClock').append(clock);
So basically, before doing the actual thing, we call the .once() method with an ID to use for tracking. This will ensure that whatever comes next in the chain is only applied to the elements onto which it has not been already applied. And now you also see why we wanted our library to depend on core/jquery.once one.
And with this, our clock is ready.