Article Using Widgets to Render Content From Plugins

Many plugin types are expected to render content, often HTML content. While this content can be generated completely within code, plugins can also elect to expose some or all of this rendering capability to widgets and optionally allow administrators to customize the plugins UI using the widget editor.

When should I use widgets to render plugin content?

Any plugin that supports rendering content can choose to expose some or all of its output using widgets. This option can be taken to simplify the definition of the rendering or to enable administrators to make changes to the rendered content using the widget editor.

Rendering plugin-defined content through widgets

To add support to a plugin to define content rendering via widgets, the plugin must implement the IScriptablePlugin interface. The IScriptablePlugin interface is defined in the Telligent.Evolution.Extensibility.UI.Version1 namespace of Telligent.Evolution.ScriptedContentFragments.dll. When implementing this interface, the plugin is also required to be a factory default widget provider and the implementation of the widgets used by the scripted plugin are expected to be stored within the folder structure of the factory default provider implementation (see the factory default widget providers topic for more details about widget source file management):

using System;
using Telligent.Evolution.Extensibility.UI.Version1;

namespace Samples
{
	public class SampleScriptablePlugin : IScriptablePlugin
	{
	    // This must be unique for each widget defined by this plugin
		private readonly Guid _fragmentId = new Guid("a76b3534-61c8-4c6c-bfbd-e72cc2a49a7a");

		private IScriptedContentFragmentController _scriptController;

		#region IPlugin 

		// ...

		#endregion

		#region IScriptedContentFragmentFactoryDefaultProvider

		// ...

		#endregion

		#region IScriptablePlugin

		public void Register(IScriptedContentFragmentController controller)
		{
			_scriptController = controller;

			var fragmentOptions = new ScriptedContentFragmentOptions(_fragmentId)
			{
				CanBeThemeVersioned = false,
				CanHaveHeader = false,
				CanHaveWrapperCss = false,
				CanReadPluginConfiguration = false,
				CanWritePluginConfiguration = false,
				IsEditable = false
			};

			_scriptController.Register(fragmentOptions);
		}

		#endregion

		// Get the name, description, cache configuration, CSS class using _scriptController.GetMetadata(_fragmentId);
		// Get the header of the widget using _scriptController.RenderHeader(_fragmentId, null)
		// Render the widget using _scriptController.Render(_fragmentId, null)
	}
}

A single plugin can define multiple widgets to meet the specific rendering needs of the plugin (generally to implement another plugin interface). When the IScriptablePlugin's Register() method is called, the plugin should register each widget using the provided controller's Register() method. The controller's Register() method enables the plugin to configure how the widget should function, specifically:

  • CanBeThemeVersioned when true, allows the widget to have multiple theme-specific versions that will be rendered based on the contextually applicable selected theme. See the Factory Default Widget Providers topic for details about storing theme-specific versions of widgets.
  • CanHaveHeader when true, allows this widget to have a header. This option customizes the widget editor when editing this widget if editing is enabled. If the header is never going to be used by the plugin, it's best to set this option to false.
  • CanHaveWrapperCss when true, allows this widget to have wrapper CSS. This option customizes the widget editor when editing this widget if editing is enabled. If the wrapper CSS is never going to be used by the plugin, it's best to set this option to false.
  • CanReadPluginConfiguration when true, allows the widget to read the configuration of the plugin (if the plugin also implements IConfigurablePlugin). Because scripted widgets can't expose their own configuration, this option enables the widget to use the $core_v2_widget.GetString/Int/Bool/Etc methods to retrieve configuration data from the plugin that defined it.
  • CanWritePluginConfiguration when true, allows the widget to edit/save the configuration of the plugin (if the plugin also implements IConfigurablePlugin). This allows the widget use the $core_v2_widget.SetString/Int/Bool/Etc methods to save configuration data and act as a configuration editor for the plugin that defined it.
  • IsEditable when true, allows the widget to be edited within the widget editor.
  • Extensions allows the addition of private widget APIs that can be used only by the widget defined by the registration. Widgets defined by plugins otherwise have full access to the regular widget API. To provide an individual widget with privileged access to specific functionality, a private API can be exposed only to specific widgets using the Extensions option.

Defining private widget APIs

A plugin making use of widgets for rendering can optionally expose private widget APIs to specific widgets it defines by populating the Extensions option when registering the widget. In this modified version of the previous example, the fragment registration includes a custom extension:

using System;
using Telligent.Evolution.Extensibility.UI.Version1;
using System.Collections.Specialized;

namespace Samples
{
	public class SampleScriptablePlugin : IScriptablePlugin
	{
		private readonly Guid _fragmentId = new Guid("");

		private IScriptedContentFragmentController _scriptController;

		#region IPlugin 

		// ...

		#endregion

		#region IScriptedContentFragmentFactoryDefaultProvider

		// ...

		#endregion

		#region IScriptablePlugin

		public void Register(IScriptedContentFragmentController controller)
		{
			_scriptController = controller;

			var fragmentOptions = new ScriptedContentFragmentOptions(_fragmentId)
			{
				CanBeThemeVersioned = false,
				CanHaveHeader = false,
				CanHaveWrapperCss = false,
				CanReadPluginConfiguration = false,
				CanWritePluginConfiguration = false,
				IsEditable = false
			};

			fragmentOptions.Extensions.Add(new SamplePrivateExtension());

			_scriptController.Register(fragmentOptions);
		}

		#endregion

		// Get the name, description, cache configuration, CSS class using _scriptController.GetMetadata(_fragmentId);
		// Get the header of the widget using _scriptController.RenderHeader(_fragmentId, null)
		// Render the widget using _scriptController.Render(_fragmentId, null)
	}

	public class SamplePrivateExtension : IContextualScriptedContentFragmentExtension
	{
		#region IContextualScriptedContentFragmentExtension

		public string ExtensionName { get { return "private"; } }

		public object GetExtension(NameValueCollection context)
		{
			return new SamplePrivateExtensionApi(context);
		}

		#endregion
	}

	public class SamplePrivateExtensionApi
	{
		private NameValueCollection _context;

		public SamplePrivateExtensionApi(NameValueCollection context)
		{
			_context = context;
		}

		public string Value
		{
			get { return _context == null ? null : _context["Value"]; }
		}
	}
}

In this sample, when registering the plugin-defined widget (with ID _fragmentId), it also registers a private API on line 41 defined by the SamplePrivateExtension class. The SamplePrivateExtension class identifies the API name ("private" which will be accessible to the widget its provided to as "$private") with the API defined by the SamplePrivateExtensionApi class. Any public properties and methods on SamplePrivateExtensionApi are exposed to the widget to be used in the implementation of this rendered output.

Note that private extensions are provided with a NameValueCollection of contextual items. When rendering a header of content script of a plugin-defined widget, the plugin can provide a set of contextual data as a NameValueCollection. This contextual data is passed through to private extensions which can use the context to alter how they function.

In the sample above, the extension expects to receive a contextual value named "Value". The plugin that defines the widget would be responsible for providing this value when rendering the widget (see the next section for a sample).

Using plugin-defined widgets

Now that you have one or more widgets defined by the plugin, you can now use those plugins to implement other plugin types that require rendered output. For example. Anywhere a string is returned or a TextWriter can be written to, the plugin can render it's widget. For example, using the previous sample, the plugin could have a property, Content, that is expected to render a string. It could render the widget defined by _fragmentId to implement this property and pass a contextual value:

public string Content
{
	get
	{
		return _scriptController.RenderContent(_fragmentId, new NameValueCollection() { { "Value", "Test" } });
	}
}

Within the widget defined by _fragmentId, calling "$private.Value" will return "Test". The widget itself can do anything supported by widgets including creating Ajax callbacks, using embedded files, etc.

Considerations for plugin-defined widget implementations

Widgets defined by plugins can do everything that regular widgets can do in addition to the functionality enabled by the plugin that defined them and access any provided private APIs.

The biggest consideration when implementing a plugin-defined widget is that you cannot likely depend on contextual values in existing widget APIs. For example, unless the widget is executed as an extension to a UI request, $core_v2_blogPost.Current would likely never have a value and $core_v2_page.GetFormValue() will also generally not have a value.