Widget Development

This is a guide for the development of widgets for the Layouts Plugin. This is intended for developers wishing to develop a layouts widget. If you’re entirely new to Discourse development, or to software development in general, we would recommend you first attempt to create a basic (non-layouts) theme component first. See “Background Knowledge” for some relevant resources. We’re also happy to assist and mentor developers who are geninuely keen to learn about developing Layouts widgets.

Also, while the primary way to develop widgets for the Layouts Plugin is by creating a widget in a Discourse theme component, you can also create widgets in a Discourse plugin. This widget development guide will focus on the theme component method. We would recommend you only consider using a plugin if you’ve first looked at creating your widget in a theme component.

Background Knowledge

The knowledge background assumed by this guide is the basics of Discourse themes and Discourse wdigets. These are both covered in some detail on meta.discourse.org. See in particular:

https://meta.discourse.org/t/developer-s-guide-to-discourse-themes/93648

You’re welcome to ask questions about those background topics here, however please read and attempt to understand these resources first.

Widget Library

Once you understand how widgets work in Discourse, the next step is to understand how widgets are used in the Layouts plugin. The plugin provides a few widget methods you need to use when developing a layouts widget.

These methods are available via the “layouts” library in the plugin. You can import it into your theme component like so:

import layouts from "discourse/plugins/discourse-layouts/discourse/lib/layouts"

However, the way you’ll see this import handled n Pavilion’s layouts widgets is like this:

import { createWidget } from 'discourse/widgets/widget';
let layouts;

try {
  layouts = requirejs('discourse/plugins/discourse-layouts/discourse/lib/layouts');
} catch(error) {
  layouts = { createLayoutsWidget: createWidget };
  console.error(error);
}

This structure adds a safeguard to the import, to account for the situation in which the theme component is installed, but the Layouts Plugin is not installed (for whatever reason). If the plugin doesn’t exist (meaning the import would fail), the error is caught and, in this case, the method being used in the layouts object is given a backup method which will mean the theme component won’t throw any exceptions that affect the behaviour of the forum it’s installed on.

createLayoutsWidget

The createLayoutsWidget method is available on the layouts library object, and is the way to create layouts widgets.

layouts.createLayoutsWidget('widget-name', {});

It uses the Discourse createWidget method to create the widget, and also adds the widget to the _layouts_widget_registry which is used in a few different places in the Layouts Plugin, for example to display a list of the available layouts widgets in /admin/layouts/widgets.

Note that this method also namespaces all layouts widgets (it pre-fixes layouts- to all layouts widget names in the Discourse widget registry). This means you don’t need to worry about your widget name conflicting with widget names in internal Discourse.

addSidebarProps

The addSidebarProps method is available on the layouts library object, and is the way to pass properties through to sidebar widgets.

layouts.addSidebarProps({ prop1: data, prop2: other_data });

This method allows you to load any data you need for your widgets in an initializer and easily make it available to your widgets. This means you can load all the data you need on the initial render and not mess around with loading data in your widgets, which should be focused on displaying data.

You can see examples of this here in the Category List widget and here and here in the Topic Lists widgets:

Widget Properties

Layouts widgets are (currently) all added to the Discourse layout via the sidebar widget in the layouts plugin. In addition to any properties added via addSidebarProps, each widget is also passed these properties, which are available in all layouts widgets via the widget attributes:

  • side: The side the widget is on

  • context: The current context (see more on contexts here).

  • controller: The instance of the current context’s controller (e.g. the topic controller if the context is the topic)

  • filter: The list filter, if the context is discovery (aka “Topic List”).

  • category: The current category, if the context is discovery or topic and there is a category present.

  • topic: The topic instance, if the context is topic.

These properties are all updated dynamically as they change in the client.

Widget Hooks

Layouts widgets have access to all the normal hooks in Disocurse widgets, which you’ll find in the discourse/widgets/widget.js file. Layouts widgets also have access to

shouldRender

This hook is run before the layouts widget is attached to the sidebar, and is passed the same properties as rendered widget (see above). You can use it to implement custom render logic in the event that various widget settings in the widget admin are not sufficient.

Sidebar events

The Layouts plugin triggers certain events you can use to handle sidebar behaviour and functionality in your widgets.

sidebar:toggle

The sidebar:toggle event can be used to control the show/hide behaviour of sidebars on both desktop and mobile. You invoke it like this:

this.appEvents.trigger('sidebar:toggle', opts);

The supported options are:

  • side: “left” or “right”. If not included both sidebars will be toggled

  • value: true or false. If not included whatever the current show / hide state of the sidebar is will be toggled.

  • target: “responsive” or “desktop”. If not included both responsive and desktop will be targeted. Note that responsive applies on desktop if the responsive threshold is reached, and on mobile devices.

You can see examples of this event being used in the Category List and Topic Lists widgets:

sidebars:after-render

The sidebars:after-render event can be used to handle DOM manipulation after sidebars are rendered. The best way of updating widget DOM is from within the widget itself, using standard widget functionality. However there are some cases in which you may need an “external” way of updating sidebar DOM. You can consume this event anywhere in Discourse to be sure the widgets are rendered.

Sidebar Plugin Outlets

The Layouts Plugin adds two plugin outlets that can be used to insert markup below each sidebar container:

  • sidebar-left-bottom: insert markup below the left sidebar

  • sidebar-right-bottom: insert markup below the right sidebar

Thes plugin outlets work the same as normal Discourse plugin outlets. Read more on plugin outlet usage here:

https://meta.discourse.org/t/beginners-guide-to-creating-discourse-plugins-part-2-plugin-outlets/31001