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;