Using FXM to federate Sitecore experiences

FXM can be used to deploy a range of Sitecore capabilities to external sites, from simple analytics tracking to complex federated experiences. Federating content on an external site is a relatively straight forward task if the requirements are limited to the following:

  • Federation of controls that don’t contain placeholders
  • The controls to be federated inherit the style and behavior of the external site

On the other hand, if the requirement is to federate one or more controls that do specify placeholders (i.e from a page layout) and that also have their own CSS and JavaScript the challenges become apparent quickly:

  • Controls that reference placeholders
  • Isolating styles required by federated content from those of the host site
  • Attaching event handlers to document elements loaded by FXM
  • Varying a control’s behavior when it’s running in FXM
  • Browser cross origin request sharing for static assets (e.g. fonts)

To explain these challenges and some possible solutions this post will demonstrate how to federate the header from the Sitecore Habitat demo site.

fxm6

Controls that reference placeholders

The Habitat header component is comprised of multiple controls specified in the layout of each page.

fxm1

Viewing the properties for any of the controls will show that they each refer to a placeholder provided by the Habitat default page layout or another control.

fxm2
Federating controls that specify a placeholder presents a problem for FXM because while a FXM page can contain multiple renderings, the placeholder field of those renderings can’t be changed. This is because the placeholder field of the rendering is used by FXM to specify a dynamic placeholder in the FXM page layout.

fxm3

A solution to this problem is build an item rendering that uses the layout of the referenced item, rather than that of the FXM page. Jeff Darchuck has blogged about a solution for rendering items to a string that I’ve repackaged to work as a general purpose item rendering. This item rendering extends the renderPlaceholder pipeline by adding a processor that will render an item using its own layout. The code for the item rendering is available on github and can be applied with the following patch.


  
    
      
        
      
    
  

A generic layout and a header view rendering are also built and then used in a new item that will contain only the Habitat header controls in its layout.

fxm4

Isolating styles required by federated content from those of the host site

When federating controls that require their own styles something must be done to isolate the styles belonging to the federated controls from those native to the external site. Otherwise, conflicts will invariably occur and neither the federated or native content will display properly on the external site. From a CSS perspective this isolation can be easily achieved by pre-pending a selector to the federated styles as shown below.

fxm5

The transform that will prepend the selector should be added to a a FXM specific style bundle. This can be accomplished by leveraging a CSS parser such as ExCSS from a pipeline processor. The PrependCssSelectorProcessor can be inserted into the FXM bundle pipeline with the patch shown below.


  
      
        <!--
        The selector that PrependCssSelectorProcessor will prepend
        to all CSS rules in the bundle
        -->
        
      

      
        
          
            styles/vendor.min.css
            styles/niteflight.min.css
          
          
          
        
      
    
  

It will also be necessary to edit the Sitecore.Fxm.Bundle.config file to add the style bundle created above. However, I wasn’t able to write a patch that would insert the new bundle, so it may be necessary to edit the config file directly. Alternatively, instead of using the FXM bundle pipeline the PrependCssSelectorTransform can be added to a bundle that’s created in code during application startup.

To take advantage of the isolated styles a new view rendering (i.e. Fxm Header) is required that copies the HTML for the header from the default Habitat layout and wraps it with the selector that was prepended.

<div class="fxm">
<div id="main-container">
        <header class="header-static">
            @Html.Sitecore().Placeholder("header-top")
            @Html.Sitecore().Placeholder("navbar")
        </header></div>
</div>

Attaching event handlers to document elements loaded by FXM

FXM loads federated controls on the external site after the document ready event has fired; this means that any code that initializes event handlers via the document ready event will fail because the HTML elements will not yet exist in the DOM. To remedy this problem event handler initialization code will need to be extended to make use of the FXM beacon’s ready event as shown below.

(function ($) {
    $(function () {
        if (typeof SCBeacon !== "undefined") {
            SCBeacon.push(['ready', InitSearch]);
        }
        else {
            InitSearch();
        }

        function InitSearch() {
            $('[data-toggle-class]').on('click keypress', function (e) {
                e.preventDefault();
                switch ($(this).data('toggle-class')) {
                    case &amp;quot;is-searching&amp;quot;:
                        $('#siteNavbar').collapse('hide');
                        break;
                }
                $($(this).attr('data-target')).toggleClass($(this).attr('data-toggle-class'));
                $(this).parent().find('input').focus();
            });
        }
    });
})(jQuery);

Varying a control’s behavior when it’s running in FXM

Some controls will need to behave differently when running in the context of FXM. Since the Sitecore.Context.Item will always be the FXM placeholder when a control is rendering in FXM it’s easy to discover whether the control is running in the context of FXM as shown below.

    public static class ItemExtensions
    {
        public static bool InFxm(this Item item)
        {
            Item contextitem = Sitecore.Context.Item;
            if (IsFxmPlaceholder(contextitem))
            {
                return true;
            }

            return false;
        }

        private static ID FxmPlaceholderID = new ID("{10E23679-55DB-4059-B8F2-E417A2F78FCB}");

        private static bool IsFxmPlaceholder(Item element)
        {
            if (element == null)
                return false;

            if (element.TemplateID == FxmPlaceholderID)
                return true;

            return false;
        }
    }

A common area of functionality that will require knowledge of whether the control is rendering in an FXM placeholder is the generation of internal links.

    public static class ItemExtensions
    {
        public static string Url(this Item item, UrlOptions options = null)
        {
            if (options == null && !item.Paths.IsMediaItem && item.InFxm())
            {
                options = LinkManager.GetDefaultUrlOptions();
                options.AlwaysIncludeServerUrl = true;
                options.SiteResolving = true;
            }

            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            if (options != null)
                return LinkManager.GetItemUrl(item, options);
            return !item.Paths.IsMediaItem ? LinkManager.GetItemUrl(item) : MediaManager.GetMediaUrl(item);
        }
    }

Browser cross origin request sharing for static assets (e.g. fonts)

Adding CORS headers and mime type mappings in the web.config will allow static assets (e.g. fonts) to overcome cross origin request sharing issues enforced by the browser.


  
    
      
      
    
  
  
    
      
        
          
        
      
    
  

Configuring FXM for an external site

There are some configuration and content changes that are necessary for FXM to function outside of a local development environment. The first is that the FXM.Protocol and FXM.HostName settings should be set in the Sitecore.FXM.config file.


  
    
      
      
    
  

Then on the external site the following three links should be included.



	

The content change is to add at least one language rule to the FXM site in the content editor. Typically this rule could just be the mapping of all requests to the default Sitecore language.

fxm8

Final thoughts

In this post I’ve shown that it’s feasible to federate even complicated Sitecore experiences (e.g. the Habitat header) with relatively minor code changes to existing feature and foundational code. I have created a fork of Habitat with all of the code, renderings, and templates mentioned in this post. Comparing the FXM fork with the master Habitat project is a good way to get an overview of all the necessary changes.