Developing widgets in MVC
MVC widgets in Kentico represent reusable components that can be easily manipulated by content editors and other non-technical users. Widgets and the Kentico page builder feature give the non-technical users more power and flexibility when adjusting page content, in addition to basic editing of text and images. By working with widgets, users can decide which components are placed on pages and where.
To examine sample widgets, explore the implementation of the pre-defined widgets on the Dancing Goat MVC sample site.
This page describes how to create widgets as an MVC site developer. You can find information about:
- Implementing widgets
- Registering widgets
- Storing files for use in the page builder
- Adding scripts and styles for widgets
Implementing widgets
On a basic level, widgets are pieces of HTML output code, which are then placed within a suitable location in the page structure. The main step in the development of a widget is to create a partial view that defines the output.
Within the MVC architecture, the widget partial view is served by a controller and a model is used to pass any required data. In many cases, widgets can utilize a default controller and view model provided by the Kentico API. See the following sub-sections for more information:
In both cases you can develop widgets with properties, which allow content editors to adjust the widget content or behavior directly in the Kentico administration interface. For widgets with configurable properties, you need to create an additional model class that represents the widget properties. See Defining widget properties in MVC to learn more.
MVC Areas
Widgets are designed to be used in the global scope and their code files must be placed in the application root of your MVC project (not in an MVC Area). Creating widgets in MVC Areas may lead to unexpected behavior.
Basic widgets
Use the following process to develop a widget:
- Prepare a partial view that defines the output of the widget according to the general MVC best practices.
- We recommend storing widget views in the ~/Views/Shared/Widgets folder, and using a view name that matches the identifier assigned to the widget upon its registration prefixed with the underscore (‘_’) character.
- Alternatively, you can use any required view location or name, and then specify it when registering the widget.
Accessing the widget’s page
If you need to work with the data of the page containing the currently processed widget, use the ComponentViewModel class as the view’s model and access its Page property. The property returns a TreeNode object representing the given page. If you need to load values from the fields of a specific page type, you can convert the TreeNode object to an instance of a specific page type wrapper class (the page containing the widget must then be of the given page type).
- Register the widget into the system. See Registering widgets.
With this approach, the widget’s view is automatically displayed using a default controller provided by the Kentico API. The values of any properties defined for the widget can be passed to the view by using the default ComponentViewModel<TPropertyModel> class as the model.
Example of widget development
To see a scenario with full code samples which will guide you through the process of developing a simple widget, visit Example - Developing a widget in MVC.
Widgets with a custom controller
When developing widgets with advanced functionality, you may need to take full control over the widget’s logic. You can do this by implementing the widget’s controller and view model, in addition to the partial view. This allows you to run any custom code within the widget’s controller, pass any type of required data to the view, or even switch between completely different views based on the current scenario.
The following steps describe the advanced development process for widgets:
Create a controller class for the widget.
- We recommend storing widget controllers in the ~/Controllers/Widgets folder.
Make the controller inherit from the WidgetController class (available in the Kentico.PageBuilder.Web.Mvc namespace).
Implement the default Index action in the controller, which is used to retrieve the widget markup. The action must return the widget’s HTML content, typically a partial view. The implementation of the action depends on the purpose of the widget.
Notes
- POST requests cannot be disabled for the Index action (e.g., by using the HttpGet attribute). POST requests to the Index action are used in the page builder feature.
- Widget controller actions used to retrieve the markup cannot be asynchronous (cannot use the asyncfunction declaration). Actions that render widget markup are called as child actions when rendering the markup of an editable area, but MVC 5 does not support asynchronous child controller actions.
Accessing the widget’s page
If you need to access fields of the page containing the currently processed widget, call the generic GetPage<TPageType> method (provided by the WidgetControllerbase class). The method takes a page type wrapper class as the PageType parameter and returns a page object of the specified page type. Alternatively, you can call the GetPage method returning only a TreeNode object representing the given page.
// Gets the page of the Article page type where the currently processed widget is placed
var article = GetPage<Article>();
Create any required view model classes used to pass data from the widget controller to the partial view according to the general MVC best practices.
- We recommend storing widget models in the ~/Models/Widgets/<widget name> folder.
- For widgets with configurable properties, do not directly pass the property model to your widget views. Passing data to views is the responsibility of the widget’s view model, and we strongly recommend keeping the models separate.
Prepare a partial view that defines the output of the widget.
- We recommend storing widget views in the ~/Views/Shared/Widgets folder.
Register the widget into the system. See Registering widgets.
With this advanced development approach, you have full responsibility and control over the widget’s controller, view model, and partial view.
Registering widgets
Every widget needs to be registered into the system to be available in the page builder. Register widgets using the RegisterWidget assembly attribute (available in the Kentico.PageBuilder.Web.Mvc namespace).
To register basic widgets (without a custom controller class), we recommend adding the assembly attributes to a dedicated code file. For example, you can create a file named PageBuilderComponentRegister.cs in your project’s ~/App_Start folder and use it to register your page builder components. For basic widgets, specify the following attribute parameters:
Identifier – the unique identifier of the widget. We recommend using a unique prefix in your widget identifiers to prevent conflicts when deploying widgets to other projects, for example matching your company’s name.
Name – the name used to identify the widget when displayed in the Kentico administration interface.
PropertiesType – only required for widgets with properties. Specifies the System.Type of the widget’s property model class.
(Optional) CustomViewName – specifies the name and location of the view that defines the widget’s output. If not set, the system searches for a corresponding _<Identifier>.cshtml view in the ~/Views/Shared/Widgets folder.
Basic widget registration example[assembly: RegisterWidget("CompanyName.CustomWidget", "Custom widget", typeof(CustomWidgetProperties), "Widgets/_CustomWidgetView")]
For widgets with a custom controller, you can add the assembly attribute directly into the controller code file (above the controller class). In this case, specify the following attribute parameters:
Identifier – the unique identifier of the widget. We recommend using a unique prefix in your widget identifiers to prevent conflicts when deploying widgets to other projects, for example matching your company’s name.
ControllerType – the System.Type of the widget’s controller class.
Name – the name used to identify the widget when displayed in the Kentico administration interface.
Controller widget registration example[assembly: RegisterWidget("CompanyName.CustomWidget", typeof(CustomWidgetController), "Custom widget")]
When registering any type of widget, you can also set the following optional attribute properties:
- Description – the description of the widget displayed as a tooltip.
- IconClass – the font icon class displayed when viewing the widgets in the widget list.
[assembly: RegisterWidget("CompanyName.CustomWidget", typeof(CustomWidgetController), "Custom widget", Description = "Widget displaying a custom message.", IconClass = "icon-one")]
Localizing widget metadata
To allow content editors to experience the page builder in their preferred UI culture, you can localize the Name and Description values of widgets.
Storing files for use in the page builder
You may wish to develop widgets that use or display various files. You can store such files in the system either as page attachments or using media libraries. These two approaches vary in different ways:
- Page attachment files are associated only with a specific page. You should store media files as page attachments only if the files are considered to be a content of a particular page (e.g. a language specific variant of an image).
- Media library files are available for all pages of a particular site. You should store files using media libraries when the file content is reusable or the files are more relevant to the general visual design of the page than to its content (e.g. a background image used on all pages with a similar layout).
This is important to remember especially when creating custom page templates from existing pages. New pages created using templates do not display any page attachments, because the attachments stay as content bound to the original pages. On the other hand, pages based on templates display media library files automatically.
Adding scripts and styles for widgets
To add JavaScript and CSS styles required by your page builder widgets, place script and stylesheet files into sub-folders under the ~/Content/Widgets directory of your MVC project (you may need to create the Widgets directory). Use sub-folders that match the identifiers of individual widgets, or a Shared sub-folder for assets used by mutliple widgets.
The system automatically creates bundles containing all .js and .css files located under ~/Content/Widgets. The bundles are then linked on all pages with page builder editable areas.
The same bundles also contain script and stylesheet files added for sections in the ~/Content/Sections directory and inline property editors in the ~/Content/InlineEditors directory (inline editor scripts and styles are only included in the administration bundles, which are linked when pages are displayed in Edit mode within the Pages application of Kentico).
CSS notes
- Only use the ~/Content/Widgets directory to add basic styles that are required for the widget to render correctly. Any site-specific styles that finalize the live site design of the widget should be handled separately within the given site’s main stylesheet.
- To avoid potential conflicts between styles from other third-party components, we recommend adding a unique prefix to your CSS classes and identifiers (for example #CompanyName-mid-button), or employ similar measures to ensure their uniqueness.
- Do not make any assumptions about the relative order of the source CSS in the resulting bundles – individual stylesheet files contained in the bundle may or may not precede each other.
Initializing widget scripts
In many cases, you will need to initialize your scripts from the views of widgets (for example if you need to call a function on page load or register an event listener). For most types of page or element events, you can use HTML Event Attributes of elements in your views.
For scripts that you want to run on page load, you need to consider the following:
- The bundles containing your main scripts are added at the end of the HTML document’s body tag, so they are not available in the widget code during the page load process. A solution is to run the initialization script during the DOMContentLoaded event.
- Widgets in the page builder interface may be added dynamically after the page is loaded. In this case, the DOMContentLoaded event has already occurred and will not fire again.
For example, the following script demonstrates how to reliably call a custom function on page load:
if (document.readyState === "loading") {
// Calls the function during the 'DOMContentLoaded' event, after the HTML document has been completely loaded
document.addEventListener("DOMContentLoaded", function () {
customFunction();
});
} else {
// Calls the function directly in cases where the widget is rendered dynamically after 'DOMContentLoaded' has occurred
customFunction();
}
This approach ensures that the initialization script runs correctly when the widget is displayed on the live site, as well as in the page builder interface.
Note: Apart from initialization code, avoid linking or executing scripts directly within widget views – this could lead to duplicated scripts on pages that contain multiple instances of the same widget.