Disclaimer: This Ain't Pretty -- But It Works
So after many hours of my nighttime and sleep chewing over this question, I have finally figured out the path. I will warn you, it's not easy. First let's discuss environment. I'm using Sitecore 8.1 - Update 3. For the most part this is a vanilla install.
Second, there are two sides to Sitecore: Content Editor and Experience Editor. They are not the same, though they LOOK like they are. They use different controls but to the everyday developer, it looks like they share the same Field Type.
Step 1 - Follow Akshay's Blog Provided In Question
In following Akshay's Blog, I was able to get the Content Editor side working, with minimal issues. There were some changes I had to make, so I will list those here:
TelephoneLink.cs
using System.Collections.Specialized;
using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Shell.Applications.ContentEditor;
using Sitecore.Text;
using Sitecore.Web.UI.Sheer;
namespace SitecoreHacker.Sandbox.Controls
{
public class TelephoneLink : Link
{
public TelephoneLink()
{
Class = "scContentControl";
Activation = true;
}
// Handles the message.
public override void HandleMessage(Message message)
{
Assert.ArgumentNotNull(message, "message");
base.HandleMessage(message); //base handles other message requests
if (message["id"] != ID)
return;
switch (message.Name)
{
case "contentlink:telephonelink":
{
var url = new UrlString(UIUtil.GetUri("control:TelephoneLinkForm"));
Insert(url.ToString(), new NameValueCollection
{
{
"height",
"425"
}
});
break;
}
default:
{
return;
}
}
}
}
}
TelephoneLinkForm.cs
using System;
using System.Text.RegularExpressions;
using Sitecore;
using Sitecore.Diagnostics;
using Sitecore.Shell.Applications.Dialogs;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
using Sitecore.Xml;
namespace SitecoreHacker.Sandbox.Controls.Dialogs.TelephoneLink
{
public class TelephoneLinkForm : LinkForm
{
protected Edit Class;
protected Edit Text;
protected Edit Title;
protected Edit Url;
private string GetTelephone()
{
var value = Url.Value;
var str = value;
if (str.Length > 0)
{
if (str.IndexOf(":", StringComparison.InvariantCulture) >= 0)
{
str = str.Substring(str.IndexOf(":", StringComparison.InvariantCulture) + 1);
}
if (!new Regex(@"^(?:\(?)(?<AreaCode>\d{3})(?:[\).\s]?)(?<Prefix>\d{3})(?:[-\.\s]?)(?<Suffix>\d{4})(?!\d)", RegexOptions.IgnoreCase).IsMatch(str))
{
return "__Canceled";
}
}
if (value.Length > 0 && value.IndexOf(":", StringComparison.InvariantCulture) < 0)
{
value = string.Concat("tel:", value);
}
return value;
}
protected override void OnLoad(EventArgs e)
{
Assert.ArgumentNotNull(e, "e");
base.OnLoad(e);
if (Context.ClientPage.IsEvent)
{
return;
}
var item = LinkAttributes["url"];
if (LinkType != "tel")
{
item = string.Empty;
}
Text.Value = LinkAttributes["text"];
Url.Value = item.Replace("tel:", "");
Class.Value = LinkAttributes["class"];
Title.Value = LinkAttributes["title"];
}
protected override void OnOK(object sender, EventArgs args)
{
Assert.ArgumentNotNull(sender, "sender");
Assert.ArgumentNotNull(args, "args");
var tel = GetTelephone();
if (tel == "__Canceled")
{
SheerResponse.Alert("The telephone number is invalid.");
return;
}
var packet = new Packet("link");
SetAttribute(packet, "text", Text);
SetAttribute(packet, "linktype", "tel");
SetAttribute(packet, "url", tel);
SetAttribute(packet, "anchor", string.Empty);
SetAttribute(packet, "title", Title);
SetAttribute(packet, "class", Class);
SheerResponse.SetDialogValue(packet.OuterXml);
base.OnOK(sender, args);
}
}
}
Step 2 - Change WebEdit Link In Core
Find the General Link Field Type and Change the Click to webedit:edittelephonelink
Responsible Disclosure
Before going further, a note about responsible disclosure.
I am aware of my responsibilities as a developer and divulging large
amounts of decompiled code on the internet. Therefore, the following
code snippets for the rest of this answer have been redacted. THEY
WILL NOT COMPILE without first decompiling the original classes and
making the changes that I specify. If you need assistance, please
contact me directly.
Step 3 - Create TelephoneEditLink.cs
This is the beginning of the not so pretty. The fact of the matter is
I only need to CHANGE one line from the original EditLink class
from Sitecore. However, override this class cleanly proved to be a near impossible. So I took the easiest way I knew how. Cut and Paste.
Original Class: Sitecore.Shell.Applications.WebEdit.Commands.EditLink
namespace SitecoreHacker.Sandbox.Commands
{
[Serializable]
public class TelephoneEditLink : WebEditLinkCommand
{
public override void Execute(CommandContext context)
{
... snip ...
}
protected static void Run(ClientPipelineArgs args)
{
... snip ...
if (args.IsPostBack)
{
... snip ...
}
else
{
var urlString = new UrlString(Context.Site.XmlControlPage);
urlString["xmlcontrol"] = "SpeakTelephoneLink"; //THIS IS THE ONE LINE THAT I NEEDED TO CHANGE
var urlHandle = new UrlHandle();
urlHandle["va"] = new XmlValue(args.Parameters["fieldValue"], "link").ToString();
urlHandle.Add(urlString);
urlString.Append("ro", field.Source);
Context.ClientPage.ClientResponse.ShowModalDialog(urlString.ToString(), "550", "650", string.Empty, true);
args.WaitForPostBack();
}
}
private static RenderFieldResult RenderLink(ClientPipelineArgs args)
{
... snip ...
}
}
}
That ONE LINE that we needed to change introduces a NEW Speak Xml
Control called SpeakTelephoneLink which is a direct port of the
GeneralLink SPEAK Xml Control.
Step 4 - Patch In TelephoneEditLink.cs
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<commands>
<command name="webedit:edittelephonelink" type="SitecoreHacker.Sandbox.Commands.TelephoneEditLink, SitecoreHacker.Sandbox" />
</commands>
</sitecore>
</configuration>
Step 5 - Create the SpeakTelephoneLink.xml File
Original File: /sitecore/shell/Applications/Dialogs/GeneralLink/GeneralLink.xml
You will want to save this file somewhere under /sitecore/shell/Applications. For the purposes of this answer, I am using /sitecore/shell/Applications/Dialogs/SpeakTelephoneLink/
<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
<SpeakTelephoneLink>
<Stylesheet Src="/sitecore/shell/Applications/Dialogs/GeneralLink/GeneralLink.css"></Stylesheet>
<Script Src="/sitecore/shell/Applications/Dialogs/GeneralLink/GeneralLink.js"></Script>
<FormDialog Icon="Network/32x32/link.png" Header="Insert a link"
Text="Select the link type and specify the appropriate properties."
OKButton="OK">
<CodeBeside Type="SitecoreHacker.Sandbox.Controls.Dialogs.SpeakTelephoneLink.SpeakTelephoneLinkForm,SitecoreHacker.Sandbox"/>
<DataContext ID="InternalLinkDataContext" ViewName="Master"/>
<DataContext ID="MediaLinkDataContext" Root="{3D6658D8-A0BF-4E75-B3E2-D050FABCF4E1}"/>
<GridPanel Height="100%" Width="100%" VAlign="top" Fixed="true" Columns="3">
<!--Left column-->
<Border ID="Modes" Height="100%" Class="left-column" Width="100%" GridPanel.Height="100%" GridPanel.Width="120px">
<!--Internal link-->
<Border ID="Internal" onclick="javascript:return scForm.invoke('OnModeChange','internal')" Class="selected">
<a href="#" class="mode" onfocus="this.blur()">
<ThemedImage Class="mode-icon" Src="Network/32x32/link.png" />
<div class="mode-text">
<Literal Text="Internal Link"/>
</div>
</a>
</Border>
<!--Media link-->
... snip ...
<!--External link-->
... snip ...
<!--Telepehone link-->
<Border ID="Telephone">
<a href="#" class="mode" onclick="javascript:return scForm.invoke('OnModeChange','tel')" onfocus="this.blur()">
<ThemedImage Class="mode-icon" Src="Network/32x32/earth.png?overlay=NetworkV2/16x16/link.png" />
<div class="mode-text">
<Literal Text="Telephone Link"/>
</div>
</a>
</Border>
<!--Anchor link-->
... snip ...
<!--Mail link-->
... snip ...
<!--Javascript link-->
... snip ...
<!--Center column-->
<Space GridPanel.Width="8px" />
<!--Right column-->
<Border Class="right-column" Height="100%" GridPanel.Width="100%" GridPanel.Height="100%">
<GridPanel Width="100%" Height="100%" ID="MainGrid">
<Border Class="section-header" GridPanel.VAlign="top">
<Literal ID="SectionHeader" Text="Select the item that you want to create a link to and specify the appropriate properties." />
</Border>
... snip ...
<Literal Text="Telephone Number:" GridPanel.NoWrap="true"/>
<Edit Width="100%" Class="scQuirksBoxModel" GridPanel.Row.ID="LinkTelephoneRow" ID="LinkTelephone"/>
... snip ...
</GridPanel>
</GridPanel>
</Border>
</GridPanel>
<Button Header="Upload" ID="UploadMedia" def:placeholder="Buttons" Click="UploadImage"/>
</FormDialog>
</SpeakTelephoneLink>
</control>
Note the <SpeakTelephoneLink>
that wraps this. This was changed from <GeneralLink>
. Also note the <CodeBeside>
.
Step 6 - Create the Code Beside: SpeakTelephoneLinkForm.cs
Original Class: Sitecore.Shell.Applications.Dialogs.GeneralLink.GeneralLinkForm
namespace SitecoreHacker.Sandbox.Controls.Dialogs.SpeakTelephoneLink
{
public class SpeakTelephoneLinkForm : LinkForm
{
... snip ...
protected Edit LinkTelephone;
private string CurrentMode
{
... snip ...
}
... snip ...
protected override void OnOK(object sender, EventArgs args)
{
Assert.ArgumentNotNull(sender, "sender");
Assert.ArgumentNotNull(args, "args");
var packet = new Packet("link");
SetCommonAttributes(packet);
bool flag;
switch (CurrentMode)
{
case "internal":
flag = SetInternalLinkAttributes(packet);
break;
case "media":
flag = SetMediaLinkAttributes(packet);
break;
case "external":
flag = SetExternalLinkAttributes(packet);
break;
case "tel":
flag = SetTelephoneLinkAttributes(packet);
break;
case "mailto":
flag = SetMailToLinkAttributes(packet);
break;
case "anchor":
flag = SetAnchorLinkAttributes(packet);
break;
case "javascript":
flag = SetJavascriptLinkAttributes(packet);
break;
default:
throw new ArgumentException("Unsupported mode: " + CurrentMode);
}
if (!flag)
return;
SheerResponse.SetDialogValue(packet.OuterXml);
base.OnOK(sender, args);
}
... snip ...
private string GetTelephone()
{
var value = LinkTelephone.Value;
var str = value;
if (str.Length > 0)
{
if (str.IndexOf(":", StringComparison.InvariantCulture) >= 0)
{
str = str.Substring(str.IndexOf(":", StringComparison.InvariantCulture) + 1);
}
if (
!new Regex(
@"^(?:\(?)(?<AreaCode>\d{3})(?:[\).\s]?)(?<Prefix>\d{3})(?:[-\.\s]?)(?<Suffix>\d{4})(?!\d)",
RegexOptions.IgnoreCase).IsMatch(str))
{
return "__Canceled";
}
}
if (value.Length > 0 && value.IndexOf(":", StringComparison.InvariantCulture) < 0)
{
value = string.Concat("tel:", value);
}
return value;
}
private bool SetTelephoneLinkAttributes(Packet packet)
{
Assert.ArgumentNotNull(packet, "packet");
var tel = GetTelephone();
if (tel == "__Canceled")
{
SheerResponse.Alert("The telephone number is invalid.");
return false;
}
SetAttribute(packet, "url", tel);
SetAttribute(packet, "text", Text);
SetAttribute(packet, "title", Title);
SetAttribute(packet, "class", Class);
return true;
}
private void SetTelephoneLinkControls()
{
if (LinkType == "tel" && string.IsNullOrEmpty(Url.Value))
LinkTelephone.Value = LinkAttributes["url"].Replace("tel:","");
ShowContainingRow(LinkTelephone);
SectionHeader.Text =
Translate.Text("Specify the Telephone, e.g. 800-555-1212");
}
... snip ...
/// <summary>The set mode specific controls.</summary>
/// <exception cref="T:System.ArgumentException">
/// </exception>
private void SetModeSpecificControls()
{
HideContainingRow(TreeviewContainer);
MediaPreview.Visible = false;
UploadMedia.Visible = false;
HideContainingRow(UrlContainer);
HideContainingRow(Querystring);
HideContainingRow(MailToContainer);
HideContainingRow(LinkAnchor);
HideContainingRow(JavascriptCode);
HideContainingRow(Target);
HideContainingRow(CustomTarget);
switch (CurrentMode)
{
case "internal":
SetInternalLinkContols();
break;
case "media":
SetMediaLinkControls();
break;
case "external":
SetExternalLinkControls();
break;
case "tel":
SetTelephoneLinkControls();
break;
case "mailto":
SetMailLinkControls();
break;
case "anchor":
SetAnchorLinkControls();
break;
case "javascript":
SetJavaScriptLinkControls();
break;
default:
throw new ArgumentException("Unsupported mode: " + CurrentMode);
}
foreach (Border control in Modes.Controls)
{
if (control != null)
control.Class = control.ID.ToLowerInvariant() == CurrentMode ? "selected" : string.Empty;
}
}
}
}
Step 7 - Compile and Deploy
If all things work out, you'll see the Telephone Link option.