Tuesday, January 26, 2016

Sitecore 8 Experience Editor: An example MVC rendering

Working With The Experience Editor

The following is a deep-dive into a rendering I recently had to develop. The goal was to allow the Content Editor to be able to use the Experience Editor 100%, and to have a very WYSIWYG feel to their content creation.

This resulted in the addition of some extra code that I normally would not have thought about. It also slightly changed how I designed the IA of the Sitecore item. Things that could have been made more extensible and reusable ended up being more flattened. While this might make future upkeep of the code slightly more time intensive, it allowed the rendering to be much more intuitive for the Content Editor.

The rendering itself is a fairly simple "Call to Action" overlay. It consists of a Title, Body, either 1 or 2 Buttons with Text and an Icon, and an Image that may or may not be used. The design stated that there was only plans for 2 formats to be used, and the markup supporting each was handled through a css class in the div wrapper.

Here are examples of the 2 possible formats:




Coming Up With The Design

The first thing to setup is the template for the CtoA. Normally I would have made the Buttons their own template, and then linked to them from the CtoA template using a Multilist. This would allow for the reuse of buttons, as well as making it easier to allow for the option of increased numbers of buttons in the future (ie: they want to use 3 or 4 buttons on a CtoA).

However, when I started to try and work with it in the Experience Editor, I found that this made things much more complex. I would have to mess around with adding placeholders to the rendering. The Content Editor would have extra steps to create the buttons, and select which ones to add to the rendering. The couldn't just create the buttons directly onto the page right away.

While it definitely would have been possible, it felt unnecessary for this particular rendering. The buttons themselves don't take much effort to make, so there wouldn't be a ton of effort being saved with their reuse. This was also a pretty stable design. While it would be nice for it to handle 3 or 4 buttons at some point, there was currently no plans for this to be needed in the near future.

What I ended up with was a pretty flat CtoA template that had the fields needed for the 2 buttons directly on the template. Any future changes to the design would take more effort (low risk), and the Content Editor would have an easier time in the meanwhile (guaranteed gain).

This might seem nitpicky for such a simple rendering, but these thoughts are definitely something to keep in mind when working on larger more involved renderings. Some parts of the design might be The Right Way™ to do it, but if it's making life difficult for the people who are going to be using it everyday then you may want to rethink things. The technical level of your Content Editors is another thing to keep in mind. Some might be ok with lots of popups and windows and complexity, others might require something more straightforward.

The template for the CtoA:






Creating The Model

The model was pretty straight forward. I know there are a few different ways of handling this, but this is the way I did it for this rendering. The Audience field is a link to another Sitecore item that contains the CSS class we are going to need in our div. I do the logic to grab that text using the Audience ID inside of the model.

The CallToActionModel.cs:

public class CallToActionModel : ModelBase<Call_To_Action>
{
public Audience Audience { get; set; }
public Icon Button1Icon { get; set; }
public Icon Button2Icon { get; set; }
public bool Button1IsVisible { get; set; }
public bool Button2IsVisible { get; set; }
public bool LogoIsVisible { get; set; }

public override void Initialize(Rendering rendering)
{
base.Initialize(rendering);

this.Audience = (this.DataSourceItem.Audience != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Audience)).GlassCast<Audience>() : null);
this.Button1Icon = (this.DataSourceItem.Button_1_Icon != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Button_1_Icon)).GlassCast<Icon>() : null);
this.Button2Icon = (this.DataSourceItem.Button_2_Icon != Guid.Empty ? Sitecore.Context.Database.GetItem(new ID(this.DataSourceItem.Button_2_Icon)).GlassCast<Icon>() : null);
this.Button1IsVisible = this.DataSourceItem.Button_1_IsVisible;
this.Button2IsVisible = this.DataSourceItem.Button_2_IsVisible;
this.LogoIsVisible = this.DataSourceItem.Logo_IsVisible;
}
}

After that you create the Sitecore Model item in Sitecore -> Layouts -> Models, and point it to your model CS file.


Setting Up The Rendering

Create the Sitecore View Rendering item in Sitecore -> Layouts -> Renderings. Fill in the following fields; Path, Model, DataSource Location, DataSource Template.

     Path: Simply the path to your cshtml file.   
     Model: Link to your Sitecore Model item.
     DataSource Location: The location of the content items this rendering will use.
     DataSource Template: The template allowed for this rendering.

Setting these last two will ensure that after the Content Editor adds the rendering to the page, they will get a nice clean popup that limits what and where they can save their items to.



Rendering Markup

There are 3 different types of editable fields that I deal with in this rendering; Text, Image, Link.

Initial setup:
First we have some code at the top of the cshtml file that determines what Model we will be using. We also grab the Audience CSS value that will be wrapping our entire control.

    @inherits GlassView<CallToActionModel>


    string audience = (Model.Audience != null ? Model.Audience.Style : string.Empty);


   <div class="box hero-bar @audience">
       ......
   </div>

Making text editable:
This is the most straightforward of the 3. Simply use the @Editable() command, and the text field that you feed it will become editable through the Experience Editor.
    
    <div class="hidden-xs">
      <div class="bx-title">
        @Editable(x => x.DataSourceItem.Title)
      </div>
      <div>
        @Editable(x => x.DataSourceItem.Body)
      </div>
    </div>





Making the image editable:
The code syntax for this one is also pretty easy, but there are a few "gotchas" you have to keep in mind from the Content Editor's point of view.

    @if (Model.LogoIsVisible && !String.IsNullOrEmpty(Model.DataSourceItem.Logo.Src))
    {
      <div class="hidden-xs">
        @RenderImage(x => x.DataSourceItem.Logo, isEditable: true)
      </div>
    }
    else if (Html.IsPageEditor() && Model.LogoIsVisible)
    {
      <div class="hidden-xs">
        @RenderImage(x => x.DataSourceItem.Logo, isEditable: true)
      </div>
    }



There are 2 things of interest to note here. The first is the use of Model.LogoIsVisible, and the second is the use of Html.IsPageEditor().

LogoIsVisible is a boolean field on the Sitecore item. We'll get into how to edit that through the Experience Editor later. The reason we are using it is because the Experience Editor gives you a stubbed image, to let you know there is an editable field there. The problem is, the Editor doesn't know when the image is purposefully being left empty. This gets in the way of the WYSIWYG experience.



By having the LogoIsVisible checkbox, the Content Editor can tell the Experience Editor that they aren't using the image, and to remove the stub.



The use of Html.IsPageEditor() combined with LogoIsVisible ensures that the code for the image only gets shown a) when we are in experience editor and the content editor *wants* to see an image, b) when we are viewing the page normally, and an image exists.

Making the buttons editable:
This last one is a little more tricky. We want both the text of the button, as well as the URL to be editable, and we want to use the Icon that is set on the Sitecore item.

  string button1_Class = (Model.Button1Icon != null ? Model.Button1Icon.Css_Class : string.Empty);
  string button2_Class = (Model.Button2Icon != null ? Model.Button2Icon.Css_Class : string.Empty);

    @if (Model.Button1IsVisible)
    {
      <div>
        @using (BeginRenderLink(x => x.DataSourceItem.Button_1_Url, new System.Collections.Specialized.NameValueCollection { { "class", "button hb-button" } }, isEditable: true))
        {
          <span class="@(button1_Class)"></span>@Editable(x => x.DataSourceItem.Button_1_Text)
        }
      </div>
    }

With the @using (BeginRenderLink()...) syntax, we make sure that the button url is editable, and we can build whatever editable markup we want within the button's code. The CSS text for the Icon is also pulled from the Model, and sets the CSS class of the <span>.







Editing Other Sitecore Item Fields

We've seen how to edit fields such as Text, Image and Link. But I've yet to find a way to edit any other field types directly on the rendering. Thankfully, it's pretty easy to expose for editing any other field we want the Content Editor to have access to.

You can create a custom Experience Editor Button, that shows up on the Experience Editor Ribbon for the rendering.



When the Content Editor clicks that button, they are presented with a popup where they can easily edit the items you have exposed for them. The only slight drawback to this is that after they have made their changes, they need to save the page they are working on for the changes to be picked up by the rendering.




Creating A Custom Experience Editor Button

To create your custom button, first switch over to the Core DB. Then go to the location /sitecore/content/Applications/WebEdit/Custom Experience Buttons. Here you will create a new item of template type /sitecore/templates/System/WebEdit/Field Editor Button.

Header - Something descriptive about your button.
Icon - I use the same icon I chose for the Rendering.
Fields - A pipe delimited list of the fields you are exposing from your Sitecore Item.
Tooltip - Something description for your button.

An example custom button:



Next you want to head over to the Sitecore Rendering you created in the Layouts folder. There is a field called Experience Editor Buttons. Find the new button you just created and add it to this field.

When you are done, you should have a new button on the ribbon bar of your rendering.




Conclusion

Creating a rendering design to be used solely by the Experience Editor requires a little more effort than normal, but can lead to some pretty easy content generation.

Here's what my final product looks like in action.


Monday, December 14, 2015

Sitecore MVC: Static rendering issue on layout

MVC is not my best strength, so I'm not sure if this was just one of those easy errors where "you should just know better", but I wasn't able to find much about this error online.

"Could not locate item containing model definition. Model path: My.Namespace.WebsiteModel"

I was creating a rather simple rendering to put in the footer of my layout, that would grab a single line text from the root item of my website. I referenced the rendering using the following code...

@Html.Sitecore().ViewRendering("~/Areas/Views/MyRendering.cshtml", new { Model = new WebsiteModel() });

Which gave me the above error. It sounds like it was looking for a Model item inside of Sitecore. But since this rendering is being used statically, I shouldn't have to create anything in Sitecore for it to work.

The following code ended up working...

@{ Html.RenderPartial("~/Areas/Views/MyRendering.cshtml", new WebsiteModel()); }
My homework now is to dig into @Html.Sitecore().ViewRendering() and see what exactly it's trying to do under the covers. I'm guessing it's more meant to be used with a Sitecore rendering item, and since I was trying to use a custom Model, it was looking for the Model item that would exist if it *was* a Sitecore item.

Wednesday, October 7, 2015

Sitecore Session State DB and On-Premise MongoDB

If you follow Sitecore's official documentation about setting up a Private Session State Database, you may notice the first "Note" at the top of the article...


Currently, among the developers I've talked to, the consensus is that MongoDB doesn't perform as well as SQL for Session State. Does this mean your stuck using MongoDB as your session store?

It seems the answer to that is "no". You are officially allowed to use whichever one you want. Sitecore just hasn't gotten around to updating their docs yet.



It took me a minute to find this, so I figured I'd repeat it and make it a little more visible until the documentation has been fixed.

Tuesday, September 22, 2015

Sitecore 8: Toggle CD Configuration Files Powershell Script

As part of Sitecore's official Configuring a CD Server, there are several configuration files and dlls that need to be disabled. This can get tedious to do if you are setting up several CD servers, or if you ever need to revert back to a CM configuration.

This powershell script should make this less time consuming: CDConfiguration.ps1

The script has 3 parameters when you run it...

  1. The folder path of your Sitecore instance's \Website (Ex: "D:\Sitecore\Dev-CD-SC8\Website\")
  2. If you are configuring a CM or a CD server.
  3. If you are using Lucene or Solr index configurations.
It will crawl through your \Include folder, and toggle the configuration files listed in the URL above. 

There are also 2 specific behaviors of the script to be aware of...
  1. It will move your SwitchMasterToWeb to a folder \zzzMustBeLast. Read more here as to the reasons why.
  2. If the configuration file exists in both an enabled and disabled state (ie: files xxx.config and xxx.config.disabled both exist), running the script with the CD parameter will assume the enabled is the most recent, and will overwrite the disabled file.
This script is useful for when you already have an instance setup and you just need to toggle the configuration files back and forth.

If you are setting up a brand new CD server, go check out Patrick Perrone's post for a PowerShell Sitecore Install Script to automate the entire process.

Thursday, July 30, 2015

Hedgehog TDS 5.1.0.9 and Sitecore 8 Update 3

Older versions of TDS have issues with Sitecore 8 Update 3, and they can manifest in a few different ways.

When installing a package that was generated using an older version of TDS, you might encounter an error about the following error...

Exception Could not find file 'C:\Inetpub\wwwroot\GasTest\Website\_DEV\DeployedItems.xml'.

In the installation screen on the Update Installation Wizard it would look like...


Or on the results screen...

Also, when you are using Visual Studios or a TFS build server to generate the package, the build might fail with the very descriptive error...

C:\Program Files (x86)\MSBuild\HedgehogDevelopment\SitecoreProject\v9.0\HedgehogDevelopment.SitecoreProject.targets (165): The package builder failed. Please see the build output log for more details.
Digging into the logs would give you the stack trace...

Building .\bin\TestCM-SC8\..\Package_TestCM-SC8\TDS.Master.scitems.update
Inner Exception Object reference not set to an instance of an object.(System.NullReferenceException):
at Sitecore.Update.Utils.ConfigurationUtils.GetConfiguration(ConfigReader reader)
at Sitecore.Update.Utils.ConfigurationUtils.GetConfigNodes(String xpath, ConfigReader reader)
at Sitecore.Update.Configuration.Factory.ReloadSettings()
at Sitecore.Update.Commands.BaseFileCommand.Serialize(XmlWriter writer, SerializationContext context)
at Sitecore.Update.Commands.SerializationCommandFactory.SerializeCommand(ICommand command, XmlWriter writer, SerializationContext context)
at Sitecore.Update.Installer.CommandToEntryConverter.ConvertFileOperationCommand(BaseFileCommand entry, Stream file)
at Sitecore.Update.Installer.CommandToEntryConverter.Convert(ICommand entry)
at Sitecore.Update.Installer.CommandToEntryConverter.InternalConvert(ICommand entry, IProcessingContext context)
at Sitecore.Install.Framework.BaseConverter`1.Convert(T entry, IProcessingContext context)
at Sitecore.Install.Framework.BaseSource`1.InternalSink.Put(T entry)
at Sitecore.Install.Framework.FilteringSink`1.Put(T entry)
at Sitecore.Update.Installer.CommandSource.InternalPopulate(ISink`1 sink)
at Sitecore.Install.Framework.BaseSource`1.Populate(ISink`1 sink)
at Sitecore.Install.PackageProject.InternalPopulate(ISink`1 sink)
at Sitecore.Install.Framework.BaseSource`1.Populate(ISink`1 sink)
at Sitecore.Install.Utils.EntrySorter.Populate(ISink`1 sink)
at Sitecore.Install.PackageGenerator.GeneratePackage(PackageProject solution, ISink`1 writer)
at Sitecore.Update.Engine.PackageGenerator.GeneratePackage(DiffInfo diff, String licenseFile, String outputPath)
Exception Cannot generate package: Object reference not set to an instance of an object.(System.Exception):
at Sitecore.Update.Engine.PackageGenerator.GeneratePackage(DiffInfo diff, String licenseFile, String outputPath)
at HedgehogDevelopment.SitecoreProject.PackageBuilder.PackageBuilder.g(g ?)
at HedgehogDevelopment.SitecoreProject.PackageBuilder.l.g(String[] ?)
5>C:\Program Files (x86)\MSBuild\HedgehogDevelopment\SitecoreProject\v9.0\HedgehogDevelopment.SitecoreProject.targets(165,5): error : The package builder failed. Please see the build output log for more details.
Fixing this error is as easy as downloading the latest version of TDS. This issue was fixed as of 5.1.0.9. More specifics about this can be found in the following links...




Wednesday, July 1, 2015

Sitecore 8 update 3, CD Servers and Index sitecore_list_index

I recently completed an upgrade to Sitecore 8 update 3. I ran into some issues after configuring the CD server following the recommended Sitecore documentation...

After configuring the CD server, I was getting numerous errors in the log file about "Index sitecore_list_index was not found".



It turns out that the sitecore_list_index should not actually be deleted from the CD, yet the standard SwitchMasterToWeb.config was doing so.

Support was kind enough to provide me with an updated SwitchMasterToWeb.config, which fixed the issue. I'm not sure if their official one has been changed yet, so I'm making the one they gave me available here for now.

SwitchMasterToWeb.config

Tuesday, June 17, 2014

Care When Packaging Item Buckets

If you ever have to create a package for items within an Item Bucket, take care to include all the parent folders along the item path.

If you only include the items themselves, Sitecore doesn't remember that they used to be in Item Buckets and the folder structure it creates to store them in will be of the template "System\Node".

Let's say you want to create a package with the following items. The "Courses" folder is your top-level Item Bucket...



The quickest and easiest way would be to go to the "58" folder, and use the "Add with subitems" button.



However, if the Sitecore instance you install the package on does not already have that exact Item Bucket folder structure... it will create the folders using the "System\Node" template.

I've ended up with folder structures that are a mix of Item Buckets and System\Nodes because of this, and according to Sitecore's support this is something to be avoided as it could potentially cause problems.

So to be safe, go through and add each Item Bucket folder in the chain, as well as any items you want to add. It's more tedious, but it will let you avoid having to Sync a bunch of System/Node folders after you've installed your package.