Implementing Custom Fields


Adding field management into your custom modules is quite easy. In this tutorial we will go over to how incorporate custom field management as well as briefly touch on how to work with custom fields and distinguish them from fields installed by your addon.


{{ img('local://help_center/implementing-custom-fields--fields.jpg')|raw }}


Defining Routes

First let's define the routes used to manage fields. We will be using the routers built-in to the Streams Platform.

To get started use the FieldRouter and AssignmentRouter router to automate defining routes for the fields and assignments management respectively:

FieldRouter::route(Addon $addon, $controller, $prefix = null, $base = '/fields');
AssignmentRouter::route(Addon $addon, $controller, $prefix = null, $base = '/assignments');

The $addon is the instance of the addon that own's these routes. You can easily get this from your service provider (see below).

The $controller is your addon's controller for field or assignment management. We will talk about these later but they extend the base FieldsController and AssignmentsController classes.

The $prefix is a path prefix that is helpful if the routes need to be prefixed for some reason. This is useful for example if you are allowing management of assignments for multiple streams. You can prefix each management area and use controllers for either stream this way.

Below is a trimmed down real-world example of the Pages module implementation of these routers:

<?php namespace Anomaly\PagesModule;

use Anomaly\Streams\Platform\Addon\AddonServiceProvider;
use Anomaly\PagesModule\Http\Controller\Admin\AssignmentsController;
use Anomaly\PagesModule\Http\Controller\Admin\FieldsController;
use Anomaly\Streams\Platform\Assignment\AssignmentRouter;
use Anomaly\Streams\Platform\Field\FieldRouter;

class PagesModuleServiceProvider extends AddonServiceProvider
{

    /**
     * Map additional routes.
     *
     * @param FieldRouter             $fields
     * @param AssignmentRouter $assignments
     */
    public function map(FieldRouter $fields, AssignmentRouter $assignments)
    {
        $fields->route($this->addon, FieldsController::class);
        $assignments->route($this->addon, AssignmentsController::class, 'admin/pages/types');
    }
}

Create Your Controllers

Notice how we specified the controllers for field and assignment management in the routers above. Let's go ahead and make those.

Fields Controller

To create a fields controller simply extend the base Anomaly\Streams\Platform\Http\Controller\FieldsController controller.

The only property that is required is the $namespace property which tells the controller which namespace to display fields for. By default only unlocked fields will be displayed as locked fields are typically installed by your addon.

<?php namespace Anomaly\PagesModule\Http\Controller\Admin;

class FieldsController extends \Anomaly\Streams\Platform\Http\Controller\FieldsController
{

    /**
     * The stream namespace.
     *
     * @var string
     */
    protected $namespace = 'pages';

}

Assignments Controller

To create an assignments controller simply extend the base Anomaly\Streams\Platform\Http\Controller\FieldsController controller.

Again the only property that is required is the $namespace property. And just like before only assignments for unlocked fields will be displayed by default.

<?php namespace Anomaly\PagesModule\Http\Controller\Admin;

class AssignmentsController extends \Anomaly\Streams\Platform\Http\Controller\AssignmentsController
{

    /**
     * The stream namespace.
     *
     * @var string
     */
    protected $namespace = 'pages';

}
Specifying Streams

The assignments router assumes a stream ID or slug will be present when accessing it's routes. For examples: admin/pages/types/assignments/{stream}

Be sure to include the ID or slug of the stream you wish to manage in your button or link to access these controllers.

Below is how we do it in the pages module. This buttons definition is on the TypeTableBuilder:

$builder->setButtons(
    [
        'edit',
        'assignments' => [
            'href' => function (TypeInterface $entry) {
                return '/admin/pages/types/assignments/' . $entry->getEntryStreamId();
            },
        ],
    ]
);

Defining Sections

Now that we have the routes defined and the controllers to handle those routes all we need to do is define the sections in the module class to help access these areas. Generally I only include a section for fields since the assignments falls under another section (like page types for example) anyways. However you could make sections for both if you like.

Below is an example of the pages module's fields section definition:

'fields' => [
    'buttons' => [
        'new_field' => [
            'data-toggle' => 'modal',
            'data-target' => '#modal',
            'href'        => 'admin/pages/fields/choose',
        ],
    ],
],

Note that the modal is built into the router and Streams Platform so the rest is completely automated.

Differentiating Fields

Now that you have addon fields and custom fields assigned to a stream you may need to distinguish between the two.

Primarily this is done with the locked attribute. Addon fields are locked and custom ones are unlocked.

Here are a few common methods and objects you will likely need to use to incorporate these into a form for example:

/* @var \Anomaly\Streams\Platform\Stream\Contract\StreamInterface $stream */
$stream->getUnlockedAssignments();

/* @var \Anomaly\Streams\Platform\Field\FieldCollection $fields */
$fields->unlocked();

/* @var \Anomaly\Streams\Platform\Assignment\AssignmentCollection $assignments */
$assignments->unlocked();

Defining a Form Section for Custom Fields

Below is an example of how you might define multiple sections with one being ONLY custom fields. This is the section handler for the UserFormBuilder for the Users module:

<?php namespace Anomaly\UsersModule\User\Form;

use Anomaly\UsersModule\User\UserModel;

class UserFormSections
{
    public function handle(UserFormBuilder $builder, UserModel $users)
    {
        $fields = [
            'first_name',
            'last_name',
            'display_name',
            'username',
            'email',
            'activated',
            'enabled',
            'password',
            'roles',
        ];

        $assignments = $users->getAssignments();

        $profileFields = $assignments->notLocked()->fieldSlugs();

        $builder->setSections(
            [
                'user' => [
                    'tabs' => [
                        'account' => [
                            'title'  => 'anomaly.module.users::tab.account',
                            'fields' => $fields,
                        ],
                        'profile' => [
                            'title'  => 'anomaly.module.users::tab.profile',
                            'fields' => $profileFields,
                        ],
                    ],
                ],
            ]
        );
    }
}


{{ img('local://help_center/implementing-custom-fields--assignments.jpg')|raw }}


{{ img('local://help_center/implementing-custom-fields--choose-field.jpg')|raw }}