Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best way to customize relation display fields #58

Open
deizel opened this issue Jul 14, 2015 · 7 comments
Open

Best way to customize relation display fields #58

deizel opened this issue Jul 14, 2015 · 7 comments

Comments

@deizel
Copy link
Contributor

deizel commented Jul 14, 2015

Recently had a requirement to display a shorter name for a related field in the index table (from the default name field to one called short_name).

This is the most elegant solution I could come up with:

    public function index()
    {
        $action = $this->Crud->action();
        $action->config([
            'scaffold.fields' => [
                'facility_id' => [
                    'formatter' => function ($name, $value, $entity) {
                        return $this->getView()->Html->link(
                            $entity->facility->short_name,
                            ['controller' => 'Facilities', 'action' => 'view', $entity->facility->id]
                        );
                    }
                ],

Other things I tried included:

  • Using an element formatter (though getting all the information I needed to create the link in a reusable way, such as the target controller name, wasn't pretty so I abandoned that idea.)
  • Changing the display field on the association (we needed the full name in the search filters, so this involved changing the displayField in beforePaginate and back again in relatedModel, which wasn't ideal either.)

Is this a common enough use case where it should be configurable like like title and label?

    public function index()
    {
        $action = $this->Crud->action();
        $action->config([
            'scaffold.fields' => [
                'facility_id' => ['displayField' => 'short_name'],
@lorenzo
Copy link
Member

lorenzo commented Jul 14, 2015

I think a good way is using the element formatter:

'scaffold.fields' => ['facility_id' => ['formatter' => 'element', 'element' => 'facility']]

Then the Element/facility.ctp will be rendered and you have full control over what is displayed.

@lorenzo
Copy link
Member

lorenzo commented Jul 14, 2015

Derp, I took the time to read the full issue description, you are already using that. That said, I'm not sold on complicating the formatting options by using an array DSL.

I'm open to see what others think about it.

@ADmad
Copy link
Member

ADmad commented Jul 14, 2015

@deizel Can't your particular case be handled just by changing display field of Facilities table to short_name and then specifying 'facility_id' => ['formatter' => 'relation'] in scaffold.fields ?

@deizel
Copy link
Contributor Author

deizel commented Jul 15, 2015

@ADmad Unfortunately, since I need the name elsewhere (e.g. search filters at the top of index, and drop downs on add/edit), setting the default display field to short_name just moves the problem elsewhere:

    public function index()
    {
        $this->Crud->on('relatedModel', function(Event $event) {
            if ($event->subject->name === 'Facilities') {
                $event->subject->query = $this->Accounts->Facilities->find('list', [
                    'valueField' => 'name'
                ]);
            }
        });

(Side note: $event->subject->query() already has the 'list' finder attached, so trying doing $event->subject->query()->find('list', ['valueField' => 'name']) instead gives you an array with null values.)

Also I failed to mention earlier, when the default display field was name, trying to dynamically change the display field to short_name in beforePaginate and then back to name in the afterPaginate and relatedModel didn't work out either. Seems these events fire before PaginatorComponent::paginate(), and trying to execute the query early causes it to throw an exception (since a query object is expected).

Ultimately, this leads me back to 'formatter' being the only direct solution, with 'callback' being less convoluted than 'element', and 'relation' not being configurable enough.

@deizel
Copy link
Contributor Author

deizel commented Jul 15, 2015

@lorenzo For reference, this was the approach with a reusable entity (after cleaning it up):

    public function index()
    {
        $action = $this->Crud->action();
        $action->config([
            'scaffold.fields' => [
                'facility_id' => [
                    'formatter' => 'element',
                    'element' => 'index/relation',
                    'assocKey' => 'facility',
                    'displayField' => 'short_name',
                    'controller' => 'Facilities',
                ],

src/Template/Element/index/relation.ctp:

<?php
echo $this->Html->link(
    $context->$options['assocKey']->$options['displayField'],
    ['controller' => $options['controller'], 'action' => 'view', $value]
);

Whatever we decide, it might be worth documenting the suggested approach to save others time. 😉

@ADmad
Copy link
Member

ADmad commented Jul 15, 2015

Well then imo yours is pretty specific use case and using a formatter element or callback is the way to go. You can help documenting it by providing a patch for the docs.

@deizel
Copy link
Contributor Author

deizel commented Jul 30, 2015

Just a quick update to say I haven't forgot about this and plan to get around to it at some point.

My solution currently looks like this for now, after a few iterations:

    public function index()
    {
        $action = $this->Crud->action();
        $action->config([
            'scaffold.fields' => [
                'facility_id' => [
                    'formatter' => 'element',
                    'element' => 'index/relation',
                    'displayField' => 'short_name',
                    'controller' => 'Facilities',
                ],
                'account_status_id' => [
                    'formatter' => 'element',
                    'element' => 'index/relation',
                    'link' => false,
                ],

src/Template/Element/index/relation.ctp:

<?php
use Cake\Utility\Hash;

$link = Hash::get($options, 'link', true);
$displayField = Hash::get($options, 'displayField', 'name');
$assocKey = Hash::get($options, 'assocKey', str_replace('_id', '', $field));
$assoc = $context->$assocKey;

if ($assoc):
    $name = $assoc->$displayField;
    if ($link):
        echo $this->Html->link($name, ['controller' => $options['controller'], 'action' => 'view', $value]);
    else:
        echo $name;
    endif;
endif;

If I remember correctly, this now handles the following cases:

  • you want a different display field
    • and provide all the values the element doesn't know (displayField, controller, assocKey)
    • you don't want to pass in displayField, because it's just name.
    • you don't want to pass in assocKey, and would rather the element guessed.
    • (you need to pass in the controller for the link, because I haven't bothered with inflection)
  • the value at assocKey is null and you don't want an empty link
  • you don't want a link at all (say, for example, there is no controller to link to)

I personally think the helper should be able to support all of this, but I'm still open to suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants