diff --git a/src/Element/YamlFormOptions.php b/src/Element/YamlFormOptions.php
index 9394575..ef778a0 100644
--- a/src/Element/YamlFormOptions.php
+++ b/src/Element/YamlFormOptions.php
@@ -32,6 +32,7 @@ class YamlFormOptions extends FormElement {
'#labels' => t('options'),
'#empty_options' => 5,
'#add_more' => 1,
+ '#add_type' => 'option',
'#process' => [
[$class, 'processOptions'],
],
@@ -52,7 +53,8 @@ class YamlFormOptions extends FormElement {
}
}
elseif (is_array($input) && isset($input['options'])) {
- return (is_string($input['options'])) ? Yaml::decode($input['options']) : self::convertValuesToOptions($input['options']);
+ $options = (is_string($input['options'])) ? Yaml::decode($input['options']) : self::convertValuesToOptions($input['options']);
+ return $options;
}
else {
return NULL;
@@ -60,7 +62,7 @@ class YamlFormOptions extends FormElement {
}
/**
- * Expand an email confirm field into two HTML5 email elements.
+ * Create a table of options.
*/
public static function processOptions(&$element, FormStateInterface $form_state, &$complete_form) {
$element['#tree'] = TRUE;
@@ -71,21 +73,15 @@ class YamlFormOptions extends FormElement {
// Wrap this $element in a
that handle #states.
YamlFormElementHelper::fixStates($element);
- // For options with optgroup display a CodeMirror YAML editor.
+ // Determine if optgroup should be used.
+ $optgroup = FALSE;
if (isset($element['#default_value']) && is_array($element['#default_value']) && self::hasOptGroup($element['#default_value'])) {
- // Build table.
- $element['options'] = [
- '#type' => 'yamlform_codemirror',
- '#mode' => 'yaml',
- '#default_value' => Yaml::encode($element['#default_value']),
- '#description' => t('Key-value pairs MUST be specified as "safe_key: \'Some readable options\'". Use of only alphanumeric characters and underscores is recommended in keys. One option per line.') . '
' .
- t('Option groups can be created by using just the group name followed by indented group options.'),
- ];
- return $element;
+ $optgroup = TRUE;
}
// Get unique key used to store the current number of options.
$number_of_options_storage_key = self::getStorageKey($element, 'number_of_options');
+ $add_optgroups_storage_key = self::getStorageKey($element, 'add_optgroups');
// Store the number of options which is the number of
// #default_values + number of empty_options.
@@ -95,7 +91,7 @@ class YamlFormOptions extends FormElement {
$number_of_empty_options = (int) $element['#empty_options'];
}
else {
- $number_of_default_values = count($element['#default_value']);
+ $number_of_default_values = self::getValueCount($element['#default_value']);
$number_of_empty_options = 1;
}
@@ -103,6 +99,7 @@ class YamlFormOptions extends FormElement {
}
$number_of_options = $form_state->get($number_of_options_storage_key);
+ $add_optgroups = $form_state->get($add_optgroups_storage_key);
$table_id = implode('_', $element['#parents']) . '_table';
// DEBUG: Disable AJAX callback by commenting out the below callback and
@@ -125,14 +122,29 @@ class YamlFormOptions extends FormElement {
$row_index = 0;
$weight = 0;
$rows = [];
- if (!$form_state->isRebuilding() && isset($element['#default_value']) && is_array($element['#default_value'])) {
+ if (isset($element['#default_value']) && is_array($element['#default_value'])) {
foreach ($element['#default_value'] as $value => $label) {
- $rows[$row_index] = self::buildOptionRow($table_id, $row_index, $value, $label, $weight++, $ajax_settings);
+ $rows[$row_index] = self::buildOptionRow($table_id, $row_index, $value, $label, $weight++, $ajax_settings, $optgroup);
+ $row_index++;
+ if (is_array($label)) {
+ foreach ($label as $subvalue => $sublabel) {
+ $rows[$row_index] = self::buildOptionRow($table_id, $row_index, $subvalue, $sublabel, $weight++, $ajax_settings, $optgroup);
+ $row_index++;
+ }
+ }
+ }
+ }
+
+ if ($add_optgroups) {
+ for ($i = 0; $i < $add_optgroups; $i++) {
+ $rows[$row_index] = self::buildOptionRow($table_id, $row_index, '', [], $weight++, $ajax_settings, $optgroup);
$row_index++;
}
+ $form_state->set($add_optgroups_storage_key, 0);
}
+
while ($row_index < $number_of_options) {
- $rows[$row_index] = self::buildOptionRow($table_id, $row_index, '', '', $weight++, $ajax_settings);
+ $rows[$row_index] = self::buildOptionRow($table_id, $row_index, '', '', $weight++, $ajax_settings, $optgroup);
$row_index++;
}
@@ -164,13 +176,38 @@ class YamlFormOptions extends FormElement {
'#ajax' => $ajax_settings,
'#name' => $table_id . '_add',
];
- $element['add']['more_options'] = [
- '#type' => 'number',
- '#min' => 1,
- '#max' => 100,
- '#default_value' => $element['#add_more'],
- '#field_suffix' => t('more @labels', ['@labels' => $element['#labels']]),
- ];
+ if (self::hasOptGroup($element['#default_value'])) {
+
+ // Add either options or option groups.
+ $element['add']['more_options'] = [
+ '#type' => 'number',
+ '#min' => 1,
+ '#max' => 100,
+ '#default_value' => $element['#add_more'],
+ '#field_suffix' => t('more'),
+ ];
+ $element['add']['more_type'] = [
+ '#type' => 'select',
+ '#options' => [
+ 'option' => t('@labels', ['@labels' => $element['#labels']]),
+ 'optgroup' => t('option groups'),
+ ],
+ '#default_value' => $element['#add_type'],
+ ];
+
+ }
+ else {
+
+ // Add only options.
+ $element['add']['more_options'] = [
+ '#type' => 'number',
+ '#min' => 1,
+ '#max' => 100,
+ '#default_value' => $element['#add_more'],
+ '#field_suffix' => t('more @labels', ['@labels' => $element['#labels']]),
+ ];
+
+ }
$element['#attached']['library'][] = 'yamlform/yamlform.element.options';
@@ -192,12 +229,15 @@ class YamlFormOptions extends FormElement {
* The option's weight.
* @param array $ajax_settings
* An array containing AJAX callback settings.
+ * @param bool $optgroup
+ * Whether the element has optgroups.
*
* @return array
* A render array containing inputs for an option's value, text, and weight.
*/
- public static function buildOptionRow($table_id, $row_index, $value, $text, $weight, array $ajax_settings) {
+ public static function buildOptionRow($table_id, $row_index, $value, $text, $weight, array $ajax_settings, $optgroup) {
$row = [];
+
$row['value'] = [
'#type' => 'textfield',
'#title' => t('Option value'),
@@ -206,15 +246,30 @@ class YamlFormOptions extends FormElement {
'#placeholder' => t('Enter value'),
'#default_value' => $value,
];
- $row['text'] = [
- '#type' => 'textfield',
- '#title' => t('Option text'),
- '#title_display' => 'invisible',
- '#size' => 25,
- '#placeholder' => t('Enter text'),
- '#default_value' => $text,
+ if ($optgroup && !is_array($text)) {
+ $row['value']['#prefix'] = '
';
+ $row['value']['#attributes'] = ['class' => ['with_indentation']];
+ $row['text'] = [
+ '#type' => 'textfield',
+ '#title' => t('Option text'),
+ '#title_display' => 'invisible',
+ '#size' => 25,
+ '#placeholder' => t('Enter text'),
+ '#default_value' => $text,
+ ];
+ }
+ else {
+ $row['text'] = [
+ '#type' => 'hidden',
+ '#value' => '',
+ ];
+ }
+
+ $row['settings'] = [
+ '#type' => 'container',
];
- $row['weight'] = [
+
+ $row['settings']['weight'] = [
'#type' => 'weight',
'#delta' => 1000,
'#title' => t('Option weight'),
@@ -225,6 +280,16 @@ class YamlFormOptions extends FormElement {
'#default_value' => $weight,
];
+ $row['settings']['depth'] = [
+ '#type' => 'hidden',
+ '#value' => $optgroup && is_array($text) ? 0 : 1,
+ ];
+
+ $row['settings']['optgroup'] = [
+ '#type' => 'hidden',
+ '#value' => $optgroup ? 1 : 0,
+ ];
+
$row['remove'] = [
'#type' => 'image_button',
'#src' => 'core/misc/icons/787878/ex.svg',
@@ -262,9 +327,23 @@ class YamlFormOptions extends FormElement {
// Add more options to the number of options.
$number_of_options_storage_key = self::getStorageKey($element, 'number_of_options');
+ $add_optgroups_storage_key = self::getStorageKey($element, 'add_optgroups');
$number_of_options = $form_state->get($number_of_options_storage_key);
$more_options = (int) $element['add']['more_options']['#value'];
- $form_state->set($number_of_options_storage_key, $number_of_options + $more_options);
+ $more_type = $element['add']['more_type']['#value'];
+
+ // Add the number of options to the selected type.
+ switch ($more_type) {
+ case 'option':
+ $form_state->set($number_of_options_storage_key, $number_of_options + $more_options);
+ $form_state->set($add_optgroups_storage_key, 0);
+ break;
+
+ case 'optgroup':
+ $form_state->set($number_of_options_storage_key, $number_of_options);
+ $form_state->set($add_optgroups_storage_key, $more_options);
+ break;
+ }
// Reset values.
$element['options']['#value'] = array_values($element['options']['#value']);
@@ -327,13 +406,19 @@ class YamlFormOptions extends FormElement {
else {
// Collect the option values in a sortable array.
$values = [];
+ $optgroup = FALSE;
foreach (Element::children($element['options']) as $child_key) {
$row = $element['options'][$child_key];
- $values[] = [
+ $value = [
'value' => $row['value']['#value'],
'text' => $row['text']['#value'],
- 'weight' => $row['weight']['#value'],
+ 'settings' => [
+ 'weight' => $row['settings']['weight']['#value'],
+ 'depth' => $row['settings']['depth']['#value'],
+ 'optgroup' => $row['settings']['optgroup']['#value'],
+ ],
];
+ $values[] = $value;
}
// Sort the option values.
@@ -376,7 +461,34 @@ class YamlFormOptions extends FormElement {
}
/**
- * Convert an array containing of option value, text, and weight to an associative array of options.
+ * Get the existing number of options count.
+ *
+ * We cannot just use a count() if we need to handle option groups.
+ *
+ * @param array $value
+ * The current value of the element.
+ *
+ * @return int
+ * The number of options.
+ */
+ public static function getValueCount(array $value) {
+ $count = 0;
+ if ($value) {
+ foreach ($value as $key => $val) {
+ $count++;
+ if (is_array($val)) {
+ $count += count($val);
+ }
+ }
+ }
+ return $count;
+ }
+
+ /**
+ * Convert values to options.
+ *
+ * Convert an array containing of option value, text, and weight to an
+ * associative array of options.
*
* @param array $values
* An array containing of option value, text, and weight.
@@ -385,11 +497,17 @@ class YamlFormOptions extends FormElement {
* An associative array of options.
*/
public static function convertValuesToOptions(array $values = []) {
+
+ foreach ($values as &$value) {
+ $value['weight'] = $value['settings']['weight'];
+ }
+
// Sort the option values.
uasort($values, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
// Now build the associative array of options.
$options = [];
+ $optgroup = FALSE;
foreach ($values as $value) {
$option_value = $value['value'];
$option_text = $value['text'];
@@ -406,7 +524,28 @@ class YamlFormOptions extends FormElement {
$option_text = $option_value;
}
- $options[$option_value] = $option_text;
+ if ($value['settings']['optgroup']) {
+ if ($value['settings']['depth'] && $optgroup !== FALSE) {
+
+ // Child of optgroup.
+ $options[$optgroup][$option_value] = $option_text;
+
+ }
+ else {
+
+ // Optgroup.
+ $optgroup = $option_value;
+ $options[$option_value] = [];
+
+ }
+ }
+ else {
+
+ // No optgroups.
+ $options[$option_value] = $option_text;
+
+ }
+
}
return $options;