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

Added 'selectable' column type #4377

Closed
wants to merge 14 commits into from
Closed

Added 'selectable' column type #4377

wants to merge 14 commits into from

Conversation

mohsin
Copy link
Contributor

@mohsin mohsin commented Jun 9, 2019

The 'dropdown', 'radio', 'balloon-selector' and ‘checkboxlist’ type form fields do not have a list form field equivalent and so they show the internally stored key instead of the translated value. So, a new column type called 'selectable' is introduced here to overcome this limitation.

The workaround for this limitation was mentioned on the OctoberCMS forum but I found it unsatisfactory as it's a global model-level attribute conversion which doesn't solve the issue--using that technique breaks the fields at the form level as their update forms now gets the converted value and so the dropdown, balloon-selector, etc. fields show the incorrect value. Also, when writing RESTful APIs using OctoberCMS, the model runs the same attribute conversions to show the translated value than the key which isn't effective either--most RESTful APIs actually send the keys itself to the client device and the value's are filled in at the client-level as it's more network efficient that way, so that's another disadvantage of using the method, that was suggested in the forum. A backend list-level solution is what's needed and this new column type helps to solve that.

Fixes #2510.

The 'dropdown', 'radio', 'checkboxlist', 'balloon-selector' type form fields do not have a list form field equivalent and so they show the internally stored key instead of the translated value. So, a new column type called 'selectable' is introduced here to overcome this limitation.
@LukeTowers
Copy link
Contributor

@SaifurRahmanMohsin I'm assuming you copied the getOptionsFromModel from the Form widget? Perhaps we should create a new \Backend\Traits\ModelOptions trait to be used by the Form widget and the Lists widget and then put that code there.

@LukeTowers
Copy link
Contributor

@SaifurRahmanMohsin feel free to make that change on your branch so it shows up here.

@mohsin
Copy link
Contributor Author

mohsin commented Jun 11, 2019

There is just one issue with that... I remember editing some code that is not common to both. I’ll check and see if I can make a common trait, if not I’ll comment back here.

modules/backend/widgets/Lists.php Outdated Show resolved Hide resolved
modules/backend/widgets/Lists.php Outdated Show resolved Hide resolved
Moved 'getOptionsFromModel' and it's helper method 'objectMethodExists' to a
trait and made the List and Form widget implement it as it's common to both.
@mohsin
Copy link
Contributor Author

mohsin commented Jun 13, 2019

@LukeTowers Hey, I've moved getOptionsFromModel to its own trait as you've suggested and made the List and Form widgets implement the trait. I've already tested it with the dropdown widget for form and list and it doesn't break anything but could you cross verify that it works fine at your end as well to ensure I didn't mess anything else up? Thanks

modules/backend/widgets/Lists.php Outdated Show resolved Hide resolved
modules/backend/widgets/Lists.php Outdated Show resolved Hide resolved
modules/backend/traits/ModelOptions.php Outdated Show resolved Hide resolved
Added code to return null when the value doesn't exist, improved method call signature and made a formatting improvement. Credit to @bennothommo

Co-Authored-By: Ben Thomson <ben@abweb.com.au>
@mohsin
Copy link
Contributor Author

mohsin commented Jun 17, 2019

@bennothommo I have verified your suggestions and they're all good and I've included them in the PR. Thank you.

@bennothommo
Copy link
Contributor

Thanks for that @SaifurRahmanMohsin. Just one last change, would you mind removing these 2 lines, as they won't be needed with my suggested change:

$fieldName = $column->columnName;
$fieldOptions = $column->config['options'] ?? null;

The ModelOptions trait has been refactored to have better field names. Also,
the code has been tested using 5 different possible ways of defining field
values for dropdown, radio and balloon selector fields as mentioned in the
OctoberCMS documentation.
@LukeTowers LukeTowers added this to the v1.0.458 milestone Jul 15, 2019
@mohsin
Copy link
Contributor Author

mohsin commented Jul 16, 2019

@LukeTowers I have tested the code using 5 different possible ways of defining the field values for dropdown, radio, and balloon selector fields (4 of which are mentioned in the
OctoberCMS documentation) and everything works fine so I guess this is good to go.

* upstream/develop: (710 commits)
  UX improvements for managing plugins.
  Support disks that can't be serialized in ImageResizer. (#5324)
  Minor UX tweak (#5325)
  Improve handling of custom editor styles in the backend
  Fix codestyle (#5323)
  More tweaks to the default publisher permissions, added separate permission for users to manage their own personal editor preferences.
  Hide stripe load indicator when a redirect response is returned (#5321)
  Code tweak to server.php (#5317)
  Warn about unsupported cache drivers being used for image resizing
  Only allow local files via view engine
  Add removeSideMenuItems function (#5285)
  Improve Italian translation (#5310)
  Default session.same_site to Lax (#5293)
  Remove hidden CMS pages from menus (#5309)
  Fixes View::make recursion
  Fix links to the coding standards (#5307)
  Allow "no records" message to be defined for relation widgets.
  Changing translation of "layout" word in Russian (#5291)
  Improve Slovenian translation (#5292)
  Only allow view files in system twig
  ...
@github-actions
Copy link

This pull request will be closed and archived in 3 days, as there has been no activity in the last 60 days.
If this is still being worked on, please respond and we will re-open this pull request.
If this pull request is critical to your business, consider joining the Premium Support Program where a Service Level Agreement is offered.

@bennothommo
Copy link
Contributor

bennothommo commented Dec 23, 2020

@SaifurRahmanMohsin are you still interested in finalising this PR? If so, would you mind reviewing this comment and see if you can implement those final changes?

* upstream/develop: (43 commits)
  Fix typo (#5433)
  Grammar tweak
  Update CONTRIBUTING to explain about compiling JS assets (#5432)
  Add support for choosing the default backend locale from the `october:install` command.
  Further improvements to the Twig SecurityPolicy
  Fix the placeholder hidden behind the counter in the form widget `number` (#5398)
  Pass the Form & Field instances to the static method used to generate field options
  Revise security policy reporting priority
  Ensure that MediaLibrary::listAllDirectories() honors config for paths to ignore (#5367)
  Use new October\Rain Mail facade (#5361)
  Register plugin after installation via plugin:install CLI (#5386)
  Cleanup in ImageResizer (#5379)
  Fixed bug where /0 would return the homepage.
  Fix unit tests (#5374)
  Disable booting backend localization if theme is missing.
  Update sk.js (#5375)
  Compile JS and lang assets
  Fix duplicate AJAX call on using Apply or Clear buttons in group filter
  Include DbTestCase from Rain library in autoloader
  temporary fix for Composer 2.0 compatibility.
  ...
@bennothommo
Copy link
Contributor

bennothommo commented Jan 18, 2021

@SaifurRahmanMohsin @LukeTowers I've been testing this out, as I have a project that will find some use for it, but it doesn't seem to support static field options from a fields.yaml file, which I feel is going to be the more common scenario for most field setups. It only supports callbacks, either directly to the model or to an external class.

It is possible this is the only way it'll work, treating the model as a single source of truth for the options, given that technically you could have more that one form that populates the model data, each potentially having their own set of options available for a field.

EDIT: I should clarify - @SaifurRahmanMohsin has made it so that the column config also passes a options parameter to the getOptionsFromModel method, however, this means you have to set the same set of options in two locations and ensure they are in sync.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 18, 2021

@bennothommo What do you mean by two locations? I remember setting this to take it from the form/list model class the same way the form's dropdown, balloon-selector, etc. fields take their values so it's already a single source of truth for both form and column definitions. Am I missing something? Hmm

@bennothommo
Copy link
Contributor

bennothommo commented Jan 18, 2021

@SaifurRahmanMohsin what I mean is, say I have the following field set up in fields.yaml for the form:

status:
    label: Status
    type: dropdown
    options:
        pending: Pending
        processing: Processing
        completed: Complete

and then I wanted to use the selectable type for a column for that field in columns.yaml for a list:

status:
    label: Status
    type: selectable

With that sort of setup, it won't know what the options are, because the options are defined in the fields.yaml config for the form, not the list. You've made an allowance for this by specifying the options in the columns.yaml as well, like so:

status:
    label: Status
    type: selectable
    options:
        pending: Pending
        processing: Processing
        completed: Complete

But that means that we now have two locations for the options that have to be kept in sync. It's probably not so bad for fields with only a small number of options, but there is every chance that people will have fields with many static options. The only way the selectable column will work around this is to define the options in the model through the callbacks (ie. getDropdownOptions or getStatusOptions), but I believe there will be a lot of people who will try the selectable type with their forms and lists set up the way I initially said, and won't realise that they have to convert the static options to use one of the callbacks instead.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 18, 2021

A workaround maybe to read the form config and take those options but I'm not sure if that's the "best practice" coz now we have a list reading a form config.

@bennothommo
Copy link
Contributor

bennothommo commented Jan 18, 2021

@SaifurRahmanMohsin That's correct - it wouldn't take into account any events that might manipulate the form config (ie. backend.form.extendFields). You'd have to pretty much run the entire initialization process for a form to ensure the correct options are available, which would be overkill.

@bennothommo
Copy link
Contributor

bennothommo commented Jan 18, 2021

All that having been said, I'm not against the way it is set up now. It certainly is faster than having to write out a partial for these types of columns and customising the partial to display the "nicer-looking" value, which is what I've had to do every time I've set up columns like this.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 19, 2021

@bennothommo I am trying to write the unit test for this as you requested but I'm unable to figure out why the system behaves weirdly. The goal here is for me to call getColumnValue and assert that with "Developer" which should be the right returned column value, thus proving it works fine. But I encounter a weird issue that doesn't let me write the test.

I made a video showing what I'm facing here:
https://youtu.be/iE9Rg0uI5V0

And here's a ready-made zip having my files to test the same:
https://drive.google.com/file/d/195QqOCHmgiUD0yAmdWuQ_oa-ZhuZIpnm/view?usp=sharing

If you can help me fix this or point me in the right direction, I'll be happy to include the test as well to this PR.

@bennothommo
Copy link
Contributor

@SaifurRahmanMohsin could you add what you have for the tests into the changes for this PR, and I'll be able to have a look into it.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 20, 2021

Ok, here's what I changed:

  1. Set role_id to 1 here.
  2. Added the selectable test:
public function testSelectableType()
{
    $user = new UserFixture;
    $list = $this->listWithSelectableFieldFixture($user);
    $selectableColumn = $list->getColumn('role_id');

    // Test if the type is set to selectable
    $this->assertEquals($selectableColumn->type, 'selectable');

    // Test if the value is processed correctly
    $this->assertEquals($list->getColumnValue($user, $selectableColumn), 'Developer');
}

protected function listWithSelectableFieldFixture($record)
{
    return new Lists(null, [
        'model' => $record,
        'columns' => [
            'id' => [
                'type' => 'text',
                'label' => 'ID'
            ],
            'role_id' => [
                'type' => 'selectable',
                'label' => 'Role',
                'options' => ['Client', 'Developer']
            ]
        ]
    ]);
}

Here I'm re-using the UserFixture since that's a model readily available in the tests framework to test this out. The role_id returns 1 and so the selection would be 'Developer'. However, I'm facing the weird bug which I mentioned in my last comment as the $list->getColumnValue($user, $selectableColumn); fails--basically $selectableColumn isn't the correct ListColumn item i.e. role_id on checking inside the method.

@bennothommo
Copy link
Contributor

@SaifurRahmanMohsin I'd say it's because role_id is not fillable, as it's filled by the role relation. I'm having a look into this now.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 20, 2021

The weird behavior occurs even if I custom define a field besides role. But sure, please do check and tell me if there's a solution. I'll be happy to check in the test case and we can have this closed soon.

@bennothommo
Copy link
Contributor

@SaifurRahmanMohsin I've deployed a unit test that works into this PR for you.

@mohsin
Copy link
Contributor Author

mohsin commented Jan 20, 2021

@bennothommo Excellent. The test passes on my machine as well. Thank you. Let me know if anything is left, otherwise you can go ahead and merge the PR.

@bennothommo
Copy link
Contributor

@SaifurRahmanMohsin I've done some changes to this PR to improve support for both form fields and list columns - would you mind giving that a test and letting me know if that works?

@LukeTowers I've also added in changes to the callback methods to match up the signatures with the docs. Let me know your thoughts, or if you want me to roll them back.

@LukeTowers
Copy link
Contributor

@bennothommo could you highlight the changes to the API you've made?

@bennothommo
Copy link
Contributor

@LukeTowers I've given it some thought and I'm going to roll it back. While I like a consistent API, this is too much of a breaking change.

@daftspunk
Copy link
Member

🔥🔥🔥 This is a crazy good feature @SaifurRahmanMohsin. Thank you! It is clear a lot of work went into building it. Although upon peer review, this implementation is an example where the code liability exceeds the underlying benefit; in other words, it adds too much technical debt to justify the feature. It is a great piece of work, just not the right implementation...

However, after some refactoring, I was able to get it to work. The final approach is to acknowledge that the list column is leveraging a form field's behaviour. So the workflow goes something like this

Lists Widget -> List Column -> type: selectable -> Form Field -> getOptions

The options logic then had to be bumped from the Form widget to the Form Field, which is a natural progression anyway. Previously there was just no reason to do it, now we have one

This feature should arrive in October CMS once the next major release comes out

Thanks again 🙏 All the best

@daftspunk daftspunk closed this Mar 6, 2021
@mohsin mohsin deleted the develop branch March 6, 2021 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

Define "array" list column type, similar to dropdown form field
5 participants