Friday, July 7th I began my new series “A Different Approach” with the post about injecting resources into the experience editor. After I made my that post live, I continued my research and I became increasingly unhappy with my solution described. There is a better solution that solves my issue and that solution is somewhere in this post! 😮
Click here to skip the details of what led to this post.
Click here to skip everything and go straight to my solution.
Click here to watch the companion vlog to this post. It explains the code in more detail.
Flashback to June 30th
It all began with another oddly themed post. In that post, I described a situation where a client wasn’t 100% satisfied with their EE experience. They were concerned with the vagueness of how some of the fields and made a valid request. The client asked if we could add helpful descriptions to the fields prior to them initializing the field’s chrome which obviously contains the Field Name and Help Text (if any).
I decided to use my Editor Enhancement Toolkit module. First, I created a new rule action that ties a field to the helpful information. Next, I created a processor that’s patched into the RenderField pipeline. The processor is simple. It processes the rules and if the rule’s conditions pass, the new rule action populates a variable with the helpful information. The variable is then wrapped in HTML markup and prepended the default markup that renders the field’s chrome.
Issue That Arose from That Solution:
Since both inline-styling and customizing existing Sitecore files i.e. the webedit.css are frowned upon, I needed to find a way to inject the stylesheet into the EE. I attempted to implement a solution using “PageExtenders” that I found on blogs by Pavel Veller, Mike Reynolds and Kamruz Jaman, but I failed.
Kamruz Jaman, the always helpful Sitecore superhero saved me from wasting a lot of time troubleshooting and told me: “Since SC8.1 or so, the ribbon changed and was put into its own iframe. Previously it was part of the same page so using the page extenders meant it would affect both the content and the ribbon.”
Update July 13th: I figured out how to get the PageExtender working, I explain it in the “My Solution” section. Kamruz also figured out a way to get it functional and informed me of this a little bit ago.
Let’s head back to the future… er… present, July 13th, 2017
What I Hope to Achieve
I want to figure out a way to implement my solution that is more flexible than hard coding the Stylesheet’s file in code and it must not involve any customizations to Sitecore items and files. Normally I would give a lecture on how customizing Sitecore items or files creates upgrade headaches, but rumor has it Speak UI is becoming extinct. Which means this post will probably lose all relevance in about 2 months. Let’s live for today and continue the quest to find a better solution to the issue. I am going to blog as I try to figure this out. Hopefully I figure this out quickly so the post doesn’t become insanely long.
Potential Solution: GetPageStylesheets Pipeline & Speak Items
I was digging through the decompiled assemblies, I can across something interesting in the Sitecore.Speak.Client assembly. It was the processors that are used in the GetPageStylesheets pipeline. There are two important templates that are used by the code in processors. They are:
- Page-Stylesheet-File: This item allows you to select a single stylesheet from the file system to include in your app.
- Page-Stylesheet-Folder: This item allows you to select a single folder from the file system. That folder should contain only stylesheets, but it doesn’t matter. The code will render out all the stylesheets that are in the selected folder, ignoring any other file type.
The items based on either template need to be created under the PageSettings item for the everything to work correctly.
Going back to the code, the GetFileNames processor was the first to catch my eye. The code gets the PageSettings item and then it’s passed to the StylesheetCollection for processing. The ProcessItem method in the StylesheetCollection loops the children of the PageSettings item and performs two checks:
- If the child item’s template is the Page-Stylesheet-File: The stylesheet’s path that is defined on the item is added to args.FileNames collection.
- If the child item’s template is the Page-Stylesheet-Folder: The StylesheetCollection gets the path to the folder defined on the item and loops through the folder’s files. If the file is a stylesheet, the file path is added to the args.FileNames collection. If the file is a different type, it is ignored. It appears that it also processes any sub-directories as well.
The end to this madness is relatively straightforward. The extension method: PageStylesheets located in the static class: SitecoreHelperExtensions is where all the magic happens. In this method, the GetPageStylesheetsArgs are set and then the getPageStylesheet pipeline is fired. The processors do their thing, finally BuildStylesheets has its moment to shine. It processes the args.FileNames collection. Looping thru the collection, it constructs the link tag for each stylesheet and sets the final value to args.HtmlString. The PageStylesheets returns the HtmlString and called on the PageCode view and rendered.
Cool pipeline. I am glad I learned more about its functionality. I need to keep it in mind when I resume my custom Speak App.
I was curious if anyone else blogged about this and of course someone did. August 25, 2015 Mark Servais posted: SPEAKing Aloud: Changing CSS. Great post. I wish I had searched for this a couple hours ago, it would have saved me some time.
Unfortunately, this potential solution was a bust. The search continues.
Potential Solution: A PageExtender, a Control and Pipeline Processors
Every rendering on the Ribbon item renders in the Ribbon… makes sense. I mentioned that so we can cross off all solutions that involve custom views being placed on the Ribbon item. This also means we should stop considering the PageCode in hopes of finding a solution.
We know that the page code controls everything on the Ribbon which lives in an iframe. If you go to a content page in the Experience Editor and inspect the page’s source, you’ll notice other code and markup. Interesting, I wonder what is controlling all that. Let’s explore that more.
After searching for some unique words found in the source such as “scClientSettings”, I was led the ScriptResources.cs and the GetClientSettings extension method. Next, I needed to find what was calling this method. After a quick search, I found two files, WebEditRibbon.cs and RibbonWebControl.cs.
This is used in three of the PageExtenders: DebuggerExtender, PreviewExtender and WebEditPageExtender. However, there is a red flag… the WebEditRibbon is marked as Obsolete. I’m going to slowing back away from the WebEditRibbon and pretend I didn’t see it.
This is located in the Sitecore.ExperienceEditor.Speak.Ribbon assembly. It’s related to the PageExtenders. When the RibbonPageExtender code runs, it fires the getExperienceEditorRibbon pipeline and the processor AddWebEditRibbon.
That processor created a new RibbonWebControl, sets a property. That object is then assigned to the args.Control.
There is a lot to this, but the part I’m interested in is the DoRender method. That method is writing out some of the stylesheets and other items seen in the page’s source. Cool.
I think I have seen enough. I have an idea that just may solve my issue while achieving my goals of flexibility and Laissez-faire Sitecore items and files.
I created a new PageExtender called “InjectAssetsPageExtender”.
When the Insert runs, it fires off a custom pipeline called “renderPageArgs” located on line 15, The first processor in this pipeline is GetStylesheets.
I borrowed the “sources” node section from a processor located around line 95 in the 001.Sitecore.Speak.Important.config. , I get the folder paths of the stylesheets from the config:
I also borrowed some of the code from the GetFileNames processor located in the Sitecore.Speak.Client assembly, that is used in the GetPageScripts pipeline. After making a lot of quick edits, the code for GetStylesheets looks like:
The method AddSource gets the values from the config. Path = path of the Stylesheet, deep = process subfolders, add additional stylesheets to the collection and pattern, this allows what file type are permitted to be added to the FilePaths collection. Next, it loops through the folder(s) defined in the config above, and adds the stylesheet paths to the FilePaths collection. After the method finishes, we assign the FilePaths list to args.FilePaths. Next up, AssignPageAssetsToControl.
Next we head back to the InjectAssetsPageExtender.
Line 16 calls AddControlToPage and passes in the args that now contains a valid, populated RenderAssetsControl control which is then added to the page using the “webedit” placeholder.
The Configuration for this in its entirety looks like:
If everything is set up correctly, you should be able to locate your CSS files by viewing the page’s source. This is what my source looks like:
Since my first post covering this topic, Kamruz Jaman and I have discussed the PageExtenders and a few other things. Initially he informed me that PageExtenders no longer affect the Experience Editor since the Ribbon was moved into an iframe.
I was excited that I was able to get a PageExtender to render outside the Ribbon and I needed to share. I opened Slack and messaged JammyKam my good news. If you know Kamruz, you can probably guess what he said. He figured out how to get the PageExtenders to work as well! 🎉 It’s like Christmas in July🎄. Kamruz showed me how he finally got it to work and it was different from my code.
If you’re curious what Kamruz’s solution is, you can read about it here. It’s a great post.
I am relieved I figured out a decent solution that met the requirements I set for myself. The code is more flexible than it had been and additional code can be added that would increase the flexibility. I had more planned, but I’m not sure if I want to spend more time on something that may become obsolete when SItecore 9 is released.
Click here to watch the companion vlog explaining the code in greater detail.
I hope you found this post informative. Thanks for reading.
Do you enjoy my oddly themed blogs and wish you had access to even more of me and my ideas? Good news, you’re in luck!
If 140 characters is your thing, follow me on Twitter.
If you hate reading and watching Sitecore videos entertains you, head over to my YouTube channel! Sometimes I entertain, sometime I provide useful Sitecore information and sometimes I can do both in the same video.
I can also be found hanging out on the Sitecore channels on Slack, I like it, although it occasionally triggers AOL chat room flashbacks from the olden days.