Categories
Bloggers
Blogs RSS feed

Adding custom dialogs to Sitefinity ContentBlock. Any content link selector for ContentBlock

by Nikola Zagorchev

As most of the built-in back-end widgets in Sitefinity, the ContentBlock could be customized and extended to meet all editors and content creators needs. Customizations of the toolbox - inserting or removing default tool sets could be made through the back-end administration, however, in order to add an additional dialog to the toolbox menu, a little bit more should be done. I will show you how a custom dialog could be added to the toolbox of the ContentBlock underlying RadEditor and more specifically - any content item link selector and inserter, including dynamic items build with the Module Builder.

 

Extending the ContentBlock

In order to be able to modify the ContentBlock widget, we will need to map its template. This is done through the Administration's Advanced settings. Select Controls section from the Tree View, then ViewMap. Create new ViewMap item with the ContentBlock Host Type: Telerik.Sitefinity.Web.UI.Fields.HtmlField, Telerik.Sitefinity
and set the LayoutTemplatePath to the path of the .ascx file of the ContentBlock mapped template in your project. Example:

 

This way we have access to the ContentBlock underlying RadEditor and the other controls used. In the template the JavaScript handling the new dialog should be added, so the dialog could be opened when the tool command is fired - the dialog icon is clicked:

<script type="text/javascript">
    Telerik.Web.UI.Editor.CommandList["InsertSpecialLink"] = function (commandName, editor, args) {
        var elem = editor.getSelectedElement(); // Returns the selected element.
        if (elem && elem.tagName == "A") {
            editor.selectElement(elem);
            argument = elem;
        }
        else {
            // Remove links if present from the current selection - because of JS error thrown in IE
            editor.fire("Unlink");
            // Remove Unlink command from the undo/redo list
            var commandsManager = editor.get_commandsManager();
            var commandIndex = commandsManager.getCommandsToUndo().length - 1;
            commandsManager.removeCommandAt(commandIndex);
            var content = editor.getSelectionHtml();
            var link = editor.get_document().createElement("A");
            link.innerHTML = content;
            argument = link;
        }
 
        var myCallbackFunction = function (sender, args) {
            // Callback function to insert the link element will the passed from the dialog parameters
            editor.pasteHtml(String.format("<a href={0} target='{1}' class='{2}'>{3}</a> ", args.href, args.target, args.className, args.name))
        }
 
        // Open the custom dialog. Sitefinity will look for it under /Sitefinity/Dialog route,
        // so if it's an aspx/html page, you need to create this folder and place it there.
        editor.showExternalDialog(
             'InsertLink.aspx',
             argument,
             400,
             600,
             myCallbackFunction,
             null,
             'Insert Link',
             true,
             Telerik.Web.UI.WindowBehaviors.Close + Telerik.Web.UI.WindowBehaviors.Move,
             false,
             false);
    };
</script>

We will register the Dialog tool in the template codebehind class:

protected void Page_Load(object sender, EventArgs e)
{
    //Add the new tool to the Editor Tools collection
    var group = new EditorToolGroup();
    this.editControl.Tools.Add(group);
    var tool = new EditorTool("InsertSpecialLink");
    group.Tools.Add(tool);
}

Note that the Dialog .aspx page by default will be searched in the /Sitefinity/Dialog folder. The Sitefinity folder is already present, so you should only create the Dialog folder and place the dialog page there.

Implementing the selector

The selector logic and elements is placed in the Dialog Webform and its codebehind class. We will need two combo boxes - one for the data types and one for the items, to select from.The data types are queried using the RestApi service and are available on the following service: "/restapi/sitefinity/related-data/data-types?format=json". It is for authenticated users only.

When the data types are resolved and one is selected, the items are queried the following way:

//Get a collection of sitefintiy content items
var typeStr = dataTypesCombo.SelectedValue;
var manager = ManagerBase.GetMappedManager(typeStr);
Type typeCurrent = TypeResolutionService.ResolveType(typeStr);
var items = manager.GetItems(typeCurrent, null, null, 0, 50);
var collection = items.Cast<IDynamicFieldsContainer>()
    .Where(d => d.GetValue<ContentLifecycleStatus>("Status") == ContentLifecycleStatus.Live);

Using the content location service the item Url is resolved, depending on whether or not the default culture fallback is selected - the item Url will be resolved for a page in the current back-end language and if the fallback is selected - in the default language. If no Url is resolved - there is no page that can show this item - the item will not be added to the dropdown:

//instantiate ContentLocationService - it can retrieve the item absolute URL from the items on pages statistics
               var contLocationService = SystemManager.GetContentLocationService();
               //we need just the Title and Url of the item to instert the link, so populate them in such an object
               foreach (var item in collection)
               {
                   var itemModel = new ContentItemModel();
                   itemModel.Title = item.GetValue<Lstring>("Title")[this.CurrentCulture].ToString();
                   string url = "#";
                   // Try resolve the item Url in the current back-end culture
                   var cultureSpecificLoc = contLocationService.GetItemDefaultLocation(item as IDataItem, CurrentCulture);
                   if (cultureSpecificLoc != null)
                   {
                       url = cultureSpecificLoc.ItemAbsoluteUrl;
                       itemModel.Url = url;
                       itemsToBind.Add(itemModel);
                   }
                   else
                   {
                       // If fallback to default culture is checked, try resolve it again
                       if (this.CheckBox1.Checked == true)
                       {
                           var cultureDefaultLoc = contLocationService.GetItemDefaultLocation(item as IDataItem);
                           if (cultureDefaultLoc != null)
                           {
                               url = cultureDefaultLoc.ItemAbsoluteUrl;
                               itemModel.Url = url;
                               itemsToBind.Add(itemModel);
                           }
                       }
                   }
               }

Finally, we pass the generated name, Url and parameters to the ContentBlock Html content:

<script type="text/javascript">
    //aattach to load or onLoad to initialize our dialog
    if (window.attachEvent) {
        window.attachEvent("onload", initDialog);
    }
    else if (window.addEventListener) {
        window.addEventListener("load", initDialog, false);
    }
 
    // declare our fields as global variables              
    var linkUrl = document.getElementById("linkUrl"); // url input
    var linkTarget = document.getElementById("linkTarget");// target input
    var linkClass = document.getElementById("linkClass");// element class input
    var linkName = document.getElementById("linkName");// item name input
    var workLink = null;
 
    //populate the field values form the selected item from the dropdown
    function OnClientSelectedIndexChanged(sender, eventArgs) {
        var item = eventArgs.get_item();
        if (item) {
            linkUrl.value = item.get_value();
            linkTarget.value = item.get_value();
            linkName.value = item.get_text();
        }
    }
 
    function getRadWindow() {
        if (window.radWindow) {
            return window.radWindow;
        }
        if (window.frameElement && window.frameElement.radWindow) {
            return window.frameElement.radWindow;
        }
        return null;
    }
 
    function initDialog() {
        var clientParameters = getRadWindow().ClientParameters; //return the arguments supplied from the parent page
 
        linkUrl.value = clientParameters.href;
        linkTarget.value = clientParameters.target;
        linkClass.value = clientParameters.className;
        linkName.value = clientParameters.innerHTML;
 
        workLink = clientParameters;
    }
 
    function insertLink() //fires when the Insert Link button is clicked
    {
        //create an object and set some custom properties to it     
        workLink.href = linkUrl.value;
        workLink.target = linkTarget.value;
        workLink.className = linkClass.value;
        workLink.name = linkName.value;
 
        getRadWindow().close(workLink); //use the close function of the getRadWindow to close the dialog and pass the arguments from the dialog to the callback function on the main page.
    }
 
    function closeDialog() {
        // closes the dialog window
        getRadWindow().close();
    }
</script>
 

I hope you find this blog post on how to extend the ContentBlock useful. It also shows how items could be queried dynamically by resolving only their type and how all data types could be queried using a built-in service. This could be used in different applications scenarios, as well.

1 comment

Leave a comment
  1. Alain LE CLERE Aug 15, 2014

    Thank you very much, your job is great, I will follow your tips.

    Alain2612

    Stiforp Invitation Mobile APP - Make Money Online

    Leave a comment