9

I want to have fields that are rendered as telephone links, e.g. <a href="tel:12345678">.

Can I do this by modifying the standard field type, or by creating a new field type? I have tried using an external link and setting the URL to tel:12345646 in the raw values, but it renders as http://tel:12345646.

There is a blog post by Akshay Sura on how to do it:

https://www.akshaysura.com/2015/02/22/add-telephone-link-to-the-general-link-field-type-in-sitecore/

But it doesn't work for me on Sitecore 8. It seems to work fine in the Content Editor, but breaks in the Experience Editor.

5
  • I tried this approach and works fine for me. Commented Oct 19, 2016 at 17:34
  • That link to Akshay's blog looks like Sitecore 8 to me.
    – Richard Seal
    Commented Oct 19, 2016 at 17:43
  • Hmmm weird, will check again then. I seems to work fine in CE but breaks on experience editor. Tried to fix it thete but then it wasn't rendering on preview :s
    – josedbaez
    Commented Oct 19, 2016 at 18:11
  • 1
    What was the error - maybe we could answer that here :)
    – Richard Seal
    Commented Oct 19, 2016 at 19:03
  • From what I can see the experience editor issue is related to <command name="webedit:editlink" type="Sitecore.Shell.Applications.WebEdit.Commands.EditLink, Sitecore.ExperienceEditor" /> Because that will raise urlString["xmlcontrol"] = "GeneralLink" and that GeneralLink doesn't understand "tel". I tried creating a new one but couldn't succeed
    – josedbaez
    Commented Oct 21, 2016 at 7:38

3 Answers 3

16

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

enter image description here

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.

enter image description here

1
  • 2
    This works perfectly. Great work man. Just a small note for everyone: The code has a regex validating for US numbers, see GetTelephone() in TelephoneLinkForm.cs
    – josedbaez
    Commented Oct 26, 2016 at 11:52
4

This is an alternative approach to supporting Tel links. I'm using sitecore 8.2 update 7 here and Glass.

It isn't quite a nice for content editors and doesn't provide Experience Support but with an bit of education I think it's acceptable if you only need limited support for this or your project doesn't allow time to implement the approach outlined by Akshay and Pete.

Instruct content editors to use the External link option in the content editor and to add their Tel link like so:

enter image description here

When saved Sitecore will actually add in http:// in front of the the link like so:

enter image description here

This looks like this in the Raw Value:

<link text="0111 111111" linktype="external" url="http://tel:0111 111111" anchor="" target="" />

The handle this we can create an Glass extension to render the link but remove the "http://" part from the link before rendering it:

public HtmlString RenderTelLink(Link link, object attributes = null, string contents = null)
    {
        if (!string.IsNullOrEmpty(link?.Url))
        {
             link.Url = link.Url.ToLowerInvariant().Replace("http://tel:", "tel:");
             return new HtmlString(GlassHtml.RenderLink(link, x => x, attributes, false, contents));
        }
        return new HtmlString(GlassHtml.RenderLink(link, x => x, attributes, false, contents));
    }

Finally in your view use this new method for links where you need to support Tel links:

@RenderTelLink(myItem.Link, new {@class = "my-custom-css-class" })

This then produces the following html markup:

<a href="tel:0111 111111" class="my-custom-css-class">0111 111111</a>

This isn't as nice as the other approaches but I think it's ok if you just need to manage a small number of Telephone links and your content editors can work in this way.

2

Quick and dirty solution based on knowledge, how Sitecore works with General Link field, and how browsers work with tel links.

Sitecore looks for :// present in the external link to identify the scheme. When you enter tel: it doesn't recognize the scheme and will add http(s)://. But if you will add tel:// then Sitecore will think that tel is a scheme. enter image description here

Also, this link will be rendered with an extra // in href attribute, e.g. tel://30977483103. But these 2 slashes would not affect the work of this link.

P.S. If you do need the proper solution, please follow Akshay Sura article mentioned in another answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.