This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.
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.
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.
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 sitefinity content itemsvar 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 insert 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"> //attach 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.
Subscribe to get all the news, info and tutorials you need to build better business apps and sites