Views

So far, we’ve used simple HTTP and JSON responses when working with routes. This may be all you need, especially if you’re implementing some kind of API. However, if you want to actually render HTML or programmatically work through the response, you may want return a view object from your route controller, instead of a simple response object.

What is a View Object

In Concrete CMS, a view defines a path to a particular PHP template, and optionally a package handle that defines that view as belonging to a particular package (which helps Concrete locate it.) There are many different types of View objects in Concrete, and while many of them started by extending a single unifying class, they mostly don’t do that anymore. Instead, View is a concept: it represents a template that a controller is going to pass data to, and the request is going to render in some capacity.

Thankfully, we can get a little more specific when we’re working with Views in the context of Routes and Controllers. The object we’ll be using most is the Condrete\Core\View\View object. This object is at the heart of all pages and single pages. The Concrete\Core\View\View object contains a path to a template, an optional package handle, an optional theme to wrap that template in, and some ability to render itself and get data out of the controller that’s been attached to it. But unlike pages, these are just routes: they don’t appear in the sitemap or have page attributes attached to them, even though they could look like real, full-fledged pages (since they render in the site’s theme.)

Example: Rendering a View Object at a Particular Route.

Let’s set up a new GET route that responds to /account/activate. We’ll add this to application/bootstrap/app.php.

$router->get('/account/activate', function() use ($app) {
});

Instead of just returning a string or a response, let’s create a view. This view will render a template found at application/views/account/activate/form, and use our custom theme green_salad to render.

$router->get('/account/activate', function() use ($app) {
    $factory = $app->make(\Concrete\Core\Http\ResponseFactoryInterface::class);
    $view = new \Concrete\Core\View\View(‘/account/activate/form’);
$view->setThemeHandle(‘green_salad’);
$contents = $view->render();
return new \Concrete\Core\Http\Response($contents);
});

That’s it! The HTML found at application/views/account/activate/form.php will be rendered, placed into the print $innerContent; found in application/themes/green_salad/view.php, just like what happens when working with single pages.

Important Note About Themes

When rendering a view template inside a theme, you need to ensure that the theme will work fine without a valid page object. This is not always the case. Many themes are coded to work with the automatic $view object being an instance of the Concrete\Core\Page\View\PageView object, which subclasses Concrete\Core\View\View. If your theme templates call methods like $view->getStylesheet() for example, rendering them without a valid page (like in this instance) will cause an error.

What’s Next

While the code above will get the job done, it suffers from some of the same problems as our previous closure-based routing. It’s quick and dirty. Let’s refactor this approach into a controller that’s encapsulated an testable.

Example: Rendering a View at a Particular Route (Better Version)

Instead of returning a closure from this route, let’s use a controller:

$router->get(‘/account/activate’, ‘Application\Controller\AccountController::activate’);

Now, let’s make sure our controller contains an activate method. This is what our application/src/Controller/AccountController.php looks like:

<?php
namespace Application\Controller;
use Concrete\Core\Http\ResponseFactoryInterface;
use Concrete\Core\View\View;
class AccountController
{
    protected $responseFactory;
    public function __construct(ResponseFactoryInterface $responseFactory)
        $this->responseFactory = $responseFactory;
    }

    public function activate()
    {
        $view = new View(‘/account/activate/form’);
        $view->setThemeHandle(‘green_salad’);
        return $this->responseFactory->view($view);
    }
}

Why is this preferred? By using the built-in ResponseFactory class that exists in the core, you can easily transform your view into the proper response, and return it to the user. Since the ResponseFactory is passed in the constructor (automatically instantiated by Concrete - you don’t have to do anything) you can test this class with mock objects easily.

Passing Data from Controller to View

Passing data from a controller into a view is as simple as using the set($key, $value) method within a controller, and attaching that controller to the view.

First, make the controller itself extend the Concrete\Core\Controller\Controller class. (this will enable the set() method.). Then, set data in your controller and attach it.

<?php
namespace Application\Controller;
use Concrete\Core\Http\ResponseFactoryInterface;
use Concrete\Core\View\View;
use Concrete\Core\User\User;
use Concrete\Core\Controller\Controller as BaseController;
class AccountController extends BaseController
{
    protected $responseFactory;
    protected $user;
    public function __construct(User $user, ResponseFactoryInterface $responseFactory)
        $this->user = $user;
        $this->responseFactory = $responseFactory;
    }

    public function activate()
    {
        $view = new View(‘/account/activate/form’);
        $view->setThemeHandle(‘green_salad’);
        $username = $this->user->getUserName();
        $this->set(‘username’, $username);
        $view->setController($this);
        return $this->responseFactory->view($view);
    }
}

Creating Custom Dialog Views in Concrete

If you’re a third party package developer, you might find that you want to use the core routing system to create custom components for Concrete. An example of this might be an AJAX dialog launched from within the Dashboard. These components use both a controller and a view, but they also extend a certain core controller in order to allow them to use the Concrete asset system. To make your own custom dialog views/controllers, just swap out

$view = new View(‘/account/activate/form’);
$view->setThemeHandle(‘green_salad’);

with

$view = new DialogView(‘/account/activate/form’);

(Note: DialogView is found at Concrete\Core\View\DialogView). The content won’t be rendered through a theme, so it’ll be perfect for inclusion as a dialog in the page, and any assets that are required in the controller (e.g. jQuery, other JavaScript/CSS libraries) will be included when the dialog is displayed.