Here is why you need to extend your Sitefinity WebForms Javascript widget

by Vesselin Vassilev


Posted On Apr 7, 2016


If you are stuck with a WebForms-only template you may need to extend the out-of-the-box Javascript widget because it is somehow limited. Its limitation is that it only supports "text/javascript" type of script. And nowadays marketing guys come up with other types of javascript code that they want on every page of your web site, for instance:
  • Facebook Pixel Code - it looks like this:
<!-- Facebook Pixel Code --> <script>
  !function(f, b, e, v, n, t, s) {
    if (f.fbq) return;
    n = f.fbq = function() {
      n.callMethod ?
        n.callMethod.apply(n, arguments) : n.queue.push(arguments)
    };
    if (!f._fbq) f._fbq = n;
    n.push = n;
    n.loaded = !0;
    n.version = '2.0';
    n.queue = [];
    t = b.createElement(e);
    t.async = !0;
    t.src = v;
    s = b.getElementsByTagName(e)[0];
    s.parentNode.insertBefore(t, s);
  }(window,document,'script', '//connect.facebook.net/en_US/fbevents.js');
fbq('init', '1480401912209426');
fbq('track', 'PageView');
</script> <noscript> <img height = '1'
width = '1'
style = "display:none"
src = "https://www.facebook.com/tr?id=1480401912209426&ev=PageView&noscript=1" /> </noscript> <!-- End Facebook Pixel Code -->


Do you see the problem above? Yes, it has a <noscript> tag which will not work properly in the built-in Javascript widget. Why it would not work - because the widget wraps everything you put in it inside of a <script type="text/javascript"> element. So your <noscript> tag goes inside of a <script> tag which is not valid. 

  • Google Knowledge Panel - Use structured data markup embedded in your public website to specify your preferred social profiles. The schema.org vocabulary and JSON-LD markup format are an open standard for embedding structured data in web pages.
Here is a basic JSON-LD template for an organization to specify several social profiles:

<script type="application/ld+json">
{
  "@context" : "http://schema.org",
  "@type" : "Organization",
  "name" : "Your Organization Name",
  "sameAs" : [
  ]
}
</script>

If you use the built-in Javascript widget in Sitefinity to add the above code to a Page Template you will end up with this error:

doubleScriptTag 
That's because we have nested script tags and there is no way we can set the type of the script tag.

What are your options:

1. Put the javascript code in a Content Block - if you do that you will notice that the Content Block actually removes any javascript code upon save. This is a security feature and is called Content Filters. By default all Content Blocks in Sitefinity use the DefaultFilters which enables all these: RemoveScriptsFixUlBoldItalicIECleanAnchorsMozEmStrong,ConvertFontToSpanConvertToXhtmlIndentHTMLContent
EncodeScriptsOptimizeSpans,ConvertCharactersToEntitiesConvertTagsStripCssExpressionsRemoveExtraBreaks

That's right, the javascript is not saved in the Content Block because of the RemoveScripts filter. So one easy way to allow the Content Block to have scripts is to set the ContentFilters to None.This is done in
Administration > Settings > Advanced > Appearance 

You need to find the "RadEditor filters for Content block widget" field and set its value to None. Do the same with the "Rad Editor's content filters" field. 
Now you can put any javascript code in a Content Block and it will be saved successfully.

2. Edit directly the master file (if any) - that would work but it's not something that marketing people will want to do (and you don't want to either).

3. Create a custom Javascript widget control that inherits the built-in JavascriptEmbedControl and override its OnPreRender method. What I'd like to achieve is a widget that offers me an option to put any kind of javascript I want.

Below is the code for the widget and its slightly modified designer:

public class CustomJavaScriptEmbedControlDesigner : JavaScriptEmbedControlDesigner
{
    protected override void InitializeControls(GenericContainer container)
    {
        base.InitializeControls(container);
 
        //the base method sets the HideAdvancedMode to True, but I want it visible so I can set
        //my custom IncludeScriptTag property to True or False
        this.PropertyEditor.HideAdvancedMode = false;
    }
}

[ControlDesigner(typeof(CustomJavaScriptEmbedControlDesigner))]
public class CustomJavascriptWidget : JavaScriptEmbedControl
{
    public bool IncludeScriptTag { get; set; }
 
    private bool IsEdit
    {
        get
        {
            return this.IsDesignMode() && !this.IsPreviewMode();
        }
    }
 
    protected override void OnPreRender(EventArgs e)
    {
        if (this.IncludeScriptTag)
        {
            base.OnPreRender(e);
        }
        else
        {
            if (this.Page != null && !this.IsEdit)
            {
                Literal literal = new Literal();
                if (!string.IsNullOrEmpty(this.Url))
                {
                    string url = this.Url;
                    if (url.StartsWith("~/"))
                    {
                        url = string.Concat(HostingEnvironment.ApplicationVirtualPath, url.Substring(1));
                        if (url.StartsWith("//"))
                        {
                            url = url.Substring(1);
                        }
                    }
                    literal.Text = string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", url);
                }
                else if (!string.IsNullOrEmpty(this.CustomJavaScriptCode))
                {
                    literal.Text = this.CustomJavaScriptCode;
                }
                if (!string.IsNullOrEmpty(literal.Text))
                {
                    switch (this.ScriptEmbedPosition)
                    {
                        case ScriptEmbedPosition.Head:
                            {
                                this.Page.Header.Controls.Add(literal);
                                return;
                            }
                        case ScriptEmbedPosition.InPlace:
                            {
                                this.Controls.Add(literal);
                                return;
                            }
                        case ScriptEmbedPosition.BeforeBodyEndTag:
                            {
                                this.Page.ClientScript.RegisterStartupScript(typeof(CustomJavascriptWidget), this.ClientID, literal.Text, false);
                                break;
                            }
                        default:
                            {
                                return;
                            }
                    }
                }
            }
        }
    }
}

It's pretty simple - by default the value of the IncludeScriptTag property is False so we can directly paste the code we have from Facebook, Google, etc. that already includes the <script> tag. It will render just fine.
If for some reason we want to fall back to the default behavior we need to go to the Advanced settings of the widget and set the IncludeScriptTag to True. 

Finally, just register your custom widget to the toolbox and start using it.


Additional Info: the above solution is appropriate when you want to insert a non text/javascript type of script on a webforms page template in Sitefinity.
In Hybrid mode you can use the new MVC Javascript widget which does not include the script tag for you, so you can paste anything you want. 
Also, if you want to paste any non-standard javascript to a single page then you can do it from the Title and Properties of the page like this:

custom script type per page 

Copyright © Sitefinity Development