Beyond Behaviors - Part 2: Rendering Lists and Forms by hand

Tutorial 20

Now that we understand the MVC pattern used in October, let's work on rendering forms and lists.

See Part 1 if not already up to speed with MVC patterns in October. We will loosely follow the same patterns introduced by the FormController and ListController behaviors. When implemented in a controller, these behaviors introduce page actions and AJAX handlers.

  • ListController adds action: index.
  • FormController adds actions: create, update, preview.

Each behavior creates a back-end widget for the functionality. This is what we will use for our form and list.

  • ListController creates a Backend\Widgets\Lists widget
  • FormController creates a Backend\Widgets\Form widget

Inside the update action, let's prepare the form widget. First we must tell the form widget what fields to render using the $this->makeConfig method and passing the path to our form field definition file. In this path the $ symbol represents the base path to the plugins directory.

public function update($id = null)
{
    $config = $this->makeConfig('$/acme/formist/models/customer/fields.yaml');
}

We should also tell the form widget about the associated model, passed to the newly generated config as the model property.

public function update($id = null)
{
    $config = $this->makeConfig('$/acme/formist/models/customer/fields.yaml');

    $config->model = new \Acme\Formist\Models\Customer;
}

Finally, the widget can be created using the $this->makeWidget method, passing the widget class name as the first argument and config object as the second argument. We should also make the widget available to the view file by passing it to the $this->vars array property.

public function update($id = null)
{
    $config = $this->makeConfig('$/acme/formist/models/customer/fields.yaml');

    $config->model = new \Acme\Formist\Models\Customer;

    $widget = $this->makeWidget('Backend\Widgets\Form', $config);

    $this->vars['widget'] = $widget;
}

To render the form, open the update.htm view file and simply call the render() method on the widget. The widget should also be wrapped in a HTML form so the postback data can be captured.

<?= Form::open() ?>
    <?= $widget->render() ?>

    <button data-request="onUpdate" class="btn btn-primary">
        Write to log
    </button>
<?= Form::close() ?>

Let's update the AJAX handler to capture the form postback data.

public function onUpdate($id = null)
{
    $data = post();

    // Check storage/logs/system.log
    trace_log($data);

    \Flash::success('Jobs done!');
}

At the end of the storage/logs/system.log file we should see our trace log entry.

[2017-01-14 20:29:04] dev.INFO: Array
(
    [_session_key] => l2rGYWWZhhUiAGb49sIy9A96FHD4ZGTOn1pI1uFE
    [_token] => hNpmptZIgNKskWN0sjPVGGKeXktMYsdj0Y1KD8rx
    [company] => 
    [first_name] => 
    [last_name] => 
    [street] => 
    [house_number] => 
    [zipcode] => 
    [city] => 
    [email] => 
    [phone] => 
)

Wait a second, our action is called "update" and the fields are empty, shouldn't we be updating something?

To tell the form about existing data, change the model in the configuration to an existing record instead. We can use the $id value taken from the URL and pass it to the Customer::find method, that's handy!

public function update($id = null)
{
    //...

    $config->model = Acme\Formist\Models\Customer::find($id);

    // ...
}

Now open the URL /backend/acme/formist/mycontroller/update/1 to find the Customer with the ID of 1. We just built a simplified version of the FormController behavior's update action!

Creating a list for the index action is just as easy. We pass the list column file to the makeConfig method, specify a new model instance and make the Backend\Widgets\Lists widget class instead.

public function index()
{
    $config = $this->makeConfig('$/acme/formist/models/customer/columns.yaml');

    $config->model = new \Acme\Formist\Models\Customer;

    $widget = $this->makeWidget('Backend\Widgets\Lists', $config);

    $this->vars['widget'] = $widget;
}

Inside the corresponding view file plugins/acme/formist/controllers/mycontroller/index.htm we can call the render method.

<?= $widget->render() ?>

Now open the URL /backend/acme/formist/mycontroller to see the rendered list. Notice if we try to sort the list, by clicking a column header, an error message is displayed:

A widget with class name 'list' has not been bound to the controller

This is because widgets themselves can provide AJAX handlers for their functionality, however the controller should be informed of this. We register the widget to the controller using the bindToController method.

public function index()
{
    // ...

    $widget = $this->makeWidget('Backend\Widgets\Lists', $config);

    $widget->bindToController();

    $this->vars['widget'] = $widget;
}

It is important to note that this binding must occur early in the page life cycle. This means bindToController must be called either in a page action method, inside the __construct() method of a controller, or inside the init() method of a widget.

Now let's link our list to our form by specifying a recordUrl in the widget configuration. Now when a record is clicked, it will take the user to the update action we created earlier.

public function index()
{
    $config = $this->makeConfig('$/acme/formist/models/customer/columns.yaml');

    $config->model = new \Acme\Formist\Models\Customer;

    $config->recordUrl = 'acme/formist/mycontroller/update/:id';

    $widget = $this->makeWidget('Backend\Widgets\Lists', $config);

    $widget->bindToController();

    $this->vars['widget'] = $widget;
}

Now we have a simplified version of the ListController behavior's index action!

Here we have demonstrated that behaviors are useful companions to building in the back-end, they can save a lot of time. However, behaviors are not a necessity to build powerful back-end interfaces. Like opening the hood of a car, there are many components you can borrow to build your own custom hot rod!

View the next part in the series Implementing a nested relationship manager.

comments powered by Disqus