Home Uniquely NZ Travel Howto Pauline Small Firms Search
Web Site Design
Advanced JavaScript Popup Windows for Images

Background

The Howto Article on Popup Windows for Images has been one of the most popular and I get a lot of feedback. I often got requests for information on how to add various "bells and whistles" such as titles and background colours. I however resisted making the original article too complex and put people off who know little JavaScript so I have instead added this "Advanced JavaScript Popup Windows for Images" supplement which gives a number of ideas for the more experienced JavaScript user and also "cut and paste" code for the less experienced. It should be read in conjuction with the earlier article. This page itself is now over 12 years old and I have gradually includeded the improvements and enhancements I have added on my own pages and it shows the chronological development and finishes with my full current code.

NOTE: This page itself has now been updated to HTML5 to be consistent with the rest of the series but the code described is generally for HTML 4.01 Transient so uses HTML attributes for presentation rather than CSS (Cascading Style Sheets). I have added a few comments where changes would be required and the last article in the series which introduces an alternative to popups, namely lightbox overlays has the updates to the code.

Some possible approaches to adding titles, background colours etc to a popup window

There are many possible ways to make these additions including using the various Parameters associated with the Document Object and I tried most of them. In practice this method gives problems with various Browsers as they implement the Document Object in different ways. This is especial true if the popup window has been opened with just the address of a graphic.

Another promising looking way is to open the popup with a HTML page providing the framework and using the Document Object to change the graphic to the one required and similarly set a new title by statements, which I do not intend to explain, such as:

popup_window.document.images[0].scr = "new_graphic.jpg" ;
popup_window.document.bgColor = "Black"
popup_window.document.title = "Title of Page"

Even this does not work well with earlier versions of Opera and one is forced to create a separate little page for each popup. This technique with separate pages is very flexible and used on one of my commercial sites where information surrounds each picture. It is however time consuming and involves many extra pages.

My favourite approach - writing popup pages on-the-fly

Example 1 Adding a Title

It is however possible to reliably write a HTML page on-the-fly using JavaScript writeln() statements with parameters passed via the function call. The following code demonstrates the technique, adds a title parameter and sets the background colour to black. It also centers the graphic giving identical displays with all browsers. If the window is open it is brought into the foreground and cleared, if not a new window is opened. The HTML is then written on-the-fly by a series of writeln() statements. The technique was first tested in Microsoft Internet Explorer 4+, Netscape 4+ and Opera 7+. Earlier versions of Opera do not display windows opened by JavaScript the same way as other windows regardless of how the code is created. This still works with all browsers but the page is not strictly complient with HTML5 as HTML attibutes are used to size the image rather than css

Code to cut and paste for Advanced Popup with Title

<script language="JavaScript"> // just <script> if pasted into HTML5 page
<!-- ;
var newwindow

function popitup(url , title) {
if (newwindow && !newwindow.closed)
{ newwindow.focus(); newwindow.document.clear() }
else
{ newwindow=window.open('','','width=404,height=316,resizable=1') }

newwindow.document.writeln('<> <head> <title>' + title + '<\/title> <\/head> <body bgcolor=\"black\"> <center>');
newwindow.document.writeln('<img src=' + url + ' title=\"' + title + '\" alt=\"' + title + '\" >');
newwindow.document.writeln('<\/center> <\/body> <\/html>');
newwindow.document.close();
}

function tidy()
{
if (newwindow && !newwindow.closed) { newwindow.close(); }
}

// Based on JavaScript provided by Peter Curtis at www.pcurtis.com -->
</script>

The code can be copied and pasted to replace the code on the current popup page but remember to add the additional title parameter to the call. If you copy by hand beware as it is impossible to tell 2x single quotes '' from a double quotes " in many proportion spaced character sets (fonts).

Example 2 - Specifying the popup size, title and background colour

If you only have a couple of different sizes such as vertical and horizontal pictures from a camera it is possible to duplicate the simpler popup as the user can move them to different places and they will stay. This is impractical when some of the images are different sizes. This is the situation when, for example, one is adding a series of screen dumps to an article on configuring Windows.

The following code allows one to specify the size of the image with each call and also the title and background colour. We still reuse the popup window but if the size has changed we resize the window to match the graphic. This feature is only supported in JavaScript 1.2 onwards but being realistic all browsers of generation 4 onwards support JavaScript 1.2 or higher ie 99% or more these days. Note: the height and width parameters should be those of the image as they have the required padding added - note the difference in padding for a window which is resized or opened the first time. As there may not be sufficient space to display all the title the full title now appears if you hover over the popup picture.

I changed all my pages to use versions of this code and most included a check on browser version and write a Caution into the page if could give problems. Again this still works with all browsers but the demonstration page is not strictly complient with HTML5 in the <html> and <head> although the image sizing etc is now in css.

Code to cut and paste for a Demonstration page with Advanced Popup with Title, Image Size and Background Colour parameters

<HTML>
<HEAD>
<TITLE>Java Popup Window Example with size, title and background colour parameters</TITLE>
<script language="JavaScript1.2">

var newwindow;
var wheight = 0, wwidth = 0;

function popitup5(url, title, iwidth, iheight, colour) {
var pwidth, pheight;

if ( !newwindow || newwindow.closed ) {
pwidth=iwidth+30;
pheight=iheight+30;
newwindow=window.open('','htmlname','width=' + pwidth +',height=' +pheight + ',resizable=1,top=50,left=10');
wheight=iheight;
wwidth=iwidth;
}

if (wheight!=iheight || wwidth!=iwidth ) {
pwidth=iwidth+30;
pheight=iheight+90;
// The resizeTo needs Javascript 1.2 or higher so the Script should also specify language="JavaScript1.2"
if (window.resizeTo) newwindow.resizeTo(pwidth, pheight);
wheight=iheight;
wwidth=iwidth;
}

newwindow.document.clear();
newwindow.focus();
newwindow.document.writeln('<html> <head> <title>' + title + '<\/title> <\/head> <body bgcolor= \"' + colour + '\"> <center>');
newwindow.document.writeln('<img src=' + url + ' title=\"' + title + '\" alt=\"' + title + '\" >');
newwindow.document.writeln('<\/center> <\/body> <\/html>');
newwindow.document.close();
newwindow.focus();
}

// Routines to tidy up popup windows when page is left
// Call with an onUnload="tidy5()" in body tag

function tidy5() {
if (newwindow && !newwindow.closed) { newwindow.close(); }
}

// Based on JavaScript provided by Peter Curtis at www.pcurtis.com -->
</script>

</HEAD>
<BODY onUnload="tidy5()" >

<A HREF="javascript:popitup5('gallery/dv17a72w.jpg','Temple of Heavenly Bliss', 384, 288,'white')"><IMG SRC="gallery/dv17a72i.jpg" style="width:160px; height:120px; border:0px; margin:5px; float:left;" ALT="Temple of Heavenly Bliss" TITLE="Temple of Heavenly Bliss"></A>

</BODY>
</HTML>

The code can be copied and pasted. If you copy by hand beware as it is impossible to tell 2x single quotes '' from a double quotes " in many proportion spaced character sets (fonts).

I am providing the same examples, from one of our very early travel pages, as on the main page, to try in all browsers. Decoration on the Sri Mariamman Temple If you click on the Offerings in the Temple of Heavenly Blissimages to left and right they will open in sequence in a correctly sized Window. Note how they come back to the foreground (in focus in the the jargon) when the thumbnails are clicked. If you drag the image window to a favoured place it will return to view in the same place. If you close the image Window it causes no problems, however when it reopens it will revert to the original place and size. Note now there is a coloured background, window title and the picture is centred in all browsers. You will also find that the image window is closed when the page is left (unloaded in the jargon), few sites tidy up extra windows.

Separating the JavaScript into a separate file.

The basic code above for the five parameter popup popitup5() formed the basis of all the popups on my web sites for a couple of years with the only significant development being in the way it was used. The code was put in an include file (.js) at the top of every page which had popups for images or feedback forms. This obviously saves a lot of space now we have several hundred pages using the code and enables any changes to be made in a single place.   The HTML now has the following line in the < head> block if you are using HTML 4.01

<script src= "epopup.js" language="JavaScript1.2" type="text/javascript"></script>

Note that I specified a relatively old version of JavaScript so almost every browser will work but one which is new enough to provide a couple of features I use for resizing a window - I now include an extra check for this in popitup5() and the language="JavaScript1.2" is largely redundant

If you are using HTML5 and type attributes should also be left out as javascript is the default (It will not validate with it present)

<script src= "epopup.js"></script>

The downside of using 'JavaScripts' to write the complex calling routines into the HTML pages is that visitors who turn JavaScript off will may not even see the small pictures (icons) on the pages so an addition warning if this is put in the HTML Body at the top of the page. The first check is obvious - the contents of an HTML <noscript> tag are only written on the page if script handling is turned off or not present in the browser. The second part calls the JavaScript browser check function in epopop.js and carries out any other checks you want and writes directly to the page. Examples of the function are in the Example at the bottom of this page.

<!--Start of Browser Checks-->
<noscript>
<p><span style="color:#FF0000;font-size:1.3em;"><b>Caution:</b> You will not be able to click to see large images unless you enable Java on your Browser.</span></p>
</noscript>
<script>browser_caution()</script>
<!--End of Browser Checks-->

Popups on my Travel pages

The next major enhancement came when we switched to digital cameras in 2004. The pictures now had the same aspect ratio as the video camera and digital processing of the images became the norm. I developed code which allows the user to specify from two possible sizes of popup depending on bandwidth of his internet connection and screen size. The size can be toggles by a function call either from a link or by double clicking on any of the images and uses a cookie to maintain the choice throughout the browser session . The write up of our Australian trip in 2004 was the first example. Having all the code and 'scripts' to write the calling routines into the HTML pages in an 'include file'makes it very easy to make changes if required - for example the Firefox browser needs a title parameter as well as an alt parameter to allow text to be displayed when one hovers over an image and that change could be made for most of my pages by only a minor modification in the .js file. Popup windows are now extensively used on the site for the images on our travel pages. The popups for pictures are an essential part of the travelogues and a lot of thought has gone into their design. The objective is to enable the reader to be able to read the text with small 'icons' illustrating the places which are either at the left or right end of the paragraphs or in blocks of 3 or 4 pictures between paragraphs depending on the density of the illustrations. The optimum size seems to be about 160x120 pixels.

It is worth reviewing the requirements for the basic pop-up function which should be able to:

The above is satisfied by the JavaScript function popitup5() already shown above and suffices for the basic access to popups which is used on a number of my web pages, especially the technical pages where the popups are of screen shots which are of very different sizes and sometimes just have a link to click. I have been surprised to find how few, if any, popup windows one finds on commercial web sites achieve all the important first five requirements.

My Travel pages are rather more specific in there use of popup images - here I have standardised on a 4:3 image ratio which matches my digital camera and video camera grabs and I can then offer two image sizes depending on the screen size and bandwidth one has available. Every picture exists as a set of three .jpg images 160x120 400x300 and 600x450 or the vertical equivalent in a fixed format (imagexxxi.jpg, imagexxxw.jpg and imagexxxb.jpg). The image size is currently chosen depending on the height of the users screen but this may be changed to a combination now tablets are increasingly common. Links are provided to switch sizes at the start of the introduction page for each journey and the size can also be toggled by double clicking although the change seems to be delayed to the next popup as the browsers seem to see the first click of the double click to display and then the double click to toggle.

Each travel page which has popups has code/scripts to:

Popup Behavior in different common Browsers

The Popups for pictures on www.pcurtis.com and www.uniquelynz.com have been test and work well with most recent versions of the mainstream browsers, Mozilla Firefox, Microsoft Internet Explorer, Google Chrome, Safari and Opera. That is all the desktop browsers with more than 2% market share (September 2011). http://en.wikipedia.org/wiki/Usage_share_of_web_browsers . There are however various factors which can still cause problems.

Popups are abused by many advertisers and most browsers offer preference settings, extensions or other ways ways to limit the abuse of popups. The same goes for various security suites. In general the default settings should not inhibit popups which follow all the rules properly although you may find that you are asked if will permit use of popups on my sites and it is safe and desirable to say yes.

Most of the browsers these days have a tabbed display rather than a series of separate windows. The best source of information on forcing window versus tab behavior is at http://stackoverflow.com/questions/726761/javascript-open-in-a-new-window-not-tab I have not tried the _blank yet but I obviously use the parameter strings in all the advanced popups.

I have been looking at some some of the less common 'lightweight' browsers under Linux on my legacy machines and find some open a popup window in a tab instead of a correctly sized window. This is much more difficult to use and has the associated problem that the reuse of a window incorrectly displayed as a tab may not work correctly and they may not be closed when the page is left. This also makes them difficult to use with many other sites such as banking sites or anywhere that help information is displayed in a popup window. Midori and Epiphany are examples of browsers which do not handle popups in the way one would expect. Both are based on WebKit code.

Chrome uses Webkit code but handles popups fine although it has a number of idiosyncrasies including failing to bring a popup back into focus (ie displayed in front of the main page) - this meant I have included a dedicated 'fudge' in the popup code for Chrome as it now has over 20% of the browser 'market'. Thanks to Tony for putting me on to this problem and providing the solution below.

// Add test for Chrome at top with other variables
var is_chrome=navigator.userAgent.toLowerCase().indexOf('chrome') > -1;

// Add before final newwindow.focus();
if (is_chrome) {newwindow.parent.blur();}

Firefox 4 also initially tended to lose focus but updated version 4s and versions 5 and higher now handle focus correctly so an earlier 'fudge' I had to implement of closing and reopening has been removed as it had other undesirable side effects - it is best to updated if you have any ananomolies.

Opera has a couple of minor anomolies - the popup windows are displayed the correct size and are also attached to there own tab which is opened and closed in tandem with the windows - this is a good compromise between tabs and windows. However Opera has a preference setting which, currently by default, prevents any popups being brought back into focus and this needs to be changed if you plan to spend much time on my pages. You reach it by clicking the Opera Button then Settings -> Preferences -> Advanced -> Content -> JavaScript Options and tick Allow raising of Windows. You can in any case bring the popup window into focus from its tab or the operating system toolbar although this can be tedious if you have a system with a 'dock' such as Ubuntu Unity. I have also noticed that closing Opera does not always close down the tabs with associated popups - see http://www.quirksmode.org/dom/events/index.html for support of events including onUnload for possible cause of Opera shortfall. Even this would not be a problem - it could even be called a feature if the picture was reloaded - unfortunately the parent page is loaded and it all looks very confusing.

Some examples and a test area of the popups

Firstly I will display the major attributes of navigator object for your browser so you know what you are testing. You will note that userAgent in particular is quite complex and one has to be careful in selecting a suitable string to test if you want to make any browser specific code:

The next paragraph starts with my 'standard' browser checks to confirm that JavaScript is enabled and Cookies are enabled - you should only see anything if you turn them off during your tests. Every page has similar tests at the start and the explanatory text which follows is also copied into almost every introductory travel page. It is followed it here by a few randomly selected pictures from our travels so you can try everything out in your particular browser.

All the pictures on the pages provide details of where they were taken if you hover the cursor over them and they can all be clicked to open a larger version in a popup window. The popup windows are reused and then closed up when you leave the page so you do not need to close them. Our digital pictures (from 2004) are in two resolutions and the initial size depends on your screen size. You can chose the small size if you have a slow internet connection or want to restrict data flow on a mobile connection. Click here for high detail popup images and click here for lower resolution images. You can also toggle the resolution by double-clicking an image but it may take an extra click to display the picture in the new size on some browsers. With some systems you may be asked to if you wish to allow popup windows as a protection against adverts. More

 

Finally an 'Include' file containing the JavaScript Code used to produce the above.

// Browser check function to write warnings into the web page when placed within a <script> tag

function browser_caution() {
var browser = navigator.appName;
var version = navigator.appVersion;
// Check JavaScript supports resizing of windows - version 1.2 or higher
if (!window.resizeTo) { document.write('<font color="#FF0000">Unfortunately you are running a browser identifying itself as ' + browser + ' version ' + version + ' which does not seem to support the JavaScript which would provide the Popup images when you click on the thumbnails on this page.</font>') }
if (navigator.cookieEnabled == false) {
document.write('<font color="#FF0000">Unfortunately you seem to have disabled Cookies in your browser and this means you will not be able to change the size of the popups used for pictures.</font>')
}
}

// Identification of specific browsers

var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1;
// var is_epiphany = navigator.userAgent.toLowerCase().indexOf('epiphany') > -1;
// var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;

// Routine for popup windows with title, size and background colour parameter (5 parameters)

var newwindow;
var wheight = 0, wwidth = 0;

function popitup5(url, title, iwidth, iheight, colour) {
var pwidth, pheight;

if ( !newwindow || newwindow.closed ) {
pwidth=iwidth+30;
pheight=iheight+30;
newwindow=window.open('','htmlname','width=' + pwidth +',height=' +pheight + ',resizable=yes,top=50,left=10,status=no,location=no');
wheight=iheight;
wwidth=iwidth;
}

if (wheight!=iheight || wwidth!=iwidth ) {
pwidth=iwidth+30;
pheight=iheight+70;
// The resizeTo needs Javascript 1.2 or higher so the include for this javascript file should specify language="JavaScript1.2"
if (window.resizeTo) newwindow.resizeTo(pwidth, pheight);
wheight=iheight;
wwidth=iwidth;
}

newwindow.document.clear();
newwindow.document.writeln('<html> <head> <title>' + title + ' <\/title> <\/head> <body bgcolor= \"' + colour + '\"> <center>');
newwindow.document.writeln('<img src=' + url + ' title=\"' + title + '\" alt=\"' + title + '\" >');
newwindow.document.writeln('<\/center> <\/body> <\/html>');
newwindow.document.close();

if (is_chrome) {newwindow.parent.blur();}

newwindow.focus();
}

//Routines used specifically for popup feedback form windows

var fbwindow
function fbpopitup(url) {
if (fbwindow && !fbwindow.closed)
{ fbwindow.location.href = url; fbwindow.focus(); }
else
{ fbwindow=window.open(url,'fhtmlname','width=520, height=420,resizable=1,scrollbars=1,top=50,left=10'); }
}

//Routines to tidy up popup windows when page is left

function tidy() {tidy5() }
function tidy5() {
tidyh();
fbtidy();
}

function tidyh() {
if (newwindow && !newwindow.closed) { newwindow.close(); }
}

function fbtidy(){
if (fbwindow && !fbwindow.closed) {fbwindow.close(); } }

//routines to simulate functions used on older pages.

function popitup(url) { popitup5(url , 'Digital Image 384x288 &copy; P Curtis', 384, 288, 'white') }
function ppopitup(url) { popitup5(url , 'Photographic Image 400x267 &copy; P Curtis',400, 267, 'white') }

function vpopitup(url) { popitup5(url , 'Digital Image 288x384 &copy; P Curtis', 288,384, 'white') }
function pvpopitup(url) { popitup5(url , 'Photographic Image 267x400 &copy; P Curtis', 267, 400, 'white') }

//Routines for cookies used to remember if user has broadband and a large screen

/* Call function as setCookie("cookiename" , cookievalue, lifetime, cookiepath)
with the lifetime required in days, -1 to delete a cookie or zero
for a temporary cookie. The Cookie Path is optional.*/

function setCookie(cookie_name, cookie_value, cookie_life, cookie_path) {
var today = new Date()
var expiry = new Date(today.getTime() + cookie_life * 24*60*60*1000)
if (cookie_value != null && cookie_value != ""){
var cookie_string =cookie_name + "=" + escape(cookie_value)
if(cookie_life){ cookie_string += "; expires=" + expiry.toGMTString()}
if(cookie_path){ cookie_string += "; path=" + cookie_path}
document.cookie = cookie_string
}
} // Based on JavaScript provided by Peter Curtis at www.pcurtis.com -->

/* Call function as getCookie("cookiename") It returns the value of a cookie
if set or null. Beware of potential ambiguities in names of cookies -
getCookie is simple and will match the end of a string so xyname
will also be matched by yname and ame. */

function getCookie(name) {
var index = document.cookie.indexOf(name + "=")
if (index == -1) { return "undefined"}
index = document.cookie.indexOf("=", index) + 1
var end_string = document.cookie.indexOf(";", index)
if (end_string == -1) { end_string = document.cookie.length }
return unescape(document.cookie.substring(index, end_string))
} // Based on JavaScript provided by Peter Curtis at www.pcurtis.com -->

// Set broadband true provided the screen resolution is sufficient for the larger popups

var default_broadband = "true"
if (screen.height < 720 || screen.width < 720 ) {default_broadband = "false"}
var cookie_time=0

// Functions to set and clear broadband

function set_broadband(){
setCookie('broadband',"true",cookie_time,"/")
}

function clear_broadband(){
setCookie('broadband',"false",cookie_time,"/")
}

// Function to toggle flag for broadband users

function toggle(){
if (getCookie('broadband') == "undefined") { setCookie('broadband',default_broadband,cookie_time,"/"); }
if (getCookie('broadband') != "true") {
setCookie('broadband',"true",cookie_time,"/");
}
else {
setCookie('broadband',"false",cookie_time,"/");
}
}

// Functions called for vertical and horizontal popups of two different sizes (image files end in i.jpg for icon, w.jpg for small image (400*300) and b.jpg for large images (600x450)

function popitup2v(url,title) {
if (getCookie('broadband') == "undefined") { setCookie('broadband',default_broadband,cookie_time,"/"); }
if ( getCookie('broadband') == "true") {url=url+'b.jpg';popitup5(url , title, 450, 600, 'white') }
else {url=url+'w.jpg'; popitup5( url , title, 300, 400, 'white') } }

function popitup2h(url,title) {
if (getCookie('broadband') == "undefined") { setCookie('broadband',default_broadband,cookie_time,"/"); }
if ( getCookie('broadband') == "true") {url=url+'b.jpg';popitup5(url , title, 600, 450, 'white') }
else { url=url+'w.jpg'; popitup5( url, title, 400, 300, 'white')
}}
/*
Functions to put in the HTML to provide simplified calls for vertical and horizontal popups with a doubleClick event handler calling toggle() so that large popups can be displayed if user has broadband
*/

var cssalign = "" ;

function hpop(image, title, alignment) {
cssalign = "display: block; margin-left: auto; margin-right: auto;"
if(alignment == "left"){ cssalign = "float:left;"};
if(alignment == "right") { cssalign = "float:right;"};
title = title + " (" + image.substring(image.lastIndexOf('/')+1) + ")";
document.write('<IMG src= \"' +image + 'i.jpg\" alt=\"' + title + '\" title=\"' + title + '\" style=\"width: 160px; height:120px; padding: 10px; ' + cssalign + ' \" onDblClick=\"toggle()\" onClick=\"popitup2h(\' ' + image + '\' ,\' ' + title + ' \' )\" \>')
}

function vpop(image, title, alignment) {
cssalign = "display: block; margin-left: auto; margin-right: auto;"
if(alignment == "left"){ cssalign = "float:left;"};
if(alignment == "right") { cssalign = "float:right;"}
title = title + " (" + image.substring(image.lastIndexOf('/')+1) + ")";
document.write('<IMG src= \"' +image + 'i.jpg\" alt=\"' + title + '\" title=\"' + title + '\" style=\"width: 120px; height:160px; padding: 10px; ' + cssalign + ' \" onDblClick=\"toggle()\" onClick=\"popitup2v(\' ' + image + '\' ,\' ' + title+ ' \' )\" \>')
}


There is a further option these days which is to covers the use of 'Lightbox Overlays instead of Popup windows. Popup windows do not work so well on the modern Smart Phones and Tablets with small screens and are based round a tabbed approach rather than implementing separate windows. This makes the approach very clumsy as every popup is opened in a new tab and the reloading and automatic closing does not work. The alternative is an approach with has tended to be called Lightbox which is a JavaScript technique used to display images and other web content using modal windows where the image is overlayed over the screen with the surround dimmed out. The overlayed window can be closed by clicking outside of it or by a close button usually a standard crosscross. Ideally the image should display full size when there is space available and shrink to fit the space available. Lightbox was originally the name of a specific JavaScript plugin, written by Lokesh Dhakar. However, common usage of the term has evolved to encompass Lightbox-style JavaScript plugins and such effects in general. The technique is now used very widely because of its simple yet elegant style and easy implementation and, most importantly it works well on small screens. The disadvantage is that it blocks use of the main window whilst a popup can just be moved aside but still be visible. I am currently using the original Lightbox but other implementations allow one to show video, web content etc in addition to just pictures and even run slide shows. I will cover Using Lightbox to Display Web Images in the third part to this series which will include code to choose which is in use as both have their advantages and will also cover some changes needed to convert to the latest web standard HTML5


Link to W3C HTML5 Validator Copyright © Peter & Pauline Curtis
Navigation revised: 10th April, 2021