diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 24c9d7e..9e7cc62 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1520,7 +1520,7 @@ function template_preprocess_field(&$variables, $hook) { * * Default template: field-multiple-value-form.html.twig. * - * Combines multiple values into a table with drag-n-drop reordering. + * Combines multiple values. * * @param array $variables * An associative array containing: @@ -1531,6 +1531,56 @@ function template_preprocess_field_multiple_value_form(&$variables) { $variables['multiple'] = $element['#cardinality_multiple']; if ($variables['multiple']) { + $items = array(); + $variables['button'] = array(); + foreach (Element::children($element) as $key) { + if ($key === 'add_more') { + $variables['button'] = &$element[$key]; + } + else { + $items[$key] = &$element[$key]; + if (isset($items[$key]['_weight'])) { + $items[$key]['_weight']['#access'] = FALSE; + } + } + } + usort($items, '_field_multiple_value_form_sort_helper'); + + $variables['title'] = array(); + if (!empty($element['#title'])) { + $variables['title'] = array( + '#type' => 'label', + '#title' => $element['#title'], + '#required' => !empty($element['#required']) ? $element['#required'] : FALSE, + ); + } + $variables['items'] = $items; + $variables['description'] = $element['#description']; + } + else { + $variables['elements'] = array(); + foreach (Element::children($element) as $key) { + $variables['elements'][] = $element[$key]; + } + } +} + +/** + * Prepares variables for individual form element templates. + * + * Default template: field-multiple-value-form.html.twig. + * + * Combines multiple values into a table with drag-n-drop reordering. + * + * @param array $variables + * An associative array containing: + * - element: A render element representing the form element. + */ +function template_preprocess_field_multiple_value_orderable_form(&$variables) { + $element = $variables['element']; + $variables['multiple'] = $element['#cardinality_multiple']; + + if ($variables['multiple']) { $table_id = Html::getUniqueId($element['#field_name'] . '_values'); $order_class = $element['#field_name'] . '-delta-order'; $header_attributes = new Attribute(array('class' => array('label'))); @@ -1792,5 +1842,8 @@ function drupal_common_theme() { 'field_multiple_value_form' => array( 'render element' => 'element', ), + 'field_multiple_value_orderable_form' => array( + 'render element' => 'element', + ), ); } diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php index 67cc15d..9447eb9 100644 --- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php +++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php @@ -226,6 +226,27 @@ public function setTranslatable($translatable) { /** * {@inheritdoc} */ + public function isOrderable() { + return !empty($this->definition['orderable']); + } + + /** + * Sets whether the field is orderable. + * + * @param bool $orderable + * Whether the field is orderable. + * + * @return $this + * The object itself for chaining. + */ + public function setOrderable($orderable) { + $this->definition['orderable'] = $orderable; + return $this; + } + + /** + * {@inheritdoc} + */ public function isRevisionable() { return !empty($this->definition['revisionable']); } diff --git a/core/lib/Drupal/Core/Field/FieldConfigBase.php b/core/lib/Drupal/Core/Field/FieldConfigBase.php index 3d6613e..e99164e 100644 --- a/core/lib/Drupal/Core/Field/FieldConfigBase.php +++ b/core/lib/Drupal/Core/Field/FieldConfigBase.php @@ -109,6 +109,15 @@ protected $required = FALSE; /** + * Flag indicating whether the field is orderable. + * + * Defaults to TRUE. + * + * @var bool + */ + protected $orderable = TRUE; + + /** * Flag indicating whether the field is translatable. * * Defaults to TRUE. @@ -342,6 +351,23 @@ public function setTranslatable($translatable) { /** * {@inheritdoc} */ + public function isOrderable() { + // Make the default state TRUE even when the cardinality doesn't allow + // multiple values. + return $this->orderable || $this->getFieldStorageDefinition()->getCardinality() == 1; + } + + /** + * {@inheritdoc} + */ + public function setOrderable($orderable) { + $this->orderable = $orderable; + return $this; + } + + /** + * {@inheritdoc} + */ public function getSettings() { return $this->settings + $this->getFieldStorageDefinition()->getSettings(); } diff --git a/core/lib/Drupal/Core/Field/WidgetBase.php b/core/lib/Drupal/Core/Field/WidgetBase.php index 575daf9..57c8902 100644 --- a/core/lib/Drupal/Core/Field/WidgetBase.php +++ b/core/lib/Drupal/Core/Field/WidgetBase.php @@ -215,7 +215,7 @@ protected function formMultipleElements(FieldItemListInterface $items, array &$f if ($elements) { $elements += array( - '#theme' => 'field_multiple_value_form', + '#theme' => $this->fieldDefinition->isOrderable() ? 'field_multiple_value_orderable_form' : 'field_multiple_value_form', '#field_name' => $field_name, '#cardinality' => $cardinality, '#cardinality_multiple' => $this->fieldDefinition->getFieldStorageDefinition()->isMultiple(), diff --git a/core/modules/field/config/schema/field.schema.yml b/core/modules/field/config/schema/field.schema.yml index 55760a7..e848367 100644 --- a/core/modules/field/config/schema/field.schema.yml +++ b/core/modules/field/config/schema/field.schema.yml @@ -38,6 +38,9 @@ field.storage.*.*: translatable: type: boolean label: 'Translatable' + orderable: + type: boolean + label: 'Orderable' indexes: type: sequence label: 'Indexes' diff --git a/core/modules/field/src/Entity/FieldConfig.php b/core/modules/field/src/Entity/FieldConfig.php index a889803..2eea868 100644 --- a/core/modules/field/src/Entity/FieldConfig.php +++ b/core/modules/field/src/Entity/FieldConfig.php @@ -36,6 +36,7 @@ * "bundle", * "label", * "description", + * "orderable", * "required", * "translatable", * "default_value", diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index 5fff1aa..6bfef55 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -39,6 +39,7 @@ * "module", * "locked", * "cardinality", + * "orderable", * "translatable", * "indexes", * "persist_with_no_fields", @@ -123,6 +124,15 @@ class FieldStorageConfig extends ConfigEntityBase implements FieldStorageConfigI protected $cardinality = 1; /** + * Flag indicating whether the field is orderable. + * + * Defaults to TRUE. + * + * @var int + */ + protected $orderable = TRUE; + + /** * Flag indicating whether the field is translatable. * * Defaults to TRUE. @@ -579,6 +589,13 @@ public function isTranslatable() { /** * {@inheritdoc} */ + public function isOrderable() { + return $this->orderable; + } + + /** + * {@inheritdoc} + */ public function isRevisionable() { // All configurable fields are revisionable. return TRUE; @@ -595,6 +612,14 @@ public function setTranslatable($translatable) { /** * {@inheritdoc} */ + public function setOrderable($orderable) { + $this->orderable = $orderable; + return $this; + } + + /** + * {@inheritdoc} + */ public function getProvider() { return 'field'; } diff --git a/core/modules/field_ui/src/Form/FieldConfigEditForm.php b/core/modules/field_ui/src/Form/FieldConfigEditForm.php index ba972a9..178623b 100644 --- a/core/modules/field_ui/src/Form/FieldConfigEditForm.php +++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php @@ -76,6 +76,16 @@ public function form(array $form, FormStateInterface $form_state) { '#weight' => -5, ); + if ($this->entity->getFieldStorageDefinition()->getCardinality() != 1) { + $form['orderable'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Orderable'), + '#default_value' => $this->entity->isOrderable(), + '#weight' => -5, + '#description' => t('Orderable multiple fields widgets are in a table with drag and drop.'), + ); + } + // Create an arbitrary entity object (used by the 'default value' widget). $ids = (object) array( 'entity_type' => $this->entity->getTargetEntityTypeId(), diff --git a/core/modules/system/templates/field-multiple-value-form.html.twig b/core/modules/system/templates/field-multiple-value-form.html.twig index 348621c..6531223 100644 --- a/core/modules/system/templates/field-multiple-value-form.html.twig +++ b/core/modules/system/templates/field-multiple-value-form.html.twig @@ -19,9 +19,17 @@ * @ingroup themeable */ #} +{% + set title_classes = [ + 'label', + required ? 'js-form-required', + required ? 'form-required', + ] +%} {% if multiple %} + {{ title }}
- {{ table }} + {{ items }} {% if description %}
{{ description }}
{% endif %} diff --git a/core/modules/system/templates/field-multiple-value-orderable-form.html.twig b/core/modules/system/templates/field-multiple-value-orderable-form.html.twig new file mode 100644 index 0000000..348621c --- /dev/null +++ b/core/modules/system/templates/field-multiple-value-orderable-form.html.twig @@ -0,0 +1,36 @@ +{# +/** + * @file + * Default theme implementation for an individual form element. + * + * Available variables for all fields: + * - multiple: Whether there are multiple instances of the field. + * + * Available variables for single cardinality fields: + * - elements: Form elements to be rendered. + * + * Available variables when there are multiple fields. + * - table: Table of field items. + * - description: Description text for the form element. + * - button: "Add another item" button. + * + * @see template_preprocess_field_multiple_value_form() + * + * @ingroup themeable + */ +#} +{% if multiple %} +
+ {{ table }} + {% if description %} +
{{ description }}
+ {% endif %} + {% if button %} +
{{ button }}
+ {% endif %} +
+{% else %} + {% for element in elements %} + {{ element }} + {% endfor %} +{% endif %} diff --git a/core/themes/classy/templates/form/field-multiple-value-form.html.twig b/core/themes/classy/templates/form/field-multiple-value-form.html.twig index bcc7b89..926b574 100644 --- a/core/themes/classy/templates/form/field-multiple-value-form.html.twig +++ b/core/themes/classy/templates/form/field-multiple-value-form.html.twig @@ -17,9 +17,17 @@ * @see template_preprocess_field_multiple_value_form() */ #} +{% + set title_classes = [ + 'label', + required ? 'js-form-required', + required ? 'form-required', + ] +%} {% if multiple %} + {{ title }}
- {{ table }} + {{ items }} {% if description %}
{{ description }}
{% endif %} diff --git a/core/themes/classy/templates/form/field-multiple-value-orderable-form.html.twig b/core/themes/classy/templates/form/field-multiple-value-orderable-form.html.twig new file mode 100644 index 0000000..bcc7b89 --- /dev/null +++ b/core/themes/classy/templates/form/field-multiple-value-orderable-form.html.twig @@ -0,0 +1,34 @@ +{# +/** + * @file + * Theme override for an individual form element. + * + * Available variables for all fields: + * - multiple: Whether there are multiple instances of the field. + * + * Available variables for single cardinality fields: + * - elements: Form elements to be rendered. + * + * Available variables when there are multiple fields. + * - table: Table of field items. + * - description: Description text for the form element. + * - button: "Add another item" button. + * + * @see template_preprocess_field_multiple_value_form() + */ +#} +{% if multiple %} +
+ {{ table }} + {% if description %} +
{{ description }}
+ {% endif %} + {% if button %} +
{{ button }}
+ {% endif %} +
+{% else %} + {% for element in elements %} + {{ element }} + {% endfor %} +{% endif %}