Nova
WordPress Integration
Component Controllers

Component Controllers

Introduction

Components' Controller classes are, often, nothing more than an extension of the base class. The base class provides a few utility methods that certain components leverage, and it provides the render() method that generates a component's output using the data from its corresponding Model along with its transpiled template.

All components must have their own controller class, even if it implements no methods of its own, owing to Nova's extensive use of namespacing. The transpiler relies on the consistency provided by this approach, as do all classes that extend the \PMC\Nova\Abstracts\Render_Component abstract class.

The base class provides a single method that can be overridden for a component, with the remaining methods either marked final or given private visibility. This ensures consistent behaviour across all components, preventing individual components from introducing unanticipated side effects.

Sample Component

The following is the <TemplateArticle /> component's controller in its entirety (at least, as of July 2023).

<?php
/**
 * Controller for the `template-article` component.
 *
 * @package pmc-nova
 */
 
declare( strict_types = 1 );
 
namespace PMC\Nova\Style_Guide\Components\Template_Article;
 
use PMC\Nova\Components\Controller as Base;
 
/**
 * Class Controller.
 */
final class Controller extends Base {}

Enqueuing Frontend JavaScript

The base controller class incorporates the \PMC\Nova\Components\Traits\Controller_Script_Utilities trait to enqueue a component's scripts. This trait defines an array that is populated with the names of any script entrypoints required by the component.

To enqueue a component's scripts, simply populate the _entrypoint_names array. For performance reasons, scripts are enqueued in the footer.

<?php
/**
 * Controller for the `byline` component.
 *
 * @package pmc-nova
 */
 
declare( strict_types = 1 );
 
namespace PMC\Nova\Style_Guide\Components\Byline;
 
use PMC\Nova\Components\Controller as Base;
 
/**
 * Class Controller.
 */
final class Controller extends Base {
	/**
	 * Entrypoints to enqueue for this component.
	 *
	 * @var array
	 */
	protected array $_entrypoint_names = [
		'byline',
	];
}

Nova uses the @wordpress/scripts (opens in a new tab) package to manage frontend scripts, allowing the plugin to leverage the utilities that PMC_Scripts provides for enqueueing the built file along with its dependencies.

_register_hooks() method

As noted in the introduction, the base controller class provides a single method that individual components can override. That method, _register_hooks(), is invoked automatically by the base class. It provides a mechanism for components to register their hook callbacks.

Rendering Output using Model Data

When a component's controller provides hook callbacks, those callbacks should be static methods. This ensures:

  • that callbacks are reusable across all uses of a specific component; and
  • that individual controller instances are destroyed after the component is rendered.

If the callback requires data from the model, it should be added to the data object and passed to the hook via the template.

Review the <Image /> and <GutenbergContent /> controllers for examples of this pattern in practice.

Exception to static rule

If a component's controller must invoke a non-Nova function once, regardless of how many times the component is used, it can register a non-static hook callback, but the callback must immediately deregister itself when invoked.

Review the <FeaturedMedia /> component for an example.

This situation is the only time that the controller's _get_inputs() method may be used.

It is expected that this situation will be rare.

Rendering Output from a Dependent Plugin

In some situations, a component includes output that comes from outside of Nova; a component could use a Core function or incorporate elements rendered by a plugin. In these cases, the component's template includes calls to either do_action() or apply_filters(), and its controller uses the _register_hooks() method to hook into those actions or filters.

Below is a simplified version of the <Newsletter /> component's controller, which demonstrates how certain external plugin data is incorporated into the template output.

<?php
/**
 * Controller for the `newsletter` component.
 *
 * @package pmc-nova
 */
 
declare( strict_types = 1 );
 
namespace PMC\Nova\Style_Guide\Components\Newsletter;
 
use PMC\Nova\Components\Controller as Base;
use PMC\Utils\Theme;
 
/**
 * Class Controller.
 */
final class Controller extends Base {
	/**
	 * Register component-specific hooks.
	 *
	 * Typically, this is used to render output from a dependent plugin or other
	 * source outside of Nova.
	 *
	 * @return void
	 */
	protected function _register_hooks(): void {
		add_filter(
			'pmc_nova_newsletter_form_action',
			[ Theme::get_instance(), 'get_newsletter_api_url' ]
		);
 
		// Hooked early to allow `pmc-recaptcha` to append to terms.
		add_filter(
			'pmc_email_capture_form_terms',
			[ Theme::get_instance(), 'get_newsletter_terms' ],
			5
		);
	}
}

Additional component whose controllers are worth reviewing:

  • <Comments />