Showing posts with label Experience editor. Show all posts
Showing posts with label Experience editor. Show all posts

Thursday, July 3, 2025

XP editor navigation issues in Sitecore JSS

Experience Editor & Sitecore JSS

As you probably know, Sitecore's experience editor does work on headless sites created with Sitecore SXA and JSS enabling editors to work in headless sites just as they would in the non-headless ones.  We had such a headless site and bumped into a few issues in the experience editor though. 

An important note here is that this headless site is not the only one on this Sitecore instance (10.2) - there are others, non-headless and non-sxa mvc sites. 

Navigation bar

Our first issue was the navigation bar. The navigation bar allows you to navigate to a specific page through a menu structure, a bit like a breadcrumb.  To enable the navigation bar, in the ribbon, on the View tab, select Navigation bar.


Our issue was that the urls in that navigation were wrong - they were using the wrong sc_site parameter in the querystring. 

SiteResolving

First thing we had to check was the configuration of the sites. When adding sites with config patches they can be ordered. We had done this properly, but the headless site is a SXA site and that does not gets added through a config file. However, there also is a config section siteResolving in the ExperienceAccelerator section. This needs to include all your non-sxa sites so you need to patch them in.
<siteResolving patch:source="Sitecore.XA.SitesToResolveAfterSxa.config">
  <site name="exm" resolve="after"/>
  <site name="website" resolve="after"/>
  <site name="unicorn" resolve="after"/>
  <site name="x" resolve="after" patch:source="xxx.Sites.config"/>
  <site name="y" resolve="after" patch:source="xxx.Sites.config"/>
  <site name="z" resolve="after" patch:source="xxx.Sites.config"/>
</siteResolving>
This is a good and necessary step forward, but unfortunately it did not solve our problem yet. 

TargetHostName

The final step to get it working was adding a target hostname to the site definition of our headless sxa-jss site. Not an ideal solution, but it does work. 

Ok, issue number one solved - the navigation bar is working.




LinkFields

We also noticed we had a problem when using LinkFields. 

When we have a RT field in the XP editor, the links inside are transformed into something like https://.../~/link.aspx?_id=5F7338F6FCFC404EB1EF67FAE1F71F7D&_z=z. In components using integrated GraphQL to fetch data however, we notice that the LinkFields are not transformed in the same way (on the same page). We fetch the jsonValue of the fields, use the Link component from the jss sdk in our Next.js code. This work perfectly in normal mode, but in the XP editor we get links like https://.../en/Test%3Fsc_site=X. If editors click on those links they get an error. 

I asked this on Sitecore StackExchange but didn't get an answer there. So I opened a Sitecore support ticket and after a few rounds with questions and answers and logs, videos and data gathering we finally came to a solution. 

But before we come to the solution, let's take a step back to see how we got there. 

Step 1 - the ?

First thing we noticed was the %3F instead of a ? to start the querystring. Apparently there is a bug called "Internal link query string encoded in Next.js app" but we actually just fixed it in the Next app itself by replacing the character. Problem 1 solved.

Step 2 - sc_site

To get the correct site in the links I first wanted to check the data that came from the graphQL queries. To do this, I wanted to use the grapQL edge UI in edit mode and apparently that is possible. How to do that is documented on StackExchange - https://sitecore.stackexchange.com/a/39193/237 - so no need to repeat it all here. In short, if you want to fetch the data in edit mode:
  1. Fetch a token from the Identity Server using Postman (or similar tool) as explained here
  2. Add the authorization header in the http headers
  3. Add sc_mode=edit to your querystring



This way we learned that the url was definitely coming from Sitecore the wrong way. So back to Sitecore... 

Maybe now is a good time to mention that although this headless site is rather new, the other mvc sites on the same platform are not. Instead, it's an old spaghetti platform that we inherited - I guess if you have been around in this business for a while you know what I mean.

And so, eventually we (to be honest, Sitecore support) found that someone patched a setting Languages.AlwaysStripLanguage to false. 

The default value for this setting is true, and it specifies if the StripLanguage processor in the "preprocessRequest" pipeline will parse and remove languages from the url, even when the languageEmbedding attribute of the linkProvider is set to "never".  

Changing this setting back to the original value (true) fixed our issue. And didn't cause any new ones... 


Conclusion

Don't touch settings that you should not touch... 

Thursday, October 26, 2023

Sitecore SXA - XP navigation bar site resolving

Sitecore Experience Editor - navigation bar site resolving issue


Situation

We inherited a Sitecore 10.2 setup with a number of sites created with old school MVC. Nothing really fancy, just a bunch of sites just as many other solutions probably. The setup is a little weird (just as many "inherited" solutions, right...) and the customer is not used to working in the XP editor. 

When we were asked to add one more site, we decided to go for a headless approach and installed JSS and SXA on the environment. This is all working fine - all sites are running fine and I dare to say the new site is much more towards all best practices. And also introducing the XP editor - especially to add new components to a page. With the SXA extensions that is working like a charm.

Although we noticed one little issue. The navigation bar in the XP editor didn't work as expected. 


We can open a page in the XP editor and it works - the url looks like
https://dev/?sc_mode=edit&sc_itemid=...&sc_lang=en&sc_version=1&sc_site=portal.
But when we then use the navigation bar to go to another page we get an error because the sc_site parameter in the url is wrong:
https://dev/site1/?sc_mode=edit&sc_itemid=...&sc_lang=en&sc_site=site1&sc_ee_fb=false.
When we manually change the sc_site parameter back to "portal" it works. It seems that it takes the first site in the site definition list.


Site configuration

First thing to check is the site configuration. That seemed to be fine - we also checked the SXA Site Manager and that as well was all ok. 

Note here that the mvc sites are in the config (files), the SXA site is configured in Sitecore (items). This makes is tricky to order them.

There is a SiteResolving config section from SXA where you can add sites that should be resolved after the SXA sites. Just to be sure we added all sites to that list in the config:
<siteResolving patch:source="Sitecore.XA.SitesToResolveAfterSxa.config">
  <site name="exm" resolve="after" />
  <site name="website" resolve="after" />
  ....
</siteResolving>
When all sites are added you can see in the SXA Site Manager that the SXA site moved up and that is now our first site.

Unfortunately, that didn't solve our issue. The site resolving in the navigation bar is still wrong. 


The solution

We contacted Sitecore Support. I thought about trying to find the responsible code to see if I could fix it myself - didn't do that as it would take too much time for something that is not convenient but also not blocking. 

And finally none of those options solved it, but well - let's call it luck. I can't remember why I decided to add a targetHostName in the site definition of the new SXA site. But I did - and actually that fixed our issue. The site resolving is working fine now.

We still don't know exactly why and don't want to spend time searching for a root cause but it is solved and might solve a site resolving issue for you as well. If anyone would know the root cause, please do share though.

Wednesday, March 1, 2017

Local datasources on standard values with branch templates

Local datasources module

The local datasource module on the Sitecore marketplace has a second version. The first version managed to create local datasource items when editing in the experience editor. In this second version we anticipated on renderings that are added by default on a template.

Branch templates with "standard values"

The idea was simple: 
  1. create a branch template based on the page template
  2. edit the created item (in the branch template - probably called $name) in the experience editor
  3. add a rendering with a local datasource (the local datasource module will create the children - a data folder and the datasource item)
  4. repeat 4 if wanted (add more renderings)
  5. use the branch template when creating items
Sounds simple, but of course when creating the items the datasource value of the rendering is still set to the item in the branch templates section - not to the child item we just created.

To fix that we added some code that I'll explain here. Code can be found on GitHub.

Event

I used the item:added event and not the item:created event. We need to be able to detect whether the item was created from a branch and can use the BranchId property of the Item for that. The item:created event however comes too soon to do that as the BranchId is not yet set at that moment. In the item:added event, we can check the BranchId. 

So we create a handler to add to item:added and patch it as first handler: patch:before="*[1]".

OnItemAdded

Our main function starts with some checks to get out as fast as possible when it is not applicable. When it is, we call our action method for the current item and all descendants. 
In case of a branch, the item:added event is called only once for the main item. 
But as this is a branch, we might also have a child with local datasources set. So we check all children - they are already created at this time.

Correct the datasources

For each item with a layout (item.Visualization.Layout != null) we grab the renderings and change the datasource value if it should be local.
var layout = LayoutDefinition.Parse(item[Sitecore.FieldIDs.LayoutField]);
var devices = layout.Devices;
var changed = devices.Cast<DeviceDefinition>().Sum(device => SetDatasourcesToLocal(device, item));
if (changed > 0)
{
    UpdateLayout(item, layout, Sitecore.FieldIDs.LayoutField);
}
We use the Parse method to get a LayoutDefinition of the "Shared layout" field. This does mean indeed that we only cover renderings placed on the shared layout. Doing the same for a rendering on the final layout is for a future version...

We loop through the devices and call a function to change the datasource values. This function loops over all renderings and checks the datasource item. If the datasource items path starts with /sitecore/template we know this was meant to be a local one. To find the correct local item, we explicitly go into the local data folder because a search over all descendants might also find a similar datasource item on another page in the branch. Once found, we set the value to the rendering in the layout.

Saving the changes

We keep track of the changes made because they are not automatically saved back into the item. The "layout" object has all the changes, but the item does not. So if we had any changes, we save the new value back into the shared layout field:
item.Editing.BeginEdit();
item.Fields[Sitecore.FieldIDs.LayoutField].Value = layout.ToXml();
item.Editing.EndEdit(false, false);

Conclusion

Version 2 is now published on the marketplace, and to be honest version 3 will not be for any time soon. But if you like the module, I'm always open for comments, ideas and contributions...

Enjoy the local datasources!

Tuesday, February 14, 2017

Local Datasources module for Sitecore Experience Editor

Local Datasources marketplace module

On the Sitecore marketplace a new module was recently added to help editors with the concept of "local" datasources. The module can be found here. 

The source code is available on GitHub. Instruction on installing and using the module are included in the readme file.

We assume people working with Sitecore and reading this know what datasources are. Some of you might even work with a "local" datasources. Local meaning that the datasource item is coupled with the "page" item where it is used in a hierarchical way by storing the datasource item underneath the page item - in a "local" data folder.

We tried to automate this proces for datasources that should not be shared amongst "pages" while working in the Experience Editor.

The solution now consists of 2 parts.
  • The first part will create the actual datasource item (and the data folder if that does not yet exists - datafolder will be pushed as latest child). The template name of the required datasource is used as base for the item name, combined with a number.
  • A second part will prevent the "Select the associated content" dialog from appearing and is discussed already in a previous post: Preventing the Sitecore "select datasource" dialog.

I wrote another blog about the functionalities of the module - here I will go deeper into the code.

Creating the datasource

Creating the datasource item is done in a processor in the getRenderingDatasource pipeline. This pipeline is started within the AddRendering command (see step 2 - the dialog). 

We check the datasource location of the rendering and determine whether it is "local". If so we start the item creation:
  1. Create the parent item for the datasource item if it does not exist 
    • based on a provided template
    • underneath the context item, and pushed down to the last child position
    • name is based on the datasource location (we should add some more data verification here)
  2. Add this parent item to the datasource roots
  3. Create the datasource item underneath the parent
    • based on the template set on the rendering as datasource template
    • using this template name of the datasource as base for the item name (combined with a number)
  4. Set the datasource item FullPath as CurrentDatasource in the pipeline arguments

A few things to notice here:
  • We use a SecurityDisabler to make sure the item can be created.
  • We use a SiteContextSwitcher (to "system") to make sure the page does not refresh (if you don't do this, the page detects a new item and refreshes - causing errors)

The datasource dialog

You can read here how we prevented the dialog from appearing and still setting the datasource value when the arguments were set correctly in the previous step using a custom AddRendering command.

As we do override the AddRendering command, the module needs a Sitecore version with the exact same code as the one we used in that command to keep all renderings without local datasources to work as expected.

The Sitecore Experience Accelerator (SXA) has it's own version of the AddRendering command - for the same reason btw. 
This means that this module should not be installed on instances were SXA is also present!
A future enhancement could be to add SXA support to the module, although it might be a better option to try to use SXA functionality in your non-SXA site - but that is a whole other story...

Future enhancements

We released this module as version 1.0.  It is working on Sitecore 8.2 and has some functionalities already but we are aware that there are still quite some areas of improvement. As mentioned here before, we might need to add some extra data checks to be more robust. But also on a functional level we still have ideas:
  • a solution for renderings that are already set on the standard values of a template (using branches maybe?)
  • remove datasource items if the rendering is removed (and no other links are found)
  • content editor support? (although we're not quite sure we actually want this)
Feel free to use the module, share more ideas for enhancements, contribute to the code...

Saturday, February 4, 2017

Preventing the "Select Datasource" dialog in the Sitecore Experience Editor

Select the Associated Content dialog

Every editor working in the Sitecore Experience Editor has undoubtedly seen this screen before:

The dailog lets you select a Datasource for a rendering. Or create a new one...  The dialog will appear if your rendering has a datasource location set on its definition item:

 

But now we had a case where we wanted this dialog not to appear. The datasource was set in a custom processor in the getRenderingDatasource pipeline so it was already available. The dialog knows about this value as it will select it by default for you but it still appears.

Rendering "Data source" field

A first attempt was successful by accident. Wouldn't be the first achievement made like this... If a datasource is set in the "Data source" field of the rendering definition item, the dialog will not appear. Due to a flaw in the code setting the datasource the value was written there instead of the actual item containing the rendering. Works like a charm! But not thread safe. You only need two editors to place that rendering at the same time and your datasources will get mixed up. 

The AddRendering command

With a little help we found the right command to override (thanks to Support and Alan Płócieniak): webedit:addrendering.

It is quite obvious the class is not meant to be overridden, so you might need a debugging tool to get it all right. Let's start by creating a class that overrides the Sitecore.Shell.Applications.WebEdit.Commands.AddRendering. The function we need is "void Run(ClientPipelineArgs args)".  Best start is to copy the code from the original class and get rid of the function you do not need. As you will notice (I am writing based on Sitecore 8.2 initial btw) you do need to keep a few private functions in your own code as well to keep the "Run" function working.

In the Run-code, locate the call to the getRenderingDatasource pipeline:
CorePipeline.Run("getRenderingDatasource", renderingDatasourceArgs);

Just below you'll see
if (!string.IsNullOrEmpty(renderingDatasourceArgs.DialogUrl) && !AddRendering.IsMorphRenderingsRequest(args))

  {
    ...
    args.Parameters["OpenProperties"] = flag.ToString().ToLowerInvariant();
    SheerResponse.ShowModalDialog(renderingDatasourceArgs.DialogUrl, "1200px", "700px", string.Empty, true);
    args.WaitForPostBack();
  }

This is the spot where the dialog would open. As we don't want this, we add an if-statement within the if-statement from above:

if (!string.IsNullOrEmpty(renderingDatasourceArgs.DialogUrl) && !IsMorphRenderingsRequest(args))
  {
    if (!string.IsNullOrEmpty(renderingDatasourceArgs.CurrentDatasource))
    {
      var datasourceItem = Client.ContentDatabase.GetItem(renderingDatasourceArgs.CurrentDatasource);
      WebEditResponse.Eval(FormattableString.Invariant(
           $"Sitecore.PageModes.ChromeManager.handleMessage('chrome:placeholder:controladded', 
              {{ id: '{itemNotNull.ID.Guid.ToString("N").ToUpperInvariant()}', 
                 openProperties: {flag.ToString().ToLowerInvariant()}, 
                 dataSource: '{datasourceItem.ID.Guid.ToString("B").ToUpperInvariant()}' }});"));
    }
  ...
  }

What are we doing here? First we check if the datasource was set in the arguments before. If so, we want our code and no dialog anymore. We fetch the datasource item from the content database. And then the magic: we send the message to Sitecore editor that the control was added, just the way it does when a rendering was completely set (see the code a few lines below in the original AddRendering). The trick is to make sure all parameters are set correctly:

  • the ID of the rendering that was added as digits ("N")
  • a value indicating whether the properties window should be opened - this is true/false and is send without any quote
  • the ID of the datasource item as guid with braces ("B")
If any of the parameters are faulty, you will get an error on the call to "Palette.aspx" and the rendering will not be added (you will also see an error popup in the browser).

As we send the value for the properties window as for all other renderings, we can keep that functionality as is. It will react on the value set when the rendering was selected (which can be tweaked on the rendering definition item itself if you don't want to leave that choice to your editors).

Configuration

You need to tell Sitecore you have a new AddRendering command. Patch the config by creating a new config file in the include folder (make sure it gets added in the end):
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="webedit:addrendering" type="YourNamespace.YourClass, YourSolution" patch:instead="*[@name='webedit:addrendering']" />
    </commands>
  </sitecore>
</configuration>

Wrap it up


This code will probably (as it was for me) be a part of a bigger solution and is not a fully working code set. If you have any questions, you can find me on Sitecore Slack or sitecore.stackexchange.com ;)

One question that apparently does pop up is how to set the datasource. Setting the datasource when a rendering is added can be done in the getRenderingDatasource pipeline, which is called in the command as we saw. Create an extra processor and add the datasource item to the arguments:
getRenderingDatasourceArgs.CurrentDatasource = datasourceItem.Paths.FullPath;

One thing to remember though: as we did overwrite some piece of Sitecore code here, you do need to check on every upgrade that all is still valid and you might need to adapt the code. 

Monday, August 15, 2016

EasyLingo 2.1: multi-site, multi-lingual and now with XP editor support

EasyLingo 2.1


EasyLingo 2.1 was just released to the marketplace. After version 1 we promised to include support for the experience editor and so we did. Kris wrote a nice post about the how-to.

Once in the experience editor, in the view tab amongst the other "bars" you can select the "Language bar" and you will get the same EasyLingo experience as in the content editor.




We also added one more (optional) version section: in Sitecore you can create versions of an item in languages that are not registered in the systems settings. These versions are now listed as well -separately- by the module. Note that these versions will always use a general flag as we cannot detect it from the system items.

More information on this great module in my previous post. You can find the code and all documentation files on GitHub.  The module itself is available on the Sitecore marketplace.


Friday, November 27, 2015

Preview.ResolveSite and the disabled Shared Layout button in Sitecore Experience Editor

After installing some Sitecore 8.1 sites, we noticed something weird in one of the new features in the experience editor: the final/shared layout button, which quite some editors were waiting for.

The symptoms:

The Final/Shared button is disabled.




Some more testing came up with the following results;

  • It happened on some Sitecore 8.1 installations, but not on all of them
  • It happened on all browsers, on different client machines
  • It happened only if we went from the LaunchPad to the Content Editor and then via the Publish ribbon to the Eperience Editor
  • It did not happen if we went from the LaunchPad immediately to the Experience Editor - and strangely enough once we had visited the Experience Editor this way the button was always enabled throughout the entire session

It took us quite a while and help from Sitecore Support (thanks Yuriy) to figure it out, but then they pointed us at a the Preview.ResolveSite setting. A small explanation is also found at Kirkegaard's blog.

The preview resolving settings

On a multisite solution, when one opens a page in the Experience Editor, by default, Sitecore resolves the corresponding site using the value of the Preview.DefaultSite setting from the \App_Config\Sitecore.config file. The default value there is "website".

However if one sets Preview.ResolveSite setting value to "true" (it is located in the same file and by default it is "false"), Sitecore tries to resolve the root item and the context site based on the current content language and the path to the item. If Sitecore cannot resolve the context site, it uses the site that is specified in the Preview.DefaultSite setting.

So, if Sitecore renders the page on the solution in context of the "website", the Final Layout button is disabled. Why that happens is still a mystery as the rest of the Experience Editor is working fine.

But anyway: setting the Preview.ResolveSite to true fixed the issue.