YouTube Video Picker. Part 4 – XML Control

Now available in the Sitecore Marketplace

This will be the last post in the series about creating a custom field type with support for Content and Page Editor (see part1, part2, and part3)

Basics

There are two main pieces to an XML control:

  • Markup. It will be a simplification but think of it as XHTML with custom tags (controls) and certain implied rules. Take a look here. Having created a few of these I tend to stay on the HTML side of things and only use custom tags where I have to (more about it later). The XML markup is compiled into C# before it gets written back as HTML, not without enrichments and modifications by Sitecore. By the way, you can find quite a few examples in WebsitesitecoreshellControls.
  • Behavior. You can do anything you like with the JavaScript in the client. Unlike Content and Page Editor there’s no jQuery or Prototype.js so it’s a Bring Your Own Script environment. On the server side your XML control is technically a WebControl so the behavior will come from the lifecycle events and the controls you use in your markup. To extend the web control itself you would use the def:inherits hint and for dialog-style forms you would probably just wrap your markup into <FormDialog> and use the <Codebeside> control. The latter is what I did with the YouTube Video picker dialog and the former we will look into a little later.

You declare your XML controls alongsite custom Web Controls:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <controlSources>
      <source mode="on" namespace="BrainJocks.YouTube.Custom.Controls" 
                        assembly="BrainJocks.YouTube.Custom" prefix="brainjockscontrols"/>

      <source mode="on" namespace="BrainJocks.YouTube.Web.XmlControls" 
                        assembly="BrainJocks.YouTube.Web" folder="/sitecore modules/BrainJocks" />
    </controlSources>    
  </sitecore>
</configuration>

With this configuration patch and provided that your XML markup defines the control as:

<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <SelectYouTubeVideo>
    <FormDialog Header="Select YouTube Video" Text="Select YouTube Video" OKButton="OK">
      <CodeBeside Type="BrainJocks.YouTube.Web.XmlControls.SelectYouTubeVideo, BrainJocks.YouTube.Web" />
      ...
    </FormDialog>
  </SelectYouTubeVideo>
</control>

And with a codebeside class:

public class SelectYouTubeVideo : Sitecore.Web.UI.Pages.DialogForm
{
    protected override void OnLoad(EventArgs e)
    {
    }

    protected override void OnOK(object sender, EventArgs args)
    {
    }
}

You can call your dialog from within Sheer UI like this:

var urlString = new Sitecore.Text.UrlString(Sitecore.UIUtil.GetUri("control:SelectYouTubeVideo"));


Sitecore.Context.ClientPage.ClientResponse.ShowModalDialog(urlString.ToString(), "650px", "700px", 
                                                           string.Empty, true);

Markup

First, let me show you how to troubleshoot your markup. With the following configuration patch:

<settings>
  <setting name="XmlControls.OutputDebugFiles">
    <patch:attribute name="value">true</patch:attribute>
  </setting>
</settings>

the C# code that Sitecore control parser (Sitecore.Web.UI.XmlControls.ControlParser) produces will be dumped into Websitesitecore modulesdebug. The generated class by default inherits from Sitecore.Web.UI.XmlControls.XmlControl which not surprisingly is a System.Web.UI.WebControls.WebControl. The generated code translates your markup (all of it, regardless of the runat attribute) into a series of AddControl() methods. It wouldn’t be of any particular importance if only the id attributes didn’t become variable names by default:

Rule #1: Use valid C# identifiers as your id attributes in the XML markup. No dashes. Or use def:id to hint the ControlParser on the variable name it should use.
Rule #2: Use <Script> and <Stylesheet> controls to place your resources into the <head>. Use regular <script> if you want to inline it.

Those special Script and Stylesheet tags will be converted into Sitecore.Web.UI.HtmlControls.Script() and Sitecore.Web.UI.HtmlControls.Stylesheet() accordingly and will be pushed into the <head> section by Sitecore. Otherwise – a regular AddControl() and the script and styles tag will render alongside your markup. Unlike <script>, the <link rel="stylesheet" doesn’t work outside of <head>. Well, it does in HTML5 but XML controls render as XHTML. At least they are trying to:

Rule #3. Don’t use self enclosing <div/>. Or any other self enclosing HTML elements that are not special controls as it will unexpectedly break your markup. The alternative is to patch Websitesitecoreshelldefault.aspx. Our use 7.1+.

HTML 4 doesn’t have a concept of self-enclosing tags, XHTML does. The XML dialog control tries to render as XHTML but fails to put <!DOCTYPE first. Here’s how it looks in the browser (Sitecore 7 Update 2):

<meta http-equiv="X-UA-Compatible" content="IE=5"/>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ...
<html>
    ...

The <meta> is coming from the Websitesitecoreshelldefault.aspx. From Sitecore Support: That line has been removed from the installation in Sitecore 7.1 rev.130926.

Behavior

The most straightforward way is to use a def:Code block right in your XML markup. Here’s how it looks in the FormDialog:

<control xmlns:def="Definition" 
         xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <FormDialog CancelButton="">
    <def:Code><![CDATA[
    
    protected override void OnLoad(EventArgs e) {
      if (CancelButton == "false") {
        CancelSpace.Visible = false;
        Cancel.Visible = false;
      }
    }
    
    ]]></def:Code>
    ...

I don’t like intermixing markup and code so I wouldn’t recommend it even for a very simple behavior like that of the FormDialog.

The next option is to use the Codebeside control. In my case I extended DialogForm that defines a virtual OnOK() and binds it to the click event of the OK button. The codebeside implementation then overrides the OnOK() behavior to read the video ID off of the RawValue hidden input and to record it as a dialog value. This way we tell Sitecore what to write back into the field:

protected override void OnOK(object sender, EventArgs args)
{
    SheerResponse.SetDialogValue(RawValue.Value);

    base.OnOK(sender, args);
}

If the behavior of your XML control needs more freedom than what you can get with the Codebeside and out of the box dialog forms you will need to use def:inherits. Here’s an example from the SelectAccount.xml:

<control xmlns:def="Definition" 
         xmlns:installer="Sitecore.Shell.Applications.Install.Controls">
  <Installer.SelectAccount 
         def:inherits="Sitecore.Shell.Applications.Install.Controls.SelectAccount, Sitecore.Client">
    ...

One last thing. The dialog controls bind hot keys:

scForm.registerKey("13", "javascript:scForm.browser.getControl('OK').click()", "");

and you may want to capture some to help your user drive your form from the keyboard. Without the following in the YouTube Video picker dialog pressing the Enter key to finish autocomplete, for example, would submit the whole thing:

var submitQueryOnEnter = function (e) {
    if (e.which == 13) {
        // prevent Sitecore from submitting the form
        e.preventDefault();
        e.stopPropagation();

        $('#SearchButton').click();
    }
};

$('#SearchQuery').keydown(submitQueryOnEnter);
$('#SearchButton').keydown(submitQueryOnEnter);

References:

Pavel Veller

1 comment on YouTube Video Picker. Part 4 – XML Control

Anders PedersenMay 21, 2014 - Reply

Thanks for a brilliant walkthrough. SheerUI still is relevant for older solutions, where SPEAK is not yet an option. A small note from your post:

“You can do anything you like with the JavaScript in the client. Unlike Content and Page Editor there’s no jQuery or Prototype.js so it’s a Bring Your Own Script environment”.

This is not absolutely true. Sitecore still injects prototype JS as well as client stylesheets. You can, indeed, use
/sitecore/shell/Controls/Lib/jQuery/jquery.noconflict.js but it’s an old version (think it’s 1.5.x), and that might be a problem for some. I’ve just copied my 1.8.3 version of jQuery to a new file, and added this at the very bottom of it:

if (typeof (window.$sc) == “undefined”) window.$sc = jQuery.noConflict();

Referencing this file, makes jQuery work together with prototype js – as long as you use jQuery the noConflict way (by not using the $ reference)

Add a Comment

Your email address will not be published. Required fields are marked *

Or request call back