View Models

The concept of a view model in SiteAdmin is derived from Microsoft's .NET framework. Though in comparison, SA's implementation is varied. Their purpose is to prevent excessive code bloat in application controllers. As mentioned in the controller documentation, a controller method should only contain logic for receiving and responding to requests. Business logic, such as querying/constructing data or performing calculations should be handled elsewhere. A ViewModel is that elsewhere.

File Location

View models reside inside of specific modules.

{application root}/siteadmin/modules/{vendor}/{module}/ViewModels/

The ViewModel Interface

All view models must implement the \sa\application\IViewModel interface.

Your First ViewModel

View Model Class

<?php
use sa\application\IViewModel;

class ShippingOptionsListViewModel implements IViewModel {
    public $defaultOptions;
    public $heading;
    private $shippingOptions;
    
    public function __construct(array $data) {
        $this->defaultOptions = $data['default_options'];
        $this->heading = $data['shipping_heading'];
        $this->shippingOptions = null;
    }
    
    public function getShippingOptions() {
        if ($this->shippingOptions != null) {
            return $this->shippingOptions;
        }
        
        $options = array();
        // <get shipping options>
        return $options;
    }
    
    public function getXssSanitationExcludes() {
        return array('defaultOptions', 'heading');
    }
}

Scope of Public Properties

When binding a view model to a subview, its public properties will made available in the subview's global scope. For example, the view model's properties defaultOptions and heading may be accessed in the subview as so,

{module}/views/_shipping_options_list.php
<h1><?= $heading ?></h1>

<?php
foreach($defaultOptions as $option) {
    // do something
}
?>

A view model's public properties are generally reserved for constants or default values. It is not recommended to populate these values by performing business logic in the constructor. For business logic, use the view model's helper methods instead.

Notice both public properties are included in the getXssSanitzationExcludes() method. Any public properties will be excluded from XSS sanitizing step.

Scope of Private Properties

As you might expect, the view model's private properties will not be exposed to the view. In addition, private methods are not affected by the getXssSanitizationExcludes() method!.

Helper Methods

In the previous example, the getShippingOptions method is exposed as a helper method. This is where complex business operations are performed, such as fetching data from a repository or constructing data structures.

Binding the View Model

There are two ways you can use your view models in an application. The general rule of thumb is to use the subviews method with controllers which respond with HTML templates. Use the direct controller method for controllers which respond with JSON, XML, or any other non-template response.

Subviews

For controller methods that respond with HTML content, it is best practice to create a parent view, then populate it with subviews which are bound to view models.

To encourage this practice, it is not possible to directly bind a view model to a parent view.

Binding view model to subview

parent_view.php
<?= $self->subViewWithModel('_shipping_options_list', 'ShippingOptionsListViewMo
del', $data) ?>

<a name="accessing-in-subview"></a>

Accessing view model in subview

Once a view model has been bound to a subview, a $viewmodel variable becomes available in the subview's context. This variable holds the view model's instance. It is used to access helper methods within the view model.

_shipping_options_list.php
<h1><?= $heading ?></h1>

<ul>
    <?php
    foreach($viewmodel->getShippingOptions as $option) {
        // render option
    }
    ?>
</ul>

Directly in Controller Method

Some controller methods respond with JSON or XML rather than a HTML template. These types of responses don't have views, so binding a view model to a subview is not possible. Instead, the view models should be used directly.

Last updated