Wednesday 18 January 2017

So where's the Anchor field gone?

Since the introduction of Sitecore 8, clients have been asking where did the anchor field go?

This blog entry applies to all versions of Sitecore 8 up to and including 8.1 update 3 (160519). 

Although the XML generated by the link field still supports an anchor field, it looks like this has been dropped (or missed) from the insert link speak dialog.  

So, if you're feeling brave you can educate your clients to edit the XML in the field in Sitecore's raw view mode.  All they have to do is add the anchor attribute to the XML, as shown below:
<link 
   text="" 
   linktype="internal" 
   class="" 
   title="" 
   target="" 
   querystring="" id="{F0612D62-D3DC-4482-9879-F1EAF48B6AF6}" 
   anchor=""
/>


It looks like the all new speak layout for inserting a link doesn't have the field anymore.

Or we can add the field to the layout...

I felt that for cleanliness we should create a new layout rather than modify the existing. This means that should this issue get addressed in a later version of Sitecore or should new features be added to the dialog, the modifications we are making, will not impact the upgrade process.  Also by doing it this way, it will be very easy to switch back (just update the speak config).

N.B. Recommend that you do all of the of following in a development environment, and then package up the Sitecore items, js, and config, and deploy.  This way you will not impact anyone else trying to work in Sitecore.

The steps we will follow are:
  1. Copy existing speak layout
  2. Create a new data-source for the anchor label
  3. Add the anchor label, field and wrapper renderings
  4. Update the InsertLinkRuleButtonDefintion item and create a new action rul
  5. Create the associated JavaScript file
  6. Create the associated c# code
  7. Re-point other renderings on the layout
  8. Update the speak configuration to use our new layout 

Copy existing speak layout:

Firstly we need to navigate to the core database in Sitecore; go to the Sitecore dashboard and select the Desktop.  Once you see the windows style desktop, locate the database icon (bottom right) click it, and select the core database from the popup menu 

From the desktop start menu open up the content editor, once open you need to locate the following item:

/sitecore/client/Applications/Dialogs/InsertLinkViaTreeDialog

Right click this item and select Duplicate.  In the naming dialog that appears give it a name like: InsertLinkViaTreeDialogWithAnchor.

Create a new data-source for the anchor label

Open up the newly created layout and then its child item (PageSettings) and locate an existing data-source item under that, like: 

/sitecore/client/Applications/Dialogs/InsertLinkViaTreeDialogWithAnchor/PageSettings/Text Link Description

Right click this item and select Duplicate.  In the naming dialog that appears give it a name like: Text Link Anchor

Select the newly created item and change the Text field's value to: Anchor:. 


Add the anchor label, field and wrapper renderings

If you select the InsertLinkViaTreeDialogWithAnchor item that was created above, and then select the Presetation Tab in Sitecore, click on the Details button and the select the Final Layout tab in the dialog that appears; you will see the speak layout with all its renderings:


Unfortunately because this item resides in the core database you cannot edit the layout here.  There are however two ways to edit the layout:

  1. Edit the raw values in the Renderings field.
  2. Use the Sitecore Rocks Visual Studio plugin - https://marketplace.visualstudio.com/items?itemName=JakobChristensen.SitecoreRocks

Personally I found it fairly easy to edit the raw values using an XML editor (the one in Visual Studio is fine) - so those are the instructions I have added here...

In Sitecore Select the View Tab and make sure that both Standard Fields and Raw Values are checked:

 
Navigate to the InsertLinkViaTreeDialogWithAnchor item in Sitecore and locate the Renderings field.  Select the entire contents of the field, and copy to the clipboard.

Open up Visual Studio and create a new XML file (Menu: File->New->File... XML File - then select open).

Once the new file opens select the entire contents and delete it.  Then paste in the raw XML from the Renderings Field that you previously copied. Visual studio should nicely format the contents for you.  It should look something like:


In this example I will be placing the Anchor field directly after the Description field.

Locate the entry that contains Id=TextDescription&amp;IsVisible=1, at the end of the line press enter to create an empty space and paste in the following entries:

    <r id="{F6C9F461-FCAF-47DC-AA01-C8C64C2EFFB8}" par="Id=AnchorRow" ph="NameContainer.Content" uid="{5467D927-5032-4E61-A322-62AE046A1CFB}" />
    <r ds="{9A1C04B1-7C75-49C1-91F8-010D2E2B1707}" id="{98316BB6-F3F5-4E6F-81F0-79F173D151D8}" par="Id=AnchorLabel" ph="AnchorRow.Content" uid="{D560CACB-798E-4A40-AA19-501AB4008E5A}" />
    <r id="{57F86E9A-1844-45CE-BF8A-62900AE17A92}" par="IsEnabled=1&amp;Id=Anchor&amp;IsVisible=1" ph="AnchorRow.Content" uid="{E3675A84-7A31-4ABE-A3C4-24385F4B350E}" />

In Sitecore select the newly created Anchor label data-source and copy its Item ID to the clipboard.  Back in the XML you have just pasted in, replace the entry:
ds="{9A1C04B1-7C75-49C1-91F8-010D2E2B1707}"
With:
ds="Item ID from your clipboard"
Once this is done copy the entire contents of the XML file in Visual Studio back into the Renderings Field in Sitecore for the InsertLinkViaTreeDialogWithAnchor item.

Don't close down the XML file in Visual Studio, as we will be revisiting this in the future.

So now we have the new field added to the dialog...

Update the InsertLinkRuleButtonDefintion item and create a new action rul

In order to get Sitecore to map the value entered into the new anchor text box in the dialog, to the XML that is saved in the link field; we need to update what happens when the insert link button is clicked.  

This is done via a Sitecore RuleDefinition, which includes an Action. In this case the RuleDefinition we are interested in is called: InsertLinkRuleButtonDefintion.  

If you select this item in Sitecore (make sure its the one under InsertLinkViaTreeDialogWithAnchor) and assuming that you have turned raw values off (Select the View tab in Sitecore and un-check Raw Values), then you will see a Rule field value that looks like this:


We need to update this rule to include a mapping for our anchor field.

In order to do this we need to create a new Action.  To locate the existing Action, switch on Raw Values in Sitecore and navigate to the RuleDefinition field, this time you should see some XML, which when formatted should look like:


N.B You can follow the steps above and create an XML file in Visual Studio and paste the contents of this field into that file, to complete the next steps if you find it easier.

In the XML copy the value (guid) from the action element's id attribute.  It should be:

{D4BA15D6-263A-43E6-93DD-CE9E41C7D8E4}

Search for this guid in Sitecore, you should find the following item:



Right click on this item and select Duplicate.  In the naming dialog that appears give the item a name like: MakeInternalLinkFromTreeViewWithAnchor.

N.B.  Its important to realize that this name drives the name of the js file that will be used to do the mappings.  We will need to use the same name when it comes to naming the js file.
 
In the newly created action navigate to the text field and insert the following text:
anchor value to [targetAnchorID,,,textControl2]'s text property, 
After:
text to [targetDisplayTextID,,,textControl1]'s text property, 
You will then need to increment all the textControl fields after the inserted text by one, so that the end results looks like:
set the hyperlink display text to [targetDisplayTextID,,,textControl1]'s text property, anchor value to [targetAnchorID,,,textControl2]'s text property, query value to [targetQueryID,,,textControl3]'s text property, target window value to [targetWindowID,,,comboBox1]'s selectedItem property, custom URL value to [customUrlID,,,textControl4]'s text property, alternative text to [targetAltTextID,,,textControl5]'s text property, style to [targetStyleID,,,textControl6]'s text property, id to [targetControlID,,,treeView1]'s selectedItemId property, and path value to [targetPathID,,,treeView2]'s selectedNode's  [targetPathProperty,,,path] property

Now that we have created the new Action we can assign this to the RuleDefinition.  Firstly copy the Item ID of the newly created Action to the clipboard.  Then select the InsertLinkRuleButtonDefintion item under InsertLinkViaTreeDialogWithAnchor and update the raw XML data in the Rule field as follows:
  1. Update the value (guid) in the action element's id attribute to the one in your clipboard. 
  2. Add an additional attribute to the action element as follows: targetAnchorID="Anchor"
Then end result should look like this:
<ruleset>
  <rule>
    <conditions>
      <condition id="{B5CF2ABD-0811-4220-9805-8F6E62F4F4AC}" uid="4B9EEC7B4D9D4938835706392363C725">
    </condition></conditions>
    <actions>
      <action id="Newly created Action item Id" uid="{9691914E-D310-42B6-84FC-DB14F54A9682}" targetDisplayTextID="TextDescription" targetQueryID="QueryString" targetWindowID="Target" targetAltTextID="AltText" targetStyleID="StyleClass" targetControlID="TreeView" targetPathID="TreeView" targetPathProperty="$path"  customUrlID="CustomUrl"  targetAnchorID="Anchor">
  </rule>
</ruleset>

We now need to ensure that the Rendering in the Layout is pointing to the correct RuleDefinition at the moment it will be pointing to the RuleDefinition for InsertLinkViaTreeDialog as that is what it was originally copied from.  In the Layout's Rendering field XML (which you should have this open in Visual Studio) you need to update the element which contains the value Id=InsertLinkButtonRule.  You will need to update the encoded guid at the beginning of the par attribute's value from:

RuleItemId=%7bCEDD4633-193B-4D79-8DCC-C49B3C8D0F53%7d  

To: 

RuleItemId=%7bNo brackets - Sitecore Item Id of InsertLinkButtonRuleDefinition%7d

Create the associated JavaScript file

At this point we are going to switch over and do some coding.  The first thing we need to do is to create a JavaScript file that needs to reside in: \sitecore\shell\client\Speak\Layouts\Renderings\Resources\Rules\ConditionsAndActions\Actions\

Create a new JavaScript file in this location called MakeInternalLinkFromTreeViewWithAnchor.js.

N.B. If you gave your Action a different name use that instead.

Copy the following code into the file:

define([], function () {
    var action = function (context, args) {
        var targetDisplayTextID = context.app[args.targetDisplayTextID],
          targetPathID = context.app[args.targetPathID],
          targetAnchorID = context.app[args.targetAnchorID],
          targetPathProperty = args.targetPathProperty,
          targetQueryID = context.app[args.targetQueryID],
          targetAltTextID = context.app[args.targetAltTextID],
          targetStyleID = context.app[args.targetStyleID],
          targetWindowID = context.app[args.targetWindowID],
          customUrlID = context.app[args.customUrlID],
          targetControlID = context.app[args.targetControlID],
            selectedItemsPropertyName = "selectedNode",
          template = '<link text="<%=displayText%>" linktype="internal" class="<%=styleClass%>" title="<%=alternateText%>" <%=target%> querystring="<%=queryString%>" id="<%=itemId%>" anchor="<%=anchor%>"/>',
          targetWindowValue, itemLink, path,
          emptyOptionID = "{A3C9DB39-1D1B-4AA1-8C68-7B9674D055EE}",
          htmlEncode = function (str) {
              return str.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
          };

        if (!targetDisplayTextID) {
            console.log("Provide at least display text for your link");
            return false;
        }

        targetWindowValue = targetWindowID.get("selectedItem");

        if (!targetWindowValue || targetWindowValue.itemId === emptyOptionID) {
            targetWindowValue = "";
        } else {
            var targetWindow = targetWindowValue.$displayName.trim();

            switch (targetWindow) {
                case 'Active Browser':
                    targetWindow = "";
                    break;
                case 'New Browser':
                    targetWindow = "_blank";
                    break;
                case "Custom":
                    targetWindow = customUrlID.get("text");
                    break;
                default:
                    targetWindow = "";
            }

            targetWindowValue = "target=\"" + targetWindow + "\"";
        }

        if (targetPathID.get(selectedItemsPropertyName) &&
              "rawItem" in targetPathID.get(selectedItemsPropertyName) &&
              targetPathID.get(selectedItemsPropertyName).rawItem[targetPathProperty]) {
            path = targetPathID.get(selectedItemsPropertyName).rawItem[targetPathProperty];
        }

        itemLink = _.template(template, {
            displayText: htmlEncode(targetDisplayTextID.get("text")),
            alternateText: htmlEncode(targetAltTextID.get("text")),
            itemId: targetControlID.get("selectedItemId"),
            queryString: htmlEncode(targetQueryID.get("text")),
            target: targetWindowValue,
            styleClass: targetStyleID.get("text"),
            anchor: targetAnchorID.get("text"),
            path: path
        });

        context.app.closeDialog(itemLink);
    };

    return action;
});

There are 3 additions in this file compared to the original:
  1. Line 5 - We've added the targetAnchorID variable.
  2. Line 14 - We've extendxed the XML template to include a mapping for the anchor field
  3. Line 63 - We've assign the text value stored in our targetAnchorID variable into the template mapper at the bottom.

Create the associated c# code

At the moment, if we were to jump to step 8 above and update the speak configuration to use our new layout it would all work in that it would add anchor text box value to the XML on click of the insert button, but it wouldn't populate the dialog with the anchor value if there was one set in the XML.  To do this we need to update the code behind. 

Going back to the layout of the InertLinkViaTreeDialogWithAnchor item and again looking at the raw value of the Renderings field (which should still be in Visual Studio), you will note on the second line down the following:
par="PageCodeTypeName=Sitecore.Speak.Applications.InsertLinkDialogTree%2c+Sitecore.Speak.Applications

This line tells the layout what c# code should be run, currently its the method: Sitecore.Speak.Applications.InsertLinkDialogTree, Sitecore.Speak.Applications.  We need to update the  method so that it will apply the necessary values to the new anchor field.  By using reflection we see that the InsertLinkDialogTree method looks like:
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Shell;
using Sitecore.Web;
using Sitecore.Web.PageCodes;
using System;
using System.Net;
using System.Xml.Linq;

namespace Sitecore.Speak.Applications
{
  public class InsertLinkDialogTree : PageCodeBase
  {
    private string targetActiveBrowserItemId = "{C5FA4571-37B3-472C-BDA1-0FADC2D2EFA7}";
    private string targetNewBrowserItemId = "{02A6C72E-17BB-48C5-8D35-AF9C494ED6BA}";
    private string targetCustomItemId = "{07CF2A84-9C22-4E85-8F3F-C301AADF5218}";

    public Sitecore.Mvc.Presentation.Rendering TreeView { get; set; }

    public Sitecore.Mvc.Presentation.Rendering ListViewToggleButton { get; set; }

    public Sitecore.Mvc.Presentation.Rendering TreeViewToggleButton { get; set; }

    public Sitecore.Mvc.Presentation.Rendering InsertEmailButton { get; set; }

    public Sitecore.Mvc.Presentation.Rendering InsertAnchorButton { get; set; }

    public Sitecore.Mvc.Presentation.Rendering TextDescription { get; set; }

    public Sitecore.Mvc.Presentation.Rendering Target { get; set; }

    public Sitecore.Mvc.Presentation.Rendering AltText { get; set; }

    public Sitecore.Mvc.Presentation.Rendering QueryString { get; set; }

    public Sitecore.Mvc.Presentation.Rendering StyleClass { get; set; }

    public Sitecore.Mvc.Presentation.Rendering CustomUrl { get; set; }

    public Sitecore.Mvc.Presentation.Rendering TargetLoadedValue { get; set; }

    public string TargetActiveBrowserItemId
    {
      get
      {
        return this.targetActiveBrowserItemId;
      }
      set
      {
        Assert.ArgumentNotNull((object) value, "value");
        this.targetActiveBrowserItemId = value;
      }
    }

    public string TargetNewBrowserItemId
    {
      get
      {
        return this.targetNewBrowserItemId;
      }
      set
      {
        Assert.ArgumentNotNull((object) value, "value");
        this.targetNewBrowserItemId = value;
      }
    }

    public string TargetCustomItemId
    {
      get
      {
        return this.targetCustomItemId;
      }
      set
      {
        Assert.ArgumentNotNull((object) value, "value");
        this.targetCustomItemId = value;
      }
    }

    public override void Initialize()
    {
      string setting = Settings.GetSetting("BucketConfiguration.ItemBucketsEnabled");
      this.ListViewToggleButton.Parameters["IsVisible"] = setting;
      this.TreeViewToggleButton.Parameters["IsVisible"] = setting;
      this.TreeView.Parameters["ShowHiddenItems"] = UserOptions.View.ShowHiddenItems.ToString();
      this.ReadQueryParamsAndUpdatePlaceholders();
    }

    private static string GetXmlAttributeValue(XElement element, string attrName)
    {
      if (element.Attribute((XName) attrName) == null)
        return string.Empty;
      return element.Attribute((XName) attrName).Value;
    }

    private void ReadQueryParamsAndUpdatePlaceholders()
    {
      string queryString1 = WebUtil.GetQueryString("ro");
      string queryString2 = WebUtil.GetQueryString("hdl");
      if (!string.IsNullOrEmpty(queryString1) && queryString1 != "{0}")
        this.TreeView.Parameters["RootItem"] = queryString1;
      this.InsertAnchorButton.Parameters["Click"] = string.Format(this.InsertAnchorButton.Parameters["Click"], (object) WebUtility.UrlEncode(queryString1), (object) WebUtility.UrlEncode(queryString2));
      this.InsertEmailButton.Parameters["Click"] = string.Format(this.InsertEmailButton.Parameters["Click"], (object) WebUtility.UrlEncode(queryString1), (object) WebUtility.UrlEncode(queryString2));
      this.ListViewToggleButton.Parameters["Click"] = string.Format(this.ListViewToggleButton.Parameters["Click"], (object) WebUtility.UrlEncode(queryString1), (object) WebUtility.UrlEncode(queryString2));
      bool flag = queryString2 != string.Empty;
      string text = string.Empty;
      if (flag)
        text = UrlHandle.Get()["va"];
      if (!(text != string.Empty))
        return;
      XElement xelement = XElement.Parse(text);
      if (!(InsertLinkDialogTree.GetXmlAttributeValue(xelement, "linktype") == "internal"))
        return;
      string xmlAttributeValue = InsertLinkDialogTree.GetXmlAttributeValue(xelement, "id");
      if (string.IsNullOrWhiteSpace(xmlAttributeValue))
        return;
      this.TreeView.Parameters["PreLoadPath"] = SelectMediaDialog.GetMediaItemFromQueryString(xmlAttributeValue).Paths.LongID.Substring(1);
      this.TextDescription.Parameters["Text"] = InsertLinkDialogTree.GetXmlAttributeValue(xelement, "text");
      this.AltText.Parameters["Text"] = InsertLinkDialogTree.GetXmlAttributeValue(xelement, "title");
      this.StyleClass.Parameters["Text"] = InsertLinkDialogTree.GetXmlAttributeValue(xelement, "class");
      this.QueryString.Parameters["Text"] = InsertLinkDialogTree.GetXmlAttributeValue(xelement, "querystring");
      this.SetupTargetDropbox(xelement);
    }

    private void SetupTargetDropbox(XElement fieldContent)
    {
      string xmlAttributeValue = InsertLinkDialogTree.GetXmlAttributeValue(fieldContent, "target");
      string str;
      if (xmlAttributeValue.Equals("_blank", StringComparison.OrdinalIgnoreCase))
        str = this.TargetNewBrowserItemId;
      else if (string.IsNullOrWhiteSpace(xmlAttributeValue))
      {
        str = this.TargetActiveBrowserItemId;
      }
      else
      {
        str = this.TargetCustomItemId;
        this.CustomUrl.Parameters["Text"] = xmlAttributeValue;
      }
      this.TargetLoadedValue.Parameters["Text"] = str;
    }
  }
}

In Visual Studio create a new class library project, and then create a new class file and copy the following into new class file:
#region using

using System;
using System.Net;
using System.Xml.Linq;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Mvc.Presentation;
using Sitecore.Shell;
using Sitecore.Speak.Applications;
using Sitecore.Web;
using Sitecore.Web.PageCodes;

#endregion

namespace YourNameSpace
{
    public class InsertLinkDialogTreeWithAnchor : PageCodeBase
    {
        private string targetActiveBrowserItemId = "{C5FA4571-37B3-472C-BDA1-0FADC2D2EFA7}";
        private string targetCustomItemId = "{07CF2A84-9C22-4E85-8F3F-C301AADF5218}";
        private string targetNewBrowserItemId = "{02A6C72E-17BB-48C5-8D35-AF9C494ED6BA}";

        public Rendering TreeView { get; set; }

        public Rendering ListViewToggleButton { get; set; }

        public Rendering TreeViewToggleButton { get; set; }

        public Rendering InsertEmailButton { get; set; }

        public Rendering InsertAnchorButton { get; set; }

        public Rendering TextDescription { get; set; }

        public Rendering Target { get; set; }

        public Rendering AltText { get; set; }

        public Rendering QueryString { get; set; }

        public Rendering StyleClass { get; set; }

        public Rendering CustomUrl { get; set; }

        public Rendering TargetLoadedValue { get; set; }

        public Rendering Anchor { get; set; }

        public string TargetActiveBrowserItemId
        {
            get { return targetActiveBrowserItemId; }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                targetActiveBrowserItemId = value;
            }
        }

        public string TargetNewBrowserItemId
        {
            get { return targetNewBrowserItemId; }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                targetNewBrowserItemId = value;
            }
        }

        public string TargetCustomItemId
        {
            get { return targetCustomItemId; }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                targetCustomItemId = value;
            }
        }

        public override void Initialize()
        {
            var setting = Settings.GetSetting("BucketConfiguration.ItemBucketsEnabled");
            ListViewToggleButton.Parameters["IsVisible"] = setting;
            TreeViewToggleButton.Parameters["IsVisible"] = setting;
            TreeView.Parameters["ShowHiddenItems"] = UserOptions.View.ShowHiddenItems.ToString();
            ReadQueryParamsAndUpdatePlaceholders();
        }

        private static string GetXmlAttributeValue(XElement element, string attrName)
        {
            return element.Attribute(attrName) == null ? string.Empty : element.Attribute(attrName).Value;
        }

        private void ReadQueryParamsAndUpdatePlaceholders()
        {
            var queryString1 = WebUtil.GetQueryString("ro");
            var queryString2 = WebUtil.GetQueryString("hdl");
            if (!string.IsNullOrEmpty(queryString1) && queryString1 != "{0}")
                TreeView.Parameters["RootItem"] = queryString1;
            InsertAnchorButton.Parameters["Click"] = string.Format(InsertAnchorButton.Parameters["Click"],
                WebUtility.UrlEncode(queryString1), WebUtility.UrlEncode(queryString2));
            InsertEmailButton.Parameters["Click"] = string.Format(InsertEmailButton.Parameters["Click"],
                WebUtility.UrlEncode(queryString1), WebUtility.UrlEncode(queryString2));
            ListViewToggleButton.Parameters["Click"] = string.Format(ListViewToggleButton.Parameters["Click"],
                WebUtility.UrlEncode(queryString1), WebUtility.UrlEncode(queryString2));
            var flag = queryString2 != string.Empty;
            var text = string.Empty;
            if (flag)
                text = UrlHandle.Get()["va"];
            if (text == string.Empty)
                return;
            var xelement = XElement.Parse(text);
            if (GetXmlAttributeValue(xelement, "linktype") != "internal")
                return;
            var xmlAttributeValue = GetXmlAttributeValue(xelement, "id");
            if (string.IsNullOrWhiteSpace(xmlAttributeValue))
                return;
            TreeView.Parameters["PreLoadPath"] =
                SelectMediaDialog.GetMediaItemFromQueryString(xmlAttributeValue).Paths.LongID.Substring(1);
            TextDescription.Parameters["Text"] = GetXmlAttributeValue(xelement, "text");
            AltText.Parameters["Text"] = GetXmlAttributeValue(xelement, "title");
            StyleClass.Parameters["Text"] = GetXmlAttributeValue(xelement, "class");
            QueryString.Parameters["Text"] = GetXmlAttributeValue(xelement, "querystring");
            Anchor.Parameters["Text"] = GetXmlAttributeValue(xelement, "anchor");
            SetupTargetDropbox(xelement);
        }

        private void SetupTargetDropbox(XElement fieldContent)
        {
            var xmlAttributeValue = GetXmlAttributeValue(fieldContent, "target");
            string str;
            if (xmlAttributeValue.Equals("_blank", StringComparison.OrdinalIgnoreCase))
                str = TargetNewBrowserItemId;
            else if (string.IsNullOrWhiteSpace(xmlAttributeValue))
            {
                str = TargetActiveBrowserItemId;
            }
            else
            {
                str = TargetCustomItemId;
                CustomUrl.Parameters["Text"] = xmlAttributeValue;
            }
            TargetLoadedValue.Parameters["Text"] = str;
        }
    }
}

 
The only differences here are:

  1. Line 48 - We've added a new property of type Rendering called Anchor
  2. Line 124 - On the last line of the ReadQueryParamsAndUpdatePlaceholders method we've assigned the value from the XML in the field to the Text parameter of our new Rendering property.
We now need to update the PageCodeTypeName value in our Layout to use the new class.  You should replace the Sitecore.Speak.Applications.InsertLinkDialogTree%2c+Sitecore.Speak.Applications entry with the full classname / library for your newly created class (remmeber to encode it).

Re-point other renderings on the layout

This step is only needed if you want to completely disconnect the new Layout from it's original.  

To do this I would recommend opening up the new Layout in Sitecore Rocks and walking through all the items that have:
  •  A data-source value - Not all the items need to be updated, some uses data-sources stored under the Settings item (at the same level as the Layout), buit the rest can be repointed to use those under this Layout. Use the Sitecore Rocks interface to navigate to the correct data-source for each rendering.
  • RuleItemId - Again in the same way use Sitecore Rocks on the 3 remaining renderings that have a RuleItemId value in the par attribute to point them to the correct RuleDefinition.
  • Target - The list of targets is controlled by the item called: TargetSearchPanelConfig.  This is referenced in the par attribute of the element that contains Id=TargetsDataSource.  Change as follows:

    SearchConfigItemId=%7bABF3B317-CFCA-441A-815A-F810AB1EDB0D%7d

    To:

    SearchConfigItemId=%7bWithout brackets - Sitecore Item Id of the TargetSearchPanelConfig%7d

    Lastly select the
    TargetSearchPanelConfig item and update the Root field value to point at the folder that contains the target values: sitecore/client/Applications/Dialogs/InsertLinkViaTreeDialogWithAnchor/PageSettings/Targets

Update the speak configuration to use our new layout 

The final step is update the Sitecore.Speak.Applications.config file, so that Sitecore knows to use the new Layout.

In the section called overrideDialogs, change:
<override dialogUrl="/sitecore/shell/Applications/Dialogs/Internal%20link.aspx" with="/sitecore/client/applications/dialogs/InsertLinkViaTreeDialog" />

To:
<override dialogUrl="/sitecore/shell/Applications/Dialogs/Internal%20link.aspx"       with="/sitecore/client/Applications/Dialogs/InsertLinkViaTreeDialogWithAnchor"/>

If you need to revert back, you can just undo the changes in the config and Sitecore will use the old layout.

Deploy all of these changes and now you should have the anchor field added to your dialog!


I will package up the change and attached to this blog at a later date.