5. jQuery

Tired of juggling with crazily long JavaScript codes? Here's the tool to help you deal with your poor cognitive load.

Overview / The dollar sign / The essential functions / Some more useful functions / Case studies / Wait, I haven't done my job! / For the next unit...

Overview

All JavaScript codes make perfect sense: addEventListener? Sure. document.querySelector()? Fine. document.getElementByTagName('div')[0].addEventListener()? Probably a bit too much, right? Here's your savior: jQuery (https://jquery.com/). It's still based on JavaScript, but it makes JavaScript programming much easier.

Before introducing the basics of jQuery, let's try to embed jQuery into our website so we can use it. You can see different versions of jQuery at https://code.jquery.com/, and of course we go for the most updated 3.x version. On this webpage, you would see various types of codes for jQuery Core, including uncompressed, minified, slim, and slim minified. Most people would probably use the minified version, which comes without any comments and with simplified codes and a reduced file size to facilitate data transmission. The slim versions further exclude some functions that are considered more marginal, such as those related to CSS animation, so for us they could be a bit oversimplified.

Get ready

There are two ways of incorporating jQuery into your website projects. First, we can use the <script> HTML tag inside <head></head> in an HTML document to retrieve the jQuery file from the official website (here v3.6.1 is used as the example), just like how we linked a JS file to our webpage:

<script src="https://code.jquery.com/jquery-3.6.1.min.js"></script>

You can move the above line into <body></body>, which may not affect how your website works eventually. But to be safe, it is still the best idea to have the above line inside <head></head> as part of the metadata of a webpage, which is always loaded first. Crucially, if you have any JS codes depending on jQuery to be run the JS codes or the link to the JS file depending on jQuery must come after the line that imports the jQuery file. Or, your website may not work properly since the jQuery codes are run when the jQuery file is still on its way.

Alternatively, if your Web space can afford an additional file sized just a few hundreds KBs, which is likely the case if you host your website at GitHub, then why not just download the entire jQuery Core file and put it in your own website? One benefit of doing this is that you don't rely on the external source of your jQuery Core file, so your website remains unaffected even when the official jQuery website is down (very unlikely, but who knows?). So assuming that you have a js directory under the root directory / as suggested in previous units, we can store the jQuery Core file to that directory. Again, the files you store in the subdirectories directly under the root directory are assumed to be used globally in your entire website, rather than specifically for a given webpage. Now, we can embed the following HTML codes inside <head></head> to load the jQuery file stored in your own website into your webpage:

<!-- The first "/" in the path refers to the root directory -->
<script src="/js/jquery-3.6.1.min.js"></script>

Because from now on, your codes will be based on the jQuery Core file, your links to other JS files you have for a particular webpage must follow the above lines used to load the jQuery Core file. Once again, if you have any jQuery codes that need to be run at the very beginning after a webpage is loaded, then you want to be sure that the jQuery Core file is loaded first.

So, now you're ready to enjoy Web programming even more, and I'm here to help. This won't be a complete guide, so just in case that you want to know further about what jQuery can make things easier, check the full jQuery API documentation at https://api.jquery.com/.

The dollar sign

Source: Jerry McGuire the movie

In our JavaScript tutorial, we have learned the window object is at the center of JavaScript programming, which refers to the Web browser window and could be omitted when we try to access the HTML document itself inside the browser window with the keyword document. Here in this tutorial, we would start with the dollar sign $, which stands for jQuery itself. For instance, the two lines of jQuery codes below are exactly the same thing, except that the first line is simpler than the second line:

$.ajax(); // You don't have to know what ajax is at this point
jQuery.ajax(); // The same thing

Not only does it help to save your effort in writing your codes, it also helps reduce the size of your JS files and speed up the loading of your entire website. Let's see how much effort jQuery saves in terms of the action we possibly need the most in our JS codes - retrieving HTML elements.

The query selector in three characters

I hope you still remember a few JS functions we have used to access an HTML document and retrieve some HTML elements. These includes getElementById(), getElementsByTagName(), getElementsByClassName(), querySelector(), and querySelectorAll(). The last two are the most crucial ones when you need to get HTML elements depending on the complex relationship between children and ancestors in a DOM. Supposed that you need to retrieve all <p> elements that have <body> as their immediate ancestor, you might need to write the expectedly long JS codes like:

document.querySelectorAll('body > p'); // Get all <p> elements immediately inside <body>

In jQuery, we can exactly replicate the same function of querySelectorAll() with three characters, namely $():

$('body > p'); // Also get all <p> elements immediately inside <body>

Just like querySelectorAll(), $() returns an array of one or more matched HTML elements as a jQuery object or an empty array that has a length of zero. Let's create the following HTML document with some practices of retrieving HTML elements using jQuery:

<body> <p id="first"> p1 </p>
<p class="special"> p2 </p>
<p class="special" id="last"> <p>
</p>
</p>
</body>

Now, let's try to do the find the following HTML elements and print their HTML contents out in the browser console:

  1. The <p> element that is the immediate child of the element whose class is last.
  2. The last <p> element whose ancestor is <body>.
  3. All <p> elements that are an immediate child of <body>.
  4. The second <p> element of the class special.

eq(), html(), and attr()

The way you completed the above task may still involve some old thinking with the idea of array, which is absolutely nothing wrong. For instance, you might use the following code to retrieve the first <p> element, where the square brackets are used to indicate the first element of the array returned by $():

$('p')[0]; // The first of all <p> elements

The above line will give you the expected results, but its problems are threefold. First, if you choose to do Web programming and access HTML elements using jQuery, then you should try your best to always stay with jQuery to maintain the consistency in your codes. If this first reason is just conceptual for you, the second reason is definitely more practical. That is, jQuery makes every other move in plain JavaScript easier. Imagine that there is an array of HTML elements, and now you need to retrieve the last element of the array of an unknown length. What do you need to do in JavaScript? First, we know the number that represents the last position of an array is always 1 less than the length of the array, since the position number starts with 0. Thus, you must get the length first and substract 1 from the length as the position number, and use this number to get the last item of an array:

let allPs = $('p'); // Get all <p> elements
let lastPos = allPs.length - 1; // Calculated the number of the last position
allPs[lastPos]; // Get the last item in the array

Gee! It's just a lot of works. The jQuery developers must have been bothered by the same problem, which drove them to create a great jQuery function to help simplify the process, namely eq(), which stands for equal. Similar to the square brackets [], eq() allows you to choose a specific item based on an index, except that you can count backward from the last item of an array with a negative number:

$('p').eq(0); // The first of all <p> elements
$('p').eq(-1); // The last of all <p> elements

Finally, when you use eq() to retrieve an HTML element from a set of matched elements, the element is wrapped in the jQuery format, meaning that you can keep taking advantage of simpler jQuery functions to complete some tasks. For example, if you need to get the HTML contents of a retrieved element, you can use the jQuery function html() (which corresponds to the innerHTML property in plain JavaScript):

$('p').eq(0).innerHTML; // 'undefined', since innerHTML doesn't work for an element wrapped using jQuery
$('p').eq(0).html(); // The HTML contents of the first of all <p> elements

If you're very sure that you just need the first element of a group of selected elements, you can further simplify the above code by removing eq(0):

$('p').html(); // Still the HTML contents of the first of all <p> elements

Let me introduce one more convenient function, attr(), which is the shortened form of attribute. This jQuery function allows you to get the value of any attribute of a retrieved HTML element. For example, if you need to get the id attribute of the first <p> element in the HTML document we created above, both lines below work:

$('p').eq(0).attr('id'); // You get "first"
$('p').attr('id'); // You get "first", too!

Isn't it so much easier to do different things in jQuery? That's why jQuery becomes so popular in Web programming. Note that jQuery is still not magic at all - it is still based on the original JavaScript and the developers of jQuery simply create functions to help simply our use of JavaScript. Thus, the convenience of jQuery comes at the cost of the computational efficiency of JavaScript, although with very powerful computers and mobile devices at our disposal, the difference is often negligible.

Some essential functions

With a general understanding of how jQuery works, let's move to some tutorials introducing jQuery functions that you will be using a lot in addition to eq(), html(), and attr() above.

val()

In the JavaScript tutorial, we had examples in which we tried to retrieve or set the value of an HTML element, with a length code like document.querySelector(some element).value. Now, it can be reduced with the jQuery function val(). This can be demonstrated with an HTML document that has the following input element in <body>:

<input type="text" value="This is a default value." />

In a JS file, we can try to show an alert box with the value of the input element with the val() function when the window is fully loaded:

window.onload = function() { var defaultInputVal = $('input').eq(0).val();
alert(defaultInputVal);
}

Of course, it means that you can set the value of an HTML element with the same function, too:

window.onload = function() { $('input').eq(0).val('This is not the default value.'); // Set a new value
alert('A new input value is set!');
}

on() and off()

Remember the addEventListener() function of an HTML object accessed in a JS file to create some interactive interface for your website? There's of couse an equivalent function in jQuery as well, which is on(). This function requires two types of data: The first one is event type (as a string), and the second one is a callback function executed when an event is fired. Let's try the click event first:

window.onload = function() { // Make sure the document is fully loaded $('input').eq(0).on('click', function() { alert('An input is clicked!'); }); }

Now let's do a more complicated trick with the keypress event. That is, to update what you type in the input to a <textarea></textarea> inline element instantly. Let's create the element we need right before the <input> element:

<!-- Set the inline style to make a larger textarea, and add a line break after it -->
<textarea style="width: 300px; height: 300px;"></textarea><br/>

What we need to do next is to write jQuery codes doing the following things: First, add an event listener for the keypress event to the <input> element. Second, set the inner HTML contents of the <textarea> element every time you edit the contents of the <input> element.

window.onload = function() { $('input').eq(0).on('keypress', function() { $('textarea').eq(0).html($(this).val()); // Do you know what this line means? }); }

In fact, jQuery comes with functions that directly correspond to the event types accepted in on(), such as click() and keypress(), which only takes a function that is executed when an event is fired. How about let's rewrite the above two sections of codes with the two functions that seem more direct?

// Try the other one by yourself!
window.onload = function() { $('input').eq(0).click(function() { // Replace on() with click() alert('An input is clicked!'); }); }

I usually still go for on() when I do jQuery programming. The main reason is that I found the equivalent functions like click() or keypress() don't work in all contexts for some reason. Maybe it's just my own issue since I'm pretty amateurish in this field.

Before we move on, I'd like to talk a bit more about the special interaction between on() and eq(). When I talked about val() earlier in this section, I specifically said that you could omit eq() if you are pretty sure that you always just need the value of the first element of a group of selected elements. However, if you omit eq() when you try to add an event listeners with on(), you're actually attaching exactly the same event listener to ALL elements selected by $(). Let's add another <input> element in the previous text-syncing example, and remove eq(0) after $() and see what happens:

...
<!-- Add another input element in the HTML file -->
<input type="text" value="This is a default value." />
<input type="text" value="This is a second input element." />
...
// Remove eq(0) in your JS file
window.onload = function() { // Attach the same even listener to all input elements
$('input').on('keypress', function() { $('textarea').eq(0).html($(this).val()); });
}

Since you already understanding the meaning of on(), you possibly already know what off() means - yes, to remove an event listener, just like removeEventListener() in plain JavaScript. To demonstrate its use, let's follow an earlier text-syncing example with a button which, if clicked, would stop syncing:

<input type="text" value="This is a default value." />
<input type="button" id="stopSync" value="Stop Syncing!" />

Then, in addition to the event listener for the text-syncing function associated to the text input field, we also need an event listener attached to the button to detach the text-syncing event listener (what a tongue twister):

window.onload = function() { // Text-syncing event listener $('input').eq(0).on('keypress', function() { $('textarea').eq(0).html($(this).val()); });
// Stop-syncing event listener
$('#stopSync').on('click', function() { $('input').eq(0).off('click'); // Specify the event type in off() })
}

Hopefully, the above tutorial allows you to get hold of the fundamental aspects of jQuery programming. It's actually the same logics that you've learned in the JavaScript tutorial, but represented in an easier way. But did you just think that the tutorial has come to an end? Nope, you wish.

Some more useful functions

Source: Bill Watterson

If the previous section is not a tutorial of useful jQuery programming, what could be? But we are actually not that far off the starting line. There are still a lot to do to equip you with the skills you need to design a great interactive website.

css()

In plain JavaScript, we have to again specify a long string of codes in order to get or set JUST ONE CSS property. JUST ONE!!! For instance, the following codes only get you an HTML document with a black background color:

document.body.style.backgroundColor = 'black';

Imagine how many lines you need to write to set multiple CSS properties, and even for a group of HTML elements. Here in jQuery, you can do it all at once with the function css(), which also adds inline CSS properties to an element or a set of elements. Let's try this function with the following blocks inside <body></body>.

<div style="height: 50px; width: 50px; background-color: yellow;"></div>
<div style="height: 50px; width: 50px; background-color: yellow;"></div>
<div style="height: 50px; width: 50px; background-color: yellow;"></div>
<input type="button" id="changeOne" value="Change the color of one!" />
<input type="button" id="changeAll" value="Change the color of all!" />

Our goal is to add a click event listener to each button and use the css() function to change the background color of the first block or all blocks. Since this involves only one CSS property, the css() function requires two string data - the first is the name of the CSS property, as in a CSS file (such as background-color), and the second is the value of that property. Let's do it.

window.onload = function() { // Use id to add an event listener
$('#changeOne').on('click', function() { // Change only the first block
$('div').eq(0).css('background-color', 'red');
});
// Use id to add an event listener
$('#changeAll').on('click', function() { // Change all blocks
$('div').css('background-color', 'red');
});
};

Now, if you need to set multiple CSS properties with css(), just put in pairs of property names and values as strings separated by a colon : inside {} and separate every two pairs with a comma, very similar to what you've done in a CSS file! We can therefore easily change the background color of a block, as well as the size of it!

...
$('#changeOne').on('click', function() { // Specify multiple CSS properties in css()
$('div').eq(0).css({'background-color': 'red', 'height': '70px', 'width': '70px'});
...

It is just this easy to change the appearance of an element with this second use of css() in jQuery! Well, you still can retrieve one CSS property at a time with the first use of css() - bummer. But again, we should not take everything for granted, and this is the best we can get so far.

Making changes to more attributes

So far we've seen some jQuery functions that allow you to retrieve/change the attributes of an element. The value attribute could be accessed with val(), and the style attribute could be accessed with css(). You can keep playing around with css() especially when you need to create an interactive Web interface that changes the appearance HTML elements based on users' actions - that is, when an action is detected, an event is fired to apply the css() function to a specific element.

But here we will learn another more elegant way of changing how a specific element looks like using the jQuery functions addClass() and removeClass(). As smart as you are, you might already know what these two functions mean, right? That is, to add and remove a specific label from the class attribute of HTML elements. Thus, the idea is that you can define the CSS properties for a particular class in your CSS file. Then, you only need to add the name of this class to HTML elements when necessary. And this approach opens up more possibilities in our Web design.

Let's wind back to our previous HTML example repeated below without the two buttons and the inline style. In this old example, we need the buttons combined with the css() function to change the appearance of one or all of the <div> elements. But now, we can define a class in our CSS file, and add this class to a clicked <div> element to change its size and color.

<div></div>
<div></div>
<div></div>

Let's move the inline styling for the <div> elements as well as the styling we used in the css() function into a CSS file defined for the class named selected:

/* Default styling for all DIVs */
div { background-color: yellow;
height: 50px;
width: 50px;
}
/* Styling for DIVs in the class (.) selected */
div.selected { background-color: red;
height: 70px;
width: 70px;
}

The last step is to associate an event listener to all <div> element using the jQuery function on(): When a <div> element is clicked, the class name selected is added to that particulart element with addClass(). Try it yourself! (and select the following section to get the solution)

$('div').on('click', function() { $(this).addClass('selected'); });

Now, how do we de-select an element using removeClass()? Two steps are necessary here. First, we need to limit the first event listener to <div> elements without the selected class name, which could be achieved using the CSS pseudo-class not() (do you still remember how to use it?). This first step is essential because you want addClass() to be applied to <div> elements without the class name and removeClass() to be applied to those with the class name. Second, we have to add an event listener to a clicked element after addClass() is applied, which is fired to apply removeClass() when the element is clicked again. If you try to create this event listener separately in the onload function, it won't work (guess why?). Again, try it yourself (select the following section for the solution)!

// Limit the first event listener to non-selected DIVs
$('div').not('.selected').on('click', function() { $(this).addClass('selected');
// Add another event listener to apply removeClass() to selected DIVs
$(this).on('click', function() { $(this).removeClass('selected'); })
});

Note that in the current example, the selecting-de-selecting function only works once. Do you know why that is? The reason is that the first click event listener with addClass() is overwritten by the second one with removeClass(), so the code could never add selected back to a given element again. I've got a solution, which requires you to enclose the selcting-de-selecting steps in a custom function, and re-associate this function to the same element after removeClass() is applied. Let me just leave this to you to help you explore the world of JavaScript/jQuery coding.

Finally, how about creating another two buttons to (de-)select all <div> elements? Assuming that the buttons have selectAll and selectNone as their id, then the following event listeners are what you need for the functions:

$('#selectAll').on('click', function() { // Add class 'selected' to all DIVs
$('div').addClass('selected');
});
$('#selectNone').on('click', function() { // Remove class 'selected' from all DIVs
$('div').removeClass('selected');
});

DOM insertion and removal

Source: 九品芝麻官 the movie

While the contents of your personal websites would very likely stay intact, your might need to work on webpages in which elements could be added and deleted. Of course, JavaScript is ready for this need, let along jQuery - these three functions append(), prepend(), and remove() would do the job for you.

  • append() - Add an HTML element to be the last child of one or all selected elements.
  • prepend() - Add an HTML element to be the first child of one or all selected elements.
  • remove() - Remove all selected HTML elements.

With these functions, we can easily (well, relatively) create a to-do list (which does not memorize things, though). Let's start with an HTML document with a table element and try to make it a to-do list. Inside <table></table>, we need thead for the headers and tbody for table rows. Again, their presentation is identical by default - we simply try to follow the semantic principles to use appropriate HTML tags for functionally different contents. Each row is represented using a tr element, in which two td elements are included to represent two columns. Inside <tbody></tbody>, the first column presents the date information, and the second presents the content of a to-do item. In each second column, we have the letter X for a clickable hyperlink that serves as a deletion button. Note that the href attribute is left blank since we don't need the "link" to redirect us to anywhere else.

<table> <thead> <tr><td>Date</td><td>Content</td></tr> </thead>
<tbody> <tr><td>2020-10-25</td><td>Create a to-do list <a href="">X</a></td></tr>
<tr><td>2020-10-26</td><td>Assignment Deadline <a href="">X</a></td></tr>
</tbody>
</table>

We can then begin our first step to make it possible to remove an item with remove() when X is clicked. Let's first build a function that is executed when a click event is fired, and let's call it removeItem. Why do we need to create this function independently? Well, when we are able to add new items to the list, we also need the X link of these items to listen to the same clicking event and then execute the same function. So, by creating an independent function, it's easier to re-use the same codes. The function must be ready to receive an event object that is passed from an event listener when an event is fire. We need this object to stop the default action of a link with its preventDefault() function, so we won't be redirected to nowhere when X is clicked. Then, since our goal is to remove the entire row represented by a tr element, and every X is the child of a tr, we can rely on this relation to get the ancestor tr element when X is clicked. This can be done with the closest() function in jQuery, which finds the ancestor HTML tag (tr in this case) closest to the element listening to a fired event (a in this case). After finding this ancestor element, we can then apply remove() to get rid of the entire row. Finally, let's attach this function to the event listener with the function on().

function removeItem(event) { // Need to stop the default action of a link
event.preventDefault();
// Get the 'tr' element that is the closest parent of the clicked 'X' (represented by 'this')
var targetRow = $(this).closest('tr');
// Remove the target 'tr' element
targetRow.remove();
};
window.onload = function() { // Make every 'a' inside 'td' listening to a clicking event and execute 'removeItem'
$('td > a').on('click', removeItem);
}

Okay! We are now ready to move to the next stage - to be able to add items to a list. For this, we firstly need to add a form in which date and content can be input separately. We hope that when we click the Add button, the two fields could be obtained as date and content respectively, which will be combined with raw HTML codes, and then inserted as the last item into the list using append(). Then, we need to make the X link in the newly added items listen to a clicking event and execute removeItem() built above.

<form> <input type="date" />
<input type="text" placeholder="Content" />
<input type="submit" value="Add" />
<input type="reset" value="Clear" />
</form>

Let's also create the function executed when clicking the Add button as an independent one, so our JS codes look more organized.

function addItem(event) { // Avoid the default action caused by the Submit button
event.preventDefault();
// Get the value of the first 'input' element as date
let date = $('input').eq(0).val();
// Get the value of the second 'input' element as content
let content = $('input').eq(1).val();
// Create a clickable 'X' link for the newly created item
let removeLink = '<a href="">X</a>'
// Insert an empty new row into 'tbody' as the last item using append()
$('tbody').append('<tr></tr>'); // Combine everything together into two columns, and make them the HTML content of the newly inserted (last) 'tr' in 'tbody'
$('tbody > tr').eq(-1).html('<td>'+date+'</td><td>'+content+removeLink+'</td>')
// Add the same 'removeItem' function to the event listener of the 'X' link of the newly created item
$('tbody > tr > a').eq(-1).on('click', removeItem);
// Simulate the clicking event for the Reset button with trigger()
$('input[type="reset"]').trigger('click');
};

Just one more small step to make everything work - add the above addItem function to the event listener of the Add button. Should I still tell you how to do this in window.onload...?

$('input[type="submit"]').on('click', addItem);

Before we wrap up this section, why don't you try to switch from append() to prepend() by yourself, and allow you to insert a new item to the beginning of the list?

Now you possibly have another question: How do I insert a new item and then re-organize the entire list based on the ascending/descending order of the date column? I won't explain carefully because it would probably take another 30 minutes to do the whole thing, but you're gonna need some loops in your JS codes for sure :)

Case Studies

We've talked about so many things in jQuery, but perhaps not all of them are useful in your case. So, how about let's try to create an elegant side/top navigation pane using HTML, CSS, and jQuery together, just like the ones below?

Menu 1

The two ideas include some lengthy codes and complex ideas, so to avoid making this section extremely long, let me explain all these in a lecture.

Synchronous asynchrony

Source: Unknown

We are entering a slightly even more technical part of Web programming, that is, to know how to make a process asynchronous. This shouldn't be a problem that concerns you in your Web design projects, since most of times you only need to present data elegantly, and your JavaScript/jQuery codes are usually executed sequentially following their order in the JS files - The first line is done, then the second line, and then the third line, and so on. For instance, in the following JS codes, the third line always waits for the second line to complete, and you always get the sum of the two numbers. There's never the case where alert() is run before the sum is computed.

var x = 5, y = 3;
var sum = x + y;
alert(sum); // Always get the sum of x and y

However, a process can be asynchronous if it is independent of the main flow of the sequential execution of your codes, which makes sense considering the efficiency in running a computer program. For instance, perhaps some contents of your website are retrieved from some external servers, but the internet connection between the server of your website and the external server is extremely slow. If this step is sequential and thus synchronous, then it would block everything else following this process, including the codes that are unrelated to this process (such as animation), which makes your Web program in efficient.

some codes here;
retrieving external info; // Extremely slow connection...
animation codes; // Wait for years to run...

To avoid such an issue, the process of retrieving the external information is made asynchronous so it can take as long as it needs to complete the process, and the rest of the codes can keep running at the same time (which confusingly matches another definition of synchrony...). This is great because now the codes that animate HTML elements won't have to be delayed. Okay, so asynchrony is great from a computational perspective, but what about the data retrieved from the external server? How can we deal with it asynchronously? This is crucial because sometimes it is the content of your website that is determined by the external information, so your asynchronous process needs to take actions to present the information when the process is done, and this is exactly what you have to learn in this section.

Now, let's assume that you hope to thoughtfully provide weather information retrieved from an external weather server in your website. Then you can sign up for a free account at OpenWeatherMap.org first, which allows you to get some basic current weather information using the following URL. We can try to get the current weather of Hsinchu City by replacing {city} with hsinchu and {country} with tw. You will also to replace {AppId} I provide in my lecture or from your account.

https://api.openweathermap.org/data/2.5/weather?q={city},{country}&appid={AppId}

By pasting this URL to your browser window, you will see almost immediately an object-like data (which is called a JSON object) returned from their weather server. You are referred to their API docs for the data represented by each field. If you need to use this information in your jQuery codes, you could enclose the URL in $.get() (which is equivalent to jQuery.get()):

$.get('https://api.openweathermap.org/data/2.5/weather?q={city},{country}&appid={AppId}');

To deal with the data completely retrieved from the weather server after the process is done, we could add a callback function to $.get() as a second parameter, which receives the retrieved data as a single parameter as well:

$.get(URL, function(result) { alert('asynchronous done!'); });

Or, more intuitively, perhaps, we can use the function done() of the object created using $.get(), which simply does a similar thing:

$.get(URL).done(function(result) { alert('asynchronous done!'); });

This line of $.get() code doesn't block the execution of the codes following it, as demosntrated below:

// Asynchronous
$.get(URL).done(function(result) {
alert('asynchronous done!'); });
// Synchronous, which is the message you get first
alert('synchronous done!');

With a general understanding of how $.get() works, let's try to design a webpage that shows the current temperature in Hsinchu. In the following HTML codes, we have a "loading" text span, which appears by default. We also have a hidden text span that will be made visible after the content is filled with the weather data retrieved from the weather server.

<h3>Current Temerature in Hsinchu</h3> <p> <span id="loading">Loading...</span> <span id="temp" style="display: none;"></span> </p>

In our JS codes, we will attempt to get the weather data in window.onload with $.get(), fill the hidden text span with the temperature information with the jQuery function html(), hide the "loading" text using css(), and then make the hidden text span visible.

$.get(URL).done(function(result) { // Get the current temperature (round to the first decimal)
let currentTemp = Math.round(result.main.temp * 10)/10;
// Put the temperature into the hidden text span
$('#temp').html(currentTemp + ' C');
// Hide the "loading" text and make the hidden span visible
$('#loading').css('display', 'none');
$('#temp').css('display', 'inline');
});

"When" to "resolve" a "Deferred" process

Alright, here's just one asynchronous process to deal with. But what if there are more? If we also hope to get some weather forecast data from the weather server, you need another request using the following URL template, which results in another asynchronous process:

https://api.openweathermap.org/data/2.5/forecast?q={city},{country}&appid={AppId}

How do we make sure that an action is taken after both asynchronous processes are done? One way that you could think of is to run one $.get() function after another $.get() is done:

$.get(currentURL).done(function(current) { $.get(forecastURL).done(function(forecase) { console.log('both are done!'); });
console.log('Got current data!'); // You get this message first
});

But this is just for two asynchronous processes. If there are more, you'll have to keep embeddeing each $.get() in another, and your JS codes would just look crazy. So, more intuitively, you would naturally think if there's anything you can do in jQuery to monitor every individual asynchronous process, and take some actions when all processes are complete. This is the case where you need Deferred(), resolve(), and when().

First of all, you can use $.Deferred() to create an object linked to an asynchronous process as follows:

// dfp = Deferred Process
var dfp = $.Deferred(function() {});

With an asynchronous process created this way, you can decide when to send a signal to tell the process that monitors all asynchronous processes that this particular process is done, and you can do it with resolve():

var dfp = $.Deferred(function() { dfp.resolve(); // Send a "done" signal });

Following this idea, we can logically create two independent asynchronic processes for the two weather data requests:

// Request the current weather data
var dfpCurrent = $.Deferred(function() { // Send you request here
$.get(currentURL).done(function(result) { // Do something after complete data transmission
// Then send a "done" signal
dfpCurrent.resolve();
});
});
// Request the weather forecast data
var dfpForecast = $.Deferred(function() { $.get(forecastURL).done(function(result) { // Send a "done" signal after complete data transmission
dfpForecast.resolve();
});
});

We have one more step to make everything work, which is to build a monitoring process for all these asynchronous processes using $.when(). You can pass all the created deferred processes to this function, and attach a done() to it to take some actions after resolve() is called for every deferred process passed to $.when():

// Pass deferred processes
$.when(dfpCurrent, dfpForecast).done(function() { // Do something after all processes are "resolved" });

Now you have the tools to build a webpage that can present both current and some future weather information simultaneously with some elegant JS codes, right? Let's try to complete this mini project!

For the next unit...

All Copyright Reserved; Tsung-Ying Chen 2021