4. Assembling views

All the functionality in the world is useless if you don’t have some method of interfacing with it. For this reason, the portal has a powerful and extensible way of implementing views. Views an be implemented very simply, however the portal has a built in framework for handling most of the tedious bits for you. Let’s start with a basic view example and go from there.

Implementing a “Hello World” View

Fundamentally a view can be implemented with only two things: a registration to the VIEW hook, and a function that outputs some content:

namespace Papal\View;
__register('VIEW', '/hello', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display() {
    echo "Hello World";
  }
}

In this case, visiting the /hello route within your application will result in the application displaying the output of the specified callback function. If you navigate to the route in your browser you’ll see just the following:

Hello World

Pretty simple stuff, right? That said, there’s a bit more going on under the hood. To demonstrate, let’s update our function definition a bit:

namespace Papal\View;
__register('VIEW', '/hello', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display($args) {
    var_dump($args);
  }
}

Now if you navigate to the view, you’ll be presented with the following output:

array(1) {
  ["urlpath"]=>
  array(1) {
    [0]=>
    string(5) "hello"
  }
}

As you can see, the components of the URL get passed to the function so that the function can act upon them.

Regular Expressions in URLs

One feature of the VIEW registration hook is that regular expressions can be used as URL components. Consider the following example:

namespace Papal\View;
__register('VIEW', '/hello/([A-Za-z]+)', ['callback' => '\Papal\View\Hello::display']);

class Hello {
  public static function display($args) {
    echo "Hello, {$args['urlpath'][1]}";
  }
}

Now if we navigate to /hello/George we’ll be presented with the following output:

Hello, George

The ViewHandler Class

The above is all good and well if you have extremely basic needs from your view. But what if you want your view to actually integrate with the rest of the application? Or what if you have certain permissions requirements for the view? For this we have the \Papal\ViewHandler class.

The Viewhandler class is intended to serve as a parent class for your views. By extending it, you give your class support for authentication and permissions requirements, and a nifty way of mapping arrays to your view object’s properties. The ViewHandler class is also the “correct” way of implementing views so that potential future updates to it will affect all of your views without you having to modify each of your views. Let’s take a look at a basic implementation that uses the portal’s Body template:

namespace Papal\View;
__register('VIEW', '/hello/([A-Za-z]+)', ['callback' => '\Papal\View\Hello::display']); 

class Hello extends \Papal\ViewHandler {
    public function __construct($args = null) {
        $setup = $this->setup($args);
        if ($setup === true) {
            \Papal\Template\Body::open("Hello World", "Hello World");
            $this->markup();
            \Papal\Template\Body::close();
        }
    }

    private function markup() {
        echo "Hello, {$this->urlpath[1]}";
    }
}

As you can see, we’re extending the ViewHandler class and declaring a constructor overload that accepts a single argument (which defaults to null). The constructor then calls the setup() object method from the ViewHandler class which determines whether to display the the view, a login page, or a 404 page. If there are no issues with the setup, the constructor then displays the output of the markup() object method in between the opening and closing of the Body template.

The private markup() object method in this case simply outputs the same string from our more basic example above, but note that this time it’s accessing urlpath as an object property rather than as part of an array passed directly to the display() method (which is now defined within the ViewHandler class and is simply a shortcut to call the object constructor). The setup() method mentioned above, takes the entries in that array and creates object properties out of them.

So what about authentication and permissions? By default all classes extending the ViewHandler class require the user to be logged in and don’t have specific permissions requirements, but this is very easy to set. Let’s take our class definition from above and make the requirements explicit and add the requirement that the user have the search capability:

class Hello extends \Papal\ViewHandler {
  public function __construct($args = null) {
    $this->requiresAuth = true;
    $this->requiresCaps = 'search';
    $this->requiresAllCaps = false;

    $setup = $this->setup($args);
    if ($setup === true) {
      \Papal\Template\Body::open("Hello World", "Hello World");
      $this->markup();
      \Papal\Template\Body::close();
    }
  }

  private function markup() {
    echo "Hello, {$this->urlpath[1]}";
  }
}

Let’s break it down:

  • requiresAuthboolean – Whether or not the view requires the user to be authenticated. For most views, you’ll want to leave this alone, however you may find it useful to override this if you have pages you wish to display publicly or if you’re, for instance, building an API route that authenticates differently.
  • requiresCapsstring | array – If this is a string, the user accessing the page must have the listed capability in the permissions list for his/her user role. If this is an array, the user accessing the page must have one of the capabilities in the permissions list for his/her user role.
  • requiresAllCapsboolean – Makes it so that the user must have all the capabilities in the requiresCaps array, rather than any single one, in order to access the view.

Structuring your HTML

While there are no specific requirements for HTML structuring, there are definitely a few best practices and a few things to keep in mind. At the moment and for the foreseeable future, the portal’s default templates make use of Bootstrap 4 and it’s generally a good idea to use it in your views as well. In fact the Bootstrap CSS and Javascript are both included in the default Header template (which is called from within the Body template). If your view requires custom CSS you can add it by passing the necessary argument to the \Papal\Template\Body::open() function or by including style tags in the view output.

Some modules provide custom registration hooks which can be used to assist with and/or automate the construction of various HTML components. For instance the PapalAdmin module provides the ability to quickly and easily assemble forms, tables, modals, and jQuery form submissions by using arbitrarily defined registration hooks.

In some rare cases, you may find that the default templates included in the portal’s core code are not sufficient for what you’re trying to accomplish. In this case you’re welcome to implement your own templates or assemble your own views from scratch. A good use case for this would be implementing publicly viewable pages with a different “theme” than the administrative back-end. You could also use some logic within the view to display different templates based on other factors such as the user ID, the time of day, or the value of a setting in the database.

As an example let’s say a developer has a user base that is still largely dependent upon an old version of Internet Explorer for reasons beyond their control. The developer could write new templates that work with the old IE version and then test the value of $_SERVER['HTTP_USER_AGENT'] to determine which templates to load.