Contextual Ribbons in Page Editor

On my blog post on Contextual Ribbons I got a comment by Jason Cox. The question was whether or not we can have contextual ribbons in the Page Editor.

Turns out, you can – and it’s a lot easier than it looks at first.

In webroot\sitecore\shell\Applications\WebEdit\WebEditRibbon.aspx there’s a codebeside file that’s being used to render the WebEditRibbonForm:

<sc:CodeBeside runat="server Type="Sitecore.Shell.Applications.WebEdit.WebEditRibbonForm, Sitecore.Client" />

If we change this to our own class, it’s very easy to get the contextual tabs. So, I commented out the existing codebeside, and replaced it with:

<sc:CodeBeside runat="server" Type="TestApplication.ContextualWebEdit.CustomWebEditRibbonForm, TestApplication" />

This class then needs to inherit from the WebEditRibbonForm. The only thing we have to do is override the RenderRibbon method. I copied the code straight from Reflector, and changed the line highlighted below.

public class CustomWebEditRibbonForm : WebEditRibbonForm
{
  protected override void RenderRibbon(Item item)
  {
    string str2;
    SiteRequest request;
    Assert.ArgumentNotNull(item, "item");
    string queryString = WebUtil.GetQueryString("mode");
    Ribbon ribbon2 = new Ribbon();
    ribbon2.ID = "Ribbon";
    ribbon2.ShowContextualTabs = true;
    ribbon2.ActiveStrip = (queryString == "preview") ? "VersionStrip" : WebUtil.GetCookieValue("sitecore_webedit"activestrip");
    Ribbon ctl = ribbon2;
    string str3 = queryString;
    if (str3 != null)
    {
      if (!(str3 == "preview"))
      {
        if (str3 == "edit")
        {
          str2 = this.IsSimpleUser() ? "/sitecore/content/Applications/WebEdit/Ribbons/Simple" : "/sitecore/content/Applications/WebEdit/Ribbons/WebEdit";
          goto Label_00A2;
        }
      }
      else
      {
        str2 = "/sitecore/content/Applications/WebEdit/Ribbons/Preview";
        goto Label_00A2;
      }
    }
    str2 = "/sitecore/content/Applications/WebEdit/Ribbons/Preview";
    Label_002:
    request = Context.Request;
    Assert.IsNotNull(request, "Site request not found.");
    CommandContext context = new CommandContext(item);
    context.Parameters["sc_pagesite"] = request.QueryString["sc_pagesite"];
    ctl.CommandContext = context;
    context.RibbonSourceUri = new ItemUri(str2, Context.Database);
  }
}

As displayed above, one change is all we need: ribbon2.ShowContextualTabs needs to be set to true. I’ve left the rest of the code exactly the way it was according to Reflector.

As a little test I’ve set the Ribbon field of an item to sitecore/content/Applications/Content Editor/Ribbons/Contextual Ribbons/Images, so I’d expect to see the Media section and its buttons appear. This should probably be set in the Standard Values instead of on a regular item, but you get the gist.

The result:

contexteditor

It works quite nice, actually.

Advertisements

Changing placeholders with Rules

Another question on StackOverflow sparked my interest.

The question is about whether there’s a way to specify what the placeholder of a templates control will be, based on a device.

I proposed the following solution:

“What you can probably do is use Sitecore’s Rules Engine, where you can create (or use a pre-existing) Condition to find out whether you are on a mobile device. Then you can also use the Action ‘Set placeholder to value’.”

This was just a theory, so I figured I’d actually get a working version up. I’m using the Jetstream demo website (which comes with the Mobile Device Detector module), so it’s using Sitecore 6.6, even though the question was regarding Sitecore 7. The basic idea translates, of course, and I’m sure there’s not too many differences (if any) regarding this anyway.

There’s a Global Rules folder located in /sitecore/system/settings/rules/conditional renderings, which will run at all times. This is called from the <insertRenderings> pipeline – from the Sitecore.Pipelines.InsertRenderings.Processors.EvaluateConditions processor (located in Sitecore.Kernel), to be exact.

globalrules

I’ll create a new Conditional Rendering Rule in that folder, and call it ‘Switch Placeholder’.

My condition will be: “Where the device is mobile”. My action will be: “Set placeholder to mobile_footer”. Of course, in my case I actually have a mobile device with layout and all, so all I’m trying to do is set the control in a different placeholder.

The condition comes with the Mobile Device Detector module. The action is in Sitecore out of the box.

 initialrule

Let’s give that a go (don’t forget to actually switch the device to mobile in Sitecore, otherwise you won’t see anything as ‘where the device is mobile’ won’t return true).

Before:

before

After:

after

Of course, there’s an issue – all renderings and sublayouts will now be moved to the mobile_footer. What one could do to prevent this is to check either which placeholder the rendering would originally target for example. Another option would be checking which rendering is actually targeted, which might be even better. A combination is possible as well of course (target only this specific rendering when it’s in this specific placeholder).

First I’ll be checking which placeholder is targeted.

To do this, I’ll have to create a new condition, as there is no existing (at least, not out-of-the-box) condition available to check this.

I’ll create my condition in /sitecore/system/settings/rules/conditional renderings/conditions/testapplication for now.

The Text field of my condition will be: where the placeholder [operatorid,StringOperator,,compares to] [Key,,,specific value]

My type field will contain the location of my class: TestApplication.SwitchPlaceholder.TargetPlaceholder, TestApplication

The actual class itself:

public class TargetPlaceholder<T> : StringOperatorCondition<T> where T:ConditionalRenderingsRuleContext
{
  public string Key { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");
    var ph = ruleContext.Reference.Settings.Placeholder;

    if (!string.IsNullOrEmpty(ph) && !string.IsNullOrEmpty(Key))
    {
      return base.Compare(Key, ph);
    }

    return false;
  }
}

Please note that the name Key (highlighted in the example) must match the name of the variable defined in the Text field of the condition ([Key,,,specific value]).

If I then select the rule:

rulev2

Now it will only change the placeholder to be ‘mobile_footer’ for any rendering which targets the placeholder ‘mobile_main_content’.

Our last step is to do this only for specific renderings.

Again, I create a new condition in the same location.

The text this time looks a little more complex:

Rendering [operatorid,StringOperator,,compares to] [renderingid,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific] rendering

But it basically translates to: A rendering needs to compare some renderingid. The ID I pass in is the ID for the /sitecore/layout item.

Fill in the type again. In my case: TestApplication.SwitchPlaceholder.TargetRendering, TestApplication

The code:

public class TargetRendering<T> : StringOperatorCondition<T> where T:ConditionalRenderingsRuleContext
{
  public string RenderingId { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(rulecontext, "ruleContext");
    var rendering = ruleContext.Reference.RenderingID.ToString();

    if (!string.IsNullOrEmpty(rendering) && !string.IsNullOrEmpty(RenderingId))
    {
      return base.Compare(RenderingId, rendering);
    }

    return false;
  }
}

Update the Rule will now be:

rulve3

Now it will only change the placeholder to mobile_footer for the BasicContentPage rendering with the placeholder mobile_main_content.

Content Editor – New window from here

If you ever have had to create a new item, but wanted to copy a bunch of text over from a different one you have had to either:

  • Copy the text, go to the new item, paste the text, save, rinse and repeat
  • Open a new window of the content editor, navigate to the item, select it and copy/ paste

I figured I could make this easier by creating something like ‘New window from here’, which will just pop up a new Content Editor window opening on the item I select.

This is actually very easy to do: I’d have to create a new command for my right-mouse click and implement a bit of code.

As always, the commands can be found in the Core database. The menu for the right-click is defined in /sitecore/content/Applications/Content Editor/Context Menues/Default (No, Menues is not a typo). I’ll create my command directly underneath the Insert button as displayed below.

 New command under the default folder

Now I need to make sure the correct command gets executed:

values

Notice I’m actually passing in an ID to my command as well. This is not required, but I wanted to show how to use that.
This leads to the following result when I right-click on an item:

menu

Now to the code. First of all, I’ll create a config file which I can stick in the Include folder:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <commands>
      <command name="testapplication:newwindow" type="TestApplication.NewWindowFromHere.NewWindow, TestApplication" />
    </commands>
  </sitecore>
</configuration>

As always, beautifully named 😉

Next up: The NewWindow class. This class needs to inherit from the Sitecore.Shell.Framework.Commands.Command class and implement the Execute method at the very least, but the command can also be disabled if that’s required by overriding the QueryState method.

Option one, without the parameter:

public class NewWindow : Command
{
  public override void Execute(CommandContext context)
  {
    Assert.IsNotNull(context, "context");
    Assert.IsNotNull(context.Items, "items");
    if (context.Items.Length >= 1)
    {
      var parameters = new Sitecore.Text.UrlString();
      parameters.Add("id", context.Items[0].ID.ToString());
      parameters.Add("fo", context.Items[0].ID.ToString());

      Sitecore.Shell.Framework.Windows.RunApplication("Content Editor", parameters.ToString());
    }
  }
}

This code is pretty straightforward. We will build a UrlString and pass this along when we start the Content Editor. It’s worth noting  that the parameter in the Message field is not used here. In this case the Message field could simply be: testapplication:newwindow instead of testapplication:newwindow(id=$Target)

Option two, with the parameter being passed in:

public override void Execute(CommandContext context)
{
  Assert.IsNotNull(context, "context");
  Assert.IsNotNull(context.Parameters["id"], "id");

  var parameters = new Sitecore.Text.UrlString();
  parameters.Add("id", context.Parameters["id"]);
  parameters.Add("fo", context.Parameters["id"]);

  Sitecore.Shell.Framework.Windows.RunApplication("Content Editor", parameters.ToString());
}

I can get to the ID passed along in the parameters very simply, as you can see in line 4. In this case the Message field needs to be testapplication:newwindow(id=$Target), otherwise the Assert in line 4 would not pass.