The content you're reading is getting on in years
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 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 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.
Nikola Zagorchev is a Tech Support Engineer at Telerik. He joined the Sitefinity Support team in March 2014.
Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.
Learn MoreSubscribe to get all the news, info and tutorials you need to build better business apps and sites
Progress collects the Personal Information set out in our Privacy Policy and the Supplemental Privacy notice for residents of California and other US States and uses it for the purposes stated in that policy.
You can also ask us not to share your Personal Information to third parties here: Do Not Sell or Share My Info
We see that you have already chosen to receive marketing materials from us. If you wish to change this at any time you may do so by clicking here.
Thank you for your continued interest in Progress. Based on either your previous activity on our websites or our ongoing relationship, we will keep you updated on our products, solutions, services, company news and events. If you decide that you want to be removed from our mailing lists at any time, you can change your contact preferences by clicking here.