Opened 5 years ago
Last modified 4 years ago
#47692 new enhancement
Optgroup in Customizer Select Control
Reported by: | chintan1896 | Owned by: | |
---|---|---|---|
Milestone: | Future Release | Priority: | normal |
Severity: | normal | Version: | 3.4 |
Component: | Customize | Keywords: | has-patch needs-testing |
Focuses: | Cc: |
Description
Optgroup add in Customizer Select Control
Attachments (4)
Change History (31)
#1
@
5 years ago
Example for select optgroup
<?php $wp_customize->add_control( new WP_Customize_Control( $wp_customize, 'optgroup_example', array( 'label' => esc_attr__( 'Optgroup example', 'text-domain' ), 'setting' => 'optgroup_example', 'type' => 'select', 'optgroup' => true, 'choices' => array( 'optgroup-label-1' => array( 'value-1'=> __( 'value-1', 'text-domain' ) ), 'optgroup-label-2' => array( 'value-2'=> __( 'value-2', 'text-domain' ) ), ), ) ) );
#3
@
5 years ago
- Keywords needs-patch added; has-patch removed
- Milestone changed from Awaiting Review to Future Release
- Version changed from 5.3 to 3.4
Support for optgroup
would also needed to be added to the JavaScript control template, I think, not just the PHP template.
I also wonder about the $optgroup
property in 47692.diff. What happens if a multidimensional array is used for $choices
but $optgroup
is false
, or vice versa?
#4
@
5 years ago
- Keywords needs-testing has-patch added; needs-patch removed
Hey @dlh ,
Please review on latest patch.
#5
@
5 years ago
- Keywords needs-patch added; needs-testing has-patch removed
Thanks for the updated patch, @chintan1896.
The new patch does guard against PHP errors, but it still seems to me to leave room for developer confusion. If $optgroup
is changed without changing the shape of $choices
, or vice versa, the control disappears.
Also, what if the developer wants some choices in an optgroup
and some at the top level of the select
?
I also don't see any change to the JavaScript control template in 47692-new.diff. Was it left out by accident?
#6
@
5 years ago
Hey @dlh,
Please review on updated patch. It is overcome developer confusion and some choices in an optgroup and some at the top level of the select.
I have not enough knowledge in underscore.js . So i can't code in JavaScript control template.
This is a new example for optgroup and now we don't need to define optgroup it's automatically add optgroup if we added multidimensional array.
<?php $wp_customize->add_control( new WP_Customize_Control( $wp_customize, 'optgroup_example', array( 'label' => esc_attr__( 'Optgroup example', 'text-domain' ), 'setting' => 'optgroup_example', 'type' => 'select', 'section' => 'title_tagline', 'choices' => array( 'simple-option' => __( 'Simple option', 'text-domain' ) , 'optgroup-label-1' => array( 'value-1'=> __( 'value-1', 'text-domain' ) ), 'optgroup-label-2' => array( 'value-2'=> __( 'value-2', 'text-domain' ) ), ), ) ) );
#8
@
4 years ago
No update, @chintan1896. This ticket still needs a patch that accounts for the JavaScript template.
#9
@
4 years ago
Which js file defines the components? I am also interested in having optgroup labels. I could take a look at the JavaScript templates but I'm not sure where to look?
Also, I believe some developer confusion could come from the fact that "choices" expect the "value" as the property name, and the "label" as the property value (in the current class-wp-customize-control.php code). Wouldn't it be more logical for the "label" to the property name, and the "value" to be the property value? Like this:
<?php <select id="<?php echo esc_attr( $input_id ); ?>" <?php echo $describedby_attr; ?> <?php $this->link(); ?>> <?php foreach ( $this->choices as $label => $value ) { echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>'; } ?> </select>
Then it would be much easier to check if "$value" is an array, and in such a case "$label" would become the optgroup label and "$value" would be parsed for the options that fall under that optgroup label. There would be no need to set an "optgroup:true", it would be implicit when the value is an array of more "label=>value" associations.
The same would have to apply to radio groups for consistency. Changing this would obviously break current setups that use these components. But it would seem more logical to me...
#10
follow-up:
↓ 13
@
4 years ago
Perhaps a way to transition this to new logic could be to add the possibility of using "options" instead of "choices" in a transition phase, explaining the difference in the documentation and inviting to transition to the new style "options" instead of "choices". Then it could be handled something like this:
<?php case 'select': if ( empty( $this->choices ) && empty( $this->options ) ) { return; } ?> <?php if ( ! empty( $this->label ) ) : ?> <label for="<?php echo esc_attr( $input_id ); ?>" class="customize-control-title"><?php echo esc_html( $this->label ); ?></label> <?php endif; ?> <?php if ( ! empty( $this->description ) ) : ?> <span id="<?php echo esc_attr( $description_id ); ?>" class="description customize-control-description"><?php echo $this->description; ?></span> <?php endif; ?> <select id="<?php echo esc_attr( $input_id ); ?>" <?php echo $describedby_attr; ?> <?php $this->link(); ?>> <?php if( ! empty( $this->choices ) ){ foreach ( $this->choices as $value => $label ) { echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>'; } } else if( ! empty( $this->options ) ){ foreach ( $this->options as $label => $value ) { if(is_array($value) && !empty( $value ) ){ echo '<optgroup label="' . esc_attr( $label ) . '">'; foreach( $value as $optgrouplabel => $optgroupvalue ){ echo '<option value="' . esc_attr( $optgroupvalue ) . '"' . selected( $this->value(), $optgroupvalue , false ) . '>' . $optgrouplabel. '</option>'; } echo '</optgroup>'; } else{ echo '<option value="' . esc_attr( $value ) . '"' . selected( $this->value(), $value, false ) . '>' . $label . '</option>'; } } } ?> </select>
#11
@
4 years ago
I'm guessing this would be where the Javascript templates are defined?
/wp-includes/js/dist/vendor/react-dom.js
If not, where exactly?
#12
@
4 years ago
@Lwangaman The JS templates are defined in \WP_Customize_Manager::render_control_templates()
.
#13
in reply to:
↑ 10
;
follow-up:
↓ 14
@
4 years ago
Hey @Lwangaman,
What if the developer wants some choices in an optgroup and some at the top level of the select? So i believe with this patch code 47692-updated.patch developer can easily manage if developer add multidimensional array so it will convert automatically in optgroup otherwise work as per current working. If you have knowledge about JavaScript template so please help me to add this functionality in WordPress.
Thanks,
Chintan
#14
in reply to:
↑ 13
@
4 years ago
Replying to chintan1896:
Hey @Lwangaman,
What if the developer wants some choices in an optgroup and some at the top level of the select? So i believe with this patch code 47692-updated.patch developer can easily manage if developer add multidimensional array so it will convert automatically in optgroup otherwise work as per current working. If you have knowledge about JavaScript template so please help me to add this functionality in WordPress.
Thanks,
Chintan
Oh I see, the boolean flag was in the original patch and not in the updated patch.
I also can see why the option label would be declared as the property value, if you want to have spaces in the label for example it would not be recommended practice to have a property name with spaces, so I guess it is best to leave the option label as the property value. Considering that, your updated patch is the closest to the current logic. I will look into the javascript portion and update here if I have any luck.
#15
@
4 years ago
I have code in JavaScript template. But i am confused how to test my code working properly or not?
Below file code - wp-includes/class-wp-customize-manager.php
<select
<# _.each( _.extend( inputAttrs ), function( value, key ) { #>
{{{ key }}}="{{ value }}"
<# }); #>
>
<# _.each( data.choices, function( val, data ) { #>
<# var value, text;
if ( _.isObject( val ) ) {
value = val.value;
text = val.text;
} else {
value = data;
text = val;
}
if ( _.isArray( value ) && ! _.isEmpty( value ) ) {
#>
<optgroup label="{{ value }}">
<# _.each( value, function( optgroup_val, optgroup_data ) { #>
<option value="{{ optgroup_val }}">{{ optgroup_data }}</option>
<# } #>
</optgroup>
<#
} else {
#>
<option value="{{ value }}">{{ text }}</option>
<#
}
#>
<# } ); #>
</select>
#16
@
4 years ago
I can see a problem there already: in javascript arrays are also objects. I'm not sure of all the use cases of the select component in the customizer but I have a hunch there could be a problem here if
_.isObject( val )
evaluates to true because we are using an array as the value. It probably would not however find a val.value property in this object.
It would be useful to know in which cases the choices value is a string and in which cases it is an object, in the current state of things.
#17
@
4 years ago
I'm guessing this might work, I will test it now:
<# } else if ( 'select' === data.type ) { #> <# delete inputAttrs.type; #> <select <# _.each( _.extend( inputAttrs ), function( value, key ) { #> {{{ key }}}="{{ value }}" <# }); #> > <# _.each( data.choices, function( val, key ) { #> <# var value, text; if ( _.isObject( val ) && _.isArray( val ) ) { var optgpval, optgptext, data; value = key; data = val; #> <optgroup label="{{ value }}"> <# _.each(data, function(val, key ) { optgpval = key; optgptext = val; #> <option value="{{ optgpval }}">{{ optgptext }}</option> <# } ); #> </optgroup> <# } else if( _.isObject( val ) && !_.isArray( val ) ){ value = val.value; text = val.text; } else { value = key; text = val; } #> <option value="{{ value }}">{{ text }}</option> <# } ); #> </select> <# } else { #>
#18
@
4 years ago
Something else is missing. I have an InspectorControls panel for a plugin's Gutenberg block that I created. Here is some sample (simplified, exemplified) code with the Select component.
First I build my choices or options as an array of objects:
let favouriteFoodsSelectOptions = [ { value: "peas", label: "Green Peas" }, { value: "apples", label: "Red apples" }, { value: "carrots", label: "Carrots" }, { value: "chocolate", label: "Chocolate" }, { value: "oranges", label: "Oranges" }, { value: "chicken", label: "Grilled chicken breast" }, { value: "turkey", label: "Roasted turkey" }, { value: "pudding", label: "Tapioca pudding" }, ];
Then inside of my edit(props) I have:
return createElement('div', {}, [ createElement(Fragment, {}, createElement(InspectorControls, {}, createElement(PanelBody, { title: __('Some options', 'plugin_namespace'), initialOpen: true, icon: 'download' }, createElement(PanelRow, {}, //Select one or more options createElement(SelectControl, { value: attributes.foods, label: __('Your favourite foods', 'plugin_namespace'), onChange: changeFoods, multiple: true, options: favouriteFoodsSelectOptions, help: __('You can select one or more of these options. In order to select more than one, hold down the CTRL key while clicking.', 'plugin_namespace') }) ) //end PanelRow ) //end PanelBody ) //end InspectorControls ) //end Fragment ])
And this is currently working.
However, I would like to be able to organize these options by food category. After implementing the PHP patch and the javascript patch I posted, I tried modifying my options array like this:
let favouriteFoodsSelectOptions = [ { label: "Vegetables", value: [ { value: "peas", label: "Green Peas" } { value: "carrots", label: "Carrots" }, ] }, { label: "Meats", value: [ { value: "chicken", label: "Grilled chicken breast" }, { value: "turkey", label: "Roasted turkey" }, ] }, { label: "Fruits", value: [ { value: "apples", label: "Red apples" }, { value: "oranges", label: "Oranges" }, ] }, { label: "Desserts", value: [ { value: "chocolate", label: "Chocolate" }, { value: "pudding", label: "Tapioca pudding" }, ] } ];
But this doesn't work, I just see the labels in the rendered select, no optgroups and no values.
There must be another intermediate file somewhere that translates between react components and \WP_Customize_Manager::render_control_templates()
#19
@
4 years ago
I see where it is in the Gutenberg development repository on Github, but where would this be in the actual WordPress source code?
https://github.com/WordPress/gutenberg/blob/master/packages/components/src/select-control/index.js
#20
@
4 years ago
@Lwangaman I'm having a little trouble following these comments. The Customizer doesn't currently use React or any Gutenberg components. Are you working from an installation with no active plugins and a default theme?
#21
@
4 years ago
Oh I see, so React components as used in Gutenberg blocks are defined elsewhere. I guess the Customizer components are distinct from the Gutenberg block editor components? My interest was that of implementing a SelectControl component for Gutenberg block development which allowed for optgroups, but I guess that code (in WordPress core) has nothing to do with this thread then? In that case, I can perhaps try to open a similar thread on the above linked Gutenberg source code for the SelectControl component...
#22
@
4 years ago
Using PHP I can confirm that the updated patch by @chintan1896 is working correctly, here is the result in the WordPress Customizer:
And here is my PHP code:
<?php $wp_customize->add_setting( 'myoptgroup', //No need to use a SERIALIZED name, as `theme_mod` settings already live under one db record array( 'default' => 'carrots', //Default setting/value to save 'type' => 'theme_mod', //Is this an 'option' or a 'theme_mod'? 'capability' => 'edit_theme_options', //Optional. Special permissions for accessing this setting. 'transport' => 'postMessage' //What triggers a refresh of the setting? 'refresh' or 'postMessage' (instant)? ) ); $wp_customize->add_control( 'myoptgroup_ctl', array( 'label' => 'My OptGroup with Food choices', 'settings' => 'myoptgroup', 'priority' => 10, 'section' => 'a_section_of_my_options', 'type' => 'select', 'choices' => array( 'Fruits' => array('apples' => 'Green apples', 'pears' => 'Pears', 'bananas' => 'Chiquita bananas' ), 'Vegetables' => array('peas' => 'Peas', 'carrots' => 'Carrots', 'tomatoes' => 'Cherry tomatoes' ), 'Meats' => array('chicken' => 'Grilled chicken breast', 'turkey' => 'Roasted turkey', 'sausages' => 'Blood sausages' ), 'Desserts' => array('icecream' => 'Mint chocolate chip ice cream', 'pudding' => 'Tapioca pudding'), 'supper' => 'Home cooked dinner meal', 'lunch' => 'Fast food take-away lunch' ) ) ); }
#23
@
4 years ago
BTW, is there no support for the "multiple" attribute in the WordPress Customizer controls? The "multiple" attribute is supported for Gutenberg (react) components...
#24
@
4 years ago
After some testing in javascript, I have come up with this working code:
<select <# _.each( _.extend( inputAttrs ), function( value, key ) { #> {{{ key }}}="{{ value }}" <# }); #> > <# _.each( data.choices, function( val, key ) { #> <# var value, text; if ( _.isObject( val ) ) { if(val.hasOwnProperty('value') && val.hasOwnProperty('text') ){ value = val.value; text = val.text; #> <option value="{{ value }}">{{ text }}</option> <# } else{ //if val is an object but doesn't directly have "value" or "text" properties, we are probably dealing with an optgroup text = key; value = val; #> <optgroup label="{{ text }}"> <# var optgroupvalue, optgrouptext; if( _.isArray( value ) ){ _.each( value, function( val, key ) { if( _.isObject( val ) && val.hasOwnProperty('value') && val.hasOwnProperty('text') ){ optgroupvalue = val.value; optgrouptext = val.text; #> <option value="{{ optgroupvalue }}">{{ optgrouptext }}</option> <# } } ); } else{ _.each( value, function( val, key ) { optgroupvalue = key; optgrouptext = val; #> <option value="{{ optgroupvalue }}">{{ optgrouptext }}</option> <# } ); } #> </optgroup> <# } } else { value = key; text = val; #> <option value="{{ value }}">{{ text }}</option> <# } #> <# } ); #> </select>
And here are my working examples:
- Example 1 : only top level options, using objects with "value" and "text" properties (already available in current wordpress core)
wp.customize.control.add(new wp.customize.Control('myoptgroup2', { type: 'select', settings: 'myoptgroup2', section: 'a_section_of_my_options', label: 'My OptGroup with Animal choices', choices: [ { value: 'tiger', text: 'Tiger' }, { value: 'lion', text: 'Lion' }, { value: 'eagle', text: 'Eagle' }, { value: 'whale', text: 'Whale' }, ] }));
- Example 2: only top level options, using key => value pairs (already available in current wordpress core)
wp.customize.control.add(new wp.customize.Control('myoptgroup2', { type: 'select', settings: 'myoptgroup2', section: 'a_section_of_my_options', label: 'My OptGroup with Animal choices', choices: { 'tiger' : 'Tiger', 'lion' : 'Lion', 'eagle' : 'Eagle', 'whale' : 'Whale', } }));
- Example 3: both top level options and optgroup options, using array of objects with "value" and "text" properties
wp.customize.control.add(new wp.customize.Control('myoptgroup2', { type: 'select', settings: 'myoptgroup2', section: 'a_section_of_my_options', label: 'My OptGroup with Animal choices', choices: { 'Land' : [ { value: 'tiger', text: 'Tiger' }, { value: 'lion', text: 'Lion' } ], 'Sea': [ { value: 'whale', text: 'Whale' }, { value: 'salmon', text: 'Salmon' } ], 'Air': [ { value: 'eagle', text: 'Eagle' }, { value: 'woodpecker', text: 'Woodpecker' } ], 'mammals' : 'Mammals', 'jungle' : 'Jungle wildlife' }, }));
- Example 4: both top level options and optgroup options, using objects with key => value pairs
wp.customize.control.add(new wp.customize.Control('myoptgroup2', { type: 'select', settings: 'myoptgroup2', section: 'a_section_of_my_options', label: 'My OptGroup with Animal choices', choices: { 'Land': { 'tiger': 'Tiger', 'lion': 'Lion' }, 'Sea': { 'whale': 'Whale', 'salmon': 'Salmon' }, 'Air': { 'eagle': 'Eagle', 'woodpecker': 'Woodpecker' }, 'mammals' : 'Mammals', 'jungle' : 'Jungle wildlife' }, }));
Of course in the last two examples, you could insert the top level options either with key:value pairs, or with an object that has "value" and "text" properties. So you can mix the flavours every which way, and they all work.
Here is an image of the result of the last two examples:
@
4 years ago
patch for wp-includes/class-wp-customize-manager.php which enables optgroup in select choices
#25
@
4 years ago
- Keywords has-patch needs-testing added; needs-patch removed
So to summarize, there are now two patches which will bring about this functionality:
1) @chintan1896 's patch https://core.trac.wordpress.org/attachment/ticket/47692/47692-updated.patch
for the PHP portion
2) my own patch https://core.trac.wordpress.org/attachment/ticket/47692/customizer_javascript_optgroup.diff for the javascript portion
My own local testing confirms that these patches are working as expected, producing the desired results. If a committing developer could please test these two patches and confirm that they add the requested functionality correctly, we could perhaps proceed to apply them?
This ticket was mentioned in PR #430 on WordPress/wordpress-develop by JohnRDOrazio.
4 years ago
#26
- Applied patch by @chintan1896 which allows for optgroups in
Customizer Select Control
s when created inPHP
(just fixed whitespacing and changed$optgroup_data
variable to$optgroup_label
($optgroup_value => $optgroup_data
to$optgroup_value => $optgroup_label
) in order to be more in line with the original code for creating Select Controls, which uses$value
=>$label
) - Applied patch by yours truly @JohnRDOrazio (@Lwangaman) which allows for optgroups in
Customizer Select Control
s when created using theJavascript API
Examples of creating a Customizer Select Control
after applying the patches (tested locally, working according to my testing):
- Creation of
Customizer Select Control
inPHP
allowing for both *top level options* and *optgroup options* together:
{{{php
<?php
$wp_customize->add_setting(
'myoptgroup', No need to use a SERIALIZED name, as
theme_mod
settings already live under one db record
array(
'default' => 'carrots', Default setting/value to save
'type' => 'theme_mod', Is this an 'option' or a 'theme_mod'?
'capability' => 'edit_theme_options', Optional. Special permissions for accessing this setting.
'transport' => 'postMessage' What triggers a refresh of the setting? 'refresh' or 'postMessage' (instant)?
)
);
$wp_customize->add_control(
'myoptgroup_ctl',
array(
'label' => 'My OptGroup with Food choices',
'settings' => 'myoptgroup',
'priority' => 10,
'section' => 'a_section_of_my_options',
'type' => 'select',
'choices' => array(
'Fruits' => array('apples' => 'Green apples', 'pears' => 'Pears', 'bananas' => 'Chiquita bananas' ),
'Vegetables' => array('peas' => 'Peas', 'carrots' => 'Carrots', 'tomatoes' => 'Cherry tomatoes' ),
'Meats' => array('chicken' => 'Grilled chicken breast', 'turkey' => 'Roasted turkey', 'sausages' => 'Blood sausages' ),
'Desserts' => array('icecream' => 'Mint chocolate chip ice cream', 'pudding' => 'Tapioca pudding'),
'supper' => 'Home cooked dinner meal',
'lunch' => 'Fast food take-away lunch'
)
)
);
}
}}}
- Creation of
Customizer Select Control
inJavascript
using only *top level options* and using an array of objects withvalue
andtext
properties (already available in current wordpress core without applying the patch, useful for comparison with the example that comes after):
{{{javascript
wp.customize.control.add(new wp.customize.Control('myoptgroup2', {
type: 'select',
settings: 'myoptgroup2',
section: 'a_section_of_my_options',
label: 'My OptGroup with Animal choices',
choices: [
{ value: 'tiger', text: 'Tiger' },
{ value: 'lion', text: 'Lion' },
{ value: 'eagle', text: 'Eagle' },
{ value: 'whale', text: 'Whale' },
]
}));
}}}
- Creation of
Customizer Select Control
inJavascript
using both *top level options* and *optgroup options*, using an array of objects withvalue
andtext
properties (after patch is applied):
{{{javascript
wp.customize.control.add(new wp.customize.Control('myoptgroup2', {
type: 'select',
settings: 'myoptgroup2',
section: 'a_section_of_my_options',
label: 'My OptGroup with Animal choices',
choices: {
'Land' : [
{ value: 'tiger', text: 'Tiger' },
{ value: 'lion', text: 'Lion' }
],
'Sea': [
{ value: 'whale', text: 'Whale' },
{ value: 'salmon', text: 'Salmon' }
],
'Air': [
{ value: 'eagle', text: 'Eagle' },
{ value: 'woodpecker', text: 'Woodpecker' }
],
'mammals' : 'Mammals',
'jungle' : 'Jungle wildlife'
},
}));
}}}
- Creation of
Customizer Select Control
inJavascript
using only *top level options* and using an object with key : value pairs (already available in current wordpress core without applying the patch, useful for comparison with the example that comes after):
{{{javascript
wp.customize.control.add(new wp.customize.Control('myoptgroup2', {
type: 'select',
settings: 'myoptgroup2',
section: 'a_section_of_my_options',
label: 'My OptGroup with Animal choices',
choices: {
'tiger' : 'Tiger',
'lion' : 'Lion',
'eagle' : 'Eagle',
'whale' : 'Whale',
}
}));
}}}
- Creation of
Customizer Select Control
inJavascript
using both *top level options* and *optgroup options*, using an object with key : value pairs (after patch is applied):
{{{javascript
wp.customize.control.add(new wp.customize.Control('myoptgroup2', {
type: 'select',
settings: 'myoptgroup2',
section: 'a_section_of_my_options',
label: 'My OptGroup with Animal choices',
choices: {
'Land': { 'tiger': 'Tiger', 'lion': 'Lion' },
'Sea': { 'whale': 'Whale', 'salmon': 'Salmon' },
'Air': { 'eagle': 'Eagle', 'woodpecker': 'Woodpecker' },
'mammals' : 'Mammals',
'jungle' : 'Jungle wildlife'
},
}));
}}}
In the examples 3 and 5, I believe you would only be able to insert the top level options as key:value
pairs, you would not be able to use an object that has value
and text
properties because choices
can only take an object
with key:value
pairs, it can no longer be an array of objects with value
and text
properties. So as long as there are *only* top-level options, you can use either style, but if you use optgoups the top-level options could only be simple key:value
pairs.
Here is an image of the result of the last examples 3 and 5 from my local testing after applying the patch:
Trac ticket: #47692
wp-includes/class-wp-customize-control.php