Problem/Motivation
There's no handy way to mark some rows as disabled for selection in a tableselect element. The feature is needed when the user should be denied to select certain options, according to a form business logic, but they still need to know that the options exist and might be available in other context.
Proposed resolution
Allow to pass an optional #disabled
property in option. When an option has #disabled === TRUE
, the corresponding checkbox will be disabled.
Workaround
Without this change there's still possible to disable certain rows:
- Add an additional
#process
callback to the tableselect element. Note that you should add also the original processor:
$form['my_table'] = [ '#type' => 'tableselect', ... '#process' => [ // This is the original #process callback. [Tableselect::class, 'processTableselect'], // Additional #process callback. [static::class, 'processDisabledRows'], ], ... ];
- The additional
#process
callback:
public static function processDisabledRows(array &$element): array { foreach (Element::children($element) as $key) { $element[$key]['#disabled'] = isset($element['#options'][$key]['#disabled']) ? $element['#options'][$key]['#disabled'] : FALSE; } return $element; }
- Set the
#disabled
property toTRUE
on the rows that you want to disable:
$form['my_table'] = [ '#type' => 'tableselect', ... '#options' => [ 'row1' => [ 'col1' => '1.1', 'col2' => '1.2', ], // This row is disabled. 'row2' => [ 'col1' => '2.1', 'col2' => '2.2', '#disabled' => TRUE, ], ], ... ];
Remaining tasks
None.
User interface changes
API changes
A tableselect element can have disabled options.
Data model changes
None.
Release notes snippet
By adding a #disabled
property set to TRUE
, it's possible to prevent used by selecting certain options in a tableselect form element.
Initial report
Currently it is not possible to disable an option on a tableselect list.
Can we allow a Tableselect checkbox to be disabled on a specific option, like it currently is possible with the attribute and ajax parameters?
Comment | File | Size | Author |
---|---|---|---|
#53 | 2895352-53.patch | 6.75 KB | neclimdul |
| |||
#48 | Screenshot 2021-05-04 at 6.59.50 PM.png | 53.32 KB | BhumikaVarshney |
#42 | 2895352-42-8.8.x.patch | 6.8 KB | claudiu.cristea |
#31 | 2895352-31.patch | 6.82 KB | claudiu.cristea |
Comments
Comment #6
2pha+1 for this
Comment #7
2phaFor some reason, the attributes of the table get put on the checkbox in the processTableselect function, things like classes etc.
eg:
Should it not be something like this?
For a tableselect I needed to implement with disabled options, I added a custom process function to the tableselect render array and just copy and pasted the original processTableselect and made these changes, it worked for me.
Comment #8
claudiu.cristeaI've opened #3026624: Disabled rows in tableselect elements but, at that moment, I was not aware about this ticket. Yes, #3026624: Disabled rows in tableselect elements is a duplicate of this one. Let's move the work here as this is older.
Done:
Comment #9
claudiu.cristeaAdding a workaround in IS for anyone interested.
Comment #10
claudiu.cristeaAnd, this is a good candidate for backporting.
Comment #11
claudiu.cristeaNeeds also a change record.
Comment #13
claudiu.cristeaLooks like a random failure.
Comment #14
strakkie CreditAttribution: strakkie as a volunteer and commentedTested the patch on Drupal 8.6.7, works like a charm.
Comment #15
claudiu.cristea@strakkie, glad to hear. Could please also do a review of the code from #8?
Comment #16
2phathe "attributes" for the table are still being put on the checkbox. Which is why I did it the way I did in #7.
Surely you want to be able to put attributes on the table and not have the exact same attributes on the checkboxes.
The way I did it allows for specifying attributes for the table itself and each individual checkbox (including disabled).
Maybe I missing something?
Comment #17
Chi CreditAttribution: Chi commentedWill this be a bit simpler?
+ '#disabled' => !empty($element['#options'][$key]['#disabled']);
Also we may put some handy CSS class on disabled rows to ease styling.
Comment #18
claudiu.cristea@2pha, the original title of the issue is Allow to disable a Tableselect option and the actual Disabling tableselect element options. IMHO, the
#attributes
issue should be split in a separate issue. Let's not solve all the problems here.@Chi, thank you, good remarks. Fixed in this new patch.
Comment #19
claudiu.cristeaSorry, forgot in an unused statement.
Comment #21
claudiu.cristeaOh, and the XPath has to check for
disabled="disabled"
attribute.Comment #23
2pha@claudiu.cristea I disagree that attributes should be separate to this.
disabled is an attribute.
If this patch is applied, then later the attributes are fixed, this patch will have to be undone.
Might as well do the other attributes at the same time.
It's also going to be confusing if 1 attribute can be set on a checkbox, but the others can't.
Comment #24
Chi CreditAttribution: Chi commentedI don't think supporting attributes will make this patch obsolete.
#disabled
property is not just for setting HTML attribute. Drupal form API applies special treatment for elements with such property.https://api.drupal.org/api/drupal/developer%21topics%21forms_api_referen...
However with this path it does not work for me. If I remove the
disabled
attribute manually through Chrome inspector and thencheck/uncheck the checkbox the new value is accepted on form submission. This does not happen on checkboxes that are outside tableselect element.
Comment #25
claudiu.cristea@Chi, isn’t that a normal behaviour? That would allow JS to enable/disable checkboxes in the tableselect, on client side, and allow the user to submit them.
Comment #26
Chi CreditAttribution: Chi commentedI do not know. But that behavior differs by some reason from the one used for checkboxes outside table select.
A note from FormBuilder::doBuildForm() documentation.
And from form builder itself.
Comment #27
Chi CreditAttribution: Chi commentedWell,
#checkboxes
element also set values for disabled elements. Given that TableSelect is just a styled form of Checkboxes I think it should be ok.Comment #28
Chi CreditAttribution: Chi commentedAs of setting attributes I think we could take the same approach as in Checkboxes.php.
This would allow to gain full control on child elements.
See #1349432: Mention checkboxes/radios options pre-setting in forms_api_reference.html.
Comment #29
alexpottI think this can be improved. Something like
Prevent users from selecting a row by adding a #disabled property set to TRUE. This property is ignored when #multiple is FALSE.
Also we're in a code block so this should be a PHP comment. Or needs to be moved outside the the @code block.
I think this should be added to either \Drupal\Tests\system\FunctionalJavascript\Form\ElementsTableSelectTest or \Drupal\Tests\system\Functional\Form\ElementsTableSelectTest
Kernel tests are not really meant for testing UX improvements like this.
Comment #30
claudiu.cristeaComment #31
claudiu.cristeaAddressed all from #29, fixed the IS, added screenshots, added change notice, added release note snippet.
Comment #33
PanchoUnrelated failure, does now pass.
Comment #34
Chi CreditAttribution: Chi commented#31 works well for me.
Comment #36
lauriiiSorry for the delay in getting feedback on this. My first thought is that it's hard to imagine what this is going to be used for. Would it be possible to get an explanation of the use case where this is needed?
This seems like a potentially confusing UX pattern, which I think we should validate with a usability expert. We should also get an accessibility review from someone on this.
Comment #37
Dropa CreditAttribution: Dropa at Mediamaisteri Oy commentedCame here to drop our potential use case for this, as we are using already similar list that shows subordinates of supervisor, who can perform actions for selected ones.
We would have some use cases for this already, but if it would work with
#states
as well, then we could do the disabling according to the selected action.As a more general example for this would be in People-page, where you see bunch of users, and you can perform various actions for them. For example if you select "Block user" from the dropdown menu, it could disable out those users that have already been blocked. This probably isn't needed in many cases, as blocking blocked users is not likely to cause any harm. However if such user would get notification of some sort, it could be confusing to get it again later.
Comment #38
rbosscher CreditAttribution: rbosscher as a volunteer commentedOur use is similar to @Dropa
We have a list of people on which we can perform bulk operations, but occasionally there are actions that should not be performed on specific people in the list.
In our situation it concerns people who have followed a certain course. This course must be renewed in a certain period. So we show a list of all students from a certain course to the user. If certain students are not yet ready for a renewal, they cannot be checked for a renewal.
We can of course choose not to show those users in the list, but in our case that is confusing, because the reason why these people do not appear in the list is then unclear. We found out that users tend to recreate a student in order to see them back in the list resulting in duplicated student data. We've also tried to have a different list with the disabled students at the bottom. But this list, at the bottom was tend to get missed. Then we tried to place the list on the top and caused a bad user experience as you first see the students that are not applicable.
In my opinion, it is therefore confusing if this option is not there, and whether it is user-friendly depends on the application.
From experience, I have been using this option for a while for two applications, and I know that this can be a bit confusing for a few users, and I have placed a custom javascript that if one clicks on the disabled checkbox, there is a clear message is why the checkbox is disabled.
I understand that it is not the most fantastic UX experience out of the box, but I think it is a workable and valid option. And with the javascript notification in place, it has caused no more confusion for over more than 1500 users that use this application at least once a year.
Comment #39
Dropa CreditAttribution: Dropa at Mediamaisteri Oy commented@rbosscher well our use cases seem to be quite close the same, I just tried to explain it with example everyone could see and feel. :)
In our exact case where this would be super helpful is when supervisor is going to enroll subordinates to training by bulk operation, supervisor does already see the status of each subordinate in the list whether they have already enrolled or not, we also could filter out those subordinates from that list who already have enrolled, but we've the exact same conclusion that it wouldn't be clear why some of the subordinates isn't showing up on the list.
The way I see this could help up the supervisors is that they wouldn't have to check line by line if subordinate has indeed enrolled already.
Also the re-enroll is actually intended option in our case, but it does also mean that enrolled person gets new position in case there's queue for that training and doing this accidentally could be harmful, as well as new mail from the status change etc. For the list I mentioned earlier, it would be fine even if the re-enroll option would be blocked out completely, but if it would support
#states
, there could be two different bulk operations, one for enrolling and one for re-enrolling, and that way one wouldn't accidentally (at least not that likely) someone they didn't mean to.Comment #40
claudiu.cristea@lauriii,
I don't see what could be confusing here. There are tons of UIs where you'll find list of checkboxes where some are disabled. Simple lists or in table rows. I can show a lot of use cases. For me it's obvious: Don't allow the user to select some options because, as an effect of form business logic, but let him see that the options exist and might be available in other context..
Could you explain why this would be confusing? Don't we have disabled form elements all around? The fact that the user should be aware about why some rows are disabled is out-of-the scope here. That should be accomplished in a very specific way, given the business logic of each form where the element is used:
Comment #42
claudiu.cristeaReroll for 8.8.x
Comment #45
jeremyr CreditAttribution: jeremyr commentedOn 8.9.10 applied patch #42. This is working great for my use case!
Comment #46
StefanieV CreditAttribution: StefanieV commentedPatch #42 works for me on 8.9.13. Thanks a lot!
Comment #48
BhumikaVarshney CreditAttribution: BhumikaVarshney as a volunteer and at OpenSense Labs commentedPatch #42 works for me on 9.3.0. Thanks!
Comment #49
neclimdulWas frustrated to find this didn't exist.
As a use case, in the Marketo module it has a page that lists all the fields defined in your marketo instances and then allows you to enable a subset of them to available for various sync and activity integrations. I wants to _list_ all fields because you need to be able to see that the field was visible to the integration and allow the admin to see various other details about the field, but it also convey that some fields can't be selected for a write list because marketo has them flagged as read only.
I think I agree with claudiu. I'll go a bit farther and say I don't think there's an inherent in-accessible behavior associated with doing this. Any _use_ of it should probably review how it might end up causing a problem but the patch just exposes a tool. We already allow checkboxes to be disabled, we're just exposing that functionality in bulk.
In fact it turns out you can kinda break tableselect today to "disable" rows today. Found this hack in the current module's code.
There's probably some other way for hacking the theme system to mangle it in place too. So developers gonna develop and at this point I think we've just made this arbitrarily hard for developers.
This patch worked perfect for providing the expected functionality so marking it back to RTBC.
Comment #50
neclimdulA little further googling, the code above is probably related to this discussion on how to accomplish this functionality in Drupal 7.
https://api.drupal.org/api/drupal/includes%21form.inc/function/theme_tab...
Comment #52
froboyPatch still works (I'm testing on 9.2.10). Are there any blockers for this?
Comment #53
neclimdulre-apply around #3222251: [November 8, 2021] Replace all isset constructs with the null coalescing operator. Nothing scary though surprised the re-apply checks didn't catch it the failure.
Comment #54
neclimdulLooking back through the comments, I'm not sure I agree with #29.3. If I'm reading the test right, we install a site and setup some routing boiler plate so we can run the same assertions after they've gone over the network? Seems like a lot of heavy lifting that's not doing anything.
Anyways, agree this is probably RTBC just not sure that change was needed.
Comment #56
claudiu.cristeaLooks like a Composer 2.2 issue
Comment #58
andypostUnrelated bot failure
Comment #60
andypostbot flux
Comment #62
claudiu.cristeabot
Comment #64
claudiu.cristeaComment #66
claudiu.cristeaComment #67
quietone CreditAttribution: quietone at PreviousNext commentedMoving to 10.0.x and testing.
Comment #68
claudiu.cristeaWhy is this moved to 10? Shouldn't new features go in 9.5.x (and 10)? Tentatively, moving back to 9
Comment #69
claudiu.cristeaIt seems that 9.5.x doesn't accept new features (?) Moving back to 10
Comment #70
alexpottComment #71
alexpottCommitted and pushed 8a36ace53d to 10.1.x and 9bc14dc2ba to 10.0.x and 00567fe61e to 9.5.x. Thanks!
Backported to 9.5.x because this new feature is small and only and addition and well tested.
Fixed help test to be consistent with the rest of the example
Following on from #3265574: Convert remaining assertions involving use of xpath to WebAssert, where possible converted use of xpath and ran test locally.