Skip to main content

Sitecore

Switch SXA Themes Based on Cookie Value

A man and woman collaborating and discussing work on a computer

The Requirement

There was a requirement to give users the ability to switch the themes of the site.
We can have multiple use cases to switch the theme of the site as mentioned below and provide a better user experience.

  • We can show a pop-up on the home page with a message like “Do you want to experience a new design, please click here”. This option would change the theme.
  • Change the theme based on the seasons, festivals, or special days.
  • The theme changes during the day and night times.

We can use a cookie value to change the themes of the site. For example, “0” for the default theme and “1” for the new theme.

Before going straight to the investigation and implementation, let’s first understand the SXA Themes

The SXA Themes

The sites’ look and feel are defined by themes which are comprised of styles, scripts, images, and fonts.

Sitecore SXA comes with OOTB SXA themes and base themes. We can create our own custom theme by inheriting base themes. These themes can be applied to sites.

Themes in the content editor tree can be found at sitecore/Media Library/Themes & sitecore/Media Library/Base Themes as shown in the below image.

For more information on themes, please visit The SXA themes | Sitecore Documentation.

Sxa Themes

Sxa Themes

The Investigation

When the Sitecore SXA page item renders out on the browser, behind the scenes the pipeline “assetService” has an “AddTheme” processor of assembly Sitecore.XA.Foundation.Theming executes and returns the Sitecore SXA theme item based on the theme which is set on the Sitecore Page Design item or design mapping and gets applied on the SXA page.

We can see in the below screenshot of showconfig.aspx the “assetService” and “AddTheme” processor.

showconfig.aspx

Existing assetService, AddTheme processor

When peeking into Sitecore.XA.Foundation.Theming.dll, found that the AddTheme processor adds the theme item which gets through calling the ThemingContext service highlighted in the below image.

AddTheme Processor

Sitecore.XA.Foundation.Theming.dll –> AddTheme Processor

When investigated on ThemeContext, found that a function “DoGetThemeItem” gets called which returns the theme that gets applied to the SXA page.

And that(“DoGetThemeItem”) is the place where we need to write our custom code to switch the themes based on cookie value.

Hence, now we need to override the “DoGetThemeItem” function by creating a custom class that gets inherited by the ThemingContext class of Sitecore.XA.Foundation.Theming like shown below.

The Implementation

using System.Web;
using Sitecore.Data;
using Sitecore.XA.Foundation.Theming;
using Sitecore.Data.Items;

namespace ThemeSwitcher
{
    public class CustomThemingContext : ThemingContext
    {
        private const string CookieName = "Default-Theme";

        protected override Item DoGetThemeItem(Item item, DeviceItem device)
        {
            HttpCookie cookie = null;
            cookie = cookie ?? HttpContext.Current.Request.Cookies.Get(CookieName);
            Item defaultThemeItem = Sitecore.Context.Database.GetItem(new ID(Templates.Themes.DefaultTheme));
            Item newThemeItem = Sitecore.Context.Database.GetItem(new ID(Templates.Themes.NewTheme));
            return (cookie?.Value == "1") ? newThemeItem : defaultThemeItem;
              
        }
    }
}

Here, we need to create a project wherein we have a class called “CustomThemingContext” and write our custom logic for theme switching as shown in above code block.

And we need to replace the existing ThemeContext service with our new custom CustomThemingContext by patching the configuration as shown in below image.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
    <sitecore>
        <mvc>
            <precompilation>
                <assemblies>
                    <assemblyIdentity name="ThemeSwitcher" />
                </assemblies>
            </precompilation>
        </mvc>

        <services>
            <register serviceType="Sitecore.XA.Foundation.Theming.IThemingContext, Sitecore.XA.Foundation.Theming"
                      implementationType="ThemeSwitcher.CustomThemingContext, ThemeSwitcher"
                      lifetime="Singleton"
                      patch:instead="*[@implementationType='Sitecore.XA.Foundation.Theming.ThemingContext, Sitecore.XA.Foundation.Theming']"/>
        </services>
    </sitecore>
</configuration>

The SXA theme contains styles, javascripts and images and their references get injected in the SXA pages. By default, the SXA pages use the MVC layout which is found at /sitecore/layout/Layouts/Foundation/Experience Accelerator/MVC/MVC Layout having the view path /Views/SxaLayout/SxaLayout.cshtml which is an Out-Of-the-Box(OOTB) razor view included in the Sitecore SXA as shown in below image.

Sxa Theme Image011

If we see line no. 11 from the above image

GenerateLinks()

Calling GenerateLinks() function

GenerateLinks() sorts through all the Sitecore themes and returns the requires styles and scripts links which gets added in the <head> tag of the razor view.

Again, peeking into Sitecore.XA.Foundation.Theming.dll and checking into AssetLinksGenerator class, we found that GenerateLinks() function which again calls to GenerateAssetLinks() function where the little customization needed and hence the cookie value will be added in the existing cache key. This will ensure that whenever there is a change in the cookie value to change the theme, the cache key will also get changed which will call the “assetService” pipeline to get the asset links of styles and scripts instead of returning the previous cached asset links.

Here, we need to create a custom AssetLinkGenerator class and add it into our project and update the GenerateAssetLinks() function as highlighted below.

To view the complete code file, please visit to ThemeSwitcher/Service/CustomAssetLinksGenerator.cs at master · jitendrachilate/ThemeSwitcher (github.com)

public virtual AssetLinks GenerateAssetLinks(IThemesProvider themesProvider)
       {
           HttpCookie cookie = null;
           cookie = cookie ?? HttpContext.Current.Request.Cookies.Get(CookieName);
           if (!Sitecore.SecurityModel.License.License.HasModule("Sitecore.SXA"))
           {
               HttpContext.Current.Response.Redirect(Settings.NoLicenseUrl + "?license=Sitecore.SXA");
               return (AssetLinks)null;
           }

           string str = string.Format("{0}#{1}#{2}#{3}#{4}", (object)this.Context.Item.ID, (object)this.Context.Device.ID, 
               (object)this.Context.Database.Name, (object)this._configuration.RequestAssetsOptimizationDisabled, (object)cookie?.Value);
           string cacheKey1;
           if (HttpContext.Current.Cache[str] != null && HttpContext.Current.Cache[cacheKey1 = this.GenerateCacheKey((int)HttpContext.Current.Cache[str])] != null)
           {
               Sitecore.Diagnostics.Log.Info("GenerateAssetLinks: returning cache value", this);
               return HttpContext.Current.Cache[cacheKey1] as AssetLinks;
           }
           AssetsArgs args = new AssetsArgs();
           CorePipeline.Run("assetService", (PipelineArgs)args);
           int hashCode = args.GetHashCode();
           string cacheKey2 = this.GenerateCacheKey(hashCode);
           if (!(HttpContext.Current.Cache[cacheKey2] is AssetLinks result) || this._configuration.RequestAssetsOptimizationDisabled)
           {
               result = new AssetLinks();
               if (!args.AssetsList.Any<AssetInclude>())
                   return result;
               args.AssetsList = (IList<AssetInclude>)args.AssetsList.OrderBy<AssetInclude, int>((Func<AssetInclude, int>)(a => a.SortOrder)).ToList<AssetInclude>();
               foreach (AssetInclude assets in (IEnumerable<AssetInclude>)args.AssetsList)
               {
                   switch (assets)
                   {
                       case ThemeInclude _:
                           this.AddThemeInclude(assets as ThemeInclude, result, themesProvider);
                           continue;
                       case UrlInclude _:
                           this.AddUrlInclude(assets as UrlInclude, result);
                           continue;
                       case PlainInclude _:
                           this.AddPlainInclude(assets as PlainInclude, result);
                           continue;
                       default:
                           continue;
                   }
               }
               this.CacheLinks(cacheKey2, result, this.DatabaseRepository.GetContentDatabase().Name.ToLowerInvariant().Equals("master", StringComparison.Ordinal) ? AssetContentRefresher.MasterCacheDependencyKeys : AssetContentRefresher.WebCacheDependencyKeys);
               this.CacheHash(str, hashCode);

           }
           return result;
       }

Custom View (for MVC Layout item)

An important thing to notice here is that this Razor view file gets updated whenever there is a change in the theme and dynamically the asset links get added in the <head> tag of the SxaLayout.cshtml. However, a developer will notice that the changes may not reflect especially after deployment in the servers because SXA razor views are precompiled into DLLs for better performance and any changes on razor view on runtime like adding theme assets links won’t show up with the changes.

To make razor view changes renders we can locate to the App_Config/Sitecore/Mvc/Sitecore.Mvc.config file, there is a setting called “Mvc.UsePhysicalViewsIfNewer”, change its default value “false” to “true”. This change will allow Sitecore’s view engine to check and compare the modified dates of the DLLs and chtmls files and based on it Sitecore will choose either the precompiled DLLs or physical view for rendering.

For more details, please refer to this blog post on making these configuration changes.

https://sitecorewithraman.wordpress.com/2023/08/06/overriding-precompiled-sxa-razor-views-in-sitecore/

Creating a New View

However, it’s recommended not to make any changes to OOTB razor views, and better to create a new custom razor view cshtml file and associate this view with the MVC Layout.

Hence, we’ll be creating a new view by copying the existing SxaLayout.cshtml razor view and let’s say name CustomSxaLayout.cshtml and call the GenerateLinks() function as shown in the below code block.

@using Sitecore.Mvc
@using Sitecore.XA.Foundation.MarkupDecorator.Extensions
@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions
@using Sitecore.XA.Foundation.Grid.Extensions
@using Sitecore.XA.Foundation.Theming.Bundler

@model Sitecore.Mvc.Presentation.RenderingModel

@{
    AssetLinks assetLinks =  ThemeSwitcher.CustomAssetLinksGenerator.GenerateLinks(new ThemesProvider());
}

<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="@Model.Item.Language.Name">
<!--<![endif]-->
<head>
    <meta charset="utf-8">
    @foreach (string style in assetLinks.Styles)
    {
        @Html.Raw(style)
    }

    @Html.Sxa().VisitorIdentification()
    @Html.Sxa().Placeholder("head")
</head>
<body class="body-background" @Html.Sxa().Body().Decorate()>
    @Html.Sitecore().Placeholder("body-top")
    @Html.Sxa().GridBody()
    @Html.Sitecore().Placeholder("body-bottom")
    @foreach (string script in assetLinks.Scripts)
    {
        @Html.Raw(script)
    }
    <!-- /#wrapper -->
</body>
</html>

At line no. 10 we’re calling the CustomAssetlinksGenerator’s GenerateLinks() function to get the corresponding theme item based on the cookie value.

Finally, navigate to /sitecore/layout/Layouts/Foundation/Experience Accelerator/MVC/MVC Layout and change the existing view path of SxaLayout.cshtml to CustomSxaLayout.cshtml as shown in the below image.

MVC Layout Item

MVC Layout Item

Please do remember to apply the unicorn serialization rule to the above highlighted “MVC Layout” item so as to prevent the changes from being overwritten by any upcoming deployment, else the changes made on the ‘Path’ field will be reverted.

Please find the code implementation at Git Repo: https://github.com/jitendrachilate/ThemeSwitcher.git

The Demo:

We see in the below gif, that there are two themes “DefaultTheme” which has a background color gray, and “NewTheme” which has a background color light pink.

When changing the cookie “Default-Theme” value to 1 from 0 the background color turns to lightpink and when changed back the cookie value to 0 from 1 the background color turns to gray.

Theme Switcher Demo

Theme Switcher Demo

Thank you for reading! Hope this blog helps you to implement theme switching in your sites. 😊

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Jitendra Chilate

Jitendra Chilate is a Computer Engineering Graduate working as a Technical Architect with over 14 years of experience in Web/.Net technology. He has extensive experience in cross-platform application development on .NET platforms and over two years of experience in Sitecore. Jitendra has worked on numerous projects in .Net and Sitecore and delivered them successfully. He likes to explore new tech trends and challenging topics.

More from this Author

Categories
Follow Us