Why a great deal of technical documentation on the internet is useless.

I am reading the Configuration providers in .NET page in order to learn how to load application settings from a JSON file in .NET 6. This page is a perfect example of why a great deal of technical documentation on the internet is useless.

The JSON configuration provider section of this page attempts to explain how to load load application settings from a JSON file using the JsonConfigurationProvider Class.

The following code block comes from that section.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ConsoleJson.Example;

// PROBLEM 1
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Configuration.Sources.Clear();

IHostEnvironment env = builder.Environment;

builder.Configuration
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true);

TransientFaultHandlingOptions options = new();
builder.Configuration.GetSection(nameof(TransientFaultHandlingOptions))
    .Bind(options);

Console.WriteLine($"TransientFaultHandlingOptions.Enabled={options.Enabled}");
Console.WriteLine($"TransientFaultHandlingOptions.AutoRetryDelay={options.AutoRetryDelay}");

using IHost host = builder.Build();

// PROBLEM 2
// Application code should start here.

// PROBLEM 3
await host.RunAsync();

Note: In the above code block I added PROBLEM comments that I reference below.

Problem 1

The line that follows this comment uses the args variable, however the args variable is not declared prior to use., In order to find out what this variable is you have to open the Host.CreateApplicationBuilder Method page and go to the CreateApplicationBuilder(String[]) section. There you will learn that the args variable should be set to “The command line arguments”.

This should never happen! Your readers should never need to go to another page on your website to find out what a variable is.

Problem 2

What does “Application code should start here” mean? If this was the very last line of code in the code block it would be easy to interpret this example. It would mean place all code above this comment at the very beginning of you entry function.

The problem is that comment is not the very last line of the code block.

Problem 3

There is another line of code after the “Application code should start here” comment. What is the purpose of this line of code? Is it clean up code? Or was the afore mentioned “Application code should start here” comment misplaced. Should this extra line of code be placed above that comment?

Conclusion

Because of Problem 2 and Problem 3 I have no idea how to modify an application to load application settings from a JSON file in .NET 6 after reading this page.

A complete working application would solve these problems!

A much better resource is the Basic example section of the Configuration in .NET page. After reading this, I was able to complete my current task.

The Carbon Programming Language is not a legitimate successor to the C++ Programming Language.

The Carbon Programming Language was recently announced as an experimental successor to C++. In my opinion the Carbon Programming Language is not a legitimate successor to the C++ Programming Language.

If you create a new programming language that is meant to be a successor of an existing programming language, do not break every single rule of the original language.

In C, C++, C#, and many simular languages data types are on the left and variable names are on the right. Anyone who has any experience in these programming languages expects any programming language that claims to be a successor to follow this most basic of syntax rules. This rule is such a basic rule of the language you should not even consider breaking it for a so called successor language.

The Carbon Programming Language breaks that rule.

Maybe people who have not ever used C and C++ will be willing to learn the Carbon Programming Language, but people with 20+ years of experience will hesitate because the code is too different.

This should be obvious.

Putting the variable name on the left and the data type on the right may make parsing easier, but millions of people who have experience in C or C++ or C# expect the data type to be on the left. Google, by going against these expectations is greatly limiting the adoption of the Carbon Programming Language.

Why does Visual Studio always do this?

Why does Visual Studio always do the wrong thing when creating a new solution or project file in an existing directory.

Consider the following scenario.

  1. There is source code but no project file in the directory c:\Path\To\ProjectName.
  2. You create a new project file named ProjectName in the directory c:\Path\To\ProjectName. You expect the project file to be directory c:\Path\To\ProjectName\ProjectName.vcxproj because that makes sense.

    Note that ProjectName is already repeated twice in that path.
  3. What Visual Studio actually does is create a directory named ProjectName in the directory c:\Path\To\ProjectName and then places the file ProjectName.vcxproj in that directory so that the full path to the project file becomes c:\Path\To\ProjectName\ProjectName\ProjectName.vcxproj.

    Now you have a path where ProjectName is repeated three times.

Every time this happens I have close the solution file, to go to the directory in Windows Explorer, move the project file up a level, delete the extra ProjectName directory, manually edit the solution file in a text editor, then reopen the solution file in Visual Studio. Please, just put the project file in the specified directory.

Friendly Reminder: If you release a NuGet package please sign all the DLLs.

Please. I am begging you. If you release NuGet packages sign both the Debug and the Release DLLs. Do not just sign the Release DLLs.

I wasted four hours today debugging the following exception: “FileLoadException: A strongly-named assembly is required. (0x80131044)”.

I was debugging the debug build of an application that uses the Titanium.Web.Proxy version 3.1.1397, obtained using NuGet. The Titanium.Web.Proxy uses BouncyCastle.NetCore version 1.8.10.

I have a python script that copies the build output from the output directory to the appropriate location on my test PC where I have the product I am working on installed. This python script replaces the files already present in the application installation directory with the files produced by building the product on my development PC.

The build process, which uses MSBuild, copies the DLLs for BouncyCastle.NetCore and Titanium.Web.Proxy to the output directory. This is useful for release builds because the installer project pulls all DLL and EXE files from the build output directory. My python script previously copied the BouncyCastle.NetCore and Titanium.Web.Proxy files to my test PC in addition to all the DLL and EXE files for the application.

The problem is that the Debug DLLs for BouncyCastle.NetCore and Titanium.Web.Proxy are not signed. When a .NET assembly depends on another assembly and the dependent assembly is not signed .NET will throw a “FileLoadException: A strongly-named assembly is required. (0x80131044)” exception when the first assembly attempts to load the dependent assembly.

It took me four hours to find out why this exception was occurring.

Featured

User Interface Developers: Please Have Mercy on Your Poor Users!

Woman screaming in rage,

This blog post is for any software engineers who are involved in programming or designing a user interface.

If someone performs a user interface action that should result in an instantaneous and clearly visible response they will only wait patiently for that response for a brief period of time.

If after a five second delay they do not see the expected clearly visible response they will perform the user interface action again, perhaps multiple times if the delay continues for another five seconds or so.

If for some reason there is a delay that prevents the expected instantaneous and clearly visible response, please, for the love of whatever deity or great power you follow, DO NOT PERFORM THE RESPONSE MULTIPLE TIMES.

The multiple user interface actions were not in any way a request to perform the response multiple times. They were multiple increasingly desperate attempts to get the response a single time.

For example, if the user is pressing enter on a desktop icon that starts a program that displays a message box before the main function is even called, they will press enter on the desktop icon multiple times if for some reason it takes between five and sixty seconds to start the application when it normally starts in far less than a second.

Trust me. The user has already started screaming at you by the time they have to hit enter on the desktop icon a third time because the message box still has not been displayed in response to the request to start the application that was made fifteen seconds earlier.

If you then start that application one time for each time they pressed the enter key on the desktop icon when your badly broken operating system finally realizes the user is desperately trying to start it the poor user might end up having a stroke because of the rage they are now experiencing!

Another example is opening a menu such as the start menu or an application menu. Normally this is a very quick operation that takes less than a second. So if five seconds after they pressed the key to open the menu the user does not see the menu, they will press the key to open it again. On the rare occasions when something delays the opening of the menu they may have pressed the open menu key four or five times before it is opened.

That does not mean they want the menu to be opened, then closed, then opened, then closed…

The multiple presses of the key to open the menu were multiple increasingly desperate attempts to open the menu once. By the second attempt the poor user is screaming at you. If you do the open menu, close menu, open menu, close menu, … foolishness it is very likely that the poor user might end up have a stroke because of the rage they are now experiencing!

These are problems I experience very often with Microsoft Windows. To be fair I have seen it with Linux and Mac OSX as well, but no where near as often as I see in in Windows.

Fuck you very much Microsoft for the wonderful user experience of Windows and every single Microsoft product I have every used! If I believed in hell I would wish for everyone responsible for these wonderful user experiences to suffer eternal torment in the hottest and most fiery section of hell!

Thank you for listening to me vent. I feel much better now.

Here is a song that expresses my heartfelt feelings towards Microsoft. Enjoy.

… you very much Microsoft
Featured

On the benefits of taking a course over learning by doing.

I am a self taught software engineer. It is something I take a great deal of pride in.

I started learning C programming language back in 1997. If I remember correctly I started by reading Sams Teach Yourself C in 21 Days and perhaps also C Programming Language by Brian W. Kernighan and Dennis M. Ritchie.

Then on January 5, 1999 I got a job working for Henter-Joyce (now Freedom Scientific) as an Associate Software Engineer. My responsibilities included writing code in the JAWS Scripting Language which a proprietary programming language that is used by the JAWS screen reader.

I quickly decided that I was not content with just writing code in the JAWS Scripting Language; I wanted to be able to modify the JAWS internals.

At that time almost all of the JAWS internal code was in the C++ programming language. Therefore I decided to teach myself C++.

To learn C++ I did the following.

  • I bought a copy of Sams Teach Yourself C++ in 21 Days and began reading it.
  • I wrote an application called SysInfo in C++ as a aid to our technical support department. SysInfo would gather information such as Video Card information, OS, memory, and send it to technical support via email. SysInfo was very similar to the current Microsoft System Information Tool.

This plan was so successful that I was promoted to Software Engineer in January 2000. I have used C++ as my primary programming language since then.

I never stopped learning. I have tried to keep up to date with new language standards and new technologies since then. But I always learned by doing. If the task I was working on required that I learn something new I would use Google and Stack Overflow and get the job done.

This has been my preferred method of gaining new knowledge until very recently.

On August 1, 2021 my knowledge of JavaScript was minimal. The last time I needed to use JavaScript as part of my job it took me two weeks to fix a bug that required that I change fewer than 10 lines of code.

Then I realized that there are some serious flaws with a browser extension for Google Chrome and Mozilla Firefox released by my employer that I am currently responsible for maintaining that necessitated a complete rewrite of the extension. I tried to make the necessary changes and found much to my horror that I could not even get started.

I had previously bought a lifetime membership to Eduonix and some courses on Udemy with the vague idea of taking the courses some day. However, I had made very minimal use of these educational resources.

I let my employer know what was going on and I was given permission to spend some time when I would normally be working on taking courses in JavaScript. I then spent every day for the next two weeks learning JavaScript.

In just four hours this Monday I rewrote one quarter of the Browser Extension. There is only one reason I did not finish the project; I was temporarily reassigned to another project.

I am not claiming that my knowledge of JavaScript is anywhere near my knowledge of C++. However, after just two weeks my knowledge of JavaScript is roughly equivalent to my knowledge of C++ back in 2001.

Looking back, I find myself wishing that I had followed a more structured approach to learning C++ back in the halcyon days of yore.

Featured

JAWS Scripting Tip: Optional Parameters

JAWS is a screen reader developed by Freedom Scientific. It includes a scripting language that allows users to customize the behavior of JAWS.

Many programming languages such as C++, C#, and Modern JavaScript support the concept of optional function parameters for which you supply a default value for the parameter. In C++ this might be done as follows.

std::wstring GetFavoriteNumberMessage(int64_t number = 42)
{
    std::wstringstream message;
    message
        << L"Hello. My favorite number is "
        << number
        << L". What is your favorite number?\n";
    return message.str();
}

See source on godbolt.org.

The JAWS scripting language supports optional parameters using the optional keyword, as follows.


string function GetFavoriteNumberMessage(optional int number)
  var string message = FormatString(
    "Hello. My favorite number is %1. What is your favorite number?",
     number)
  return message
endFunction

However, there is no direct method of assigning a default value for the optional parameter. If you were to call the function is as without specifying a value for the number parameter the number will be inserted into the returned message as an empty string. This is most likely not the desired behavior.

When the caller does not specify the value of an optional parameter, it does not resolve to 0 or false within the function; this is a common misconception. Instead the unspecified parameter resolves to nullptr. The misconception that the unspecified parameter resolves to 0 or false is a result of the fact that when nullptr is typecast to an int the result is 0 or false. However, if the unspecified parameter is typecast to a string the result is an empty string. This is why in the above example the number will be inserted into the returned message as an empty string; the number parameter is typecast to a string by the FormatString function. If the unspecified parameter did resolve to 0 or false within the function it would be inserted to the returned message as 0!

This problem can be resolved as follows.

string function GetFavoriteNumberMessage(optional int number)
    if (!DidCallerSpecifyTheValueOfTheParameter(number))
      number = 42
    endIf
    var string message = FormatString(
      "Hello. My favorite number is %1. What is your favorite number?",
      number)
    return message
endFunction

Unfortunately, there is no DidCallerSpecifyTheValueOfTheParameter function in the JAWS scripting language, so do not bother looking it up. However, there is a function that can achieve the same goal.

Behind the scenes all variables in the JAWS scripting language are VARIANTs (yes we are aware that there are modern alternatives that are vastly superior but the JAWS scripting language is very old; it has its roots in the JAWS macro language that was included in JAWS for DOS). One benefit of a VARIANT is it supports the concept of a VARTYPE or variant type. Furthermore, there is a VT_EMPTY variant type. Finally, there is a rarely used GetVariantType built-in script function that “determines the type of a variable”.

Thus, it is possible to assign a default value for an optional parameter as follows.

string function GetFavoriteNumberMessage(optional int number)
    if (GetVariantType(number) == VT_EMPTY)
      number = 42
    endIf
    var string message = FormatString(
      "Hello. My favorite number is %1. What is your favorite number?",
      number)
    return message
endFunction

Note that some people may think this is unnecessary because it is enough to rely on the perceived unspecified parameter resolves to 0 or false behavior. This is wrong for two reasons.

  • As I have already pointed this perceived behavior is not what is actually happening. The unspecified parameter actually resolves to nullptr.
  • In many cases, even if the perceived unspecified parameter resolves to 0 or false behavior may not be what is desired.

Every other programming language that supports the concept of optional parameters; such as C++, C#, and Modern JavaScript ; allows the value of the optional parameters to be explicitly defined. The purpose of the following code block is to add the same capabilities to optional parameters in the JAWS Scripting Language.

if (GetVariantType(number) == VT_EMPTY)
  number = 42
endIf

This is the only way to assign a non nullptr value for an optional parameter in the JAWS Scripting Language. You should consider doing this even for parameters where the default value is 0 or nullptr because explicitly specifying the value of your default parameters rather that making people who read your code guess what the behavior will be is just being polite.

Note that using this technique is absolutely necessary when overriding a built-in script function such as the SayObjectTypeAndText function in your scripts. The default value of the includeContainerName is true, not false. Failure to do this when overriding the SayObjectTypeAndText function will change the behavior of your function from the default resulting in hard to find bugs.

Debugging Tip: How to Set a Breakpoint In a Windows API Function

Introduction

This article is based on an EMail message I sent to the software development team at Vispero on August 26, 2008. I had just spent hours debugging an odd bug in JAWS®. When focus was in a particular window while JAWS was running I heard constant, rapid fire, beeping. I searched the JAWS source code for calls to the Beep and MessageBeep functions and was not able to determine the cause of the bug. I was at a loss. Then I asked the following question, is there a way I can just set breakpoints in the Beep and MessageBeep functions? This article discusses what I found during my quest for an answer to that question.

I am writing this article now because I encountered this issue again today and just spent five hours retracing my steps.

Note: After I wrote this article I learned that things are much easier in Visual Studio 2017 and Visual Studio 2019. In these versions of Visual Studio all that you need to do is open the “New Function Breakpoint” dialog box using the “Debug/New Breakpoint/Function Breakpoint…” menu item and enter the undecorated name of the function in the dialog box. If you are interested in learning how things used to be, follow me down the rabbit hole.

Falling down the rabbit hole

It turns out that it is possible to set a breakpoint in a Windows API function in Visual Studio. I do not know when this functionality was added to Visual Studio but I do know that it was available in Visual Studio 2005. The question is how.

The Set function breakpoints section of the Use breakpoints in the Visual Studio debugger page provides some relevant information; it provides very limited information on setting a breakpoint on a function with a known name. Simply use the “Debug/New Breakpoint/Function Breakpoint…” menu item to open the “New Function Breakpoint” dialog box and type a magical incantation in the “Function Name” edit.

Unfortunately, the information provided here is infuriating since it reveals that the feature is supported but it does not provide enough information to allow you to actually use the feature. In other words, it does not tell you what magical incantation to use.

Use the context operator in native C++.

{function, , [module]} [+]

Example: {MethodA, , App1.dll}+2

The article How Can I Debug Windows API Functions provides more information but it does not explain the context operator. It does reveal how to set a breakpoint in the MessageBeep function.

{,,USER32.DLL}_MessageBeep@4

This page mentions that you need to use the “decorated form of the function name” and suggests that you refer to the Viewing Decorated Names page for more information. The Viewing Decorated Names page in turn refers you to the Use a listing and Using DUMPBIN to View Decorated Names pages. Unfortunately, it turns out that neither of the techniques mentioned on the Viewing Decorated Names page will allow you to determine the decorated name for a function that is exported from a DLL. The DBH tool is supposed to be able to provide this information via the -d command-line option but in my tests using the following command it only provides undecorated names these days (bad Microsoft!).

dbh -s:srv*C:\SymbolCache*http://msdl.microsoft.com/Download/Symbols -d C:\Windows\System32\User32.dll enum *CreateWindowExW*

This command provides the following output.

Symbol Search Path: srv*C:\SymbolCache*http://msdl.microsoft.com/Download/Symbols

 index            address     name
     1            1007760 :   CreateWindowExW

Note that this is not the decorated name of the function since there is no @ character.

Unfortunately, I do not know an easy way to determine the “decorated form of the function name” other than to figure it out yourself using the information provided at Using Win32 calling conventions. Most Windows API function use the __stdcall calling convention. The general syntax for the decorated name of a __stdcall function is as follows.

_[UndecoratedFunctionName]@[SizeOfParametersInBytes]

This provides enough information for you to write the part of the magical incantation that comes after the } character, but what about the beginning of the magical incantation? Based on the example provided for the MessageBeep function one could assume that it should simply be the name of the DLL that implements the Windows API function, but is more information available?

Fortunately, the Context Operator in the Visual Studio Debugger (C++) page does provide a more detailed explanation for the first part of the magical incantation. This page reveals part of the syntax for the Context Operator.

{,,[module]} expression

Of course, this very annoyingly leaves a question unanswered. Why does this text between the { character and the } character start with two commas? What are you leaving out?

I have not been able to find official Microsoft documentation that answers that question yet. However, I do have an answer for you from Breaking on System Functions with the Context Operator. This page reveals that the full syntax of the Context Operator is as follows.

{[function],[source],[module]} expression

In most cases you will not need to worry about [function] and , you will only need to worry about the module and the expression.

Thus, the syntax for the magical incantation you will need to set the breakpoint is as follows.

{,,[module]}_[UndecoratedFunctionName]@[SizeOfParametersInBytes]

Pulling everything together.

To set a breakpoint in any Windows API function, such as the CreateWindowExW function, do the following.

  1. Look up the Windows API function in the Microsoft documentation. In the Requirements section the DLL the function is implemented in is provided; in this case it is User32.dll.
  2. Determine the size of the function parameters in bytes, as follows.
ParameterSize in bits on a 32-bit systemSize in bytes on a 32-bit systemSize in bits on a 64-bit systemSize in bytes on a 64-bit system
DWORD dwExStyle324324
LPCWSTR lpClassName324648
LPCWSTR lpWindowName324648
DWORD dwStyle324324
int X324324
int Y324324
int nWidth324324
int nHeight324324
HWND hWndParent324648
HMENU hMenu324648
HINSTANCE hInstance324648
LPVOID lpParam324648
Totals4868
  1. Fill in the blanks.

If you are debugging a 32-bit application the magical incantation is as follows.

{,,User32.dll}_CreateWindowExW@48

If you are debugging a 64-bit application the magical incantation is as follows.

{,,User32.dll}_CreateWindowExW@68
  1. Enter the appropriate magical incantation in the “Function Name” edit of the “New Function Breakpoint” dialog box and press the OK button.

Note that in order for this to work you will need to set up Visual Studio to use the Microsoft symbol server. To do this select the “Debug/Options…” menu item to open the Debugging section of the “Options” dialog box. Then select “Debugging/Symbols” in the tree view. Next press tab to move focus to the “Symbol file (.pdb) locations” check list box and check the “Microsoft Symbol Servers” item. Then press the OK button.

References

Down the Rabbit Hole (CC BY-SA 2.0) by Valerie Hinojosa

On writing code with debugging in mind

/*************************************************
*                                                *
* .=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-. *
* |                   ______                   | *
* |                .-"      "-.                | *
* |               /            \               | *
* |   _          |              |          _   | *
* |  ( \         |,  .-.  .-.  ,|         / )  | *
* |   > "=._     | )(__/  \__)( |     _.=" <   | *
* |  (_/"=._"=._ |/     /\     \| _.="_.="\_)  | *
* |         "=._"(_     ^^     _)"_.="         | *
* |             "=\__|IIIIII|__/="             | *
* |            _.="| \IIIIII/ |"=._            | *
* |  _     _.="_.="\          /"=._"=._     _  | *
* | ( \_.="_.="     `--------`     "=._"=._/ ) | *
* |  > _.="                            "=._ <  | *
* | (_/                                    \_) | *
* |                                            | *
* '-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=' *
*                                                *
*    ABANDON ALL HOPE, ALL YE WHO ENTER HERE     *
*************************************************/
ASCII art image of a skull and crossbones with the text “abandon all hope, all ye who enter here” displayed below the skull and crossbones.

One factor of code quality that does not get enough attention is how easy is it to debug the code. No matter how skilled the team that writes the software, there will be bugs. When these bugs are found some poor soul will need to debug the code to determine the cause of the bug. Unfortunately this can quickly turn into a nightmare because many functions are not debuggable. Sometimes even well designed code is so difficult to debug that it causes the poor soul who has to debug it to abandon all hope.

Non-Debuggable Function

The following function is very simple. At first glance it seems to be a concise and well designed function. However, it is a major contributor to the debugging this application is a nightmare problem.


inline bool CCoolAppDocument::IsValidStartOffset(long nOffset) const
{
    return nOffset >=0 && nOffset < GetLength();
}

You may be wondering, how can this innocent, one-line function cause someone to abandon all hope?

When the function works as expected (meaning it returns the value you expect) it cannot. However, when the function returns false when you think it should return true the nightmare begins. The most critical piece of information that might explain why this function is not returning the expected value, the document length, is hidden away within a function call. As a result, there is no way you can look at parameter and variable values in the debugger watch window and know why the function is behaving in the way that it is.

Debuggable Function

The following function does exactly the same thing but it can be debugged in a microsecond.


inline bool CCoolAppDocument::IsValidStartOffset(long nOffset) const
{
    const auto documentLength = GetLength();
    return (nOffset >=0 && nOffset < documentLength);
}

All you have to do to debug this function is look at the value of the nOffset parameter and the documentLength variable in the watch window and then the reason for the unexpected behavior is immediately obvious.

In the case of today’s nightmare that inspired this article, the problem was that both the nOffset parameter and the documentLength variable happened to be equal to 296.

Additional Comments

I know that some of you are wondering whether I am aware that some debuggers will provide the return value of a function that was just called. The answer is of course I am aware of that. However, as far as I know not all debuggers support this functionality. In addition, this functionality is not as easy to use or as intuitive as the watch variable feature.

To me it just makes sense to make a very small change to make it possible to diagnose problems with the more intuitive and easier to use watch variable feature of the debugger.

Using a select element to change the active stylesheet for a web page.

This article discusses the use of a select element to change the active stylesheet for a web page. It also provides step by step instructions on how to create the select element.

  • Add a link element to the head of your web page. Set the rel attribute to “stylesheet,” the href attribute to the URL of the default stylesheet you wish to use for your web page, and the id attribute to a unique value.

    Code Example:

    <link
      rel="stylesheet"
      href="https://www.w3.org/StyleSheets/Core/Swiss"
      id="W3C-Core-Styles" />
    

  • Add the select element. Set the value attribue of each option element to the URL of the stylesheet and the contents of each option element to the text you wish to be displayed for the combo box.

    Code Example:

    <select id="SeclectW3CCoreStyle">
    <option
      value="https://www.w3.org/StyleSheets/Core/Chocolate">
    Chocolate
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Midnight">
    Midnight
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Modernist">
    Modernist
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Oldstyle">
    Oldstyle
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Steely">
    Steely
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Swiss">
    Swiss
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Traditional">
    Traditional
    </option>
    <option
      value="https://www.w3.org/StyleSheets/Core/Ultramarine">
    Ultramarine
    </option>
    </select>
    

    Typically, I add this select element, along with a lable and the Set Style button, to the footer at the bottom of the page.

  • Add a Set Style button, as follows.

    Code Example:

    <input
      id="SetW3CCoreStyleButton"
      onclick="SetStyle('SeclectW3CCoreStyle', 'W3C-Core-Styles')"
      type="button"
      value="Set Style" />
    

  • Add the following JavaScript code to a script element in your page or to a .js file on your website.

    function isEmptyOrSpaces(str){
      if (str == null){
        return true;
      }
      if (str.length == 0){
        return true;
      }
      if (str.trim() === ''){
        return true;
      }
      return false;
    }
    
    function SetStyle(selectStyleElementId, linkElementId){
      console.log(
        "SetStyle('" + selectStyleElementId
        + "', '" + linkElementId
        + "') called.");
      let seclectStyleElement =  document.getElementById
        selectStyleElementId);
      if (!seclectStyleElement){
        console.log(
          "Could not find the '" + selectStyleElementId
          + "' element.");
        return false;
      }
      let seclectedStyle = seclectStyleElement.value;
      if (isEmptyOrSpaces(seclectedStyle)){
        console.log("Could not get the selected style.");
        return false;
      }
      let linkElement = document.getElementById(linkElementId);
      if (!linkElement){
        console.log(
          "Could not find the '" + linkElementId
          + "' element.");
        return false;
      }
      linkElement.setAttribute("href", seclectedStyle);
      return true;
    }
    

This example is fully functional. It does lack one key feature, the ability to store the selected style in a cookie. The article Store and Retrieve Cookies demonstrates the use of the localStorage.setItem and localStorage.getItem functions.

Modify the SetStyle function by adding the folloing line just above the ‘return true;’ line at the bottom of the function.


window.localStorage.setItem(
  "SeclectedStyle", seclectedStyle);

Note that the localStorage object is only supported on modern web browsers. That said, the Local storage with Window.localStorage page provides JavaScript code to emulate the feature for older browsers.

The next step is to set the style and the selected item in the select element when the web page loads. To acomplish this you need to add the following function.


function SetStyleFromCookie(selectStyleElementId, linkElementId){
  console.log(
    "SetStyleFromCookie('" + selectStyleElementId + "', '" + linkElementId + "') called.");
  let seclectedStyle = window.localStorage.getItem(
    "SeclectedStyle");
  if (isEmptyOrSpaces(seclectedStyle)){
    console.log(
      "Failed to get the value of the 'SeclectedStyle' cookie. Using the default value.");
    seclectedStyle = 'https://www.w3.org/StyleSheets/Core/Swiss';
  }
  let linkElement = document.getElementById(linkElementId);
  if (!linkElement){
    console.log("Could not find the '" + linkElementId + "' element.");
    return false;
  }
  linkElement.setAttribute("href", seclectedStyle);
  let seclectStyleElement =  document.getElementById(selectStyleElementId);
  if (!seclectStyleElement){
    console.log("Could not find the '" + selectStyleElementId + "' element.");
    return false;
  }
  seclectStyleElement.value = seclectedStyle;
  return true;
}

Finally, use the following code to call the SetStyleFromCookie function when the web page is loaded.


window.addEventListener('DOMContentLoaded', (event) => {
  SetStyleFromCookie('SeclectW3CCoreStyle', 'W3C-Core-Styles');
});

References

To see this code in action, go to Using a select element to change the active stylesheet for a web page.